openvibers/openvibe.bench Commits

Design's arena may frame Bench's renders; a stopped entry can run again (#65)

OP OpenVibers committed 12 days ago · 5d803233d8 · 2 files, +13 −7

Design's /arena becomes a view of Bench's nine design kinds (plan L51), so its blind pairs are drawn in
the same sandboxed render frames as /battle: https://openvibe.design joins the frame ancestors.

The wrapper's clock stops an entry after the kind's limit (20 s for most design kinds). A voter
comparing two pages often needs longer, so the stopped note now has a 'Run it again' button that
reloads the same entry and restarts the clock.

Changes

ddiff --git a/server/bench/render.js b/server/bench/render.js
iindex 6c36e57..f6d5aa7 100644
---- a/server/bench/render.js
++++ b/server/bench/render.js
@@@ -26,8 +26,9 @@ const VENDOR = Object.freeze({
three: RENDER_ORIGIN + '/vendor/three.module.js',
addons: RENDER_ORIGIN + '/vendor/three/addons/',
});
--/** The pages that may frame the wrapper and the model documents. */
--const FRAME_ANCESTORS = 'https://bench.openvibe.services https://openvibe.report';
++/** The pages that may frame the wrapper and the model documents. OpenVibe.Design shows its arena's blind pairs in
++ * these frames (plan L51: Design's /arena is a view of Bench's design kinds). */
++const FRAME_ANCESTORS = 'https://bench.openvibe.services https://openvibe.report https://openvibe.design';
/** The wrapper's own policy: its one inline script under a fresh nonce, its child frame only from the render origin. */
function wrapperCsp(nonce) {
return "default-src 'none'; script-src 'nonce-" + nonce + "'; style-src 'unsafe-inline'; frame-src " + RENDER_ORIGIN
@@@ -227,12 +228,15 @@ function wrapperDocument(opts) {
+ '<div class="bar"><b>' + esc(title) + '</b><span><span class="clock" id="clock">' + secs + 's</span> '
+ '<button type="button" id="stop">Stop</button></span></div>'
+ '<iframe id="frame" title="' + esc(title) + '" sandbox="' + SANDBOX + '" referrerpolicy="no-referrer" loading="lazy" src="' + esc(RENDER_ORIGIN + '/render/e/' + encodeURIComponent(entryId)) + '"></iframe>'
-- + '<div class="stopped" id="stopped">The entry stopped after ' + secs + ' seconds.</div>'
++ + '<div class="stopped" id="stopped">The entry stopped after ' + secs + ' seconds. <button type="button" id="again">Run it again</button></div>'
+ '<script nonce="' + esc(nonce) + '">(function(){var frame=document.getElementById("frame");var clock=document.getElementById("clock");'
-- + 'var stopped=document.getElementById("stopped");var left=' + secs + ';'
-- + 'function stop(){frame.src="about:blank";frame.style.display="none";stopped.style.display="block";clock.textContent="0s";}'
-- + 'document.getElementById("stop").addEventListener("click",stop);'
-- + 'var timer=setInterval(function(){left-=1;clock.textContent=left>0?left+"s":"0s";if(left<=0){clearInterval(timer);stop();}},1000);'
++ + 'var stopped=document.getElementById("stopped");var src=frame.getAttribute("src");var left=' + secs + ';var timer=0;'
++ + 'function tick(){left-=1;clock.textContent=left>0?left+"s":"0s";if(left<=0)stop();}'
++ + 'function stop(){clearInterval(timer);frame.src="about:blank";frame.style.display="none";stopped.style.display="block";clock.textContent="0s";}'
++ // A voter comparing two pages may need longer than the clock: the same entry runs again, from the start.
++ + 'function again(){clearInterval(timer);left=' + secs + ';clock.textContent=left+"s";stopped.style.display="none";frame.style.display="";frame.src=src;timer=setInterval(tick,1000);}'
++ + 'document.getElementById("stop").addEventListener("click",stop);document.getElementById("again").addEventListener("click",again);'
++ + 'timer=setInterval(tick,1000);'
+ 'addEventListener("message",function(e){if(e&&e.data==="bench:stop")stop();});})();</script></body></html>';
}
ddiff --git a/test/showcase.test.js b/test/showcase.test.js
iindex ef5c254..30e607e 100644
---- a/test/showcase.test.js
++++ b/test/showcase.test.js
@@@ -98,6 +98,8 @@ const HOSTILE = [
assert.ok(w.text.includes('src="' + renderLib.RENDER_ORIGIN + '/render/e/'), 'the child loads from the render origin');
assert.ok(!w.headers.get('x-frame-options'), 'no X-Frame-Options DENY: frame-ancestors decides');
assert.ok(wcsp.includes('frame-ancestors https://bench.openvibe.services'), 'the showcase page may frame it');
++ assert.ok(wcsp.includes('https://openvibe.design'), 'Design\'s arena may frame it (plan L51)');
++ assert.ok(w.text.includes('id="again"') && w.text.includes('function again()'), 'a stopped entry can run again');
const ecsp = (await t.get('/render/e/' + entryA)).headers.get('content-security-policy') || '';
assert.ok(ecsp.includes('frame-ancestors ' + renderLib.RENDER_ORIGIN), 'the wrapper on the render origin may frame the model document: ' + ecsp);
assert.ok(!/<meta[^>]*frame-ancestors/.test(w.text), 'no frame-ancestors in a meta tag (ignored there)');