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