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