Commit f994e3a4 authored by Klaus Weidner's avatar Klaus Weidner Committed by Commit Bot

WebXR: Handle same-process iframe DOM overlay

For same-process iframes, propagate the Document-level IsXrOverlay flag,
and use the correct element as the overlay layer.

This moves the document->SetIsXrOverlay setters from xr_system to
fullscreen to ensure it gets applied consistently. There are multiple
documents that need updating when iframes are being used. (Handling
out-of-process iframes needs additional logic that's being added
in a followup CL.)

Bug: 1101193
Change-Id: Icf80e65e1b927888c6140acbc998ed90d469b5e1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2283823
Commit-Queue: Klaus Weidner <klausw@chromium.org>
Reviewed-by: default avatarPhilip Jägenstedt <foolip@chromium.org>
Reviewed-by: default avatarAlexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798312}
parent 2b1c7fdb
......@@ -4672,7 +4672,25 @@ static PaintLayer* GetXrOverlayLayer(Document& document) {
if (!document.IsXrOverlay())
return nullptr;
Element* fullscreen_element = Fullscreen::FullscreenElementFrom(document);
// When DOM overlay mode is active in iframe content, the parent frame's
// document will also be marked as being in DOM overlay mode, with the iframe
// element being in fullscreen mode. Find the innermost reachable fullscreen
// element to use as the XR overlay layer. This is the overlay element for
// same-process iframes, or an iframe element for OOPIF if the overlay element
// is in another process.
Document* content_document = &document;
Element* fullscreen_element =
Fullscreen::FullscreenElementFrom(*content_document);
while (auto* frame_owner =
DynamicTo<HTMLFrameOwnerElement>(fullscreen_element)) {
content_document = frame_owner->contentDocument();
if (!content_document) {
// This is an OOPIF iframe, treat it as the fullscreen element.
break;
}
fullscreen_element = Fullscreen::FullscreenElementFrom(*content_document);
}
if (!fullscreen_element)
return nullptr;
......
......@@ -182,6 +182,17 @@ void Unfullscreen(Element& element) {
UnsetFullscreenFlag(element);
document.RemoveFromTopLayer(&element);
// WebXR DOM Overlay mode doesn't allow changing the fullscreen element, this
// is enforced in AllowedToRequestFullscreen. In this mode, unfullscreening
// should only be happening via ExitFullscreen. This may involve previous
// nested fullscreen elements being unfullscreened first, ignore those. This
// matches kPseudoXrOverlay rules in SelectorChecker::CheckPseudoClass().
if (document.IsXrOverlay() && element == old_element) {
// If this was the active fullscreen element, we're exiting fullscreen mode,
// and this also ends WebXR DOM Overlay mode.
document.SetIsXrOverlay(false, &element);
}
Element* new_element = Fullscreen::FullscreenElementFrom(document);
if (old_element != new_element) {
FullscreenRequestType new_request_type =
......@@ -740,6 +751,18 @@ void Fullscreen::ContinueRequestFullscreen(Document& document,
// 13.1. Let |doc| be |element|'s node document.
Document& doc = element->GetDocument();
// If this fullscreen request is for WebXR DOM Overlay mode, apply that
// property to the document. This updates styling (setting the background
// transparent) and adds the :xr-overlay pseudoclass.
if (request_type & FullscreenRequestType::kForXrOverlay) {
// There's never more than one overlay element per document. (It's either
// the actual overlay element, or a containing iframe element if the
// actual element is in a different document.) It can't be changed during
// the session, that's enforced by AllowedToRequestFullscreen().
DCHECK(!doc.IsXrOverlay());
doc.SetIsXrOverlay(true, element);
}
// 13.2. If |element| is |doc|'s fullscreen element, continue.
if (element == FullscreenElementFrom(doc))
continue;
......
......@@ -17,6 +17,8 @@ std::string FullscreenRequestTypeToDebugString(FullscreenRequestType req) {
: "Unprefixed");
if (req & FullscreenRequestType::kForCrossProcessDescendant)
result << "|ForCrossProcessDescendant";
if (req & FullscreenRequestType::kForXrOverlay)
result << "|ForXrOverlay";
return result.str();
}
#endif
......
......@@ -33,6 +33,10 @@ enum class FullscreenRequestType {
// has requested and is about to enter fullscreen.
kForCrossProcessDescendant = 2,
// For WebXR DOM Overlay, in this mode the element and parent iframes use a
// transparent background.
kForXrOverlay = 4,
// Explicit name for "no options" for backwards compatibility and convenience
kUnprefixed = kNull,
};
......
......@@ -602,7 +602,6 @@ void XRSystem::OverlayFullscreenEventManager::Invoke(
if (event->type() == event_type_names::kFullscreenchange) {
// Succeeded, proceed with session creation.
element->GetDocument().GetViewportData().SetExpandIntoDisplayCutout(true);
element->GetDocument().SetIsXrOverlay(true, element);
xr_->OnRequestSessionReturned(query_, std::move(result_));
}
......@@ -618,25 +617,29 @@ void XRSystem::OverlayFullscreenEventManager::RequestFullscreen() {
Element* element = query_->DOMOverlayElement();
DCHECK(element);
bool wait_for_fullscreen_change = true;
if (element == Fullscreen::FullscreenElementFrom(element->GetDocument())) {
// It's possible that the requested element is already fullscreen, in which
// case we must not wait for a fullscreenchange event since it won't arrive.
// Detect that and proceed directly with session creation in this case. This
// can happen if the site used Fullscreen API to place the element into
// This can happen if the site used Fullscreen API to place the element into
// fullscreen mode before requesting the session, and if the session can
// proceed without needing a consent prompt. (Showing a dialog exits
// proceed without needing a permission prompt. (Showing a dialog exits
// fullscreen mode.)
//
// We still need to do the RequestFullscreen call to apply the kForXrOverlay
// property which sets the background transparent.
DVLOG(2) << __func__ << ": requested element already fullscreen";
element->GetDocument().SetIsXrOverlay(true, element);
xr_->OnRequestSessionReturned(query_, std::move(result_));
return;
wait_for_fullscreen_change = false;
}
// Set up event listeners for success and failure.
element->GetDocument().addEventListener(event_type_names::kFullscreenchange,
this, true);
element->GetDocument().addEventListener(event_type_names::kFullscreenerror,
this, true);
if (wait_for_fullscreen_change) {
// Set up event listeners for success and failure.
element->GetDocument().addEventListener(event_type_names::kFullscreenchange,
this, true);
element->GetDocument().addEventListener(event_type_names::kFullscreenerror,
this, true);
}
// Use the event-generating unprefixed version of RequestFullscreen to ensure
// that the fullscreen event listener is informed once this completes.
......@@ -650,7 +653,13 @@ void XRSystem::OverlayFullscreenEventManager::RequestFullscreen() {
ScopedAllowFullscreen scope(ScopedAllowFullscreen::kXrOverlay);
Fullscreen::RequestFullscreen(*element, options,
FullscreenRequestType::kUnprefixed);
FullscreenRequestType::kUnprefixed |
FullscreenRequestType::kForXrOverlay);
if (!wait_for_fullscreen_change) {
// Element was already fullscreen, proceed with session creation.
xr_->OnRequestSessionReturned(query_, std::move(result_));
}
}
void XRSystem::OverlayFullscreenEventManager::Trace(Visitor* visitor) const {
......@@ -801,7 +810,6 @@ void XRSystem::ExitPresent(base::OnceClosure on_exited) {
if (doc->IsXrOverlay()) {
Element* fullscreen_element = Fullscreen::FullscreenElementFrom(*doc);
DVLOG(3) << __func__ << ": fullscreen_element=" << fullscreen_element;
doc->SetIsXrOverlay(false, fullscreen_element);
// Restore the FrameView background color that was changed in
// OnRequestSessionReturned. The layout view can be null on navigation.
......
......@@ -69,37 +69,29 @@ let testBasicProperties = function(overlayElement, session, fakeDeviceController
});
};
let testFullscreen = function(overlayElement, session, fakeDeviceController, t) {
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.
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);
}
// Wait for a rAF call before proceeding.
await new Promise((resolve) => session.requestAnimationFrame(resolve));
return Promise.all(promises);
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");
......
<!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>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment