[WebXR] Implement requestSession()
authorsvillar@igalia.com <svillar@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 May 2020 16:08:47 +0000 (16:08 +0000)
committersvillar@igalia.com <svillar@igalia.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 May 2020 16:08:47 +0000 (16:08 +0000)
https://bugs.webkit.org/show_bug.cgi?id=211888

Reviewed by Youenn Fablet.

LayoutTests/imported/w3c:

* web-platform-tests/webxr/xrDevice_requestSession_immersive.https-expected.txt: Added.
* web-platform-tests/webxr/xrDevice_requestSession_immersive_no_gesture.https-expected.txt: Added.
* web-platform-tests/webxr/xrDevice_requestSession_immersive_unsupported.https-expected.txt: Added.
* web-platform-tests/webxr/xrDevice_requestSession_no_mode.https-expected.txt: Added.
* web-platform-tests/webxr/xrDevice_requestSession_non_immersive_no_gesture.https-expected.txt: Added.
* web-platform-tests/webxr/xrDevice_requestSession_optionalFeatures.https-expected.txt: Added.
* web-platform-tests/webxr/xrDevice_requestSession_requiredFeatures_unknown.https-expected.txt: Added.
* web-platform-tests/webxr/xrSession_features_deviceSupport.https-expected.txt: Added.

Source/WebCore:

This patch adds a preliminar implementation of the requestSession()
API used to get a WebXRSession from the UA. It includes a fairly good
amount of checks to verify that the request can be satisfied given the
device's enabled features per session modes. The specs also describe how
to request persmission for some actions using the Permissions API which
WebKit does not currently implement. That should be done in a follow up
patch perhaps using a similar approach to other APIs as Geolocation for
example.

In order to get some of the requestSession() tests passing the session
finalization (shutdown) had to be implemented too.

Several tests where unskipped for WPE port as they're now passing.

* Modules/webxr/WebXRRenderState.cpp:
(WebCore::WebXRRenderState::create): Pass XRSessionMode as argument
instead of a WebXRSession.
* Modules/webxr/WebXRRenderState.h: Ditto.
* Modules/webxr/WebXRSession.cpp:
(WebCore::WebXRSession::create): Added.
(WebCore::WebXRSession::WebXRSession): Added.
(WebCore::WebXRSession::renderState const):
(WebCore::WebXRSession::inputSources const):
(WebCore::WebXRSession::shutdown): Shutdown process called on session end.
(WebCore::WebXRSession::end): Implemented session ending.
* Modules/webxr/WebXRSession.h: Added create() and private constructor. Fixed some naming.
* Modules/webxr/WebXRSystem.cpp:
(WebCore::WebXRSystem::DummyInlineDevice::DummyInlineDevice): Contructor for the
dummy inline device.
(WebCore::WebXRSystem::ensureImmersiveXRDeviceIsSelected): Use a counter instead of a boolean
to track testing devices as there might be more than 1.
(WebCore::WebXRSystem::obtainCurrentDevice): Implemented from specs text.
(WebCore::WebXRSystem::immersiveSessionRequestIsAllowedForGlobalObject const): Ditto.
(WebCore::WebXRSystem::inlineSessionRequestIsAllowedForGlobalObject const): Ditto.
(WebCore::WebXRSystem::resolveRequestedFeatures const): Ditto.
(WebCore::WebXRSystem::isXRPermissionGranted const): Ditto.
(WebCore::WebXRSystem::requestSession): Ditto.
(WebCore::WebXRSystem::registerSimulatedXRDeviceForTesting): Use a counter instead of a bool.
Also use a reference in the method argument.
(WebCore::WebXRSystem::unregisterSimulatedXRDeviceForTesting): Ditto.
(WebCore::WebXRSystem::sessionEnded): Added, used by sessions to notify the XRSystem.
* Modules/webxr/WebXRSystem.h:
* Modules/webxr/WebXRSystem.idl: Call requestSession with Document.
* dom/TaskSource.h: Added a WebXR task source.
* platform/xr/PlatformXR.h: Specs state that devices have a list of enabled features per session
mode so store them in a hashmap instead of in a Vector.
(PlatformXR::Device::supports const): Use the new Hashmap of session modes.
(PlatformXR::Device::setEnabledFeatures): Ditto.
(PlatformXR::Device::enabledFeatures const): Ditto.
(PlatformXR::Device::setSupportedModes): Deleted.
* platform/xr/openxr/PlatformXR.cpp:
(PlatformXR::Instance::Impl::collectSupportedSessionModes): Return session modes if any.
(PlatformXR::Instance::enumerateImmersiveXRDevices): Fill in features per session mode.
* testing/WebXRTest.cpp:
(WebCore::WebXRTest::simulateDeviceConnection): Fill in features per session mode.
(WebCore::WebXRTest::disconnectAllDevices): Pass a reference to unregister.

LayoutTests:

* platform/wpe/TestExpectations: Added several previously skipped tests
that are now passing.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@261863 268f45cc-cd09-0410-ab3c-d52691b4dbfc

24 files changed:
LayoutTests/ChangeLog
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive_no_gesture.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive_unsupported.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_no_mode.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_non_immersive_no_gesture.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_optionalFeatures.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_requiredFeatures_unknown.https-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/webxr/xrSession_features_deviceSupport.https-expected.txt [new file with mode: 0644]
LayoutTests/platform/wpe/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/Modules/webxr/WebXRInputSourceArray.h
Source/WebCore/Modules/webxr/WebXRRenderState.cpp
Source/WebCore/Modules/webxr/WebXRRenderState.h
Source/WebCore/Modules/webxr/WebXRSession.cpp
Source/WebCore/Modules/webxr/WebXRSession.h
Source/WebCore/Modules/webxr/WebXRSystem.cpp
Source/WebCore/Modules/webxr/WebXRSystem.h
Source/WebCore/Modules/webxr/WebXRSystem.idl
Source/WebCore/dom/TaskSource.h
Source/WebCore/platform/xr/PlatformXR.h
Source/WebCore/platform/xr/openxr/PlatformXR.cpp
Source/WebCore/testing/WebXRTest.cpp

index 6c8cf28..af760c9 100644 (file)
@@ -1,3 +1,13 @@
+2020-05-14  Sergio Villar Senin  <svillar@igalia.com>
+
+        [WebXR] Implement requestSession()
+        https://bugs.webkit.org/show_bug.cgi?id=211888
+
+        Reviewed by Youenn Fablet.
+
+        * platform/wpe/TestExpectations: Added several previously skipped tests
+        that are now passing.
+
 2020-05-19  Antti Koivisto  <antti@apple.com>
 
         Animation of font-size with rem values is incorrect
index 72054f6..9ee1748 100644 (file)
@@ -1,3 +1,19 @@
+2020-05-14  Sergio Villar Senin  <svillar@igalia.com>
+
+        [WebXR] Implement requestSession()
+        https://bugs.webkit.org/show_bug.cgi?id=211888
+
+        Reviewed by Youenn Fablet.
+
+        * web-platform-tests/webxr/xrDevice_requestSession_immersive.https-expected.txt: Added.
+        * web-platform-tests/webxr/xrDevice_requestSession_immersive_no_gesture.https-expected.txt: Added.
+        * web-platform-tests/webxr/xrDevice_requestSession_immersive_unsupported.https-expected.txt: Added.
+        * web-platform-tests/webxr/xrDevice_requestSession_no_mode.https-expected.txt: Added.
+        * web-platform-tests/webxr/xrDevice_requestSession_non_immersive_no_gesture.https-expected.txt: Added.
+        * web-platform-tests/webxr/xrDevice_requestSession_optionalFeatures.https-expected.txt: Added.
+        * web-platform-tests/webxr/xrDevice_requestSession_requiredFeatures_unknown.https-expected.txt: Added.
+        * web-platform-tests/webxr/xrSession_features_deviceSupport.https-expected.txt: Added.
+
 2020-05-19  Carlos Alberto Lopez Perez  <clopez@igalia.com>
 
         Import the Flexbox test suite from the W3C Web Platform Tests
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive.https-expected.txt
new file mode 100644 (file)
index 0000000..d6d97b2
--- /dev/null
@@ -0,0 +1,5 @@
+
+PASS Tests requestSession resolves when supported 
+PASS Tests requestSession accepts XRSessionInit dictionary 
+PASS Tests requestSession ignores unknown optionalFeatures 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive_no_gesture.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive_no_gesture.https-expected.txt
new file mode 100644 (file)
index 0000000..27963fa
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Requesting immersive session outside of a user gesture rejects 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive_unsupported.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive_unsupported.https-expected.txt
new file mode 100644 (file)
index 0000000..4d90367
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Requesting an immersive session when unsupported rejects 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_no_mode.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_no_mode.https-expected.txt
new file mode 100644 (file)
index 0000000..f0fd1b6
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Requesting a session with no mode rejects 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_non_immersive_no_gesture.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_non_immersive_no_gesture.https-expected.txt
new file mode 100644 (file)
index 0000000..f3c296e
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Requesting non-immersive session outside of a user gesture succeeds 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_optionalFeatures.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_optionalFeatures.https-expected.txt
new file mode 100644 (file)
index 0000000..da2f225
--- /dev/null
@@ -0,0 +1,6 @@
+
+PASS Tests requestSession accepts XRSessionInit dictionary 
+PASS Tests requestSession accepts XRSessionInit dictionary with empty feature lists 
+PASS Tests requestSession ignores unknown strings in optionalFeatures 
+PASS Tests requestSession ignores unknown objects in optionalFeatures 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_requiredFeatures_unknown.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_requiredFeatures_unknown.https-expected.txt
new file mode 100644 (file)
index 0000000..38a28a8
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Tests requestSession rejects for unknown requiredFeatures 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/webxr/xrSession_features_deviceSupport.https-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/webxr/xrSession_features_deviceSupport.https-expected.txt
new file mode 100644 (file)
index 0000000..2f7ea46
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Immersive XRSession requests with no supported device should reject 
+
index 3ec3fdb..34601db 100644 (file)
@@ -1016,6 +1016,14 @@ webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/idlharness.https.windo
 webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_isSessionSupported_inline.https.html [ Pass ]
 webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_isSessionSupported_immersive.https.html [ Pass ]
 webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_isSessionSupported_immersive_unsupported.https.html [ Pass ]
+webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive.https.html [ Pass ]
+webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive_no_gesture.https.html [ Pass ]
+webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_immersive_unsupported.https.html [ Pass ]
+webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_no_mode.https.html [ Pass ]
+webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_non_immersive_no_gesture.https.html [ Pass ]
+webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_optionalFeatures.https.html [ Pass ]
+webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrDevice_requestSession_requiredFeatures_unknown.https.html [ Pass ]
+webkit.org/b/209859 imported/w3c/web-platform-tests/webxr/xrSession_features_deviceSupport.https.html [ Pass ]
 
 # Passing since r259006.
 imported/w3c/web-platform-tests/css/css-grid/alignment/self-baseline/grid-self-baseline-changes-grid-area-size-010.html
index 604bc2e..04d5fc5 100644 (file)
@@ -1,3 +1,67 @@
+2020-05-19  Sergio Villar Senin  <svillar@igalia.com>
+
+        [WebXR] Implement requestSession()
+        https://bugs.webkit.org/show_bug.cgi?id=211888
+
+        Reviewed by Youenn Fablet.
+
+        This patch adds a preliminar implementation of the requestSession()
+        API used to get a WebXRSession from the UA. It includes a fairly good
+        amount of checks to verify that the request can be satisfied given the
+        device's enabled features per session modes. The specs also describe how
+        to request persmission for some actions using the Permissions API which
+        WebKit does not currently implement. That should be done in a follow up
+        patch perhaps using a similar approach to other APIs as Geolocation for
+        example.
+
+        In order to get some of the requestSession() tests passing the session
+        finalization (shutdown) had to be implemented too.
+
+        Several tests where unskipped for WPE port as they're now passing.
+
+        * Modules/webxr/WebXRRenderState.cpp:
+        (WebCore::WebXRRenderState::create): Pass XRSessionMode as argument
+        instead of a WebXRSession.
+        * Modules/webxr/WebXRRenderState.h: Ditto.
+        * Modules/webxr/WebXRSession.cpp:
+        (WebCore::WebXRSession::create): Added.
+        (WebCore::WebXRSession::WebXRSession): Added.
+        (WebCore::WebXRSession::renderState const):
+        (WebCore::WebXRSession::inputSources const):
+        (WebCore::WebXRSession::shutdown): Shutdown process called on session end.
+        (WebCore::WebXRSession::end): Implemented session ending.
+        * Modules/webxr/WebXRSession.h: Added create() and private constructor. Fixed some naming.
+        * Modules/webxr/WebXRSystem.cpp:
+        (WebCore::WebXRSystem::DummyInlineDevice::DummyInlineDevice): Contructor for the
+        dummy inline device.
+        (WebCore::WebXRSystem::ensureImmersiveXRDeviceIsSelected): Use a counter instead of a boolean
+        to track testing devices as there might be more than 1.
+        (WebCore::WebXRSystem::obtainCurrentDevice): Implemented from specs text.
+        (WebCore::WebXRSystem::immersiveSessionRequestIsAllowedForGlobalObject const): Ditto.
+        (WebCore::WebXRSystem::inlineSessionRequestIsAllowedForGlobalObject const): Ditto.
+        (WebCore::WebXRSystem::resolveRequestedFeatures const): Ditto.
+        (WebCore::WebXRSystem::isXRPermissionGranted const): Ditto.
+        (WebCore::WebXRSystem::requestSession): Ditto.
+        (WebCore::WebXRSystem::registerSimulatedXRDeviceForTesting): Use a counter instead of a bool.
+        Also use a reference in the method argument.
+        (WebCore::WebXRSystem::unregisterSimulatedXRDeviceForTesting): Ditto.
+        (WebCore::WebXRSystem::sessionEnded): Added, used by sessions to notify the XRSystem.
+        * Modules/webxr/WebXRSystem.h:
+        * Modules/webxr/WebXRSystem.idl: Call requestSession with Document.
+        * dom/TaskSource.h: Added a WebXR task source.
+        * platform/xr/PlatformXR.h: Specs state that devices have a list of enabled features per session
+        mode so store them in a hashmap instead of in a Vector.
+        (PlatformXR::Device::supports const): Use the new Hashmap of session modes.
+        (PlatformXR::Device::setEnabledFeatures): Ditto.
+        (PlatformXR::Device::enabledFeatures const): Ditto.
+        (PlatformXR::Device::setSupportedModes): Deleted.
+        * platform/xr/openxr/PlatformXR.cpp:
+        (PlatformXR::Instance::Impl::collectSupportedSessionModes): Return session modes if any.
+        (PlatformXR::Instance::enumerateImmersiveXRDevices): Fill in features per session mode.
+        * testing/WebXRTest.cpp:
+        (WebCore::WebXRTest::simulateDeviceConnection): Fill in features per session mode.
+        (WebCore::WebXRTest::disconnectAllDevices): Pass a reference to unregister.
+
 2020-05-19  Antti Koivisto  <antti@apple.com>
 
         Animation of font-size with rem values is incorrect
index f421f02..5458b35 100644 (file)
@@ -39,6 +39,9 @@ class WebXRInputSourceArray : public RefCounted<WebXRInputSourceArray> {
 public:
     unsigned length() const;
     WebXRInputSource* item(unsigned) const;
+
+private:
+    WebXRInputSourceArray();
 };
 
 } // namespace WebCore
index 06789f8..c0f895b 100644 (file)
@@ -37,26 +37,11 @@ namespace WebCore {
 
 WTF_MAKE_ISO_ALLOCATED_IMPL(WebXRRenderState);
 
-Ref<WebXRRenderState> WebXRRenderState::create(const WebXRSession& session)
+Ref<WebXRRenderState> WebXRRenderState::create(XRSessionMode mode)
 {
-    // When an XRRenderState object is created for an XRSession session, the user agent MUST initialize the render state by running the following steps:
-    //   1. Let state be the newly created XRRenderState object.
-
-    //   2. Initialize state’s depthNear to 0.1.
-    //   3. Initialize state’s depthFar to 1000.0.
-    // (Default-initialized in the XRRenderState class definition.)
-
-    //   4. If session is an immersive session, initialize state’s inlineVerticalFieldOfView to null.
-    //   5. Else initialize state’s inlineVerticalFieldOfView to PI * 0.5.
-    // FIXME: "immersive session" support
-    UNUSED_PARAM(session);
-    Optional<double> inlineVerticalFieldOfView { piOverTwoDouble };
-
-    //   6. Initialize state’s baseLayer to null.
-    //   7. Initialize state’s outputContext to null.
-    // (Initialized to null by default.)
-
-    return adoptRef(*new WebXRRenderState(WTFMove(inlineVerticalFieldOfView)));
+    // https://immersive-web.github.io/webxr/#initialize-the-render-state
+    // depthNear, depthFar and baseLayer are initialized in the class definition
+    return adoptRef(*new WebXRRenderState(mode == XRSessionMode::Inline ? makeOptional(piOverTwoDouble) : WTF::nullopt));
 }
 
 WebXRRenderState::WebXRRenderState(Optional<double>&& inlineVerticalFieldOfView)
index 3f292ac..95e9656 100644 (file)
@@ -27,6 +27,7 @@
 
 #if ENABLE(WEBXR)
 
+#include "XRSessionMode.h"
 #include <wtf/IsoMalloc.h>
 #include <wtf/Optional.h>
 #include <wtf/Ref.h>
@@ -37,12 +38,11 @@ namespace WebCore {
 
 class WebXRWebGLLayer;
 struct XRRenderStateInit;
-class WebXRSession;
 
 class WebXRRenderState : public RefCounted<WebXRRenderState> {
     WTF_MAKE_ISO_ALLOCATED(WebXRRenderState);
 public:
-    static Ref<WebXRRenderState> create(const WebXRSession&);
+    static Ref<WebXRRenderState> create(XRSessionMode);
     ~WebXRRenderState();
 
     double depthNear() const;
@@ -51,14 +51,15 @@ public:
     RefPtr<WebXRWebGLLayer> baseLayer() const;
 
 private:
-    WebXRRenderState(Optional<double>&&);
-    WebXRRenderState(const XRRenderStateInit&);
+    explicit WebXRRenderState(Optional<double>&& fieldOfView);
+    explicit WebXRRenderState(const XRRenderStateInit&);
 
+    // https://immersive-web.github.io/webxr/#initialize-the-render-state
     struct {
-        double near { 0.1 };
-        double far { 1000 };
+        double near { 0.1 }; // in meters
+        double far { 1000 }; // in meters
     } m_depth;
-    Optional<double> m_inlineVerticalFieldOfView;
+    Optional<double> m_inlineVerticalFieldOfView; // in radians
     RefPtr<WebXRWebGLLayer> m_baseLayer;
 };
 
index ed3562f..a13acc0 100644 (file)
 
 #if ENABLE(WEBXR)
 
+#include "WebXRSystem.h"
 #include <wtf/IsoMallocInlines.h>
 
 namespace WebCore {
 
 WTF_MAKE_ISO_ALLOCATED_IMPL(WebXRSession);
 
+Ref<WebXRSession> WebXRSession::create(Document& document, WebXRSystem& system, XRSessionMode mode, PlatformXR::Device& device)
+{
+    return adoptRef(*new WebXRSession(document, system, mode, device));
+}
+
+WebXRSession::WebXRSession(Document& document, WebXRSystem& system, XRSessionMode mode, PlatformXR::Device& device)
+    : ActiveDOMObject(&document)
+    , m_xrSystem(system)
+    , m_mode(mode)
+    , m_device(makeWeakPtr(device))
+    , m_activeRenderState(WebXRRenderState::create(mode))
+{
+    // TODO: If no other features of the user agent have done so already,
+    // perform the necessary platform-specific steps to initialize the device’s
+    // tracking and rendering capabilities, including showing any necessary
+    // instructions to the user.
+}
+
 WebXRSession::~WebXRSession() = default;
 
 XREnvironmentBlendMode WebXRSession::environmentBlendMode() const
@@ -48,7 +67,7 @@ XRVisibilityState WebXRSession::visibilityState() const
 
 const WebXRRenderState& WebXRSession::renderState() const
 {
-    return *m_renderState;
+    return *m_activeRenderState;
 }
 
 const WebXRInputSourceArray& WebXRSession::inputSources() const
@@ -73,8 +92,44 @@ void WebXRSession::cancelAnimationFrame(int)
 {
 }
 
-void WebXRSession::end(EndPromise&&)
+// https://immersive-web.github.io/webxr/#shut-down-the-session
+void WebXRSession::shutdown()
+{
+    // 1. Let session be the target XRSession object.
+    // 2. Set session's ended value to true.
+    m_ended = true;
+
+    // 3. If the active immersive session is equal to session, set the active immersive session to null.
+    // 4. Remove session from the list of inline sessions.
+    m_xrSystem.sessionEnded(*this);
+
+    // TODO: complete the implementation
+    // 5. Reject any outstanding promises returned by session with an InvalidStateError, except for any promises returned by end().
+    // 6. If no other features of the user agent are actively using them, perform the necessary platform-specific steps to shut down the device's tracking and rendering capabilities. This MUST include:
+    //  6.1. Releasing exclusive access to the XR device if session is an immersive session.
+    //  6.2. Deallocating any graphics resources acquired by session for presentation to the XR device.
+    //  6.3. Putting the XR device in a state such that a different source may be able to initiate a session with the same device if session is an immersive session.
+    // 7. Queue a task that fires an XRSessionEvent named end on session.
+}
+
+// https://immersive-web.github.io/webxr/#dom-xrsession-end
+void WebXRSession::end(EndPromise&& promise)
 {
+    // The shutdown() call bellow might remove the sole reference to session
+    // that could exist (the XRSystem owns the sessions) so let's protect this.
+    Ref<WebXRSession> protectedThis(*this);
+    // 1. Let promise be a new Promise.
+    // 2. Shut down the target XRSession object.
+    shutdown();
+
+    // 3. Queue a task to perform the following steps:
+    queueTaskKeepingObjectAlive(*this, TaskSource::WebXR, [promise = WTFMove(promise)] () mutable {
+        // 3.1 Wait until any platform-specific steps related to shutting down the session have completed.
+        // 3.2 Resolve promise.
+        promise.resolve();
+    });
+
+    // 4. Return promise.
 }
 
 const char* WebXRSession::activeDOMObjectName() const
index f011501..c445cc2 100644 (file)
@@ -35,6 +35,7 @@
 #include "WebXRSpace.h"
 #include "XREnvironmentBlendMode.h"
 #include "XRReferenceSpaceType.h"
+#include "XRSessionMode.h"
 #include "XRVisibilityState.h"
 #include <wtf/Ref.h>
 #include <wtf/RefCounted.h>
@@ -45,6 +46,7 @@ namespace WebCore {
 
 class XRFrameRequestCallback;
 class WebXRReferenceSpace;
+class WebXRSystem;
 struct XRRenderStateInit;
 
 class WebXRSession final : public RefCounted<WebXRSession>, public EventTargetWithInlineData, public ActiveDOMObject {
@@ -53,6 +55,7 @@ public:
     using RequestReferenceSpacePromise = DOMPromiseDeferred<IDLInterface<WebXRReferenceSpace>>;
     using EndPromise = DOMPromiseDeferred<void>;
 
+    static Ref<WebXRSession> create(Document&, WebXRSystem&, XRSessionMode, PlatformXR::Device&);
     virtual ~WebXRSession();
 
     using RefCounted<WebXRSession>::ref;
@@ -74,6 +77,8 @@ public:
     bool ended() const { return m_ended; }
 
 private:
+    WebXRSession(Document&, WebXRSystem&, XRSessionMode, PlatformXR::Device&);
+
     // EventTarget
     EventTargetInterface eventTargetInterface() const override { return WebXRSessionEventTargetInterfaceType; }
     ScriptExecutionContext* scriptExecutionContext() const override { return ActiveDOMObject::scriptExecutionContext(); }
@@ -84,11 +89,18 @@ private:
     const char* activeDOMObjectName() const override;
     void stop() override;
 
+    void shutdown();
+
     XREnvironmentBlendMode m_environmentBlendMode;
     XRVisibilityState m_visibilityState;
-    RefPtr<WebXRRenderState> m_renderState;
     RefPtr<WebXRInputSourceArray> m_inputSources;
     bool m_ended { false };
+
+    WebXRSystem& m_xrSystem;
+    XRSessionMode m_mode;
+    WeakPtr<PlatformXR::Device> m_device;
+    RefPtr<WebXRRenderState> m_activeRenderState;
+    RefPtr<WebXRRenderState> m_pendingRenderState;
 };
 
 } // namespace WebCore
index 98422ec..f88e274 100644 (file)
 
 #if ENABLE(WEBXR)
 
+#include "DOMWindow.h"
 #include "Document.h"
 #include "FeaturePolicy.h"
+#include "IDLTypes.h"
+#include "JSWebXRSession.h"
+#include "JSXRReferenceSpaceType.h"
 #include "PlatformXR.h"
 #include "RuntimeEnabledFeatures.h"
+#include "SecurityOrigin.h"
+#include "UserGestureIndicator.h"
 #include "WebXRSession.h"
+#include "XRReferenceSpaceType.h"
+#include "XRSessionInit.h"
+#include <JavaScriptCore/JSGlobalObject.h>
 #include <wtf/IsoMallocInlines.h>
+#include <wtf/Scope.h>
 
 namespace WebCore {
 
 WTF_MAKE_ISO_ALLOCATED_IMPL(WebXRSystem);
 
+WebXRSystem::DummyInlineDevice::DummyInlineDevice()
+{
+    setEnabledFeatures(XRSessionMode::Inline, { XRReferenceSpaceType::Viewer });
+}
+
 Ref<WebXRSystem> WebXRSystem::create(ScriptExecutionContext& scriptExecutionContext)
 {
     return adoptRef(*new WebXRSystem(scriptExecutionContext));
@@ -56,8 +71,7 @@ WebXRSystem::~WebXRSystem() = default;
 void WebXRSystem::ensureImmersiveXRDeviceIsSelected()
 {
     // Don't ask platform code for XR devices, we're using simulated ones.
-    // TODO: should be have a MockPlatformXR implementation instead ?
-    if (UNLIKELY(m_testingMode))
+    if (UNLIKELY(m_testingDevices))
         return;
 
     if (m_activeImmersiveDevice)
@@ -96,6 +110,20 @@ void WebXRSystem::ensureImmersiveXRDeviceIsSelected()
     // TODO: 9. Queue a task to fire an event named devicechange on the context object.
 }
 
+
+PlatformXR::Device* WebXRSystem::obtainCurrentDevice(XRSessionMode mode, const JSFeaturesArray& requiredFeatures, const JSFeaturesArray& optionalFeatures)
+{
+    if (mode == XRSessionMode::ImmersiveAr || mode == XRSessionMode::ImmersiveVr) {
+        ensureImmersiveXRDeviceIsSelected();
+        return m_activeImmersiveDevice.get();
+    }
+    if (!requiredFeatures.isEmpty() || !optionalFeatures.isEmpty())
+        return m_inlineXRDevice.get();
+
+    return &m_defaultInlineDevice;
+}
+
+
 // https://immersive-web.github.io/webxr/#dom-xrsystem-issessionsupported
 void WebXRSystem::isSessionSupported(XRSessionMode mode, IsSessionSupportedPromise&& promise)
 {
@@ -106,7 +134,7 @@ void WebXRSystem::isSessionSupported(XRSessionMode mode, IsSessionSupportedPromi
         return;
     }
 
-    // 3. If the requesting documents origin is not allowed to use the "xr-spatial-tracking" feature policy,
+    // 3. If the requesting document's origin is not allowed to use the "xr-spatial-tracking" feature policy,
     //    reject promise with a "SecurityError" DOMException and return it.
     auto document = downcast<Document>(scriptExecutionContext());
     if (!isFeaturePolicyAllowedByDocumentAndAllOwners(FeaturePolicy::Type::XRSpatialTracking, *document, LogFeaturePolicyFailure::Yes)) {
@@ -136,26 +164,265 @@ void WebXRSystem::isSessionSupported(XRSessionMode mode, IsSessionSupportedPromi
     });
 }
 
-void WebXRSystem::requestSession(XRSessionMode, const XRSessionInit&, RequestSessionPromise&&)
+// https://immersive-web.github.io/webxr/#immersive-session-request-is-allowed
+bool WebXRSystem::immersiveSessionRequestIsAllowedForGlobalObject(DOMWindow& globalObject, Document& document) const
+{
+    // 1. If the request was not made while the global object has transient
+    //    activation or when launching a web application, return false
+    // TODO: add a check for "not launching a web application".
+    if (!globalObject.hasTransientActivation())
+        return false;
+
+    // 2. If the requesting document is not considered trustworthy, return false.
+    // https://immersive-web.github.io/webxr/#trustworthy.
+    if (&document != globalObject.document())
+        return false;
+
+    // https://immersive-web.github.io/webxr/#active-and-focused
+    if (!document.hasFocus() || !document.securityOrigin().isSameOriginAs(globalObject.document()->securityOrigin()))
+        return false;
+
+    // 3. If user intent to begin an immersive session is not well understood,
+    //    either via explicit consent or implicit consent, return false
+    if (!UserGestureIndicator::processingUserGesture())
+        return false;
+
+    return true;
+}
+
+// https://immersive-web.github.io/webxr/#inline-session-request-is-allowed
+bool WebXRSystem::inlineSessionRequestIsAllowedForGlobalObject(DOMWindow& globalObject, Document& document, const XRSessionInit& init) const
+{
+    // 1. If the session request contained any required features or optional features and the request was not made
+    //    while the global object has transient activation or when launching a web application, return false.
+    bool sessionRequestContainedAnyFeature = !init.optionalFeatures.isEmpty() || !init.requiredFeatures.isEmpty();
+    if (sessionRequestContainedAnyFeature && !globalObject.hasTransientActivation() /* TODO: || !launching a web app */)
+        return false;
+
+    // 2. If the requesting document is not responsible, return false.
+    if (&document != globalObject.document())
+        return false;
+
+    return true;
+}
+
+
+struct WebXRSystem::ResolvedRequestedFeatures {
+    FeaturesArray granted;
+    FeaturesArray consentRequired;
+    FeaturesArray consentOptional;
+};
+
+#define RETURN_FALSE_OR_CONTINUE(mustReturn) { \
+    if (mustReturn) {\
+        return false; \
+    } \
+    continue; \
+}
+
+// https://immersive-web.github.io/webxr/#resolve-the-requested-features
+Optional<WebXRSystem::ResolvedRequestedFeatures> WebXRSystem::resolveRequestedFeatures(XRSessionMode mode, const XRSessionInit& init, PlatformXR::Device* device, JSC::JSGlobalObject& globalObject) const
+{
+    // 1. Let consentRequired be an empty list of DOMString.
+    // 2. Let consentOptional be an empty list of DOMString.
+    ResolvedRequestedFeatures resolvedFeatures;
+
+    // 3. Let device be the result of obtaining the current device for mode, requiredFeatures, and optionalFeatures.
+    // 4. Let granted be a list of DOMString initialized to device's list of enabled features for mode.
+    // 5. If device is null or device's list of supported modes does not contain mode, run the following steps:
+    //  5.1 Return the tuple (consentRequired, consentOptional, granted)
+    if (!device || !device->supports(mode))
+        return resolvedFeatures;
+
+    resolvedFeatures.granted = device->enabledFeatures(mode);
+
+    // 6. Add every feature descriptor in the default features table associated
+    //    with mode to the indicated feature list if it is not already present.
+    // https://immersive-web.github.io/webxr/#default-features
+    auto requiredFeaturesWithDefaultFeatures = init.requiredFeatures;
+    requiredFeaturesWithDefaultFeatures.append(convertEnumerationToJS(globalObject, XRReferenceSpaceType::Viewer));
+    if (mode == XRSessionMode::ImmersiveAr || mode == XRSessionMode::ImmersiveVr)
+        requiredFeaturesWithDefaultFeatures.append(convertEnumerationToJS(globalObject, XRReferenceSpaceType::Local));
+
+    // 7. For each feature in requiredFeatures|optionalFeatures perform the following steps:
+    // 8. For each feature in optionalFeatures perform the following steps:
+    // We're merging both loops in a single lambda. The only difference is that a failure on any required features
+    // implies cancelling the whole process while failures in optional features are just skipped.
+    enum class ParsingMode { Strict, Loose };
+    auto parseFeatures = [&device, &globalObject, mode, &resolvedFeatures] (const JSFeaturesArray& sessionFeatures, ParsingMode parsingMode) -> bool {
+        bool returnOnFailure = parsingMode == ParsingMode::Strict;
+        for (const auto& sessionFeature : sessionFeatures) {
+            // 1. If the feature is null, continue to the next entry.
+            if (sessionFeature.isNull())
+                continue;
+
+            // 2. If feature is not a valid feature descriptor, perform the following steps
+            //   2.1. Let s be the result of calling ? ToString(feature).
+            //   2.2. If s is not a valid feature descriptor or is undefined, (return null|continue to next entry).
+            //   2.3. Set feature to s.
+            auto feature = parseEnumeration<XRReferenceSpaceType>(globalObject, sessionFeature);
+            if (!feature)
+                RETURN_FALSE_OR_CONTINUE(returnOnFailure);
+
+            // 3. If feature is already in granted, continue to the next entry.
+            if (resolvedFeatures.granted.contains(feature.value()))
+                continue;
+
+            // 4. If the requesting document's origin is not allowed to use any feature policy required by feature
+            //    as indicated by the feature requirements table, (return null|continue to next entry).
+
+            // 5. If session's XR device is not capable of supporting the functionality described by feature or the
+            //    user agent has otherwise determined to reject the feature, (return null|continue to next entry).
+            if (!device->enabledFeatures(mode).contains(feature.value()))
+                RETURN_FALSE_OR_CONTINUE(returnOnFailure);
+
+            // 6. If the functionality described by feature requires explicit consent, append it to (consentRequired|consentOptional).
+            // 7. Else append feature to granted.
+            resolvedFeatures.granted.append(feature.value());
+        }
+        return true;
+    };
+
+    if (!parseFeatures(requiredFeaturesWithDefaultFeatures, ParsingMode::Strict))
+        return WTF::nullopt;
+
+    parseFeatures(init.optionalFeatures, ParsingMode::Loose);
+    return resolvedFeatures;
+}
+
+// https://immersive-web.github.io/webxr/#request-the-xr-permission
+bool WebXRSystem::isXRPermissionGranted(XRSessionMode mode, const XRSessionInit& init, PlatformXR::Device* device, JSC::JSGlobalObject& globalObject) const
 {
-    PlatformXR::Instance::singleton();
+    // 1. Set status's granted to an empty FrozenArray.
+    // 2. Let requiredFeatures be descriptor's requiredFeatures.
+    // 3. Let optionalFeatures be descriptor's optionalFeatures.
+    // 4. Let device be the result of obtaining the current device for mode, requiredFeatures, and optionalFeatures.
 
-    // When the requestSession(mode) method is invoked, the user agent MUST return a new Promise promise and run the following steps in parallel:
-    //   1. Let immersive be true if mode is "immersive-vr" or "immersive-ar", and false otherwise.
-    //   2. If immersive is true:
-    //     1. If pending immersive session is true or active immersive session is not null, reject promise with an "InvalidStateError" DOMException and abort these steps.
-    //     2. Else set pending immersive session to be true.
-    //   3. Ensure an XR device is selected.
-    //   4. If the XR device is null, reject promise with null.
-    //   5. Else if the XR device's list of supported modes does not contain mode, reject promise with a "NotSupportedError" DOMException.
-    //   6. Else If immersive is true and the algorithm is not triggered by user activation, reject promise with a "SecurityError" DOMException and abort these steps.
-    //   7. If promise was rejected and immersive is true, set pending immersive session to false.
-    //   8. If promise was rejected, abort these steps.
-    //   9. Let session be a new XRSession object.
-    //   10. Initialize the session with session and mode.
-    //   11. If immersive is true, set the active immersive session to session, and set pending immersive session to false.
-    //   12. Else append session to the list of inline sessions.
-    //   13. Resolve promise with session.
+    // 5. Let result be the result of resolving the requested features given requiredFeatures,optionalFeatures, and mode.
+    auto resolvedFeatures = resolveRequestedFeatures(mode, init, device, globalObject);
+
+    // 6. If result is null, run the following steps:
+    //  6.1. Set status's state to "denied".
+    //  6.2. Abort these steps.
+    if (!resolvedFeatures)
+        return false;
+
+    // 7. Let (consentRequired, consentOptional, granted) be the fields of result.
+    // 8. The user agent MAY at this point ask the user's permission for the calling algorithm to use any of the features
+    //    in consentRequired and consentOptional. The results of these prompts should be included when determining if there
+    //    is a clear signal of user intent for enabling these features.
+    // 9. For each feature in consentRequired perform the following steps:
+    //  9.1. The user agent MAY at this point ask the user's permission for the calling algorithm to use feature. The results
+    //       of these prompts should be included when determining if there is a clear signal of user intent to enable feature.
+    //  9.2. If a clear signal of user intent to enable feature has not been determined, set status's state to "denied" and
+    //       abort these steps.
+    //  9.3. If feature is not in granted, append feature to granted.
+    // 10. For each feature in consentOptional perform the following steps:
+    //  10.1. The user agent MAY at this point ask the user's permission for the calling algorithm to use feature. The results
+    //        of these prompts should be included when determining if there is a clear signal of user intent to enable feature.
+    //  10.2. If a clear signal of user intent to enable feature has not been determined, continue to the next entry.
+    //  10.3. If feature is not in granted, append feature to granted.
+    // 11. Set status's granted to granted.
+    // 12. Set device's list of enabled features for mode to granted.
+    // 13. Set status's state to "granted".
+    return true;
+}
+
+
+// https://immersive-web.github.io/webxr/#dom-xrsystem-requestsession
+void WebXRSystem::requestSession(Document& document, XRSessionMode mode, const XRSessionInit& init, RequestSessionPromise&& promise)
+{
+    // 1. Let promise be a new Promise.
+    // 2. Let immersive be true if mode is an immersive session mode, and false otherwise.
+    // 3. Let global object be the relevant Global object for the XRSystem on which this method was invoked.
+    bool immersive = mode == XRSessionMode::ImmersiveAr || mode == XRSessionMode::ImmersiveVr;
+    auto* globalObject = document.domWindow();
+    if (!globalObject) {
+        promise.reject(Exception { InvalidAccessError});
+        return;
+    }
+
+    // 4. Check whether the session request is allowed as follows:
+    if (immersive) {
+        if (!immersiveSessionRequestIsAllowedForGlobalObject(*globalObject, document)) {
+            promise.reject(Exception { SecurityError });
+            return;
+        }
+        if (m_pendingImmersiveSession || m_activeImmersiveSession) {
+            promise.reject(Exception { InvalidStateError });
+            return;
+        }
+        m_pendingImmersiveSession = true;
+    } else if (!inlineSessionRequestIsAllowedForGlobalObject(*globalObject, document, init)) {
+        promise.reject(Exception { SecurityError });
+        return;
+    }
+
+    // 5. Run the following steps in parallel:
+    queueTaskKeepingObjectAlive(*this, TaskSource::WebXR, [this, document = makeWeakPtr(document), immersive, init, mode, promise = WTFMove(promise)] () mutable {
+        // 5.1 Let requiredFeatures be options' requiredFeatures.
+        // 5.2 Let optionalFeatures be options' optionalFeatures.
+        // 5.3 Set device to the result of obtaining the current device for mode, requiredFeatures, and optionalFeatures.
+        auto* device = obtainCurrentDevice(mode, init.requiredFeatures, init.optionalFeatures);
+
+        // 5.4 Queue a task to perform the following steps:
+        queueTaskKeepingObjectAlive(*this, TaskSource::WebXR, [this, document = WTFMove(document), device = makeWeakPtr(device), immersive, init, mode, promise = WTFMove(promise)] () mutable {
+            auto rejectPromiseWithNotSupportedError = makeScopeExit([&] () {
+                promise.reject(Exception { NotSupportedError });
+                m_pendingImmersiveSession = false;
+            });
+
+            // 5.4.1 If device is null or device's list of supported modes does not contain mode, run the following steps:
+            //  - Reject promise with a "NotSupportedError" DOMException.
+            //  - If immersive is true, set pending immersive session to false.
+            //  - Abort these steps.
+            if (!device || !device->supports(mode))
+                return;
+
+            auto* globalObject = document ? document->execState() : nullptr;
+            if (!globalObject)
+                return;
+
+            // WebKit does not currently support the Permissions API. https://w3c.github.io/permissions/
+            // However we do implement here the permission request algorithm without the
+            // Permissions API bits as it handles, among others, the session features parsing. We also
+            // do it here before creating the session as there is no need to do it on advance.
+            // TODO: we just perform basic checks without asking any permission to the user so far. Maybe we should implement
+            // a mechanism similar to what others do involving passing a message to the UI process.
+
+            // 5.4.4 Let descriptor be an XRPermissionDescriptor initialized with session, requiredFeatures, and optionalFeatures
+            // 5.4.5 Let status be an XRPermissionStatus, initially null
+            // 5.4.6 Request the xr permission with descriptor and status.
+            // 5.4.7 If status' state is "denied" run the following steps: (same as above in 5.4.1)
+            if (!isXRPermissionGranted(mode, init, device.get(), *globalObject))
+                return;
+
+            // 5.4.2 Let session be a new XRSession object.
+            // 5.4.3 Initialize the session with session, mode, and device.
+            auto session = WebXRSession::create(*document, *this, mode, *device);
+
+            // 5.4.8 Potentially set the active immersive session as follows:
+            if (immersive) {
+                m_activeImmersiveSession = session.copyRef();
+                m_pendingImmersiveSession = false;
+            } else
+                m_inlineSessions.add(session.copyRef());
+
+            // 5.4.9 Resolve promise with session.
+            promise.resolve(session);
+            rejectPromiseWithNotSupportedError.release();
+
+            // TODO:
+            // 5.4.10 Queue a task to perform the following steps: NOTE: These steps ensure that initial inputsourceschange
+            // events occur after the initial session is resolved.
+            //     1. Set session's promise resolved flag to true.
+            //     2. Let sources be any existing input sources attached to session.
+            //     3. If sources is non-empty, perform the following steps:
+            //        1. Set session's list of active XR input sources to sources.
+            //        2. Fire an XRInputSourcesChangeEvent named inputsourceschange on session with added set to sources.
+
+        });
+    });
 }
 
 const char* WebXRSystem::activeDOMObjectName() const
@@ -167,32 +434,40 @@ void WebXRSystem::stop()
 {
 }
 
-void WebXRSystem::registerSimulatedXRDeviceForTesting(const PlatformXR::Device& device)
+void WebXRSystem::registerSimulatedXRDeviceForTesting(PlatformXR::Device& device)
 {
     if (!RuntimeEnabledFeatures::sharedFeatures().webXREnabled())
         return;
-    m_testingMode = true;
+    m_testingDevices++;
     if (device.supports(PlatformXR::SessionMode::ImmersiveVr) || device.supports(PlatformXR::SessionMode::ImmersiveAr)) {
-        m_immersiveDevices.append(makeWeakPtr(device));
-        m_activeImmersiveDevice = m_immersiveDevices.last();
+        m_immersiveDevices.add(device);
+        m_activeImmersiveDevice = makeWeakPtr(device);
     }
     if (device.supports(PlatformXR::SessionMode::Inline))
         m_inlineXRDevice = makeWeakPtr(device);
 }
 
-void WebXRSystem::unregisterSimulatedXRDeviceForTesting(PlatformXR::Device* device)
+void WebXRSystem::unregisterSimulatedXRDeviceForTesting(PlatformXR::Device& device)
 {
     if (!RuntimeEnabledFeatures::sharedFeatures().webXREnabled())
         return;
     ASSERT(m_immersiveDevices.contains(device));
-    m_immersiveDevices.removeFirst(device);
-    if (m_activeImmersiveDevice == device)
+    ASSERT(m_testingDevices);
+    m_immersiveDevices.remove(device);
+    if (m_activeImmersiveDevice == &device)
         m_activeImmersiveDevice = nullptr;
-    if (m_inlineXRDevice == device)
+    if (m_inlineXRDevice == &device)
         m_inlineXRDevice = makeWeakPtr(m_defaultInlineDevice);
-    m_testingMode = false;
+    m_testingDevices--;
 }
 
+void WebXRSystem::sessionEnded(WebXRSession& session)
+{
+    if (m_activeImmersiveSession == &session)
+        m_activeImmersiveSession = nullptr;
+
+    m_inlineSessions.remove(session);
+}
 
 } // namespace WebCore
 
index 2e9ce84..9aad128 100644 (file)
 #include "JSDOMPromiseDeferred.h"
 #include "WebGLContextAttributes.h"
 #include "WebGLRenderingContextBase.h"
+#include "XRReferenceSpaceType.h"
 #include "XRSessionMode.h"
+#include <wtf/HashSet.h>
 #include <wtf/IsoMalloc.h>
+#include <wtf/Optional.h>
 #include <wtf/RefCounted.h>
+#include <wtf/WeakHashSet.h>
 #include <wtf/WeakPtr.h>
 
+namespace JSC {
+class JSGlobalObject;
+}
+
 namespace WebCore {
 
+class DOMWindow;
 class ScriptExecutionContext;
 class WebXRSession;
 struct XRSessionInit;
@@ -57,15 +66,17 @@ public:
     using RefCounted<WebXRSystem>::deref;
 
     void isSessionSupported(XRSessionMode, IsSessionSupportedPromise&&);
-    void requestSession(XRSessionMode, const XRSessionInit&, RequestSessionPromise&&);
+    void requestSession(Document&, XRSessionMode, const XRSessionInit&, RequestSessionPromise&&);
 
     // This is also needed by WebGLRenderingContextBase::makeXRCompatible() and HTMLCanvasElement::createContextWebGL().
     void ensureImmersiveXRDeviceIsSelected();
     bool hasActiveImmersiveXRDevice() { return !!m_activeImmersiveDevice; }
 
+    void sessionEnded(WebXRSession&);
+
     // For testing purpouses only.
-    void registerSimulatedXRDeviceForTesting(const PlatformXR::Device&);
-    void unregisterSimulatedXRDeviceForTesting(PlatformXR::Device*);
+    void registerSimulatedXRDeviceForTesting(PlatformXR::Device&);
+    void unregisterSimulatedXRDeviceForTesting(PlatformXR::Device&);
 
 protected:
     // EventTarget
@@ -81,23 +92,36 @@ protected:
 private:
     WebXRSystem(ScriptExecutionContext&);
 
+    using FeaturesArray = PlatformXR::Device::ListOfEnabledFeatures;
+    using JSFeaturesArray = Vector<JSC::JSValue>;
+    PlatformXR::Device* obtainCurrentDevice(XRSessionMode, const JSFeaturesArray& requiredFeatures, const JSFeaturesArray& optionalFeatures);
+
+    bool immersiveSessionRequestIsAllowedForGlobalObject(DOMWindow&, Document&) const;
+    bool inlineSessionRequestIsAllowedForGlobalObject(DOMWindow&, Document&, const XRSessionInit&) const;
+
+    struct ResolvedRequestedFeatures;
+    Optional<ResolvedRequestedFeatures> resolveRequestedFeatures(XRSessionMode, const XRSessionInit&, PlatformXR::Device*, JSC::JSGlobalObject&) const;
+    bool isXRPermissionGranted(XRSessionMode, const XRSessionInit&, PlatformXR::Device*, JSC::JSGlobalObject&) const;
+
     // https://immersive-web.github.io/webxr/#default-inline-xr-device
     class DummyInlineDevice final : public PlatformXR::Device {
     public:
-        DummyInlineDevice()
-        {
-            m_supportedModes.append(XRSessionMode::Inline);
-        }
+        DummyInlineDevice();
     };
     DummyInlineDevice m_defaultInlineDevice;
 
     bool m_immersiveXRDevicesHaveBeenEnumerated { false };
-    bool m_testingMode { false };
+    uint m_testingDevices { 0 };
+
+    bool m_pendingImmersiveSession { false };
+    RefPtr<WebXRSession> m_activeImmersiveSession;
 
-    WeakPtr<WebXRSession> m_activeImmersiveSession;
+    // We use a set here although the specs talk about a list of inline sessions
+    // https://immersive-web.github.io/webxr/#list-of-inline-sessions.
+    HashSet<Ref<WebXRSession>> m_inlineSessions;
 
     WeakPtr<PlatformXR::Device> m_activeImmersiveDevice;
-    Vector<WeakPtr<PlatformXR::Device>> m_immersiveDevices;
+    WeakHashSet<PlatformXR::Device> m_immersiveDevices;
     WeakPtr<PlatformXR::Device> m_inlineXRDevice;
 };
 
index cad27e3..2c7c008 100644 (file)
@@ -33,7 +33,7 @@
 ] interface WebXRSystem : EventTarget {
     // Methods
     Promise<void> isSessionSupported(XRSessionMode mode);
-    [NewObject] Promise<WebXRSession> requestSession(XRSessionMode mode, optional XRSessionInit options);
+    [NewObject, CallWith=Document] Promise<WebXRSession> requestSession(XRSessionMode mode, optional XRSessionInit options);
 
     // Events
     attribute EventHandler ondevicechange;
index 8548eee..2f794a1 100644 (file)
@@ -39,6 +39,7 @@ enum class TaskSource : uint8_t {
     PostedMessageQueue,
     UserInteraction,
     WebGL,
+    WebXR,
 
     // Internal to WebCore
     InternalAsyncTask, // Safe to re-order or delay.
index e02f09e..0b0413b 100644 (file)
 #pragma once
 
 #include <memory>
+#include <wtf/HashMap.h>
 #include <wtf/Vector.h>
 #include <wtf/WeakPtr.h>
 
 namespace PlatformXR {
 
-enum class SessionMode {
+enum class SessionMode : uint8_t {
     Inline,
     ImmersiveVr,
     ImmersiveAr,
@@ -48,18 +49,19 @@ public:
     Device();
     DeviceId id() const { return m_id; }
 
-    using ListOfSupportedModes = Vector<SessionMode>;
     using ListOfEnabledFeatures = Vector<ReferenceSpaceType>;
-
-    bool supports(SessionMode mode) const { return m_supportedModes.contains(mode); }
-    void setSupportedModes(const ListOfSupportedModes& modes) { m_supportedModes = modes; }
-    void setEnabledFeatures(const ListOfEnabledFeatures& features) { m_enabledFeatures = features; }
+    bool supports(SessionMode mode) const { return m_enabledFeaturesMap.contains(mode); }
+    void setEnabledFeatures(SessionMode mode, const ListOfEnabledFeatures& features) { m_enabledFeaturesMap.set(mode, features); }
+    ListOfEnabledFeatures enabledFeatures(SessionMode mode) const { return m_enabledFeaturesMap.get(mode); }
 
     inline bool operator==(const Device& other) const { return m_id == other.m_id; }
 
 protected:
-    ListOfSupportedModes m_supportedModes;
-    ListOfEnabledFeatures m_enabledFeatures;
+    // https://immersive-web.github.io/webxr/#xr-device-concept
+    // Each XR device has a list of enabled features for each XRSessionMode in its list of supported modes,
+    // which is a list of feature descriptors which MUST be initially an empty list.
+    using EnabledFeaturesPerModeMap = WTF::HashMap<SessionMode, ListOfEnabledFeatures, WTF::IntHash<SessionMode>, WTF::StrongEnumHashTraits<SessionMode>>;
+    EnabledFeaturesPerModeMap m_enabledFeaturesMap;
 
 private:
     DeviceId m_id;
index a0a85d2..4a64705 100644 (file)
@@ -24,6 +24,7 @@
 
 #if USE_OPENXR
 #include <openxr/openxr.h>
+#include <wtf/Optional.h>
 #include <wtf/text/StringConcatenateNumbers.h>
 #include <wtf/text/WTFString.h>
 #endif // USE_OPENXR
@@ -67,7 +68,7 @@ public:
     ~Impl();
 
 #if USE_OPENXR
-    void collectSupportedSessionModes(Device&, XrSystemId);
+    Optional<Vector<SessionMode>> collectSupportedSessionModes(Device&, XrSystemId);
     XrInstance m_instance { XR_NULL_HANDLE };
 #endif // USE_OPENXR
 };
@@ -150,13 +151,13 @@ Instance::Impl::~Impl()
 
 #if USE_OPENXR
 
-void Instance::Impl::collectSupportedSessionModes(Device& device, XrSystemId systemId)
+Optional<Vector<SessionMode>> Instance::Impl::collectSupportedSessionModes(Device& device, XrSystemId systemId)
 {
     uint32_t viewConfigurationCount;
     XrResult result = xrEnumerateViewConfigurations(m_instance, systemId, 0, &viewConfigurationCount, nullptr);
     if (result != XR_SUCCESS) {
         WTFLogAlways("xrEnumerateViewConfigurations(): error %s\n", resultToString(result, m_instance).utf8().data());
-        return;
+        return WTF::nullopt;
     }
 
     XrViewConfigurationType viewConfigurations[viewConfigurationCount];
@@ -164,23 +165,23 @@ void Instance::Impl::collectSupportedSessionModes(Device& device, XrSystemId sys
     if (result != XR_SUCCESS) {
         WTFLogAlways("xrEnumerateViewConfigurations(): error %s\n", resultToString(result, m_instance).utf8().data());
         WTFLogAlways("xrEnumerateViewConfigurations(): error %s\n", resultToString(result, m_instance).utf8().data());
-        return;
+        return WTF::nullopt;
     }
 
-    Device::ListOfSupportedModes supportedModes;
+    Vector<SessionMode> supportedModes;
     for (uint32_t i = 0; i < viewConfigurationCount; ++i) {
         auto viewConfigurationProperties = createStructure<XrViewConfigurationProperties, XR_TYPE_VIEW_CONFIGURATION_PROPERTIES>();
         result = xrGetViewConfigurationProperties(m_instance, systemId, viewConfigurations[i], &viewConfigurationProperties);
         if (result != XR_SUCCESS) {
             WTFLogAlways("xrGetViewConfigurationProperties(): error %s\n", resultToString(result, m_instance).utf8().data());
-            return;
+            return WTF::nullopt;
         }
         if (viewConfigurationProperties.viewConfigurationType == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO)
             supportedModes.append(SessionMode::ImmersiveAr);
         else if (viewConfigurationProperties.viewConfigurationType == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO)
             supportedModes.append(SessionMode::ImmersiveVr);
     }
-    device.setSupportedModes(supportedModes);
+    return supportedModes;
 }
 
 #endif // USE_OPENXR
@@ -218,7 +219,14 @@ void Instance::enumerateImmersiveXRDevices()
     }
 
     auto device = makeUnique<Device>();
-    m_impl->collectSupportedSessionModes(*device, systemId);
+    auto sessionModes = m_impl->collectSupportedSessionModes(*device, systemId);
+    if (sessionModes) {
+        for (auto& mode : sessionModes.value()) {
+            // TODO: fill in features
+            device->setEnabledFeatures(mode, { });
+        }
+    }
+
     m_immersiveXRDevices.append(WTFMove(device));
 #endif // USE_OPENXR
 }
index b20a433..20b2cb5 100644 (file)
@@ -54,13 +54,14 @@ void WebXRTest::simulateDeviceConnection(ScriptExecutionContext& context, const
             simulatedDevice.views().append(view.releaseReturnValue());
         }
 
+        Vector<XRReferenceSpaceType> features;
         if (init.supportedFeatures) {
-            Vector<XRReferenceSpaceType> features;
-            for (auto& feature : init.supportedFeatures.value()) {
-                if (auto referenceSpaceType = parseEnumeration<XRReferenceSpaceType>(*context.execState(), feature))
-                    features.append(referenceSpaceType.value());
+            if (auto* globalObject = context.execState()) {
+                for (auto& feature : init.supportedFeatures.value()) {
+                    if (auto referenceSpaceType = parseEnumeration<XRReferenceSpaceType>(*globalObject, feature))
+                        features.append(referenceSpaceType.value());
+                }
             }
-            simulatedDevice.setEnabledFeatures(features);
         }
 
         if (init.boundsCoordinates) {
@@ -87,7 +88,9 @@ void WebXRTest::simulateDeviceConnection(ScriptExecutionContext& context, const
             if (init.supportsImmersive)
                 supportedModes.append(XRSessionMode::ImmersiveVr);
         }
-        simulatedDevice.setSupportedModes(supportedModes);
+
+        for (auto& mode : supportedModes)
+            simulatedDevice.setEnabledFeatures(mode, features);
 
         m_context->registerSimulatedXRDeviceForTesting(simulatedDevice);
 
@@ -107,7 +110,7 @@ void WebXRTest::simulateUserActivation(Document& document, XRSimulateUserActivat
 void WebXRTest::disconnectAllDevices(DOMPromiseDeferred<void>&& promise)
 {
     for (auto& device : m_devices)
-        m_context->unregisterSimulatedXRDeviceForTesting(&device->simulatedXRDevice());
+        m_context->unregisterSimulatedXRDeviceForTesting(device->simulatedXRDevice());
     promise.resolve();
 }