1use std::cell::Cell;
6use std::collections::HashMap;
7use std::f64::consts::{FRAC_PI_2, PI};
8use std::rc::Rc;
9use std::{mem, ptr};
10
11use script_bindings::reflector::reflect_dom_object;
12use servo_base::cross_process_instant::CrossProcessInstant;
13use dom_struct::dom_struct;
14use euclid::{RigidTransform3D, Transform3D, Vector3D};
15use ipc_channel::ipc::IpcReceiver;
16use ipc_channel::router::ROUTER;
17use js::jsapi::JSObject;
18use js::rust::MutableHandleValue;
19use js::typedarray::HeapFloat32Array;
20use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
21use rustc_hash::FxBuildHasher;
22use script_bindings::trace::RootedTraceableBox;
23use stylo_atoms::Atom;
24use webxr_api::{
25 self, ApiSpace, ContextId as WebXRContextId, Display, EntityTypes, EnvironmentBlendMode,
26 Event as XREvent, Frame, FrameUpdateEvent, HitTestId, HitTestSource, InputFrame, InputId, Ray,
27 SelectEvent, SelectKind, Session, SessionId, View, Viewer, Visibility, util,
28};
29
30use crate::conversions::Convert;
31use crate::canvas_context::CanvasContext;
32use crate::dom::bindings::trace::HashMapTracedValues;
33use crate::dom::bindings::buffer_source::create_buffer_source;
34use crate::dom::bindings::callback::ExceptionHandling;
35use script_bindings::cell::DomRefCell;
36use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
37use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
38use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::{
39 XRHitTestOptionsInit, XRHitTestTrackableType,
40};
41use crate::dom::bindings::codegen::Bindings::XRInputSourceArrayBinding::XRInputSourceArray_Binding::XRInputSourceArrayMethods;
42use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType;
43use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::{
44 XRRenderStateInit, XRRenderStateMethods,
45};
46use crate::dom::bindings::codegen::Bindings::XRSessionBinding::{
47 XREnvironmentBlendMode, XRFrameRequestCallback, XRInteractionMode, XRSessionMethods,
48 XRVisibilityState,
49};
50use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionMode;
51use crate::dom::bindings::error::{Error, ErrorResult};
52use crate::dom::bindings::inheritance::Castable;
53use crate::dom::bindings::num::Finite;
54use crate::dom::bindings::refcounted::Trusted;
55use crate::dom::bindings::reflector::{DomGlobal};
56use crate::dom::bindings::root::{Dom, DomRoot, MutDom, MutNullableDom};
57use crate::dom::bindings::utils::to_frozen_array;
58use crate::dom::event::Event;
59use crate::dom::eventtarget::EventTarget;
60use crate::dom::promise::Promise;
61use crate::dom::window::Window;
62use crate::dom::xrboundedreferencespace::XRBoundedReferenceSpace;
63use crate::dom::xrframe::XRFrame;
64use crate::dom::xrhittestsource::XRHitTestSource;
65use crate::dom::xrinputsourcearray::XRInputSourceArray;
66use crate::dom::xrinputsourceevent::XRInputSourceEvent;
67use crate::dom::xrreferencespace::XRReferenceSpace;
68use crate::dom::xrreferencespaceevent::XRReferenceSpaceEvent;
69use crate::dom::xrrenderstate::XRRenderState;
70use crate::dom::xrrigidtransform::XRRigidTransform;
71use crate::dom::xrsessionevent::XRSessionEvent;
72use crate::dom::xrspace::XRSpace;
73use crate::realms::InRealm;
74use crate::script_runtime::JSContext;
75use crate::script_runtime::CanGc;
76
77#[dom_struct]
78pub(crate) struct XRSession {
79 eventtarget: EventTarget,
80 blend_mode: XREnvironmentBlendMode,
81 mode: XRSessionMode,
82 visibility_state: Cell<XRVisibilityState>,
83 viewer_space: MutNullableDom<XRSpace>,
84 #[no_trace]
85 session: DomRefCell<Session>,
86 frame_requested: Cell<bool>,
87 pending_render_state: MutNullableDom<XRRenderState>,
88 active_render_state: MutDom<XRRenderState>,
89 #[no_trace]
91 inline_projection_matrix: DomRefCell<Transform3D<f32, Viewer, Display>>,
92
93 next_raf_id: Cell<i32>,
94 #[ignore_malloc_size_of = "closures are hard"]
95 raf_callback_list: DomRefCell<Vec<(i32, Option<Rc<XRFrameRequestCallback>>)>>,
96 #[ignore_malloc_size_of = "closures are hard"]
97 current_raf_callback_list: DomRefCell<Vec<(i32, Option<Rc<XRFrameRequestCallback>>)>>,
98 input_sources: Dom<XRInputSourceArray>,
99 #[conditional_malloc_size_of]
101 end_promises: DomRefCell<Vec<Rc<Promise>>>,
102 ended: Cell<bool>,
104 #[no_trace]
105 next_hit_test_id: Cell<HitTestId>,
106 #[ignore_malloc_size_of = "Promise"]
107 pending_hit_test_promises:
108 DomRefCell<HashMapTracedValues<HitTestId, Rc<Promise>, FxBuildHasher>>,
109 outside_raf: Cell<bool>,
112 #[no_trace]
113 input_frames: DomRefCell<HashMap<InputId, InputFrame>>,
114 framerate: Cell<f32>,
115 #[conditional_malloc_size_of]
116 update_framerate_promise: DomRefCell<Option<Rc<Promise>>>,
117 reference_spaces: DomRefCell<Vec<Dom<XRReferenceSpace>>>,
118}
119
120impl XRSession {
121 fn new_inherited(
122 session: Session,
123 render_state: &XRRenderState,
124 input_sources: &XRInputSourceArray,
125 mode: XRSessionMode,
126 ) -> XRSession {
127 XRSession {
128 eventtarget: EventTarget::new_inherited(),
129 blend_mode: session.environment_blend_mode().convert(),
130 mode,
131 visibility_state: Cell::new(XRVisibilityState::Visible),
132 viewer_space: Default::default(),
133 session: DomRefCell::new(session),
134 frame_requested: Cell::new(false),
135 pending_render_state: MutNullableDom::new(None),
136 active_render_state: MutDom::new(render_state),
137 inline_projection_matrix: Default::default(),
138
139 next_raf_id: Cell::new(0),
140 raf_callback_list: DomRefCell::new(vec![]),
141 current_raf_callback_list: DomRefCell::new(vec![]),
142 input_sources: Dom::from_ref(input_sources),
143 end_promises: DomRefCell::new(vec![]),
144 ended: Cell::new(false),
145 next_hit_test_id: Cell::new(HitTestId(0)),
146 pending_hit_test_promises: DomRefCell::new(HashMapTracedValues::new_fx()),
147 outside_raf: Cell::new(true),
148 input_frames: DomRefCell::new(HashMap::new()),
149 framerate: Cell::new(0.0),
150 update_framerate_promise: DomRefCell::new(None),
151 reference_spaces: DomRefCell::new(Vec::new()),
152 }
153 }
154
155 pub(crate) fn new(
156 window: &Window,
157 session: Session,
158 mode: XRSessionMode,
159 frame_receiver: IpcReceiver<Frame>,
160 can_gc: CanGc,
161 ) -> DomRoot<XRSession> {
162 let ivfov = if mode == XRSessionMode::Inline {
163 Some(FRAC_PI_2)
164 } else {
165 None
166 };
167 let render_state = XRRenderState::new(window, 0.1, 1000.0, ivfov, None, Vec::new(), can_gc);
168 let input_sources = XRInputSourceArray::new(window, can_gc);
169 let ret = reflect_dom_object(
170 Box::new(XRSession::new_inherited(
171 session,
172 &render_state,
173 &input_sources,
174 mode,
175 )),
176 window,
177 can_gc,
178 );
179 ret.attach_event_handler();
180 ret.setup_raf_loop(frame_receiver);
181 ret
182 }
183
184 pub(crate) fn with_session<R, F: FnOnce(&Session) -> R>(&self, with: F) -> R {
185 let session = self.session.borrow();
186 with(&session)
187 }
188
189 pub(crate) fn is_ended(&self) -> bool {
190 self.ended.get()
191 }
192
193 pub(crate) fn is_immersive(&self) -> bool {
194 self.mode != XRSessionMode::Inline
195 }
196
197 pub(crate) fn has_layers_feature(&self) -> bool {
199 false
202 }
203
204 fn setup_raf_loop(&self, frame_receiver: IpcReceiver<Frame>) {
205 let this = Trusted::new(self);
206 let global = self.global();
207 let task_source = global
208 .task_manager()
209 .dom_manipulation_task_source()
210 .to_sendable();
211 ROUTER.add_typed_route(
212 frame_receiver,
213 Box::new(move |message| {
214 let frame: Frame = message.unwrap();
215 let time = CrossProcessInstant::now();
216 let this = this.clone();
217 task_source.queue(task!(xr_raf_callback: move |cx| {
218 this.root().raf_callback(cx, frame, time);
219 }));
220 }),
221 );
222
223 self.session.borrow_mut().start_render_loop();
224 }
225
226 pub(crate) fn is_outside_raf(&self) -> bool {
227 self.outside_raf.get()
228 }
229
230 fn attach_event_handler(&self) {
231 let this = Trusted::new(self);
232 let global = self.global();
233 let task_source = global
234 .task_manager()
235 .dom_manipulation_task_source()
236 .to_sendable();
237 let callback =
238 ProfileGenericCallback::new(global.time_profiler_chan().clone(), move |message| {
239 let this = this.clone();
240 task_source.queue(task!(xr_event_callback: move |cx| {
241 this.root().event_callback(cx, message.unwrap());
242 }))
243 })
244 .expect("Could not create callback");
245
246 self.session.borrow_mut().set_event_dest(callback);
248 }
249
250 pub(crate) fn setup_initial_inputs(&self) {
256 let initial_inputs = self.session.borrow().initial_inputs().to_owned();
257
258 if initial_inputs.is_empty() {
259 return;
261 }
262
263 let this = Trusted::new(self);
264 self.global()
267 .task_manager()
268 .dom_manipulation_task_source()
269 .queue(task!(session_initial_inputs: move |cx| {
270 let this = this.root();
271 this.input_sources.add_input_sources(cx, &this, &initial_inputs);
272 }));
273 }
274
275 fn event_callback(&self, cx: &mut js::context::JSContext, event: XREvent) {
276 match event {
277 XREvent::SessionEnd => {
278 self.ended.set(true);
281 self.global().as_window().Navigator().Xr().end_session(self);
283 for promise in self.end_promises.borrow_mut().drain(..) {
287 promise.resolve_native_with_cx(cx, &());
288 }
289 let event = XRSessionEvent::new(
291 self.global().as_window(),
292 atom!("end"),
293 false,
294 false,
295 self,
296 CanGc::from_cx(cx),
297 );
298 event.upcast::<Event>().fire(cx, self.upcast());
299 },
300 XREvent::Select(input, kind, ty, frame) => {
301 use stylo_atoms::Atom;
302 const START_ATOMS: [Atom; 2] = [atom!("selectstart"), atom!("squeezestart")];
303 const EVENT_ATOMS: [Atom; 2] = [atom!("select"), atom!("squeeze")];
304 const END_ATOMS: [Atom; 2] = [atom!("selectend"), atom!("squeezeend")];
305
306 let source = self.input_sources.find(input);
308 let atom_index = if kind == SelectKind::Squeeze { 1 } else { 0 };
309 if let Some(source) = source {
310 let frame =
311 XRFrame::new(self.global().as_window(), self, frame, CanGc::from_cx(cx));
312 frame.set_active(true);
313 if ty == SelectEvent::Start {
314 let event = XRInputSourceEvent::new(
315 self.global().as_window(),
316 START_ATOMS[atom_index].clone(),
317 false,
318 false,
319 &frame,
320 &source,
321 CanGc::from_cx(cx),
322 );
323 event.upcast::<Event>().fire(cx, self.upcast());
324 } else {
325 if ty == SelectEvent::Select {
326 let event = XRInputSourceEvent::new(
327 self.global().as_window(),
328 EVENT_ATOMS[atom_index].clone(),
329 false,
330 false,
331 &frame,
332 &source,
333 CanGc::from_cx(cx),
334 );
335 event.upcast::<Event>().fire(cx, self.upcast());
336 }
337 let event = XRInputSourceEvent::new(
338 self.global().as_window(),
339 END_ATOMS[atom_index].clone(),
340 false,
341 false,
342 &frame,
343 &source,
344 CanGc::from_cx(cx),
345 );
346 event.upcast::<Event>().fire(cx, self.upcast());
347 }
348 frame.set_active(false);
349 }
350 },
351 XREvent::VisibilityChange(v) => {
352 let v = match v {
353 Visibility::Visible => XRVisibilityState::Visible,
354 Visibility::VisibleBlurred => XRVisibilityState::Visible_blurred,
355 Visibility::Hidden => XRVisibilityState::Hidden,
356 };
357 self.visibility_state.set(v);
358 let event = XRSessionEvent::new(
359 self.global().as_window(),
360 atom!("visibilitychange"),
361 false,
362 false,
363 self,
364 CanGc::from_cx(cx),
365 );
366 event.upcast::<Event>().fire(cx, self.upcast());
367 self.dirty_layers();
370 },
371 XREvent::AddInput(info) => {
372 self.input_sources.add_input_sources(cx, self, &[info]);
373 },
374 XREvent::RemoveInput(id) => {
375 self.input_sources.remove_input_source(cx, self, id);
376 },
377 XREvent::UpdateInput(id, source) => {
378 self.input_sources
379 .add_remove_input_source(cx, self, id, source);
380 },
381 XREvent::InputChanged(id, frame) => {
382 self.input_frames.borrow_mut().insert(id, frame);
383 },
384 XREvent::ReferenceSpaceChanged(base_space, transform) => {
385 self.reference_spaces
386 .borrow()
387 .iter()
388 .filter(|space| {
389 let base = match space.ty() {
390 XRReferenceSpaceType::Local => webxr_api::BaseSpace::Local,
391 XRReferenceSpaceType::Viewer => webxr_api::BaseSpace::Viewer,
392 XRReferenceSpaceType::Local_floor => webxr_api::BaseSpace::Floor,
393 XRReferenceSpaceType::Bounded_floor => {
394 webxr_api::BaseSpace::BoundedFloor
395 },
396 _ => panic!("unsupported reference space found"),
397 };
398 base == base_space
399 })
400 .for_each(|space| {
401 let offset = XRRigidTransform::new(
402 self.global().as_window(),
403 transform,
404 CanGc::from_cx(cx),
405 );
406 let event = XRReferenceSpaceEvent::new(
407 self.global().as_window(),
408 atom!("reset"),
409 false,
410 false,
411 space,
412 Some(&*offset),
413 CanGc::from_cx(cx),
414 );
415 event.upcast::<Event>().fire(cx, space.upcast());
416 });
417 },
418 }
419 }
420
421 fn raf_callback(
423 &self,
424 cx: &mut js::context::JSContext,
425 mut frame: Frame,
426 time: CrossProcessInstant,
427 ) {
428 debug!("WebXR RAF callback {:?}", frame);
429
430 if let Some(pending) = self.pending_render_state.take() {
434 self.active_render_state.set(&pending);
438 if !self.is_immersive() {
441 self.update_inline_projection_matrix()
442 }
443 }
444
445 for event in frame.events.drain(..) {
447 self.handle_frame_event(event, CanGc::from_cx(cx));
448 }
449
450 if !self
456 .active_render_state
457 .get()
458 .has_sub_images(&frame.sub_images[..])
459 {
460 warn!("Rendering blank XR frame");
465 self.session.borrow_mut().render_animation_frame();
466 return;
467 }
468
469 {
473 let mut current = self.current_raf_callback_list.borrow_mut();
474 assert!(current.is_empty());
475 mem::swap(&mut *self.raf_callback_list.borrow_mut(), &mut current);
476 }
477
478 let time = self.global().performance().to_dom_high_res_time_stamp(time);
479 let frame = XRFrame::new(self.global().as_window(), self, frame, CanGc::from_cx(cx));
480
481 frame.set_active(true);
483 frame.set_animation_frame(true);
484
485 self.apply_frame_updates(&frame);
487
488 self.layers_begin_frame(&frame);
490
491 self.outside_raf.set(false);
493 let len = self.current_raf_callback_list.borrow().len();
494 for i in 0..len {
495 let callback = self.current_raf_callback_list.borrow()[i].1.clone();
496 if let Some(callback) = callback {
497 let _ = callback.Call__(cx, time, &frame, ExceptionHandling::Report);
498 }
499 }
500 self.outside_raf.set(true);
501 *self.current_raf_callback_list.borrow_mut() = vec![];
502
503 self.layers_end_frame(&frame);
505
506 frame.set_active(false);
508
509 self.session.borrow_mut().render_animation_frame();
511 }
512
513 fn update_inline_projection_matrix(&self) {
514 debug_assert!(!self.is_immersive());
515 let render_state = self.active_render_state.get();
516 let size = if let Some(base) = render_state.GetBaseLayer() {
517 base.size()
518 } else {
519 return;
520 };
521 let mut clip_planes = util::ClipPlanes::default();
522 let near = *render_state.DepthNear() as f32;
523 let far = *render_state.DepthFar() as f32;
524 clip_planes.update(near, far);
525 let top = *render_state
526 .GetInlineVerticalFieldOfView()
527 .expect("IVFOV should be non null for inline sessions") /
528 2.;
529 let top = near * top.tan() as f32;
530 let bottom = top;
531 let left = top * size.width as f32 / size.height as f32;
532 let right = left;
533 let matrix = util::frustum_to_projection_matrix(left, right, top, bottom, clip_planes);
534 *self.inline_projection_matrix.borrow_mut() = matrix;
535 }
536
537 pub(crate) fn inline_view(&self) -> View<Viewer> {
539 debug_assert!(!self.is_immersive());
540 View {
541 transform: RigidTransform3D::identity(),
543 projection: *self.inline_projection_matrix.borrow(),
544 }
545 }
546
547 pub(crate) fn session_id(&self) -> SessionId {
548 self.session.borrow().id()
549 }
550
551 pub(crate) fn dirty_layers(&self) {
552 if let Some(layer) = self.RenderState().GetBaseLayer() {
553 layer.context().mark_as_dirty();
554 }
555 }
556
557 fn layers_begin_frame(&self, frame: &XRFrame) {
559 if let Some(layer) = self.active_render_state.get().GetBaseLayer() {
560 layer.begin_frame(frame);
561 }
562 self.active_render_state.get().with_layers(|layers| {
563 for layer in layers {
564 layer.begin_frame(frame);
565 }
566 });
567 }
568
569 fn layers_end_frame(&self, frame: &XRFrame) {
571 if let Some(layer) = self.active_render_state.get().GetBaseLayer() {
572 layer.end_frame(frame);
573 }
574 self.active_render_state.get().with_layers(|layers| {
575 for layer in layers {
576 layer.end_frame(frame);
577 }
578 });
579 }
580
581 fn apply_frame_updates(&self, _frame: &XRFrame) {
583 for (id, frame) in self.input_frames.borrow_mut().drain() {
585 let source = self.input_sources.find(id);
586 if let Some(source) = source {
587 source.update_gamepad_state(frame);
588 }
589 }
590 }
591
592 fn handle_frame_event(&self, event: FrameUpdateEvent, can_gc: CanGc) {
593 match event {
594 FrameUpdateEvent::HitTestSourceAdded(id) => {
595 if let Some(promise) = self.pending_hit_test_promises.borrow_mut().remove(&id) {
596 promise.resolve_native(
597 &XRHitTestSource::new(self.global().as_window(), id, self, can_gc),
598 can_gc,
599 );
600 } else {
601 warn!(
602 "received hit test add request for unknown hit test {:?}",
603 id
604 )
605 }
606 },
607 _ => self.session.borrow_mut().apply_event(event),
608 }
609 }
610
611 fn apply_nominal_framerate(&self, cx: &mut js::context::JSContext, rate: f32) {
613 if self.framerate.get() == rate || self.ended.get() {
614 return;
615 }
616
617 self.framerate.set(rate);
618
619 let event = XRSessionEvent::new(
620 self.global().as_window(),
621 Atom::from("frameratechange"),
622 false,
623 false,
624 self,
625 CanGc::from_cx(cx),
626 );
627 event.upcast::<Event>().fire(cx, self.upcast());
628 }
629}
630
631impl XRSessionMethods<crate::DomTypeHolder> for XRSession {
632 event_handler!(end, GetOnend, SetOnend);
634
635 event_handler!(select, GetOnselect, SetOnselect);
637
638 event_handler!(selectstart, GetOnselectstart, SetOnselectstart);
640
641 event_handler!(selectend, GetOnselectend, SetOnselectend);
643
644 event_handler!(squeeze, GetOnsqueeze, SetOnsqueeze);
646
647 event_handler!(squeezestart, GetOnsqueezestart, SetOnsqueezestart);
649
650 event_handler!(squeezeend, GetOnsqueezeend, SetOnsqueezeend);
652
653 event_handler!(
655 visibilitychange,
656 GetOnvisibilitychange,
657 SetOnvisibilitychange
658 );
659
660 event_handler!(
662 inputsourceschange,
663 GetOninputsourceschange,
664 SetOninputsourceschange
665 );
666
667 event_handler!(frameratechange, GetOnframeratechange, SetOnframeratechange);
669
670 fn RenderState(&self) -> DomRoot<XRRenderState> {
672 self.active_render_state.get()
673 }
674
675 fn UpdateRenderState(&self, init: &XRRenderStateInit, _: InRealm) -> ErrorResult {
677 if self.ended.get() {
679 return Err(Error::InvalidState(None));
680 }
681 if let Some(Some(ref layer)) = init.baseLayer &&
683 Dom::from_ref(layer.session()) != Dom::from_ref(self)
684 {
685 return Err(Error::InvalidState(None));
686 }
687
688 if init.inlineVerticalFieldOfView.is_some() && self.is_immersive() {
690 return Err(Error::InvalidState(None));
691 }
692
693 if init.baseLayer.is_some() && (self.has_layers_feature() || init.layers.is_some()) {
696 return Err(Error::NotSupported(None));
697 }
698
699 if let Some(Some(ref layers)) = init.layers {
700 for layer in layers {
702 let count = layers
703 .iter()
704 .filter(|other| other.layer_id() == layer.layer_id())
705 .count();
706 if count > 1 {
707 return Err(Error::Type(c"Duplicate entry in WebXR layers".to_owned()));
708 }
709 }
710
711 for layer in layers {
713 if layer.session() != self {
714 return Err(Error::Type(
715 c"Layer from different session in WebXR layers".to_owned(),
716 ));
717 }
718 }
719 }
720
721 let pending = self
723 .pending_render_state
724 .or_init(|| self.active_render_state.get().clone_object());
725
726 if let Some(ref layers) = init.layers {
728 let layers = layers.as_deref().unwrap_or_default();
729 pending.set_base_layer(None);
730 pending.set_layers(layers.iter().map(|x| &**x).collect());
731 let layers = layers
732 .iter()
733 .filter_map(|layer| {
734 let context_id = WebXRContextId::from(layer.context_id());
735 let layer_id = layer.layer_id()?;
736 Some((context_id, layer_id))
737 })
738 .collect();
739 self.session.borrow_mut().set_layers(layers);
740 }
741
742 if let Some(near) = init.depthNear {
745 let mut near = *near;
746 if near < 0. {
750 near = 0.;
751 }
752 pending.set_depth_near(near);
753 }
754 if let Some(far) = init.depthFar {
755 let mut far = *far;
756 if far < 0. {
762 far = 0.;
763 }
764 pending.set_depth_far(far);
765 }
766 if let Some(fov) = init.inlineVerticalFieldOfView {
767 let mut fov = *fov;
768 if fov < 0. {
772 fov = 0.0001;
773 } else if fov > PI {
774 fov = PI - 0.0001;
775 }
776 pending.set_inline_vertical_fov(fov);
777 }
778 if let Some(ref layer) = init.baseLayer {
779 pending.set_base_layer(layer.as_deref());
780 pending.set_layers(Vec::new());
781 let layers = layer
782 .iter()
783 .filter_map(|layer| {
784 let context_id = WebXRContextId::from(layer.context_id());
785 let layer_id = layer.layer_id()?;
786 Some((context_id, layer_id))
787 })
788 .collect();
789 self.session.borrow_mut().set_layers(layers);
790 }
791
792 if init.depthFar.is_some() || init.depthNear.is_some() {
793 self.session
794 .borrow_mut()
795 .update_clip_planes(*pending.DepthNear() as f32, *pending.DepthFar() as f32);
796 }
797
798 Ok(())
799 }
800
801 fn RequestAnimationFrame(&self, callback: Rc<XRFrameRequestCallback>) -> i32 {
803 let raf_id = self.next_raf_id.get();
805 self.next_raf_id.set(raf_id + 1);
806 self.raf_callback_list
807 .borrow_mut()
808 .push((raf_id, Some(callback)));
809
810 raf_id
811 }
812
813 fn CancelAnimationFrame(&self, frame: i32) {
815 let mut list = self.raf_callback_list.borrow_mut();
816 if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
817 pair.1 = None;
818 }
819
820 let mut list = self.current_raf_callback_list.borrow_mut();
821 if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
822 pair.1 = None;
823 }
824 }
825
826 fn EnvironmentBlendMode(&self) -> XREnvironmentBlendMode {
828 self.blend_mode
829 }
830
831 fn VisibilityState(&self) -> XRVisibilityState {
833 self.visibility_state.get()
834 }
835
836 fn RequestReferenceSpace(
838 &self,
839 ty: XRReferenceSpaceType,
840 comp: InRealm,
841 can_gc: CanGc,
842 ) -> Rc<Promise> {
843 let p = Promise::new_in_current_realm(comp, can_gc);
844
845 if !self.is_immersive() &&
851 (ty == XRReferenceSpaceType::Bounded_floor || ty == XRReferenceSpaceType::Unbounded)
852 {
853 p.reject_error(Error::NotSupported(None), can_gc);
854 return p;
855 }
856
857 match ty {
858 XRReferenceSpaceType::Unbounded => {
859 p.reject_error(Error::NotSupported(None), can_gc)
861 },
862 ty => {
863 if ty != XRReferenceSpaceType::Viewer &&
864 (!self.is_immersive() || ty != XRReferenceSpaceType::Local)
865 {
866 let s = ty.as_str();
867 if !self
868 .session
869 .borrow()
870 .granted_features()
871 .iter()
872 .any(|f| *f == s)
873 {
874 p.reject_error(Error::NotSupported(None), can_gc);
875 return p;
876 }
877 }
878 if ty == XRReferenceSpaceType::Bounded_floor {
879 let space =
880 XRBoundedReferenceSpace::new(self.global().as_window(), self, can_gc);
881 self.reference_spaces
882 .borrow_mut()
883 .push(Dom::from_ref(space.reference_space()));
884 p.resolve_native(&space, can_gc);
885 } else {
886 let space = XRReferenceSpace::new(self.global().as_window(), self, ty, can_gc);
887 self.reference_spaces
888 .borrow_mut()
889 .push(Dom::from_ref(&*space));
890 p.resolve_native(&space, can_gc);
891 }
892 },
893 }
894 p
895 }
896
897 fn InputSources(&self) -> DomRoot<XRInputSourceArray> {
899 DomRoot::from_ref(&*self.input_sources)
900 }
901
902 fn End(&self, cx: &mut js::context::JSContext) -> Rc<Promise> {
904 let global = self.global();
905 let p = Promise::new2(cx, &global);
906 if self.ended.get() && self.end_promises.borrow().is_empty() {
907 p.resolve_native_with_cx(cx, &());
917 return p;
918 }
919 self.end_promises.borrow_mut().push(p.clone());
920 self.ended.set(true);
924 global.as_window().Navigator().Xr().end_session(self);
925 self.session.borrow_mut().end_session();
926 for source in 0..self.input_sources.Length() {
928 self.input_sources
929 .remove_input_source(cx, self, InputId(source));
930 }
931 p
932 }
933
934 fn RequestHitTestSource(&self, options: &XRHitTestOptionsInit, can_gc: CanGc) -> Rc<Promise> {
936 let p = Promise::new(&self.global(), can_gc);
937
938 if !self
939 .session
940 .borrow()
941 .granted_features()
942 .iter()
943 .any(|f| f == "hit-test")
944 {
945 p.reject_error(Error::NotSupported(None), can_gc);
946 return p;
947 }
948
949 let id = self.next_hit_test_id.get();
950 self.next_hit_test_id.set(HitTestId(id.0 + 1));
951
952 let space = options.space.space();
953 let ray = if let Some(ref ray) = options.offsetRay {
954 ray.ray()
955 } else {
956 Ray {
957 origin: Vector3D::new(0., 0., 0.),
958 direction: Vector3D::new(0., 0., -1.),
959 }
960 };
961
962 let mut types = EntityTypes::default();
963
964 if let Some(ref tys) = options.entityTypes {
965 for ty in tys {
966 match ty {
967 XRHitTestTrackableType::Point => types.point = true,
968 XRHitTestTrackableType::Plane => types.plane = true,
969 XRHitTestTrackableType::Mesh => types.mesh = true,
970 }
971 }
972 } else {
973 types.plane = true;
974 }
975
976 let source = HitTestSource {
977 id,
978 space,
979 ray,
980 types,
981 };
982 self.pending_hit_test_promises
983 .borrow_mut()
984 .insert(id, p.clone());
985
986 self.session.borrow().request_hit_test(source);
987
988 p
989 }
990
991 fn InteractionMode(&self) -> XRInteractionMode {
993 XRInteractionMode::World_space
996 }
997
998 fn GetFrameRate(&self) -> Option<Finite<f32>> {
1000 let session = self.session.borrow();
1001 if self.mode == XRSessionMode::Inline || session.supported_frame_rates().is_empty() {
1002 None
1003 } else {
1004 Finite::new(self.framerate.get())
1005 }
1006 }
1007
1008 fn GetSupportedFrameRates(
1010 &self,
1011 cx: JSContext,
1012 can_gc: CanGc,
1013 ) -> Option<RootedTraceableBox<HeapFloat32Array>> {
1014 let session = self.session.borrow();
1015 if self.mode == XRSessionMode::Inline || session.supported_frame_rates().is_empty() {
1016 None
1017 } else {
1018 let framerates = session.supported_frame_rates();
1019 rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>());
1020 Some(
1021 create_buffer_source(cx, framerates, array.handle_mut(), can_gc)
1022 .expect("Failed to construct supported frame rates array"),
1023 )
1024 }
1025 }
1026
1027 fn EnabledFeatures(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
1029 let session = self.session.borrow();
1030 let features = session.granted_features();
1031 to_frozen_array(features, cx, retval, can_gc)
1032 }
1033
1034 fn IsSystemKeyboardSupported(&self) -> bool {
1036 false
1039 }
1040
1041 fn UpdateTargetFrameRate(
1043 &self,
1044 rate: Finite<f32>,
1045 comp: InRealm,
1046 can_gc: CanGc,
1047 ) -> Rc<Promise> {
1048 let promise = Promise::new_in_current_realm(comp, can_gc);
1049 {
1050 let session = self.session.borrow();
1051 let supported_frame_rates = session.supported_frame_rates();
1052
1053 if self.mode == XRSessionMode::Inline ||
1054 supported_frame_rates.is_empty() ||
1055 self.ended.get()
1056 {
1057 promise.reject_error(Error::InvalidState(None), can_gc);
1058 return promise;
1059 }
1060
1061 if !supported_frame_rates.contains(&*rate) {
1062 promise.reject_error(
1063 Error::Type(c"Provided framerate not supported".into()),
1064 can_gc,
1065 );
1066 return promise;
1067 }
1068 }
1069
1070 *self.update_framerate_promise.borrow_mut() = Some(promise.clone());
1071
1072 let this = Trusted::new(self);
1073 let global = self.global();
1074 let task_source = global
1075 .task_manager()
1076 .dom_manipulation_task_source()
1077 .to_sendable();
1078
1079 let callback =
1080 ProfileGenericCallback::new(global.time_profiler_chan().clone(), move |message| {
1081 let this = this.clone();
1082 task_source.queue(task!(update_session_framerate: move |cx| {
1083 let session = this.root();
1084 session.apply_nominal_framerate(cx, message.unwrap());
1085 if let Some(promise) = session.update_framerate_promise.borrow_mut().take() {
1086 promise.resolve_native_with_cx(cx, &());
1087 };
1088 }));
1089 })
1090 .expect("Could not create callback");
1091
1092 self.session.borrow_mut().update_frame_rate(*rate, callback);
1093
1094 promise
1095 }
1096}
1097
1098pub(crate) type ApiPose = RigidTransform3D<f32, ApiSpace, webxr_api::Native>;
1100pub(crate) type ApiRigidTransform = RigidTransform3D<f32, ApiSpace, ApiSpace>;
1102
1103#[derive(Clone, Copy)]
1104pub(crate) struct BaseSpace;
1105
1106pub(crate) type BaseTransform = RigidTransform3D<f32, webxr_api::Native, BaseSpace>;
1107
1108#[expect(unsafe_code)]
1109pub(crate) fn cast_transform<T, U, V, W>(
1110 transform: RigidTransform3D<f32, T, U>,
1111) -> RigidTransform3D<f32, V, W> {
1112 unsafe { mem::transmute(transform) }
1113}
1114
1115impl Convert<XREnvironmentBlendMode> for EnvironmentBlendMode {
1116 fn convert(self) -> XREnvironmentBlendMode {
1117 match self {
1118 EnvironmentBlendMode::Opaque => XREnvironmentBlendMode::Opaque,
1119 EnvironmentBlendMode::AlphaBlend => XREnvironmentBlendMode::Alpha_blend,
1120 EnvironmentBlendMode::Additive => XREnvironmentBlendMode::Additive,
1121 }
1122 }
1123}