From 97a3fa705173cca1d7c30df340fcde43242d882e Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Mon, 29 Jun 2026 11:15:39 -0700 Subject: [PATCH 1/4] toggleOverlay proposal --- index.bs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/index.bs b/index.bs index 8f5131b6..e5571e96 100644 --- a/index.bs +++ b/index.bs @@ -643,6 +643,7 @@ enum XRVisibilityState { undefined updateRenderState(optional XRRenderStateInit state = {}); Promise<undefined> updateTargetFrameRate(float rate); [NewObject] Promise<XRReferenceSpace> requestReferenceSpace(XRReferenceSpaceType type); + undefined toggleOverlay(); unsigned long requestAnimationFrame(XRFrameRequestCallback callback); undefined cancelAnimationFrame(unsigned long handle); @@ -669,10 +670,37 @@ Each {{XRSession}} has an animation frame, which is an Each {{XRSession}} has a set of granted features, which is a [=/set=] of {{DOMString}}s corresponding to the [=feature descriptors=] that have been granted to the {{XRSession}}. +Each {{XRSession}} has a browser overlay shown boolean, initially `false`, that indicates if the user agent is showing a browser overlay over the session. + The enabledFeatures attribute returns the features in the [=XRSession/set of granted features=] as a new array of {{DOMString}}s. The isSystemKeyboardSupported attribute indicates that the {{XRSystem}} has the ability to display the system keyboard while the {{XRSession}} is active. If {{XRSession/isSystemKeyboardSupported}} is true, Web APIs that would trigger the overlay keyboard (such as [=focus=]) will show the system keyboard. The {{XRSession}} MUST set the [=visibility state=] of the {{XRSession}} to {{XRVisibilityState/"visible-blurred"}} while the keyboard is shown. +A browser overlay is user agent UI that displays the non-immersive browser interface over an [=immersive session=]. The user agent controls the size, position, appearance, and available controls of the [=browser overlay=]. + +
+ +The toggleOverlay() method requests that the user agent toggle whether a [=browser overlay=] is shown over an [=immersive session=]. When invoked, it MUST run the following steps: + + 1. Let |session| be [=this=]. + 1. If |session|'s [=XRSession/ended=] value is `true`, throw an "{{InvalidStateError}}" {{DOMException}} and abort these steps. + 1. If |session| is not an [=immersive session=], throw a "{{NotSupportedError}}" {{DOMException}} and abort these steps. + 1. If |session|'s [=XRSession/browser overlay shown=] boolean is `true`, run the following steps: + 1. Hide the [=browser overlay=] shown over |session|. + 1. Set |session|'s [=XRSession/browser overlay shown=] boolean to `false`. + 1. If no other user agent UI or platform state requires the [=XRSession/visibility state=] to be {{XRVisibilityState/"hidden"}} or {{XRVisibilityState/"visible-blurred"}}, set |session|'s [=XRSession/visibility state=] to {{XRVisibilityState/"visible"}}. + 1. Abort these steps. + 1. If the user agent can show a [=browser overlay=] over |session| while preserving the [=immersive session=], run the following steps: + 1. Show a [=browser overlay=] over |session|. + 1. Set |session|'s [=XRSession/browser overlay shown=] boolean to `true`. + 1. If no other user agent UI or platform state requires the [=XRSession/visibility state=] to be {{XRVisibilityState/"hidden"}}, set |session|'s [=XRSession/visibility state=] to {{XRVisibilityState/"visible-blurred"}}. + 1. Abort these steps. + 1. Otherwise, [=Shut down the session|shut down=] |session|. + +Note: If the user agent is unable to show a [=browser overlay=] over an [=immersive session=], calling {{XRSession/toggleOverlay()}} can end the session and return the user to the non-immersive browser interface. + +
+
To initialize the session, given |session|, |mode|, |granted|, and |device|, the user agent MUST run the following steps: @@ -2873,6 +2901,7 @@ Broadly speaking, there are two options for user agents who wish to support [=tr
Note: Examples of [=trusted UI=] include: - The default 2D mode browser shown when not in immersive mode + - A [=browser overlay=] shown in response to {{XRSession/toggleOverlay()}} - A prompt shown within immersive mode which can only be interacted with via a reserved hardware button to prevent spoofing - Pausing the immersive session and showing some form of native system environment in which a prompt can be shown @@ -3112,6 +3141,7 @@ Changes from the Candida - Transient intent addition (GitHub #1343) - First draft for adding a property to XRInputSource to say it's visible elsewhere (GitHub #1353) - Clarify rgb vs srgb behavior (GitHub #1359) +- Add XRSession.toggleOverlay()

From aafde89413162ab2ef863b324bb3b2551f7a49ba Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Thu, 2 Jul 2026 10:58:34 -0700 Subject: [PATCH 2/4] Clarify browser overlay toggle requirements Add a note that browser overlays can display cross-origin content, unlike the HTML-in-Canvas API. Require XRSession.toggleOverlay() to be invoked with transient activation and throw SecurityError otherwise. --- index.bs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.bs b/index.bs index e5571e96..19b597bf 100644 --- a/index.bs +++ b/index.bs @@ -678,11 +678,15 @@ The isSystemKeyboardSupported attribute ind A browser overlay is user agent UI that displays the non-immersive browser interface over an [=immersive session=]. The user agent controls the size, position, appearance, and available controls of the [=browser overlay=]. +Note: A [=browser overlay=] can display cross-origin content, which is not possible when using the HTML-in-Canvas API. +
The toggleOverlay() method requests that the user agent toggle whether a [=browser overlay=] is shown over an [=immersive session=]. When invoked, it MUST run the following steps: 1. Let |session| be [=this=]. + 1. Let |global object| be the [=relevant Global object=] for the {{XRSession}} on which this method was invoked. + 1. If |global object| does not have [=transient activation=], throw a "{{SecurityError}}" {{DOMException}} and abort these steps. 1. If |session|'s [=XRSession/ended=] value is `true`, throw an "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. If |session| is not an [=immersive session=], throw a "{{NotSupportedError}}" {{DOMException}} and abort these steps. 1. If |session|'s [=XRSession/browser overlay shown=] boolean is `true`, run the following steps: From d4dba6abd1c1dc083be9041fb6d943d02260faf4 Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Tue, 7 Jul 2026 13:55:53 -0700 Subject: [PATCH 3/4] address comments from 7/7/2026 meeting --- index.bs | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/index.bs b/index.bs index 19b597bf..c4bb79d8 100644 --- a/index.bs +++ b/index.bs @@ -536,6 +536,7 @@ Values given in the feature lists are considered a valid feature descriptor - The string "tracked-sources" - The string "inline-stereo" - The string "secondary-views" + - The string "browser-overlay" Future iterations of this specification and additional modules may expand the list of accepted [=feature descriptors=]. @@ -607,6 +608,8 @@ The [=feature descriptor/inline-stereo=] feature descriptor requests that an [=i The [=feature descriptor/inline-stereo=] feature descriptor only applies to {{XRSessionMode/"inline"}} sessions. It MUST NOT be granted to [=immersive sessions=]. +The [=feature descriptor/browser-overlay=] feature descriptor requests that an [=immersive session=] can call {{XRSession/toggleOverlay()}} to show browser UI without ending the session. It only applies to [=immersive sessions=]. It MUST NOT be granted to [=inline sessions=]. + [=Requested features=] can only be enabled for a session if the [=XRSession/XR device=] is capable of supporting the feature, which means that the feature is known to be supported by the [=XRSession/XR device=] in some configurations, even if the current configuration has not yet been verified as supporting the feature. The user agent MAY apply more rigorous constraints if desired in order to yield a more consistent user experience. Note: For example, several VR devices support either configuring a safe boundary for the user to move around within or skipping boundary configuration and operating in a mode where the user is expected to stand in place. Such a device can be considered to be [=capable of supporting=] {{"bounded-floor"}} {{XRReferenceSpace}}s even if they are currently not configured with safety boundaries, because it's expected that the user could configure the device appropriately if the experience required it. This is to allow user agents to avoid fully initializing the [=XRSession/XR device=] or waiting for the user's environment to be recognized prior to [=resolve the requested features|resolving the requested features=] if desired. If, however, the user agent knows the boundary state at the time the session is requested without additional initialization it may choose to reject the {{"bounded-floor"}} feature if the safety boundary is not already configured. @@ -670,7 +673,7 @@ Each {{XRSession}} has an animation frame, which is an Each {{XRSession}} has a set of granted features, which is a [=/set=] of {{DOMString}}s corresponding to the [=feature descriptors=] that have been granted to the {{XRSession}}. -Each {{XRSession}} has a browser overlay shown boolean, initially `false`, that indicates if the user agent is showing a browser overlay over the session. +Each {{XRSession}} has a browser overlay active boolean, initially `false`, that indicates if a prior successful invocation of {{XRSession/toggleOverlay()}} is showing browser UI while the session is {{XRVisibilityState/"visible-blurred"}} or {{XRVisibilityState/"hidden"}}. The enabledFeatures attribute returns the features in the [=XRSession/set of granted features=] as a new array of {{DOMString}}s. @@ -685,23 +688,30 @@ Note: A [=browser overlay=] can display cross-origin content, which is not possi The toggleOverlay() method requests that the user agent toggle whether a [=browser overlay=] is shown over an [=immersive session=]. When invoked, it MUST run the following steps: 1. Let |session| be [=this=]. - 1. Let |global object| be the [=relevant Global object=] for the {{XRSession}} on which this method was invoked. - 1. If |global object| does not have [=transient activation=], throw a "{{SecurityError}}" {{DOMException}} and abort these steps. 1. If |session|'s [=XRSession/ended=] value is `true`, throw an "{{InvalidStateError}}" {{DOMException}} and abort these steps. 1. If |session| is not an [=immersive session=], throw a "{{NotSupportedError}}" {{DOMException}} and abort these steps. - 1. If |session|'s [=XRSession/browser overlay shown=] boolean is `true`, run the following steps: - 1. Hide the [=browser overlay=] shown over |session|. - 1. Set |session|'s [=XRSession/browser overlay shown=] boolean to `false`. - 1. If no other user agent UI or platform state requires the [=XRSession/visibility state=] to be {{XRVisibilityState/"hidden"}} or {{XRVisibilityState/"visible-blurred"}}, set |session|'s [=XRSession/visibility state=] to {{XRVisibilityState/"visible"}}. + 1. If [=feature descriptor/browser-overlay=] is not included in |session|'s [=XRSession/set of granted features=], throw a "{{NotSupportedError}}" {{DOMException}} and abort these steps. + 1. Let |global object| be the [=relevant Global object=] for the {{XRSession}} on which this method was invoked. + 1. If |global object| does not have [=transient activation=], throw a "{{SecurityError}}" {{DOMException}} and abort these steps. + 1. If |session|'s [=XRSession/browser overlay active=] boolean is `true`, run the following steps: + 1. If |session|'s [=XRSession/visibility state=] is {{XRVisibilityState/"hidden"}}, run the following steps: + 1. The user agent MAY request the user's permission to resume |session|. + 1. If permission is denied, [=Shut down the session|shut down=] |session| and abort these steps. + 1. Perform the necessary platform-specific steps to resume |session|. + 1. Otherwise, hide the [=browser overlay=] shown over |session|. + 1. Set |session|'s [=XRSession/browser overlay active=] boolean to `false`. + 1. Set |session|'s [=XRSession/visibility state=] to {{XRVisibilityState/"visible"}}. 1. Abort these steps. 1. If the user agent can show a [=browser overlay=] over |session| while preserving the [=immersive session=], run the following steps: 1. Show a [=browser overlay=] over |session|. - 1. Set |session|'s [=XRSession/browser overlay shown=] boolean to `true`. - 1. If no other user agent UI or platform state requires the [=XRSession/visibility state=] to be {{XRVisibilityState/"hidden"}}, set |session|'s [=XRSession/visibility state=] to {{XRVisibilityState/"visible-blurred"}}. + 1. Set |session|'s [=XRSession/browser overlay active=] boolean to `true`. + 1. Set |session|'s [=XRSession/visibility state=] to {{XRVisibilityState/"visible-blurred"}}. 1. Abort these steps. - 1. Otherwise, [=Shut down the session|shut down=] |session|. + 1. Perform the necessary platform-specific steps to suspend |session| and show browser UI. + 1. Set |session|'s [=XRSession/browser overlay active=] boolean to `true`. + 1. Set |session|'s [=XRSession/visibility state=] to {{XRVisibilityState/"hidden"}}. -Note: If the user agent is unable to show a [=browser overlay=] over an [=immersive session=], calling {{XRSession/toggleOverlay()}} can end the session and return the user to the non-immersive browser interface. +Note: If the user agent is unable to show a [=browser overlay=] over an [=immersive session=], calling {{XRSession/toggleOverlay()}} can suspend the session and return the user to the non-immersive browser interface. While the session is suspended its [=XRSession/visibility state=] is {{XRVisibilityState/"hidden"}}, so its {{XRSession/requestAnimationFrame()}} callbacks will not be processed. Suspending and resuming the session this way does not end the session or create a new session, so no event indicating that the session ended or started is fired. Calling {{XRSession/toggleOverlay()}} again can resume the session. If the user agent requests permission to resume the session and permission is denied, the session will be [=shut down the session|shut down=].
@@ -3145,7 +3155,7 @@ Changes from the Candida - Transient intent addition (GitHub #1343) - First draft for adding a property to XRInputSource to say it's visible elsewhere (GitHub #1353) - Clarify rgb vs srgb behavior (GitHub #1359) -- Add XRSession.toggleOverlay() +- Add XRSession.toggleOverlay() and the browser-overlay feature descriptor

From 708a0cda7b415f6e702853073316fe28c3b1954d Mon Sep 17 00:00:00 2001 From: Rik Cabanier Date: Thu, 9 Jul 2026 09:10:41 -0700 Subject: [PATCH 4/4] adddress review comment --- index.bs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.bs b/index.bs index c4bb79d8..7f3ca824 100644 --- a/index.bs +++ b/index.bs @@ -698,7 +698,10 @@ The toggleOverlay() method requests that the u 1. The user agent MAY request the user's permission to resume |session|. 1. If permission is denied, [=Shut down the session|shut down=] |session| and abort these steps. 1. Perform the necessary platform-specific steps to resume |session|. - 1. Otherwise, hide the [=browser overlay=] shown over |session|. + 1. Set |session|'s [=XRSession/browser overlay active=] boolean to `false`. + 1. Set |session|'s [=XRSession/visibility state=] to {{XRVisibilityState/"visible"}}. + 1. Abort these steps. + 1. Hide the [=browser overlay=] shown over |session|. 1. Set |session|'s [=XRSession/browser overlay active=] boolean to `false`. 1. Set |session|'s [=XRSession/visibility state=] to {{XRVisibilityState/"visible"}}. 1. Abort these steps.