script/dom/webxr/
xrsystem.rs1use std::cell::Cell;
6
7use dom_struct::dom_struct;
8use ipc_channel::ipc::{self as ipc_crate, IpcReceiver};
9use ipc_channel::router::ROUTER;
10use js::context::JSContext;
11use js::realm::CurrentRealm;
12use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
13use profile_traits::ipc;
14use script_bindings::cell::DomRefCell;
15use script_bindings::reflector::reflect_dom_object;
16use servo_base::id::PipelineId;
17use servo_config::pref;
18use webxr_api::{Error as XRError, Frame, Session, SessionInit, SessionMode};
19
20use crate::conversions::Convert;
21use crate::dom::bindings::codegen::Bindings::XRSystemBinding::{
22 XRSessionInit, XRSessionMode, XRSystemMethods,
23};
24use crate::dom::bindings::conversions::{ConversionResult, FromJSValConvertible};
25use crate::dom::bindings::error::Error;
26use crate::dom::bindings::inheritance::Castable;
27use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
28use crate::dom::bindings::reflector::DomGlobal;
29use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
30use crate::dom::bindings::trace::RootedTraceableBox;
31use crate::dom::eventtarget::EventTarget;
32use crate::dom::gamepad::Gamepad;
33use crate::dom::promise::{Promise, RootedPromise};
34use crate::dom::window::Window;
35use crate::dom::xrsession::XRSession;
36use crate::dom::xrtest::XRTest;
37use crate::event_loop::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(cx: &mut JSContext, window: &Window) -> DomRoot<XRSystem> {
65 reflect_dom_object(
66 cx,
67 Box::new(XRSystem::new_inherited(window.pipeline_id())),
68 window,
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 Dom::from_ref(&*active) == Dom::from_ref(session)
92 {
93 self.active_immersive_session.set(None);
94 session.dirty_layers();
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, cx: &mut CurrentRealm, mode: XRSessionMode) -> RootedPromise {
117 let promise = Promise::new_in_realm_rooted(cx);
119 let mut trusted = Some(TrustedPromise::from(&promise));
120 let global = self.global();
121 let task_source = global
122 .task_manager()
123 .dom_manipulation_task_source()
124 .to_sendable();
125
126 let callback = ProfileGenericCallback::new(move |message| {
127 let trusted = if let Some(trusted) = trusted.take() {
129 trusted
130 } else {
131 error!("supportsSession callback called twice!");
132 return;
133 };
134 let message: Result<(), webxr_api::Error> = if let Ok(message) = message {
135 message
136 } else {
137 error!("supportsSession callback given incorrect payload");
138 return;
139 };
140 if let Ok(()) = message {
141 task_source.queue(trusted.resolve_task(true));
142 } else {
143 task_source.queue(trusted.resolve_task(false));
144 };
145 })
146 .expect("Could not create callback");
147
148 if let Some(mut r) = global.as_window().webxr_registry() {
149 r.supports_session(mode.convert(), callback);
150 }
151
152 promise
153 }
154
155 fn RequestSession(
157 &self,
158 realm: &mut CurrentRealm,
159 mode: XRSessionMode,
160 init: RootedTraceableBox<XRSessionInit>,
161 ) -> RootedPromise {
162 let global = self.global();
163 let window = global.as_window();
164 let promise = Promise::new_in_realm_rooted(realm);
165
166 if mode != XRSessionMode::Inline {
167 if !ScriptThread::is_user_interacting() {
168 if pref!(dom_webxr_unsafe_assume_user_intent) {
169 warn!(
170 "The dom.webxr.unsafe-assume-user-intent preference assumes user intent to enter WebXR."
171 );
172 } else {
173 promise.reject_error(realm, Error::Security(None));
174 return promise;
175 }
176 }
177
178 if self.pending_or_active_session() {
179 promise.reject_error(realm, Error::InvalidState(None));
180 return promise;
181 }
182
183 self.set_pending();
184 }
185
186 let mut required_features = vec![];
187 let mut optional_features = vec![];
188
189 if let Some(ref r) = init.requiredFeatures {
190 for feature in r {
191 if let Ok(ConversionResult::Success(s)) =
192 String::from_jsval(realm, feature.handle(), ())
193 {
194 required_features.push(s)
195 } else {
196 warn!("Unable to convert required feature to string");
197 if mode != XRSessionMode::Inline {
198 self.pending_immersive_session.set(false);
199 }
200 promise.reject_error(realm, Error::NotSupported(None));
201 return promise;
202 }
203 }
204 }
205
206 if let Some(ref o) = init.optionalFeatures {
207 for feature in o {
208 if let Ok(ConversionResult::Success(s)) =
209 String::from_jsval(realm, feature.handle(), ())
210 {
211 optional_features.push(s)
212 } else {
213 warn!("Unable to convert optional feature to string");
214 }
215 }
216 }
217
218 if !required_features.contains(&"viewer".to_string()) {
219 required_features.push("viewer".to_string());
220 }
221
222 if !required_features.contains(&"local".to_string()) && mode != XRSessionMode::Inline {
223 required_features.push("local".to_string());
224 }
225
226 let init = SessionInit {
227 required_features,
228 optional_features,
229 first_person_observer_view: pref!(dom_webxr_first_person_observer_view),
230 };
231
232 let mut trusted = Some(TrustedPromise::from(&promise));
233 let this = Trusted::new(self);
234 let task_source = global
235 .task_manager()
236 .dom_manipulation_task_source()
237 .to_sendable();
238 let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
239 let (frame_sender, frame_receiver) = ipc_crate::channel().unwrap();
240 let mut frame_receiver = Some(frame_receiver);
241 ROUTER.add_typed_route(
242 receiver.to_ipc_receiver(),
243 Box::new(move |message| {
244 let trusted = trusted.take().unwrap();
246 let this = this.clone();
247 let frame_receiver = frame_receiver.take().unwrap();
248 let message: Result<Session, webxr_api::Error> = if let Ok(message) = message {
249 message
250 } else {
251 error!("requestSession callback given incorrect payload");
252 return;
253 };
254 task_source.queue(task!(request_session: move |cx| {
255 this.root().session_obtained(cx, message, &trusted.root(cx), mode, frame_receiver);
256 }));
257 }),
258 );
259 if let Some(mut r) = window.webxr_registry() {
260 r.request_session(mode.convert(), init, sender, frame_sender);
261 }
262 promise
263 }
264
265 fn Test(&self, cx: &mut JSContext) -> DomRoot<XRTest> {
267 self.test.or_init(|| XRTest::new(cx, &self.global()))
268 }
269}
270
271impl XRSystem {
272 fn session_obtained(
273 &self,
274 cx: &mut JSContext,
275 response: Result<Session, XRError>,
276 promise: &RootedPromise,
277 mode: XRSessionMode,
278 frame_receiver: IpcReceiver<Frame>,
279 ) {
280 let session = match response {
281 Ok(session) => session,
282 Err(e) => {
283 warn!("Error requesting XR session: {:?}", e);
284 if mode != XRSessionMode::Inline {
285 self.pending_immersive_session.set(false);
286 }
287 promise.reject_error(cx, Error::NotSupported(None));
288 return;
289 },
290 };
291 let session = XRSession::new(cx, self.global().as_window(), session, mode, frame_receiver);
292 if mode == XRSessionMode::Inline {
293 self.active_inline_sessions
294 .borrow_mut()
295 .push(Dom::from_ref(&*session));
296 } else {
297 self.set_active_immersive_session(&session);
298 }
299 promise.resolve_native(cx, &session);
300 session.setup_initial_inputs();
303 }
304
305 pub(crate) fn dispatch_sessionavailable(&self) {
307 let xr = Trusted::new(self);
308 self.global()
309 .task_manager()
310 .dom_manipulation_task_source()
311 .queue(task!(fire_sessionavailable_event: move |cx| {
312 let xr = xr.root();
314 let _guard = ScriptThread::user_interacting_guard();
315 xr.upcast::<EventTarget>().fire_bubbling_event(cx, atom!("sessionavailable"));
316 }));
317 }
318}