diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/webxr/dom-overlay | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/webxr/dom-overlay')
5 files changed, 528 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webxr/dom-overlay/META.yml b/testing/web-platform/tests/webxr/dom-overlay/META.yml new file mode 100644 index 0000000000..be2b6e613a --- /dev/null +++ b/testing/web-platform/tests/webxr/dom-overlay/META.yml @@ -0,0 +1 @@ +spec: https://immersive-web.github.io/dom-overlays/ diff --git a/testing/web-platform/tests/webxr/dom-overlay/ar_dom_overlay.https.html b/testing/web-platform/tests/webxr/dom-overlay/ar_dom_overlay.https.html new file mode 100644 index 0000000000..250adf9b24 --- /dev/null +++ b/testing/web-platform/tests/webxr/dom-overlay/ar_dom_overlay.https.html @@ -0,0 +1,298 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/webxr_util.js"></script> +<script src="../resources/webxr_test_constants.js"></script> +<script src="../resources/webxr_test_asserts.js"></script> + +<style type="text/css"> + div { + padding: 10px; + min-width: 10px; + min-height: 10px; + } + iframe { + border: 0; + width: 20px; + height: 20px; + } +</style> +<div id="div_overlay"> + <div id="inner_a"> + </div> + <div id="inner_b"> + </div> + <!-- This SVG iframe is treated as cross-origin content. --> + <iframe id="iframe" src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="20" width="20" fill="red" fill-opacity="0.3"/></svg>'> + </iframe> + <canvas> + </canvas> +</div> +<div id="div_other"> + <p>test text</p> +</div> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + viewerOrigin: IDENTITY_TRANSFORM, + supportedFeatures: ALL_FEATURES, + environmentBlendMode: "alpha-blend", + interactionMode: "screen-space" +}; + +let testBasicProperties = function(overlayElement, session, fakeDeviceController, t) { + assert_equals(session.mode, 'immersive-ar'); + assert_not_equals(session.environmentBlendMode, 'opaque'); + + assert_true(overlayElement != null); + assert_true(overlayElement instanceof Element); + + // Verify that the DOM overlay type is one of the known types. + assert_in_array(session.domOverlayState.type, + ["screen", "floating", "head-locked"]); + + // Verify SameObject property for domOverlayState + assert_equals(session.domOverlayState, session.domOverlayState); + + // The overlay element should have a transparent background. + assert_equals(window.getComputedStyle(overlayElement).backgroundColor, + 'rgba(0, 0, 0, 0)'); + + // Check that the pseudostyle is set. + assert_equals(document.querySelector(':xr-overlay'), overlayElement); + + return new Promise((resolve) => { + session.requestAnimationFrame((time, xrFrame) => { + resolve(); + }); + }); +}; + +let testFullscreen = async function(overlayElement, session, fakeDeviceController, t) { + // If the browser implements DOM Overlay using Fullscreen API, + // it must not be possible to change the DOM Overlay element by using + // Fullscreen API, and attempts to do so must be rejected. + // Since this is up to the UA, this test also passes if the fullscreen + // element is different from the overlay element. + + // Wait for a rAF call before proceeding. + await new Promise((resolve) => session.requestAnimationFrame(resolve)); + + assert_implements_optional(document.fullscreenElement == overlayElement, + "WebXR DOM overlay is not using Fullscreen API"); + let elem = document.getElementById('div_other'); + assert_not_equals(elem, null); + assert_not_equals(elem, overlayElement); + + try { + await elem.requestFullscreen(); + assert_unreached("fullscreen change should be blocked"); + } catch { + // pass if the call rejects + } + // This is an async function, its return value is automatically a promise. +}; + +let watcherStep = new Event("watcherstep"); +let watcherDone = new Event("watcherdone"); + +let testInput = function(overlayElement, session, fakeDeviceController, t) { + let debug = xr_debug.bind(this, 'testInput'); + + // Use two DIVs for this test. "inner_a" uses a "beforexrselect" handler + // that uses preventDefault(). Controller interactions with it should trigger + // that event, and not generate an XR select event. + + let inner_a = document.getElementById('inner_a'); + assert_true(inner_a != null); + let inner_b = document.getElementById('inner_b'); + assert_true(inner_b != null); + + let got_beforexrselect = false; + inner_a.addEventListener('beforexrselect', (ev) => { + ev.preventDefault(); + got_beforexrselect = true; + }); + + let eventWatcher = new EventWatcher( + t, session, ["watcherstep", "select", "watcherdone"]); + + // Set up the expected sequence of events. The test triggers two select + // actions, but only the second one should generate a "select" event. + // Use a "watcherstep" in between to verify this. + let eventPromise = eventWatcher.wait_for( + ["watcherstep", "select", "watcherdone"]); + + let input_source = + fakeDeviceController.simulateInputSourceConnection(SCREEN_CONTROLLER); + session.requestReferenceSpace('viewer').then(function(viewerSpace) { + // Press the primary input button and then release it a short time later. + debug('got viewerSpace'); + requestSkipAnimationFrame(session, (time, xrFrame) => { + debug('got rAF 1'); + input_source.setOverlayPointerPosition(inner_a.offsetLeft + 1, + inner_a.offsetTop + 1); + input_source.startSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 2'); + input_source.endSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 3'); + // Need to process one more frame to allow select to propagate. + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 4'); + session.dispatchEvent(watcherStep); + + assert_true(got_beforexrselect); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 5'); + input_source.setOverlayPointerPosition(inner_b.offsetLeft + 1, + inner_b.offsetTop + 1); + input_source.startSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 6'); + input_source.endSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 7'); + // Need to process one more frame to allow select to propagate. + session.dispatchEvent(watcherDone); + }); + }); + }); + }); + }); + }); + }); + }); + return eventPromise; +}; + +let testCrossOriginContent = function(overlayElement, session, fakeDeviceController, t) { + let debug = xr_debug.bind(this, 'testCrossOriginContent'); + + let iframe = document.getElementById('iframe'); + assert_true(iframe != null); + let inner_b = document.getElementById('inner_b'); + assert_true(inner_b != null); + + let eventWatcher = new EventWatcher( + t, session, ["watcherstep", "select", "watcherdone"]); + + // Set up the expected sequence of events. The test triggers two select + // actions, but only the second one should generate a "select" event. + // Use a "watcherstep" in between to verify this. + let eventPromise = eventWatcher.wait_for( + ["watcherstep", "select", "watcherdone"]); + + let input_source = + fakeDeviceController.simulateInputSourceConnection(SCREEN_CONTROLLER); + session.requestReferenceSpace('viewer').then(function(viewerSpace) { + // Press the primary input button and then release it a short time later. + requestSkipAnimationFrame(session, (time, xrFrame) => { + debug('got rAF 1'); + input_source.setOverlayPointerPosition(iframe.offsetLeft + 1, + iframe.offsetTop + 1); + input_source.startSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 2'); + input_source.endSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 3'); + // Need to process one more frame to allow select to propagate. + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 4'); + session.dispatchEvent(watcherStep); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 5'); + input_source.setOverlayPointerPosition(inner_b.offsetLeft + 1, + inner_b.offsetTop + 1); + input_source.startSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 6'); + input_source.endSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 7'); + // Need to process one more frame to allow select to propagate. + session.dispatchEvent(watcherDone); + }); + }); + }); + }); + }); + }); + }); + }); + return eventPromise; +}; + +xr_promise_test( +"Ensures DOM Overlay rejected without root element", +(t) => { + return navigator.xr.test.simulateDeviceConnection(fakeDeviceInitParams) + .then(() => { + return new Promise((resolve, reject) => { + navigator.xr.test.simulateUserActivation(() => { + resolve( + promise_rejects_dom(t, "NotSupportedError", + navigator.xr.requestSession('immersive-ar', + {requiredFeatures: ['dom-overlay']}) + .then(session => session.end()), + "Should reject when not specifying DOM overlay root") + ); + }); + }); + }); +}); + +xr_session_promise_test( + "Ensures DOM Overlay feature works for immersive-ar, body element", + testBasicProperties.bind(this, document.body), + fakeDeviceInitParams, 'immersive-ar', + {requiredFeatures: ['dom-overlay'], + domOverlay: { root: document.body } }); + +xr_session_promise_test( + "Ensures DOM Overlay feature works for immersive-ar, div element", + testBasicProperties.bind(this, document.getElementById('div_overlay')), + fakeDeviceInitParams, 'immersive-ar', + {requiredFeatures: ['dom-overlay'], + domOverlay: { root: document.getElementById('div_overlay') } }); + +xr_session_promise_test( + "Ensures DOM Overlay input deduplication works", + testInput.bind(this, document.getElementById('div_overlay')), + fakeDeviceInitParams, 'immersive-ar', { + requiredFeatures: ['dom-overlay'], + domOverlay: { root: document.getElementById('div_overlay') } + }); + +xr_session_promise_test( + "Ensures DOM Overlay Fullscreen API doesn't change DOM overlay", + testFullscreen.bind(this, document.getElementById('div_overlay')), + fakeDeviceInitParams, 'immersive-ar', { + requiredFeatures: ['dom-overlay'], + domOverlay: { root: document.getElementById('div_overlay') } + }); + +xr_session_promise_test( + "Ensures DOM Overlay interactions on cross origin iframe are ignored", + testCrossOriginContent.bind(this, document.getElementById('div_overlay')), + fakeDeviceInitParams, 'immersive-ar', { + requiredFeatures: ['dom-overlay'], + domOverlay: { root: document.getElementById('div_overlay') } + }); + +</script> diff --git a/testing/web-platform/tests/webxr/dom-overlay/ar_dom_overlay_hit_test.https.html b/testing/web-platform/tests/webxr/dom-overlay/ar_dom_overlay_hit_test.https.html new file mode 100644 index 0000000000..6fdf2498ed --- /dev/null +++ b/testing/web-platform/tests/webxr/dom-overlay/ar_dom_overlay_hit_test.https.html @@ -0,0 +1,128 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../resources/webxr_util.js"></script> +<script src="../resources/webxr_test_constants.js"></script> +<script src="../resources/webxr_test_constants_fake_world.js"></script> +<script src="../resources/webxr_test_asserts.js"></script> + +<style type="text/css"> + div { + padding: 10px; + min-width: 10px; + min-height: 10px; + } + iframe { + border: 0; + width: 20px; + height: 20px; + } +</style> +<div id="div_overlay"> + <div id="inner_b"> + </div> + <!-- This SVG iframe is treated as cross-origin content. --> + <iframe id="iframe" src='data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect height="20" width="20" fill="red" fill-opacity="0.3"/></svg>'> + </iframe> + <canvas> + </canvas> +</div> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + viewerOrigin: IDENTITY_TRANSFORM, + supportedFeatures: ALL_FEATURES, + world: createFakeWorld(5.0, 2.0, 5.0), // see webxr_test_constants_fake_world.js for details +}; + +const hitTestOptionsInit = { + profile: "generic-touchscreen", + offsetRay: new XRRay(), +}; + +const SCREEN_POINTER_TRANSFORM = { + position: [0, 0, 0], // middle of the screen + orientation: [0, 0, 0, 1] // forward-facing +}; + +const screen_controller_init = { + handedness: "none", + targetRayMode: "screen", + pointerOrigin: SCREEN_POINTER_TRANSFORM, // aka mojo_from_pointer + profiles: ["generic-touchscreen",] +}; + +const testCrossOriginContent = function(overlayElement, session, fakeDeviceController, t) { + const iframe = document.getElementById('iframe'); + const inner_b = document.getElementById('inner_b'); + + let debug = xr_debug.bind(this, 'testCrossOriginContent'); + + const input_source = + fakeDeviceController.simulateInputSourceConnection(screen_controller_init); + debug('start'); + return session.requestReferenceSpace('viewer').then(function(viewerSpace) { + debug('got viewerSpace'); + return session.requestHitTestSourceForTransientInput(hitTestOptionsInit) + .then((hitTestSource) => { + debug('got hitTestSource'); + return new Promise((resolve) => { + // Press the primary input button and then release it a short time later. + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 1'); + input_source.setOverlayPointerPosition(iframe.offsetLeft + 1, + iframe.offsetTop + 1); + input_source.startSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + input_source.endSelection(); + + // There should be no results for transient input for cross origin content: + const results = xrFrame.getHitTestResultsForTransientInput(hitTestSource); + t.step(() => { + assert_equals(results.length, 0, "Hit test results should be suppressed for cross-origin content"); + }); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 2'); + // Need to process one more frame to allow select to propagate + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 3'); + input_source.setOverlayPointerPosition(inner_b.offsetLeft + 1, + inner_b.offsetTop + 1); + input_source.startSelection(); + + session.requestAnimationFrame((time, xrFrame) => { + debug('got rAF 4'); + input_source.endSelection(); + + const results = xrFrame.getHitTestResultsForTransientInput(hitTestSource); + t.step(() => { + // TODO(bialpio): this assertion is currently failing, FIXME + assert_equals(results.length, 1, "Hit test results should be available for same-origin content"); + }); + debug('resolving'); + resolve(); + }); + }); + }); + }); + }); + }); + }); + }); +}; + +xr_session_promise_test( + "Ensures DOM Overlay interactions on cross origin iframe do not cause hit test results to come up", + testCrossOriginContent.bind(this, document.getElementById('div_overlay')), + fakeDeviceInitParams, 'immersive-ar', { + requiredFeatures: ['dom-overlay', 'hit-test'], + domOverlay: { root: document.getElementById('div_overlay') } + }); + +</script> diff --git a/testing/web-platform/tests/webxr/dom-overlay/idlharness.https.window.js b/testing/web-platform/tests/webxr/dom-overlay/idlharness.https.window.js new file mode 100644 index 0000000000..0f901e5b5d --- /dev/null +++ b/testing/web-platform/tests/webxr/dom-overlay/idlharness.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js + +'use strict'; + +// https://immersive-web.github.io/dom-overlays/ + +idl_test( + ['webxr-dom-overlays'], + ['webxr', 'html', 'dom', 'SVG'], + async idl_array => { + self.svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + idl_array.add_objects({ + Document: ['document'], + HTMLElement: ['document.body'], + SVGElement: ['svgElement'], + Window: ['window'] + }); + } +); diff --git a/testing/web-platform/tests/webxr/dom-overlay/nested_fullscreen.https.html b/testing/web-platform/tests/webxr/dom-overlay/nested_fullscreen.https.html new file mode 100644 index 0000000000..a8fc70fca6 --- /dev/null +++ b/testing/web-platform/tests/webxr/dom-overlay/nested_fullscreen.https.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="../resources/webxr_util.js"></script> +<script src="../resources/webxr_test_constants.js"></script> +<script src="../resources/webxr_test_asserts.js"></script> + +<style type="text/css"> + div { + padding: 10px; + min-width: 10px; + min-height: 10px; + } + iframe { + border: 0; + width: 20px; + height: 20px; + } +</style> +<div id="div_overlay"> + <canvas> + </canvas> +</div> +<div id="div_other"> + <p>test text</p> +</div> + +<script> + +const fakeDeviceInitParams = { + supportedModes: ["immersive-ar"], + views: VALID_VIEWS, + viewerOrigin: IDENTITY_TRANSFORM, + supportedFeatures: ALL_FEATURES, +}; + +// This test verifies that WebXR DOM Overlay mode works when the document is +// already in fullscreen mode when the session starts. (This should work both +// for a fullscreen-based overlay implementation and for one that treats the +// overlay as an independent output.) +promise_test( + async (setup) => { + setup.add_cleanup(() => document.exitFullscreen()); + + // Fullscreen the <body> element before running the test. Currently, this + // can't be an arbitrary element because the simulateUserActivation call + // adds a button to <body> which is only clickable if it's visible. + await test_driver.bless("fullscreen", + () => document.body.requestFullscreen()); + + const overlayElement = document.getElementById('div_overlay'); + + xr_session_promise_test( + "Check XR session from fullscreen", + (session, fakeDeviceController, t) => { + // The overlay element should have a transparent background. + assert_equals(window.getComputedStyle(overlayElement).backgroundColor, + 'rgba(0, 0, 0, 0)'); + + // Check that the pseudostyle is set. + assert_equals(document.querySelector(':xr-overlay'), overlayElement); + + // Wait for one animation frame before exiting. + return new Promise((resolve) => session.requestAnimationFrame(resolve)); + }, + fakeDeviceInitParams, 'immersive-ar', { + requiredFeatures: ['dom-overlay'], + domOverlay: { root: overlayElement } + } + ); + + // The setup promise_test automatically succeeds if it gets here + // without raising an exception. It'll pass even on systems that + // don't support WebXR or DOM Overlay. + }, + "fullscreen setup" +); + +</script> |