diff --git a/components/script/dom/vrdisplay.rs b/components/script/dom/vrdisplay.rs index 6c4bea1568c..dd8f29195b7 100644 --- a/components/script/dom/vrdisplay.rs +++ b/components/script/dom/vrdisplay.rs @@ -4,6 +4,7 @@ use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods; use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods; use crate::dom::bindings::codegen::Bindings::VRDisplayBinding; use crate::dom::bindings::codegen::Bindings::VRDisplayBinding::VRDisplayMethods; @@ -13,6 +14,7 @@ use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGL use crate::dom::bindings::codegen::Bindings::WindowBinding::FrameRequestCallback; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRFrameRequestCallback; +use crate::dom::bindings::error::Error; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::num::Finite; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; @@ -347,6 +349,24 @@ impl VRDisplayMethods for VRDisplay { }, }; + // WebVR spec: Repeat calls while already presenting will update the VRLayers being displayed. + if self.presenting.get() { + *self.layer.borrow_mut() = layer_bounds; + self.layer_ctx.set(Some(&layer_ctx)); + promise.resolve_native(&()); + return promise; + } + + let xr = self.global().as_window().Navigator().Xr(); + + if xr.pending_or_active_session() { + // WebVR spec doesn't mandate anything here, however + // the WebXR spec expects there to be only one immersive XR session at a time, + // and WebVR is deprecated + promise.reject_error(Error::InvalidState); + return promise; + } + self.request_present(layer_bounds, Some(&layer_ctx), Some(promise.clone()), |p| { p.resolve_native(&()) }); @@ -447,14 +467,6 @@ impl VRDisplay { ) where F: FnOnce(Rc) + Send + 'static, { - // WebVR spec: Repeat calls while already presenting will update the VRLayers being displayed. - if self.presenting.get() { - *self.layer.borrow_mut() = layer_bounds; - self.layer_ctx.set(ctx); - promise.map(resolve); - return; - } - // Request Present let (sender, receiver) = ipc::channel(self.global().time_profiler_chan().clone()).unwrap(); self.webvr_thread() @@ -557,6 +569,8 @@ impl VRDisplay { fn init_present(&self) { self.presenting.set(true); + let xr = self.global().as_window().Navigator().Xr(); + xr.set_active_immersive_session(&self); let (sync_sender, sync_receiver) = webgl_channel().unwrap(); *self.frame_data_receiver.borrow_mut() = Some(sync_receiver); @@ -623,6 +637,8 @@ impl VRDisplay { fn stop_present(&self) { self.presenting.set(false); + let xr = self.global().as_window().Navigator().Xr(); + xr.deactivate_session(); *self.frame_data_receiver.borrow_mut() = None; let api_sender = self.layer_ctx.get().unwrap().webgl_sender(); diff --git a/components/script/dom/webidls/XRSession.webidl b/components/script/dom/webidls/XRSession.webidl index 7cf25bf8c36..a07fa355e6e 100644 --- a/components/script/dom/webidls/XRSession.webidl +++ b/components/script/dom/webidls/XRSession.webidl @@ -17,7 +17,7 @@ interface XRSession : EventTarget { // // Attributes readonly attribute XRSessionMode mode; // readonly attribute XRPresentationContext outputContext; - // readonly attribute XREnvironmentBlendMode environmentBlendMode; + readonly attribute XREnvironmentBlendMode environmentBlendMode; attribute double depthNear; attribute double depthFar; diff --git a/components/script/dom/xr.rs b/components/script/dom/xr.rs index 3e18ee0d279..13838ac2b20 100644 --- a/components/script/dom/xr.rs +++ b/components/script/dom/xr.rs @@ -10,7 +10,7 @@ use crate::dom::bindings::codegen::Bindings::XRBinding::{XRMethods, XRSessionMod use crate::dom::bindings::error::Error; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; -use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::gamepad::Gamepad; @@ -23,6 +23,7 @@ use crate::dom::xrsession::XRSession; use dom_struct::dom_struct; use ipc_channel::ipc::IpcSender; use profile_traits::ipc; +use std::cell::Cell; use std::rc::Rc; use webvr_traits::{WebVRDisplayData, WebVRDisplayEvent, WebVREvent, WebVRMsg}; use webvr_traits::{WebVRGamepadData, WebVRGamepadEvent, WebVRGamepadState}; @@ -32,6 +33,8 @@ pub struct XR { eventtarget: EventTarget, displays: DomRefCell>>, gamepads: DomRefCell>>, + pending_immersive_session: Cell, + active_immersive_session: MutNullableDom, } impl XR { @@ -40,6 +43,8 @@ impl XR { eventtarget: EventTarget::new_inherited(), displays: DomRefCell::new(Vec::new()), gamepads: DomRefCell::new(Vec::new()), + pending_immersive_session: Cell::new(false), + active_immersive_session: Default::default(), } } @@ -48,6 +53,26 @@ impl XR { root.register(); root } + + pub fn pending_or_active_session(&self) -> bool { + self.pending_immersive_session.get() || self.active_immersive_session.get().is_some() + } + + pub fn set_pending(&self) { + self.pending_immersive_session.set(true) + } + + pub fn set_active_immersive_session(&self, session: &VRDisplay) { + // XXXManishearth when we support non-immersive (inline) sessions we should + // ensure they never reach these codepaths + self.pending_immersive_session.set(false); + self.active_immersive_session.set(Some(session)) + } + + pub fn deactivate_session(&self) { + self.pending_immersive_session.set(false); + self.active_immersive_session.set(None) + } } impl Drop for XR { @@ -79,6 +104,13 @@ impl XRMethods for XR { return promise; } + if self.pending_or_active_session() { + promise.reject_error(Error::InvalidState); + return promise; + } + // we set pending immersive session to true further down + // to handle rejections in a cleaner way + let displays = self.get_displays(); let displays = match displays { @@ -94,6 +126,8 @@ impl XRMethods for XR { promise.reject_error(Error::Security); } + self.set_pending(); + let session = XRSession::new(&self.global(), &displays[0]); session.xr_present(promise.clone()); promise diff --git a/components/script/dom/xrsession.rs b/components/script/dom/xrsession.rs index 93837255f37..997d88b34bf 100644 --- a/components/script/dom/xrsession.rs +++ b/components/script/dom/xrsession.rs @@ -5,6 +5,7 @@ use crate::dom::bindings::codegen::Bindings::VRDisplayBinding::VRDisplayMethods; use crate::dom::bindings::codegen::Bindings::XRBinding::XRSessionMode; use crate::dom::bindings::codegen::Bindings::XRSessionBinding; +use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XREnvironmentBlendMode; use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRFrameRequestCallback; use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRSessionMethods; use crate::dom::bindings::codegen::Bindings::XRWebGLLayerBinding::XRWebGLLayerMethods; @@ -26,6 +27,7 @@ pub struct XRSession { eventtarget: EventTarget, display: Dom, base_layer: MutNullableDom, + blend_mode: XREnvironmentBlendMode, } impl XRSession { @@ -34,6 +36,8 @@ impl XRSession { eventtarget: EventTarget::new_inherited(), display: Dom::from_ref(display), base_layer: Default::default(), + // we don't yet support any AR devices + blend_mode: XREnvironmentBlendMode::Opaque, } } @@ -102,4 +106,9 @@ impl XRSessionMethods for XRSession { fn CancelAnimationFrame(&self, frame: i32) { self.display.xr_cancel_raf(frame) } + + /// https://immersive-web.github.io/webxr/#dom-xrsession-environmentblendmode + fn EnvironmentBlendMode(&self) -> XREnvironmentBlendMode { + self.blend_mode + } }