Skip to content

Commit

Permalink
Update WebXR DOM Overlay to match spec change requests
Browse files Browse the repository at this point in the history
Instead of depending on Fullscreen API's styling, use a separate
:xr-dom-overlay pseudoclass with its own copy of the relevant styles.
Lazy-load this stylesheet when DOM Overlay mode is active.

Update the Fullscreen API integration to specifically allow XR session
setup to configure the fullscreen element, while blocking app-side
element changes to avoid inconsistent behavior.

Update the WPT test to cover more scenarios and improve compatibility
with potential implementations that aren't screen space.

Bug: 991747
Change-Id: I2b578570f695f72019c7efccb4c797cdb90e87f7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2057120
Reviewed-by: Philip Jägenstedt <foolip@chromium.org>
Reviewed-by: Lan Wei <lanwei@chromium.org>
Reviewed-by: Piotr Bialecki <bialpio@chromium.org>
Commit-Queue: Klaus Weidner <klausw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#743218}
  • Loading branch information
klausw authored and chromium-wpt-export-bot committed Feb 20, 2020
1 parent 58988ab commit 4b205ad
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 22 deletions.
1 change: 1 addition & 0 deletions resources/chromium/webxr-test.js
Expand Up @@ -1233,6 +1233,7 @@ class MockXRInputSource {
// Pointer data for DOM Overlay, set by setOverlayPointerPosition()
if (this.overlay_pointer_position_) {
input_state.overlayPointerPosition = this.overlay_pointer_position_;
this.overlay_pointer_position_ = null;
}

return input_state;
Expand Down
202 changes: 180 additions & 22 deletions webxr/dom-overlay/ar_dom_overlay.https.html
Expand Up @@ -11,13 +11,22 @@
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>
<canvas />
<!-- 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>
Expand All @@ -32,44 +41,95 @@
supportedFeatures: ALL_FEATURES,
};

let watcherStep = new Event("watcherstep");
let watcherDone = new Event("watcherdone");

let testFunction = function(overlayElement, session, fakeDeviceController, t) {
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);

assert_equals(session.domOverlayState.type, "screen");
// 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_true(session.domOverlayState === session.domOverlayState);

// add: "select", "no_event",
let eventWatcher = new EventWatcher(
t, session, ["watcherstep", "select", "watcherdone"]);
let eventPromise = eventWatcher.wait_for(
["watcherstep", "select", "watcherdone"]);
// 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 = 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.

let rafPromise = new Promise((resolve) => {
session.requestAnimationFrame((time, xrFrame) => {
resolve();
});
});
let promises = [rafPromise];

if (document.fullscreenElement == overlayElement) {
let elem = document.getElementById('div_other');
assert_true(elem != null);
assert_not_equals(elem, overlayElement);

let fullscreenPromise = new Promise((resolve, reject) => {
elem.requestFullscreen().then(() => {
assert_unreached("fullscreen change should be blocked");
reject();
}).catch(() => {
resolve();
});
});
promises.push(fullscreenPromise);
}

return Promise.all(promises);
};

let watcherStep = new Event("watcherstep");
let watcherDone = new Event("watcherdone");

let testInput = function(overlayElement, session, fakeDeviceController, t) {

// 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;
});

// The overlay element should have a transparent background.
assert_equals(window.getComputedStyle(overlayElement).backgroundColor,
'rgba(0, 0, 0, 0)');
let eventWatcher = new EventWatcher(
t, session, ["watcherstep", "select", "watcherdone"]);

// Try fullscreening a different element, this should fail.
let elem = document.getElementById('div_other');
assert_true(elem != null);
assert_not_equals(elem, overlayElement);
// 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);
Expand All @@ -80,6 +140,62 @@
inner_a.offsetTop + 1);
input_source.startSelection();

session.requestAnimationFrame((time, xrFrame) => {
input_source.endSelection();

session.requestAnimationFrame((time, xrFrame) => {
// Need to process one more frame to allow select to propagate.
session.requestAnimationFrame((time, xrFrame) => {
session.dispatchEvent(watcherStep);

assert_true(got_beforexrselect);

session.requestAnimationFrame((time, xrFrame) => {
input_source.setOverlayPointerPosition(inner_b.offsetLeft + 1,
inner_b.offsetTop + 1);
input_source.startSelection();

session.requestAnimationFrame((time, xrFrame) => {
input_source.endSelection();

session.requestAnimationFrame((time, xrFrame) => {
// Need to process one more frame to allow select to propagate.
session.dispatchEvent(watcherDone);
});
});
});
});
});
});
});
});
return eventPromise;
};

let testCrossOriginContent = function(overlayElement, session, fakeDeviceController, t) {
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.
session.requestAnimationFrame((time, xrFrame) => {
input_source.setOverlayPointerPosition(iframe.offsetLeft + 1,
iframe.offsetTop + 1);
input_source.startSelection();

session.requestAnimationFrame((time, xrFrame) => {
input_source.endSelection();

Expand Down Expand Up @@ -110,16 +226,58 @@
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",
testFunction.bind(this, document.body),
"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 element selection works",
testFunction.bind(this, document.getElementById('div_overlay')),
"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') }
Expand Down

0 comments on commit 4b205ad

Please sign in to comment.