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 || {
241 this.root().event_callback(message.unwrap(), CanGc::deprecated_note());
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 || {
270 let this = this.root();
271 this.input_sources.add_input_sources(&this, &initial_inputs, CanGc::deprecated_note());
272 }));
273 }
274
275 fn event_callback(&self, event: XREvent, can_gc: CanGc) {
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(&(), can_gc);
288 }
289 let event = XRSessionEvent::new(
291 self.global().as_window(),
292 atom!("end"),
293 false,
294 false,
295 self,
296 can_gc,
297 );
298 event.upcast::<Event>().fire(self.upcast(), can_gc);
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 = XRFrame::new(self.global().as_window(), self, frame, can_gc);
311 frame.set_active(true);
312 if ty == SelectEvent::Start {
313 let event = XRInputSourceEvent::new(
314 self.global().as_window(),
315 START_ATOMS[atom_index].clone(),
316 false,
317 false,
318 &frame,
319 &source,
320 can_gc,
321 );
322 event.upcast::<Event>().fire(self.upcast(), can_gc);
323 } else {
324 if ty == SelectEvent::Select {
325 let event = XRInputSourceEvent::new(
326 self.global().as_window(),
327 EVENT_ATOMS[atom_index].clone(),
328 false,
329 false,
330 &frame,
331 &source,
332 can_gc,
333 );
334 event.upcast::<Event>().fire(self.upcast(), can_gc);
335 }
336 let event = XRInputSourceEvent::new(
337 self.global().as_window(),
338 END_ATOMS[atom_index].clone(),
339 false,
340 false,
341 &frame,
342 &source,
343 can_gc,
344 );
345 event.upcast::<Event>().fire(self.upcast(), can_gc);
346 }
347 frame.set_active(false);
348 }
349 },
350 XREvent::VisibilityChange(v) => {
351 let v = match v {
352 Visibility::Visible => XRVisibilityState::Visible,
353 Visibility::VisibleBlurred => XRVisibilityState::Visible_blurred,
354 Visibility::Hidden => XRVisibilityState::Hidden,
355 };
356 self.visibility_state.set(v);
357 let event = XRSessionEvent::new(
358 self.global().as_window(),
359 atom!("visibilitychange"),
360 false,
361 false,
362 self,
363 can_gc,
364 );
365 event.upcast::<Event>().fire(self.upcast(), can_gc);
366 self.dirty_layers();
369 },
370 XREvent::AddInput(info) => {
371 self.input_sources.add_input_sources(self, &[info], can_gc);
372 },
373 XREvent::RemoveInput(id) => {
374 self.input_sources.remove_input_source(self, id, can_gc);
375 },
376 XREvent::UpdateInput(id, source) => {
377 self.input_sources
378 .add_remove_input_source(self, id, source, can_gc);
379 },
380 XREvent::InputChanged(id, frame) => {
381 self.input_frames.borrow_mut().insert(id, frame);
382 },
383 XREvent::ReferenceSpaceChanged(base_space, transform) => {
384 self.reference_spaces
385 .borrow()
386 .iter()
387 .filter(|space| {
388 let base = match space.ty() {
389 XRReferenceSpaceType::Local => webxr_api::BaseSpace::Local,
390 XRReferenceSpaceType::Viewer => webxr_api::BaseSpace::Viewer,
391 XRReferenceSpaceType::Local_floor => webxr_api::BaseSpace::Floor,
392 XRReferenceSpaceType::Bounded_floor => {
393 webxr_api::BaseSpace::BoundedFloor
394 },
395 _ => panic!("unsupported reference space found"),
396 };
397 base == base_space
398 })
399 .for_each(|space| {
400 let offset =
401 XRRigidTransform::new(self.global().as_window(), transform, can_gc);
402 let event = XRReferenceSpaceEvent::new(
403 self.global().as_window(),
404 atom!("reset"),
405 false,
406 false,
407 space,
408 Some(&*offset),
409 can_gc,
410 );
411 event.upcast::<Event>().fire(space.upcast(), can_gc);
412 });
413 },
414 }
415 }
416
417 fn raf_callback(
419 &self,
420 cx: &mut js::context::JSContext,
421 mut frame: Frame,
422 time: CrossProcessInstant,
423 ) {
424 debug!("WebXR RAF callback {:?}", frame);
425
426 if let Some(pending) = self.pending_render_state.take() {
430 self.active_render_state.set(&pending);
434 if !self.is_immersive() {
437 self.update_inline_projection_matrix()
438 }
439 }
440
441 for event in frame.events.drain(..) {
443 self.handle_frame_event(event, CanGc::from_cx(cx));
444 }
445
446 if !self
452 .active_render_state
453 .get()
454 .has_sub_images(&frame.sub_images[..])
455 {
456 warn!("Rendering blank XR frame");
461 self.session.borrow_mut().render_animation_frame();
462 return;
463 }
464
465 {
469 let mut current = self.current_raf_callback_list.borrow_mut();
470 assert!(current.is_empty());
471 mem::swap(&mut *self.raf_callback_list.borrow_mut(), &mut current);
472 }
473
474 let time = self.global().performance().to_dom_high_res_time_stamp(time);
475 let frame = XRFrame::new(self.global().as_window(), self, frame, CanGc::from_cx(cx));
476
477 frame.set_active(true);
479 frame.set_animation_frame(true);
480
481 self.apply_frame_updates(&frame);
483
484 self.layers_begin_frame(&frame);
486
487 self.outside_raf.set(false);
489 let len = self.current_raf_callback_list.borrow().len();
490 for i in 0..len {
491 let callback = self.current_raf_callback_list.borrow()[i].1.clone();
492 if let Some(callback) = callback {
493 let _ = callback.Call__(cx, time, &frame, ExceptionHandling::Report);
494 }
495 }
496 self.outside_raf.set(true);
497 *self.current_raf_callback_list.borrow_mut() = vec![];
498
499 self.layers_end_frame(&frame);
501
502 frame.set_active(false);
504
505 self.session.borrow_mut().render_animation_frame();
507 }
508
509 fn update_inline_projection_matrix(&self) {
510 debug_assert!(!self.is_immersive());
511 let render_state = self.active_render_state.get();
512 let size = if let Some(base) = render_state.GetBaseLayer() {
513 base.size()
514 } else {
515 return;
516 };
517 let mut clip_planes = util::ClipPlanes::default();
518 let near = *render_state.DepthNear() as f32;
519 let far = *render_state.DepthFar() as f32;
520 clip_planes.update(near, far);
521 let top = *render_state
522 .GetInlineVerticalFieldOfView()
523 .expect("IVFOV should be non null for inline sessions") /
524 2.;
525 let top = near * top.tan() as f32;
526 let bottom = top;
527 let left = top * size.width as f32 / size.height as f32;
528 let right = left;
529 let matrix = util::frustum_to_projection_matrix(left, right, top, bottom, clip_planes);
530 *self.inline_projection_matrix.borrow_mut() = matrix;
531 }
532
533 pub(crate) fn inline_view(&self) -> View<Viewer> {
535 debug_assert!(!self.is_immersive());
536 View {
537 transform: RigidTransform3D::identity(),
539 projection: *self.inline_projection_matrix.borrow(),
540 }
541 }
542
543 pub(crate) fn session_id(&self) -> SessionId {
544 self.session.borrow().id()
545 }
546
547 pub(crate) fn dirty_layers(&self) {
548 if let Some(layer) = self.RenderState().GetBaseLayer() {
549 layer.context().mark_as_dirty();
550 }
551 }
552
553 fn layers_begin_frame(&self, frame: &XRFrame) {
555 if let Some(layer) = self.active_render_state.get().GetBaseLayer() {
556 layer.begin_frame(frame);
557 }
558 self.active_render_state.get().with_layers(|layers| {
559 for layer in layers {
560 layer.begin_frame(frame);
561 }
562 });
563 }
564
565 fn layers_end_frame(&self, frame: &XRFrame) {
567 if let Some(layer) = self.active_render_state.get().GetBaseLayer() {
568 layer.end_frame(frame);
569 }
570 self.active_render_state.get().with_layers(|layers| {
571 for layer in layers {
572 layer.end_frame(frame);
573 }
574 });
575 }
576
577 fn apply_frame_updates(&self, _frame: &XRFrame) {
579 for (id, frame) in self.input_frames.borrow_mut().drain() {
581 let source = self.input_sources.find(id);
582 if let Some(source) = source {
583 source.update_gamepad_state(frame);
584 }
585 }
586 }
587
588 fn handle_frame_event(&self, event: FrameUpdateEvent, can_gc: CanGc) {
589 match event {
590 FrameUpdateEvent::HitTestSourceAdded(id) => {
591 if let Some(promise) = self.pending_hit_test_promises.borrow_mut().remove(&id) {
592 promise.resolve_native(
593 &XRHitTestSource::new(self.global().as_window(), id, self, can_gc),
594 can_gc,
595 );
596 } else {
597 warn!(
598 "received hit test add request for unknown hit test {:?}",
599 id
600 )
601 }
602 },
603 _ => self.session.borrow_mut().apply_event(event),
604 }
605 }
606
607 fn apply_nominal_framerate(&self, rate: f32, can_gc: CanGc) {
609 if self.framerate.get() == rate || self.ended.get() {
610 return;
611 }
612
613 self.framerate.set(rate);
614
615 let event = XRSessionEvent::new(
616 self.global().as_window(),
617 Atom::from("frameratechange"),
618 false,
619 false,
620 self,
621 can_gc,
622 );
623 event.upcast::<Event>().fire(self.upcast(), can_gc);
624 }
625}
626
627impl XRSessionMethods<crate::DomTypeHolder> for XRSession {
628 event_handler!(end, GetOnend, SetOnend);
630
631 event_handler!(select, GetOnselect, SetOnselect);
633
634 event_handler!(selectstart, GetOnselectstart, SetOnselectstart);
636
637 event_handler!(selectend, GetOnselectend, SetOnselectend);
639
640 event_handler!(squeeze, GetOnsqueeze, SetOnsqueeze);
642
643 event_handler!(squeezestart, GetOnsqueezestart, SetOnsqueezestart);
645
646 event_handler!(squeezeend, GetOnsqueezeend, SetOnsqueezeend);
648
649 event_handler!(
651 visibilitychange,
652 GetOnvisibilitychange,
653 SetOnvisibilitychange
654 );
655
656 event_handler!(
658 inputsourceschange,
659 GetOninputsourceschange,
660 SetOninputsourceschange
661 );
662
663 event_handler!(frameratechange, GetOnframeratechange, SetOnframeratechange);
665
666 fn RenderState(&self) -> DomRoot<XRRenderState> {
668 self.active_render_state.get()
669 }
670
671 fn UpdateRenderState(&self, init: &XRRenderStateInit, _: InRealm) -> ErrorResult {
673 if self.ended.get() {
675 return Err(Error::InvalidState(None));
676 }
677 if let Some(Some(ref layer)) = init.baseLayer &&
679 Dom::from_ref(layer.session()) != Dom::from_ref(self)
680 {
681 return Err(Error::InvalidState(None));
682 }
683
684 if init.inlineVerticalFieldOfView.is_some() && self.is_immersive() {
686 return Err(Error::InvalidState(None));
687 }
688
689 if init.baseLayer.is_some() && (self.has_layers_feature() || init.layers.is_some()) {
692 return Err(Error::NotSupported(None));
693 }
694
695 if let Some(Some(ref layers)) = init.layers {
696 for layer in layers {
698 let count = layers
699 .iter()
700 .filter(|other| other.layer_id() == layer.layer_id())
701 .count();
702 if count > 1 {
703 return Err(Error::Type(c"Duplicate entry in WebXR layers".to_owned()));
704 }
705 }
706
707 for layer in layers {
709 if layer.session() != self {
710 return Err(Error::Type(
711 c"Layer from different session in WebXR layers".to_owned(),
712 ));
713 }
714 }
715 }
716
717 let pending = self
719 .pending_render_state
720 .or_init(|| self.active_render_state.get().clone_object());
721
722 if let Some(ref layers) = init.layers {
724 let layers = layers.as_deref().unwrap_or_default();
725 pending.set_base_layer(None);
726 pending.set_layers(layers.iter().map(|x| &**x).collect());
727 let layers = layers
728 .iter()
729 .filter_map(|layer| {
730 let context_id = WebXRContextId::from(layer.context_id());
731 let layer_id = layer.layer_id()?;
732 Some((context_id, layer_id))
733 })
734 .collect();
735 self.session.borrow_mut().set_layers(layers);
736 }
737
738 if let Some(near) = init.depthNear {
741 let mut near = *near;
742 if near < 0. {
746 near = 0.;
747 }
748 pending.set_depth_near(near);
749 }
750 if let Some(far) = init.depthFar {
751 let mut far = *far;
752 if far < 0. {
758 far = 0.;
759 }
760 pending.set_depth_far(far);
761 }
762 if let Some(fov) = init.inlineVerticalFieldOfView {
763 let mut fov = *fov;
764 if fov < 0. {
768 fov = 0.0001;
769 } else if fov > PI {
770 fov = PI - 0.0001;
771 }
772 pending.set_inline_vertical_fov(fov);
773 }
774 if let Some(ref layer) = init.baseLayer {
775 pending.set_base_layer(layer.as_deref());
776 pending.set_layers(Vec::new());
777 let layers = layer
778 .iter()
779 .filter_map(|layer| {
780 let context_id = WebXRContextId::from(layer.context_id());
781 let layer_id = layer.layer_id()?;
782 Some((context_id, layer_id))
783 })
784 .collect();
785 self.session.borrow_mut().set_layers(layers);
786 }
787
788 if init.depthFar.is_some() || init.depthNear.is_some() {
789 self.session
790 .borrow_mut()
791 .update_clip_planes(*pending.DepthNear() as f32, *pending.DepthFar() as f32);
792 }
793
794 Ok(())
795 }
796
797 fn RequestAnimationFrame(&self, callback: Rc<XRFrameRequestCallback>) -> i32 {
799 let raf_id = self.next_raf_id.get();
801 self.next_raf_id.set(raf_id + 1);
802 self.raf_callback_list
803 .borrow_mut()
804 .push((raf_id, Some(callback)));
805
806 raf_id
807 }
808
809 fn CancelAnimationFrame(&self, frame: i32) {
811 let mut list = self.raf_callback_list.borrow_mut();
812 if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
813 pair.1 = None;
814 }
815
816 let mut list = self.current_raf_callback_list.borrow_mut();
817 if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
818 pair.1 = None;
819 }
820 }
821
822 fn EnvironmentBlendMode(&self) -> XREnvironmentBlendMode {
824 self.blend_mode
825 }
826
827 fn VisibilityState(&self) -> XRVisibilityState {
829 self.visibility_state.get()
830 }
831
832 fn RequestReferenceSpace(
834 &self,
835 ty: XRReferenceSpaceType,
836 comp: InRealm,
837 can_gc: CanGc,
838 ) -> Rc<Promise> {
839 let p = Promise::new_in_current_realm(comp, can_gc);
840
841 if !self.is_immersive() &&
847 (ty == XRReferenceSpaceType::Bounded_floor || ty == XRReferenceSpaceType::Unbounded)
848 {
849 p.reject_error(Error::NotSupported(None), can_gc);
850 return p;
851 }
852
853 match ty {
854 XRReferenceSpaceType::Unbounded => {
855 p.reject_error(Error::NotSupported(None), can_gc)
857 },
858 ty => {
859 if ty != XRReferenceSpaceType::Viewer &&
860 (!self.is_immersive() || ty != XRReferenceSpaceType::Local)
861 {
862 let s = ty.as_str();
863 if !self
864 .session
865 .borrow()
866 .granted_features()
867 .iter()
868 .any(|f| *f == s)
869 {
870 p.reject_error(Error::NotSupported(None), can_gc);
871 return p;
872 }
873 }
874 if ty == XRReferenceSpaceType::Bounded_floor {
875 let space =
876 XRBoundedReferenceSpace::new(self.global().as_window(), self, can_gc);
877 self.reference_spaces
878 .borrow_mut()
879 .push(Dom::from_ref(space.reference_space()));
880 p.resolve_native(&space, can_gc);
881 } else {
882 let space = XRReferenceSpace::new(self.global().as_window(), self, ty, can_gc);
883 self.reference_spaces
884 .borrow_mut()
885 .push(Dom::from_ref(&*space));
886 p.resolve_native(&space, can_gc);
887 }
888 },
889 }
890 p
891 }
892
893 fn InputSources(&self) -> DomRoot<XRInputSourceArray> {
895 DomRoot::from_ref(&*self.input_sources)
896 }
897
898 fn End(&self, can_gc: CanGc) -> Rc<Promise> {
900 let global = self.global();
901 let p = Promise::new(&global, can_gc);
902 if self.ended.get() && self.end_promises.borrow().is_empty() {
903 p.resolve_native(&(), can_gc);
913 return p;
914 }
915 self.end_promises.borrow_mut().push(p.clone());
916 self.ended.set(true);
920 global.as_window().Navigator().Xr().end_session(self);
921 self.session.borrow_mut().end_session();
922 for source in 0..self.input_sources.Length() {
924 self.input_sources
925 .remove_input_source(self, InputId(source), can_gc);
926 }
927 p
928 }
929
930 fn RequestHitTestSource(&self, options: &XRHitTestOptionsInit, can_gc: CanGc) -> Rc<Promise> {
932 let p = Promise::new(&self.global(), can_gc);
933
934 if !self
935 .session
936 .borrow()
937 .granted_features()
938 .iter()
939 .any(|f| f == "hit-test")
940 {
941 p.reject_error(Error::NotSupported(None), can_gc);
942 return p;
943 }
944
945 let id = self.next_hit_test_id.get();
946 self.next_hit_test_id.set(HitTestId(id.0 + 1));
947
948 let space = options.space.space();
949 let ray = if let Some(ref ray) = options.offsetRay {
950 ray.ray()
951 } else {
952 Ray {
953 origin: Vector3D::new(0., 0., 0.),
954 direction: Vector3D::new(0., 0., -1.),
955 }
956 };
957
958 let mut types = EntityTypes::default();
959
960 if let Some(ref tys) = options.entityTypes {
961 for ty in tys {
962 match ty {
963 XRHitTestTrackableType::Point => types.point = true,
964 XRHitTestTrackableType::Plane => types.plane = true,
965 XRHitTestTrackableType::Mesh => types.mesh = true,
966 }
967 }
968 } else {
969 types.plane = true;
970 }
971
972 let source = HitTestSource {
973 id,
974 space,
975 ray,
976 types,
977 };
978 self.pending_hit_test_promises
979 .borrow_mut()
980 .insert(id, p.clone());
981
982 self.session.borrow().request_hit_test(source);
983
984 p
985 }
986
987 fn InteractionMode(&self) -> XRInteractionMode {
989 XRInteractionMode::World_space
992 }
993
994 fn GetFrameRate(&self) -> Option<Finite<f32>> {
996 let session = self.session.borrow();
997 if self.mode == XRSessionMode::Inline || session.supported_frame_rates().is_empty() {
998 None
999 } else {
1000 Finite::new(self.framerate.get())
1001 }
1002 }
1003
1004 fn GetSupportedFrameRates(
1006 &self,
1007 cx: JSContext,
1008 can_gc: CanGc,
1009 ) -> Option<RootedTraceableBox<HeapFloat32Array>> {
1010 let session = self.session.borrow();
1011 if self.mode == XRSessionMode::Inline || session.supported_frame_rates().is_empty() {
1012 None
1013 } else {
1014 let framerates = session.supported_frame_rates();
1015 rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>());
1016 Some(
1017 create_buffer_source(cx, framerates, array.handle_mut(), can_gc)
1018 .expect("Failed to construct supported frame rates array"),
1019 )
1020 }
1021 }
1022
1023 fn EnabledFeatures(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
1025 let session = self.session.borrow();
1026 let features = session.granted_features();
1027 to_frozen_array(features, cx, retval, can_gc)
1028 }
1029
1030 fn IsSystemKeyboardSupported(&self) -> bool {
1032 false
1035 }
1036
1037 fn UpdateTargetFrameRate(
1039 &self,
1040 rate: Finite<f32>,
1041 comp: InRealm,
1042 can_gc: CanGc,
1043 ) -> Rc<Promise> {
1044 let promise = Promise::new_in_current_realm(comp, can_gc);
1045 {
1046 let session = self.session.borrow();
1047 let supported_frame_rates = session.supported_frame_rates();
1048
1049 if self.mode == XRSessionMode::Inline ||
1050 supported_frame_rates.is_empty() ||
1051 self.ended.get()
1052 {
1053 promise.reject_error(Error::InvalidState(None), can_gc);
1054 return promise;
1055 }
1056
1057 if !supported_frame_rates.contains(&*rate) {
1058 promise.reject_error(
1059 Error::Type(c"Provided framerate not supported".into()),
1060 can_gc,
1061 );
1062 return promise;
1063 }
1064 }
1065
1066 *self.update_framerate_promise.borrow_mut() = Some(promise.clone());
1067
1068 let this = Trusted::new(self);
1069 let global = self.global();
1070 let task_source = global
1071 .task_manager()
1072 .dom_manipulation_task_source()
1073 .to_sendable();
1074
1075 let callback =
1076 ProfileGenericCallback::new(global.time_profiler_chan().clone(), move |message| {
1077 let this = this.clone();
1078 task_source.queue(task!(update_session_framerate: move || {
1079 let session = this.root();
1080 session.apply_nominal_framerate(message.unwrap(), CanGc::deprecated_note());
1081 if let Some(promise) = session.update_framerate_promise.borrow_mut().take() {
1082 promise.resolve_native(&(), CanGc::deprecated_note());
1083 };
1084 }));
1085 })
1086 .expect("Could not create callback");
1087
1088 self.session.borrow_mut().update_frame_rate(*rate, callback);
1089
1090 promise
1091 }
1092}
1093
1094pub(crate) type ApiPose = RigidTransform3D<f32, ApiSpace, webxr_api::Native>;
1096pub(crate) type ApiRigidTransform = RigidTransform3D<f32, ApiSpace, ApiSpace>;
1098
1099#[derive(Clone, Copy)]
1100pub(crate) struct BaseSpace;
1101
1102pub(crate) type BaseTransform = RigidTransform3D<f32, webxr_api::Native, BaseSpace>;
1103
1104#[expect(unsafe_code)]
1105pub(crate) fn cast_transform<T, U, V, W>(
1106 transform: RigidTransform3D<f32, T, U>,
1107) -> RigidTransform3D<f32, V, W> {
1108 unsafe { mem::transmute(transform) }
1109}
1110
1111impl Convert<XREnvironmentBlendMode> for EnvironmentBlendMode {
1112 fn convert(self) -> XREnvironmentBlendMode {
1113 match self {
1114 EnvironmentBlendMode::Opaque => XREnvironmentBlendMode::Opaque,
1115 EnvironmentBlendMode::AlphaBlend => XREnvironmentBlendMode::Alpha_blend,
1116 EnvironmentBlendMode::Additive => XREnvironmentBlendMode::Additive,
1117 }
1118 }
1119}