script/dom/webxr/
xrsystem.rs1use std::cell::Cell;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use ipc_channel::ipc::{self as ipc_crate, IpcReceiver};
10use ipc_channel::router::ROUTER;
11use js::context::JSContext;
12use js::realm::CurrentRealm;
13use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
14use profile_traits::ipc;
15use script_bindings::cell::DomRefCell;
16use script_bindings::reflector::reflect_dom_object_with_cx;
17use servo_base::id::PipelineId;
18use servo_config::pref;
19use webxr_api::{Error as XRError, Frame, Session, SessionInit, SessionMode};
20
21use crate::conversions::Convert;
22use crate::dom::bindings::codegen::Bindings::XRSystemBinding::{
23 XRSessionInit, XRSessionMode, XRSystemMethods,
24};
25use crate::dom::bindings::conversions::{ConversionResult, FromJSValConvertible};
26use crate::dom::bindings::error::Error;
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
29use crate::dom::bindings::reflector::DomGlobal;
30use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
31use crate::dom::bindings::trace::RootedTraceableBox;
32use crate::dom::eventtarget::EventTarget;
33use crate::dom::gamepad::Gamepad;
34use crate::dom::promise::Promise;
35use crate::dom::window::Window;
36use crate::dom::xrsession::XRSession;
37use crate::dom::xrtest::XRTest;
38use crate::script_thread::ScriptThread;
39
40#[dom_struct]
41pub(crate) struct XRSystem {
42 eventtarget: EventTarget,
43 gamepads: DomRefCell<Vec<Dom<Gamepad>>>,
44 pending_immersive_session: Cell<bool>,
45 active_immersive_session: MutNullableDom<XRSession>,
46 active_inline_sessions: DomRefCell<Vec<Dom<XRSession>>>,
47 test: MutNullableDom<XRTest>,
48 #[no_trace]
49 pipeline: PipelineId,
50}
51
52impl XRSystem {
53 fn new_inherited(pipeline: PipelineId) -> XRSystem {
54 XRSystem {
55 eventtarget: EventTarget::new_inherited(),
56 gamepads: DomRefCell::new(Vec::new()),
57 pending_immersive_session: Cell::new(false),
58 active_immersive_session: Default::default(),
59 active_inline_sessions: DomRefCell::new(Vec::new()),
60 test: Default::default(),
61 pipeline,
62 }
63 }
64
65 pub(crate) fn new(cx: &mut JSContext, window: &Window) -> DomRoot<XRSystem> {
66 reflect_dom_object_with_cx(
67 Box::new(XRSystem::new_inherited(window.pipeline_id())),
68 window,
69 cx,
70 )
71 }
72
73 pub(crate) fn pending_or_active_session(&self) -> bool {
74 self.pending_immersive_session.get() || self.active_immersive_session.get().is_some()
75 }
76
77 pub(crate) fn set_pending(&self) {
78 self.pending_immersive_session.set(true)
79 }
80
81 pub(crate) fn set_active_immersive_session(&self, session: &XRSession) {
82 self.pending_immersive_session.set(false);
85 self.active_immersive_session.set(Some(session))
86 }
87
88 pub(crate) fn end_session(&self, session: &XRSession) {
90 if let Some(active) = self.active_immersive_session.get() &&
92 Dom::from_ref(&*active) == Dom::from_ref(session)
93 {
94 self.active_immersive_session.set(None);
95 session.dirty_layers();
98 }
99 self.active_inline_sessions
100 .borrow_mut()
101 .retain(|sess| Dom::from_ref(&**sess) != Dom::from_ref(session));
102 }
103}
104
105impl Convert<SessionMode> for XRSessionMode {
106 fn convert(self) -> SessionMode {
107 match self {
108 XRSessionMode::Immersive_vr => SessionMode::ImmersiveVR,
109 XRSessionMode::Immersive_ar => SessionMode::ImmersiveAR,
110 XRSessionMode::Inline => SessionMode::Inline,
111 }
112 }
113}
114
115impl XRSystemMethods<crate::DomTypeHolder> for XRSystem {
116 fn IsSessionSupported(&self, cx: &mut CurrentRealm, mode: XRSessionMode) -> Rc<Promise> {
118 let promise = Promise::new_in_realm(cx);
120 let mut trusted = Some(TrustedPromise::new(promise.clone()));
121 let global = self.global();
122 let task_source = global
123 .task_manager()
124 .dom_manipulation_task_source()
125 .to_sendable();
126
127 let callback =
128 ProfileGenericCallback::new(global.time_profiler_chan().clone(), move |message| {
129 let trusted = if let Some(trusted) = trusted.take() {
131 trusted
132 } else {
133 error!("supportsSession callback called twice!");
134 return;
135 };
136 let message: Result<(), webxr_api::Error> = if let Ok(message) = message {
137 message
138 } else {
139 error!("supportsSession callback given incorrect payload");
140 return;
141 };
142 if let Ok(()) = message {
143 task_source.queue(trusted.resolve_task(true));
144 } else {
145 task_source.queue(trusted.resolve_task(false));
146 };
147 })
148 .expect("Could not create callback");
149
150 if let Some(mut r) = global.as_window().webxr_registry() {
151 r.supports_session(mode.convert(), callback);
152 }
153
154 promise
155 }
156
157 fn RequestSession(
159 &self,
160 realm: &mut CurrentRealm,
161 mode: XRSessionMode,
162 init: RootedTraceableBox<XRSessionInit>,
163 ) -> Rc<Promise> {
164 let global = self.global();
165 let window = global.as_window();
166 let promise = Promise::new_in_realm(realm);
167
168 if mode != XRSessionMode::Inline {
169 if !ScriptThread::is_user_interacting() {
170 if pref!(dom_webxr_unsafe_assume_user_intent) {
171 warn!(
172 "The dom.webxr.unsafe-assume-user-intent preference assumes user intent to enter WebXR."
173 );
174 } else {
175 promise.reject_error_with_cx(realm, Error::Security(None));
176 return promise;
177 }
178 }
179
180 if self.pending_or_active_session() {
181 promise.reject_error_with_cx(realm, Error::InvalidState(None));
182 return promise;
183 }
184
185 self.set_pending();
186 }
187
188 let mut required_features = vec![];
189 let mut optional_features = vec![];
190
191 if let Some(ref r) = init.requiredFeatures {
192 for feature in r {
193 if let Ok(ConversionResult::Success(s)) =
194 String::safe_from_jsval(realm, feature.handle(), ())
195 {
196 required_features.push(s)
197 } else {
198 warn!("Unable to convert required feature to string");
199 if mode != XRSessionMode::Inline {
200 self.pending_immersive_session.set(false);
201 }
202 promise.reject_error_with_cx(realm, Error::NotSupported(None));
203 return promise;
204 }
205 }
206 }
207
208 if let Some(ref o) = init.optionalFeatures {
209 for feature in o {
210 if let Ok(ConversionResult::Success(s)) =
211 String::safe_from_jsval(realm, feature.handle(), ())
212 {
213 optional_features.push(s)
214 } else {
215 warn!("Unable to convert optional feature to string");
216 }
217 }
218 }
219
220 if !required_features.contains(&"viewer".to_string()) {
221 required_features.push("viewer".to_string());
222 }
223
224 if !required_features.contains(&"local".to_string()) && mode != XRSessionMode::Inline {
225 required_features.push("local".to_string());
226 }
227
228 let init = SessionInit {
229 required_features,
230 optional_features,
231 first_person_observer_view: pref!(dom_webxr_first_person_observer_view),
232 };
233
234 let mut trusted = Some(TrustedPromise::new(promise.clone()));
235 let this = Trusted::new(self);
236 let task_source = global
237 .task_manager()
238 .dom_manipulation_task_source()
239 .to_sendable();
240 let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
241 let (frame_sender, frame_receiver) = ipc_crate::channel().unwrap();
242 let mut frame_receiver = Some(frame_receiver);
243 ROUTER.add_typed_route(
244 receiver.to_ipc_receiver(),
245 Box::new(move |message| {
246 let trusted = trusted.take().unwrap();
248 let this = this.clone();
249 let frame_receiver = frame_receiver.take().unwrap();
250 let message: Result<Session, webxr_api::Error> = if let Ok(message) = message {
251 message
252 } else {
253 error!("requestSession callback given incorrect payload");
254 return;
255 };
256 task_source.queue(task!(request_session: move |cx| {
257 this.root().session_obtained(cx, message, trusted.root(), mode, frame_receiver);
258 }));
259 }),
260 );
261 if let Some(mut r) = window.webxr_registry() {
262 r.request_session(mode.convert(), init, sender, frame_sender);
263 }
264 promise
265 }
266
267 fn Test(&self, cx: &mut JSContext) -> DomRoot<XRTest> {
269 self.test.or_init(|| XRTest::new(cx, &self.global()))
270 }
271}
272
273impl XRSystem {
274 fn session_obtained(
275 &self,
276 cx: &mut JSContext,
277 response: Result<Session, XRError>,
278 promise: Rc<Promise>,
279 mode: XRSessionMode,
280 frame_receiver: IpcReceiver<Frame>,
281 ) {
282 let session = match response {
283 Ok(session) => session,
284 Err(e) => {
285 warn!("Error requesting XR session: {:?}", e);
286 if mode != XRSessionMode::Inline {
287 self.pending_immersive_session.set(false);
288 }
289 promise.reject_error_with_cx(cx, Error::NotSupported(None));
290 return;
291 },
292 };
293 let session = XRSession::new(cx, self.global().as_window(), session, mode, frame_receiver);
294 if mode == XRSessionMode::Inline {
295 self.active_inline_sessions
296 .borrow_mut()
297 .push(Dom::from_ref(&*session));
298 } else {
299 self.set_active_immersive_session(&session);
300 }
301 promise.resolve_native_with_cx(cx, &session);
302 session.setup_initial_inputs();
305 }
306
307 pub(crate) fn dispatch_sessionavailable(&self) {
309 let xr = Trusted::new(self);
310 self.global()
311 .task_manager()
312 .dom_manipulation_task_source()
313 .queue(task!(fire_sessionavailable_event: move |cx| {
314 let xr = xr.root();
316 let _guard = ScriptThread::user_interacting_guard();
317 xr.upcast::<EventTarget>().fire_bubbling_event(cx, atom!("sessionavailable"));
318 }));
319 }
320}