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