1use std::cell::Cell;
6use std::rc::Rc;
7
8use base::id::PipelineId;
9use dom_struct::dom_struct;
10use ipc_channel::ipc::{self as ipc_crate, IpcReceiver};
11use ipc_channel::router::ROUTER;
12use profile_traits::ipc;
13use servo_config::pref;
14use webxr_api::{Error as XRError, Frame, Session, SessionInit, SessionMode};
15
16use crate::conversions::Convert;
17use crate::dom::bindings::cell::DomRefCell;
18use crate::dom::bindings::codegen::Bindings::XRSystemBinding::{
19 XRSessionInit, XRSessionMode, XRSystemMethods,
20};
21use crate::dom::bindings::conversions::{ConversionResult, SafeFromJSValConvertible};
22use crate::dom::bindings::error::Error;
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
25use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
26use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
27use crate::dom::bindings::trace::RootedTraceableBox;
28use crate::dom::eventtarget::EventTarget;
29use crate::dom::gamepad::Gamepad;
30use crate::dom::globalscope::GlobalScope;
31use crate::dom::promise::Promise;
32use crate::dom::window::Window;
33use crate::dom::xrsession::XRSession;
34use crate::dom::xrtest::XRTest;
35use crate::realms::InRealm;
36use crate::script_runtime::CanGc;
37use crate::script_thread::ScriptThread;
38
39#[dom_struct]
40pub(crate) struct XRSystem {
41 eventtarget: EventTarget,
42 gamepads: DomRefCell<Vec<Dom<Gamepad>>>,
43 pending_immersive_session: Cell<bool>,
44 active_immersive_session: MutNullableDom<XRSession>,
45 active_inline_sessions: DomRefCell<Vec<Dom<XRSession>>>,
46 test: MutNullableDom<XRTest>,
47 #[no_trace]
48 pipeline: PipelineId,
49}
50
51impl XRSystem {
52 fn new_inherited(pipeline: PipelineId) -> XRSystem {
53 XRSystem {
54 eventtarget: EventTarget::new_inherited(),
55 gamepads: DomRefCell::new(Vec::new()),
56 pending_immersive_session: Cell::new(false),
57 active_immersive_session: Default::default(),
58 active_inline_sessions: DomRefCell::new(Vec::new()),
59 test: Default::default(),
60 pipeline,
61 }
62 }
63
64 pub(crate) fn new(window: &Window, can_gc: CanGc) -> DomRoot<XRSystem> {
65 reflect_dom_object(
66 Box::new(XRSystem::new_inherited(window.pipeline_id())),
67 window,
68 can_gc,
69 )
70 }
71
72 pub(crate) fn pending_or_active_session(&self) -> bool {
73 self.pending_immersive_session.get() || self.active_immersive_session.get().is_some()
74 }
75
76 pub(crate) fn set_pending(&self) {
77 self.pending_immersive_session.set(true)
78 }
79
80 pub(crate) fn set_active_immersive_session(&self, session: &XRSession) {
81 self.pending_immersive_session.set(false);
84 self.active_immersive_session.set(Some(session))
85 }
86
87 pub(crate) fn end_session(&self, session: &XRSession) {
89 if let Some(active) = self.active_immersive_session.get() {
91 if Dom::from_ref(&*active) == Dom::from_ref(session) {
92 self.active_immersive_session.set(None);
93 session.dirty_layers();
96 }
97 }
98 self.active_inline_sessions
99 .borrow_mut()
100 .retain(|sess| Dom::from_ref(&**sess) != Dom::from_ref(session));
101 }
102}
103
104impl Convert<SessionMode> for XRSessionMode {
105 fn convert(self) -> SessionMode {
106 match self {
107 XRSessionMode::Immersive_vr => SessionMode::ImmersiveVR,
108 XRSessionMode::Immersive_ar => SessionMode::ImmersiveAR,
109 XRSessionMode::Inline => SessionMode::Inline,
110 }
111 }
112}
113
114impl XRSystemMethods<crate::DomTypeHolder> for XRSystem {
115 fn IsSessionSupported(&self, mode: XRSessionMode, can_gc: CanGc) -> Rc<Promise> {
117 let promise = Promise::new(&self.global(), can_gc);
119 let mut trusted = Some(TrustedPromise::new(promise.clone()));
120 let global = self.global();
121 let task_source = global
122 .task_manager()
123 .dom_manipulation_task_source()
124 .to_sendable();
125 let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
126 ROUTER.add_typed_route(
127 receiver.to_ipc_receiver(),
128 Box::new(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 );
149 if let Some(mut r) = global.as_window().webxr_registry() {
150 r.supports_session(mode.convert(), sender);
151 }
152
153 promise
154 }
155
156 fn RequestSession(
158 &self,
159 mode: XRSessionMode,
160 init: RootedTraceableBox<XRSessionInit>,
161 comp: InRealm,
162 can_gc: CanGc,
163 ) -> Rc<Promise> {
164 let global = self.global();
165 let window = global.as_window();
166 let promise = Promise::new_in_current_realm(comp, can_gc);
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(Error::Security, can_gc);
176 return promise;
177 }
178 }
179
180 if self.pending_or_active_session() {
181 promise.reject_error(Error::InvalidState(None), can_gc);
182 return promise;
183 }
184
185 self.set_pending();
186 }
187
188 let mut required_features = vec![];
189 let mut optional_features = vec![];
190 let cx = GlobalScope::get_cx();
191
192 if let Some(ref r) = init.requiredFeatures {
193 for feature in r {
194 if let Ok(ConversionResult::Success(s)) =
195 String::safe_from_jsval(cx, feature.handle(), (), can_gc)
196 {
197 required_features.push(s)
198 } else {
199 warn!("Unable to convert required feature to string");
200 if mode != XRSessionMode::Inline {
201 self.pending_immersive_session.set(false);
202 }
203 promise.reject_error(Error::NotSupported, can_gc);
204 return promise;
205 }
206 }
207 }
208
209 if let Some(ref o) = init.optionalFeatures {
210 for feature in o {
211 if let Ok(ConversionResult::Success(s)) =
212 String::safe_from_jsval(cx, feature.handle(), (), can_gc)
213 {
214 optional_features.push(s)
215 } else {
216 warn!("Unable to convert optional feature to string");
217 }
218 }
219 }
220
221 if !required_features.contains(&"viewer".to_string()) {
222 required_features.push("viewer".to_string());
223 }
224
225 if !required_features.contains(&"local".to_string()) && mode != XRSessionMode::Inline {
226 required_features.push("local".to_string());
227 }
228
229 let init = SessionInit {
230 required_features,
231 optional_features,
232 first_person_observer_view: pref!(dom_webxr_first_person_observer_view),
233 };
234
235 let mut trusted = Some(TrustedPromise::new(promise.clone()));
236 let this = Trusted::new(self);
237 let task_source = global
238 .task_manager()
239 .dom_manipulation_task_source()
240 .to_sendable();
241 let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
242 let (frame_sender, frame_receiver) = ipc_crate::channel().unwrap();
243 let mut frame_receiver = Some(frame_receiver);
244 ROUTER.add_typed_route(
245 receiver.to_ipc_receiver(),
246 Box::new(move |message| {
247 let trusted = trusted.take().unwrap();
249 let this = this.clone();
250 let frame_receiver = frame_receiver.take().unwrap();
251 let message: Result<Session, webxr_api::Error> = if let Ok(message) = message {
252 message
253 } else {
254 error!("requestSession callback given incorrect payload");
255 return;
256 };
257 task_source.queue(task!(request_session: move || {
258 this.root().session_obtained(message, trusted.root(), mode, frame_receiver, CanGc::note());
259 }));
260 }),
261 );
262 if let Some(mut r) = window.webxr_registry() {
263 r.request_session(mode.convert(), init, sender, frame_sender);
264 }
265 promise
266 }
267
268 fn Test(&self) -> DomRoot<XRTest> {
270 self.test
271 .or_init(|| XRTest::new(&self.global(), CanGc::note()))
272 }
273}
274
275impl XRSystem {
276 fn session_obtained(
277 &self,
278 response: Result<Session, XRError>,
279 promise: Rc<Promise>,
280 mode: XRSessionMode,
281 frame_receiver: IpcReceiver<Frame>,
282 can_gc: CanGc,
283 ) {
284 let session = match response {
285 Ok(session) => session,
286 Err(e) => {
287 warn!("Error requesting XR session: {:?}", e);
288 if mode != XRSessionMode::Inline {
289 self.pending_immersive_session.set(false);
290 }
291 promise.reject_error(Error::NotSupported, can_gc);
292 return;
293 },
294 };
295 let session = XRSession::new(
296 self.global().as_window(),
297 session,
298 mode,
299 frame_receiver,
300 CanGc::note(),
301 );
302 if mode == XRSessionMode::Inline {
303 self.active_inline_sessions
304 .borrow_mut()
305 .push(Dom::from_ref(&*session));
306 } else {
307 self.set_active_immersive_session(&session);
308 }
309 promise.resolve_native(&session, can_gc);
310 session.setup_initial_inputs();
313 }
314
315 pub(crate) fn dispatch_sessionavailable(&self) {
317 let xr = Trusted::new(self);
318 self.global()
319 .task_manager()
320 .dom_manipulation_task_source()
321 .queue(
322 task!(fire_sessionavailable_event: move || {
323 let xr = xr.root();
325 let _guard = ScriptThread::user_interacting_guard();
326 xr.upcast::<EventTarget>().fire_bubbling_event(atom!("sessionavailable"), CanGc::note());
327 })
328 );
329 }
330}