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_weak_referenceable_dom_object;
12use dom_struct::dom_struct;
13use js::context::JSContext;
14use js::realm::CurrentRealm;
15use euclid::{RigidTransform3D, Transform3D, Vector3D};
16use ipc_channel::ipc::IpcReceiver;
17use ipc_channel::router::ROUTER;
18use js::jsapi::JSObject;
19use js::rust::MutableHandleValue;
20use js::typedarray::HeapFloat32Array;
21use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
22use rustc_hash::FxBuildHasher;
23use script_bindings::trace::RootedTraceableBox;
24use servo_base::cross_process_instant::CrossProcessInstant;
25use stylo_atoms::Atom;
26use webxr_api::{
27 self, ApiSpace, ContextId as WebXRContextId, Display, EntityTypes, EnvironmentBlendMode,
28 Event as XREvent, Frame, FrameUpdateEvent, HitTestId, HitTestSource, InputFrame, InputId, Ray,
29 SelectEvent, SelectKind, Session, SessionId, View, Viewer, Visibility, util,
30};
31
32use crate::conversions::Convert;
33use crate::canvas_context::CanvasContext;
34use crate::dom::bindings::trace::HashMapTracedValues;
35use crate::dom::bindings::buffer_source::create_buffer_source;
36use crate::dom::bindings::callback::ExceptionHandling;
37use script_bindings::cell::DomRefCell;
38use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
39use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
40use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::{
41 XRHitTestOptionsInit, XRHitTestTrackableType,
42};
43use crate::dom::bindings::codegen::Bindings::XRInputSourceArrayBinding::XRInputSourceArray_Binding::XRInputSourceArrayMethods;
44use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType;
45use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::{
46 XRRenderStateInit, XRRenderStateMethods,
47};
48use crate::dom::bindings::codegen::Bindings::XRSessionBinding::{
49 XREnvironmentBlendMode, XRFrameRequestCallback, XRInteractionMode, XRSessionMethods,
50 XRVisibilityState,
51};
52use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionMode;
53use crate::dom::bindings::error::{Error, ErrorResult};
54use crate::dom::bindings::inheritance::Castable;
55use crate::dom::bindings::num::Finite;
56use crate::dom::bindings::refcounted::Trusted;
57use crate::dom::bindings::reflector::{DomGlobal};
58use crate::dom::bindings::root::{Dom, DomRoot, MutDom, MutNullableDom};
59use crate::dom::bindings::utils::to_frozen_array;
60use crate::dom::event::Event;
61use crate::dom::eventtarget::EventTarget;
62use crate::dom::promise::Promise;
63use crate::dom::window::Window;
64use crate::dom::xrboundedreferencespace::XRBoundedReferenceSpace;
65use crate::dom::xrframe::XRFrame;
66use crate::dom::xrhittestsource::XRHitTestSource;
67use crate::dom::xrinputsourcearray::XRInputSourceArray;
68use crate::dom::xrinputsourceevent::XRInputSourceEvent;
69use crate::dom::xrreferencespace::XRReferenceSpace;
70use crate::dom::xrreferencespaceevent::XRReferenceSpaceEvent;
71use crate::dom::xrrenderstate::XRRenderState;
72use crate::dom::xrrigidtransform::XRRigidTransform;
73use crate::dom::xrsessionevent::XRSessionEvent;
74use crate::dom::xrspace::XRSpace;
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 cx: &mut JSContext,
156 window: &Window,
157 session: Session,
158 mode: XRSessionMode,
159 frame_receiver: IpcReceiver<Frame>,
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(cx, window, 0.1, 1000.0, ivfov, None, Vec::new());
167 let input_sources = XRInputSourceArray::new(cx, window);
168 let ret = reflect_weak_referenceable_dom_object(
169 cx,
170 Rc::new(XRSession::new_inherited(
171 session,
172 &render_state,
173 &input_sources,
174 mode,
175 )),
176 window,
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 |cx| {
217 this.root().raf_callback(cx, frame, time);
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 |cx| {
240 this.root().event_callback(cx, message.unwrap());
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 |cx| {
269 let this = this.root();
270 this.input_sources.add_input_sources(cx, &this, &initial_inputs);
271 }));
272 }
273
274 fn event_callback(&self, cx: &mut JSContext, event: XREvent) {
275 match event {
276 XREvent::SessionEnd => {
277 self.ended.set(true);
280 self.global()
282 .as_window()
283 .Navigator(cx)
284 .Xr(cx)
285 .end_session(self);
286 for promise in self.end_promises.borrow().iter() {
290 promise.resolve_native(cx, &());
291 }
292 self.end_promises.safe_borrow_mut(cx.no_gc()).clear();
293 let event = XRSessionEvent::new(
295 cx,
296 self.global().as_window(),
297 atom!("end"),
298 false,
299 false,
300 self,
301 );
302 event.upcast::<Event>().fire(cx, self.upcast());
303 },
304 XREvent::Select(input, kind, ty, frame) => {
305 use stylo_atoms::Atom;
306 const START_ATOMS: [Atom; 2] = [atom!("selectstart"), atom!("squeezestart")];
307 const EVENT_ATOMS: [Atom; 2] = [atom!("select"), atom!("squeeze")];
308 const END_ATOMS: [Atom; 2] = [atom!("selectend"), atom!("squeezeend")];
309
310 let source = self.input_sources.find(input);
312 let atom_index = if kind == SelectKind::Squeeze { 1 } else { 0 };
313 if let Some(source) = source {
314 let frame = XRFrame::new(cx, self.global().as_window(), self, frame);
315 frame.set_active(true);
316 if ty == SelectEvent::Start {
317 let event = XRInputSourceEvent::new(
318 cx,
319 self.global().as_window(),
320 START_ATOMS[atom_index].clone(),
321 false,
322 false,
323 &frame,
324 &source,
325 );
326 event.upcast::<Event>().fire(cx, self.upcast());
327 } else {
328 if ty == SelectEvent::Select {
329 let event = XRInputSourceEvent::new(
330 cx,
331 self.global().as_window(),
332 EVENT_ATOMS[atom_index].clone(),
333 false,
334 false,
335 &frame,
336 &source,
337 );
338 event.upcast::<Event>().fire(cx, self.upcast());
339 }
340 let event = XRInputSourceEvent::new(
341 cx,
342 self.global().as_window(),
343 END_ATOMS[atom_index].clone(),
344 false,
345 false,
346 &frame,
347 &source,
348 );
349 event.upcast::<Event>().fire(cx, self.upcast());
350 }
351 frame.set_active(false);
352 }
353 },
354 XREvent::VisibilityChange(v) => {
355 let v = match v {
356 Visibility::Visible => XRVisibilityState::Visible,
357 Visibility::VisibleBlurred => XRVisibilityState::Visible_blurred,
358 Visibility::Hidden => XRVisibilityState::Hidden,
359 };
360 self.visibility_state.set(v);
361 let event = XRSessionEvent::new(
362 cx,
363 self.global().as_window(),
364 atom!("visibilitychange"),
365 false,
366 false,
367 self,
368 );
369 event.upcast::<Event>().fire(cx, self.upcast());
370 self.dirty_layers();
373 },
374 XREvent::AddInput(info) => {
375 self.input_sources.add_input_sources(cx, self, &[info]);
376 },
377 XREvent::RemoveInput(id) => {
378 self.input_sources.remove_input_source(cx, self, id);
379 },
380 XREvent::UpdateInput(id, source) => {
381 self.input_sources
382 .add_remove_input_source(cx, self, id, source);
383 },
384 XREvent::InputChanged(id, frame) => {
385 self.input_frames.borrow_mut().insert(id, frame);
386 },
387 XREvent::ReferenceSpaceChanged(base_space, transform) => {
388 self.reference_spaces
389 .borrow()
390 .iter()
391 .filter(|space| {
392 let base = match space.ty() {
393 XRReferenceSpaceType::Local => webxr_api::BaseSpace::Local,
394 XRReferenceSpaceType::Viewer => webxr_api::BaseSpace::Viewer,
395 XRReferenceSpaceType::Local_floor => webxr_api::BaseSpace::Floor,
396 XRReferenceSpaceType::Bounded_floor => {
397 webxr_api::BaseSpace::BoundedFloor
398 },
399 _ => panic!("unsupported reference space found"),
400 };
401 base == base_space
402 })
403 .for_each(|space| {
404 let offset =
405 XRRigidTransform::new(cx, self.global().as_window(), transform);
406 let event = XRReferenceSpaceEvent::new(
407 cx,
408 self.global().as_window(),
409 atom!("reset"),
410 false,
411 false,
412 space,
413 Some(&*offset),
414 );
415 event.upcast::<Event>().fire(cx, space.upcast());
416 });
417 },
418 }
419 }
420
421 fn raf_callback(&self, cx: &mut JSContext, mut frame: Frame, time: CrossProcessInstant) {
423 debug!("WebXR RAF callback {:?}", frame);
424
425 if let Some(pending) = self.pending_render_state.take() {
429 self.active_render_state.set(&pending);
433 if !self.is_immersive() {
436 self.update_inline_projection_matrix()
437 }
438 }
439
440 for event in frame.events.drain(..) {
442 self.handle_frame_event(cx, event);
443 }
444
445 if !self
451 .active_render_state
452 .get()
453 .has_sub_images(&frame.sub_images[..])
454 {
455 warn!("Rendering blank XR frame");
460 self.session.borrow_mut().render_animation_frame();
461 return;
462 }
463
464 {
468 let mut current = self.current_raf_callback_list.borrow_mut();
469 assert!(current.is_empty());
470 mem::swap(&mut *self.raf_callback_list.borrow_mut(), &mut current);
471 }
472
473 let time = self
474 .global()
475 .performance(cx)
476 .to_dom_high_res_time_stamp(time);
477 let frame = XRFrame::new(cx, self.global().as_window(), self, frame);
478
479 frame.set_active(true);
481 frame.set_animation_frame(true);
482
483 self.apply_frame_updates(&frame);
485
486 self.layers_begin_frame(cx, &frame);
488
489 self.outside_raf.set(false);
491 let len = self.current_raf_callback_list.borrow().len();
492 for i in 0..len {
493 let callback = self.current_raf_callback_list.borrow()[i].1.clone();
494 if let Some(callback) = callback {
495 let _ = callback.Call__(cx, time, &frame, ExceptionHandling::Report);
496 }
497 }
498 self.outside_raf.set(true);
499 *self.current_raf_callback_list.borrow_mut() = vec![];
500
501 self.layers_end_frame(&frame);
503
504 frame.set_active(false);
506
507 self.session.borrow_mut().render_animation_frame();
509 }
510
511 fn update_inline_projection_matrix(&self) {
512 debug_assert!(!self.is_immersive());
513 let render_state = self.active_render_state.get();
514 let size = if let Some(base) = render_state.GetBaseLayer() {
515 base.size()
516 } else {
517 return;
518 };
519 let mut clip_planes = util::ClipPlanes::default();
520 let near = *render_state.DepthNear() as f32;
521 let far = *render_state.DepthFar() as f32;
522 clip_planes.update(near, far);
523 let top = *render_state
524 .GetInlineVerticalFieldOfView()
525 .expect("IVFOV should be non null for inline sessions") /
526 2.;
527 let top = near * top.tan() as f32;
528 let bottom = top;
529 let left = top * size.width as f32 / size.height as f32;
530 let right = left;
531 let matrix = util::frustum_to_projection_matrix(left, right, top, bottom, clip_planes);
532 *self.inline_projection_matrix.borrow_mut() = matrix;
533 }
534
535 pub(crate) fn inline_view(&self) -> View<Viewer> {
537 debug_assert!(!self.is_immersive());
538 View {
539 transform: RigidTransform3D::identity(),
541 projection: *self.inline_projection_matrix.borrow(),
542 }
543 }
544
545 pub(crate) fn session_id(&self) -> SessionId {
546 self.session.borrow().id()
547 }
548
549 pub(crate) fn dirty_layers(&self) {
550 if let Some(layer) = self.RenderState().GetBaseLayer() {
551 layer.context().mark_as_dirty();
552 }
553 }
554
555 fn layers_begin_frame(&self, cx: &mut JSContext, frame: &XRFrame) {
557 if let Some(layer) = self.active_render_state.get().GetBaseLayer() {
558 layer.begin_frame(cx, frame);
559 }
560 self.active_render_state.get().with_layers(|layers| {
561 for layer in layers {
562 layer.begin_frame(cx, frame);
563 }
564 });
565 }
566
567 fn layers_end_frame(&self, frame: &XRFrame) {
569 if let Some(layer) = self.active_render_state.get().GetBaseLayer() {
570 layer.end_frame(frame);
571 }
572 self.active_render_state.get().with_layers(|layers| {
573 for layer in layers {
574 layer.end_frame(frame);
575 }
576 });
577 }
578
579 fn apply_frame_updates(&self, _frame: &XRFrame) {
581 for (id, frame) in self.input_frames.borrow_mut().drain() {
583 let source = self.input_sources.find(id);
584 if let Some(source) = source {
585 source.update_gamepad_state(frame);
586 }
587 }
588 }
589
590 fn handle_frame_event(&self, cx: &mut JSContext, event: FrameUpdateEvent) {
591 match event {
592 FrameUpdateEvent::HitTestSourceAdded(id) => {
593 if let Some(promise) = self.pending_hit_test_promises.borrow_mut().remove(&id) {
594 let hit_test_source =
595 XRHitTestSource::new(cx, self.global().as_window(), id, self);
596 promise.resolve_native(cx, &hit_test_source);
597 } else {
598 warn!(
599 "received hit test add request for unknown hit test {:?}",
600 id
601 )
602 }
603 },
604 _ => self.session.borrow_mut().apply_event(event),
605 }
606 }
607
608 fn apply_nominal_framerate(&self, cx: &mut JSContext, rate: f32) {
610 if self.framerate.get() == rate || self.ended.get() {
611 return;
612 }
613
614 self.framerate.set(rate);
615
616 let event = XRSessionEvent::new(
617 cx,
618 self.global().as_window(),
619 Atom::from("frameratechange"),
620 false,
621 false,
622 self,
623 );
624 event.upcast::<Event>().fire(cx, self.upcast());
625 }
626}
627
628impl XRSessionMethods<crate::DomTypeHolder> for XRSession {
629 event_handler!(end, GetOnend, SetOnend);
631
632 event_handler!(select, GetOnselect, SetOnselect);
634
635 event_handler!(selectstart, GetOnselectstart, SetOnselectstart);
637
638 event_handler!(selectend, GetOnselectend, SetOnselectend);
640
641 event_handler!(squeeze, GetOnsqueeze, SetOnsqueeze);
643
644 event_handler!(squeezestart, GetOnsqueezestart, SetOnsqueezestart);
646
647 event_handler!(squeezeend, GetOnsqueezeend, SetOnsqueezeend);
649
650 event_handler!(
652 visibilitychange,
653 GetOnvisibilitychange,
654 SetOnvisibilitychange
655 );
656
657 event_handler!(
659 inputsourceschange,
660 GetOninputsourceschange,
661 SetOninputsourceschange
662 );
663
664 event_handler!(frameratechange, GetOnframeratechange, SetOnframeratechange);
666
667 fn RenderState(&self) -> DomRoot<XRRenderState> {
669 self.active_render_state.get()
670 }
671
672 fn UpdateRenderState(&self, cx: &mut JSContext, init: &XRRenderStateInit) -> ErrorResult {
674 if self.ended.get() {
676 return Err(Error::InvalidState(None));
677 }
678 if let Some(Some(ref layer)) = init.baseLayer &&
680 Dom::from_ref(layer.session()) != Dom::from_ref(self)
681 {
682 return Err(Error::InvalidState(None));
683 }
684
685 if init.inlineVerticalFieldOfView.is_some() && self.is_immersive() {
687 return Err(Error::InvalidState(None));
688 }
689
690 if init.baseLayer.is_some() && (self.has_layers_feature() || init.layers.is_some()) {
693 return Err(Error::NotSupported(None));
694 }
695
696 if let Some(Some(ref layers)) = init.layers {
697 for layer in layers {
699 let count = layers
700 .iter()
701 .filter(|other| other.layer_id() == layer.layer_id())
702 .count();
703 if count > 1 {
704 return Err(Error::Type(c"Duplicate entry in WebXR layers".to_owned()));
705 }
706 }
707
708 for layer in layers {
710 if layer.session() != self {
711 return Err(Error::Type(
712 c"Layer from different session in WebXR layers".to_owned(),
713 ));
714 }
715 }
716 }
717
718 let pending = self
720 .pending_render_state
721 .or_init(|| self.active_render_state.get().clone_object(cx));
722
723 if let Some(ref layers) = init.layers {
725 let layers = layers.as_deref().unwrap_or_default();
726 pending.set_base_layer(None);
727 pending.set_layers(layers.iter().map(|x| &**x).collect());
728 let layers = layers
729 .iter()
730 .filter_map(|layer| {
731 let context_id = WebXRContextId::from(layer.context_id());
732 let layer_id = layer.layer_id()?;
733 Some((context_id, layer_id))
734 })
735 .collect();
736 self.session.borrow_mut().set_layers(layers);
737 }
738
739 if let Some(near) = init.depthNear {
742 let mut near = *near;
743 if near < 0. {
747 near = 0.;
748 }
749 pending.set_depth_near(near);
750 }
751 if let Some(far) = init.depthFar {
752 let mut far = *far;
753 if far < 0. {
759 far = 0.;
760 }
761 pending.set_depth_far(far);
762 }
763 if let Some(fov) = init.inlineVerticalFieldOfView {
764 let mut fov = *fov;
765 if fov < 0. {
769 fov = 0.0001;
770 } else if fov > PI {
771 fov = PI - 0.0001;
772 }
773 pending.set_inline_vertical_fov(fov);
774 }
775 if let Some(ref layer) = init.baseLayer {
776 pending.set_base_layer(layer.as_deref());
777 pending.set_layers(Vec::new());
778 let layers = layer
779 .iter()
780 .filter_map(|layer| {
781 let context_id = WebXRContextId::from(layer.context_id());
782 let layer_id = layer.layer_id()?;
783 Some((context_id, layer_id))
784 })
785 .collect();
786 self.session.borrow_mut().set_layers(layers);
787 }
788
789 if init.depthFar.is_some() || init.depthNear.is_some() {
790 self.session
791 .borrow_mut()
792 .update_clip_planes(*pending.DepthNear() as f32, *pending.DepthFar() as f32);
793 }
794
795 Ok(())
796 }
797
798 fn RequestAnimationFrame(&self, callback: Rc<XRFrameRequestCallback>) -> i32 {
800 let raf_id = self.next_raf_id.get();
802 self.next_raf_id.set(raf_id + 1);
803 self.raf_callback_list
804 .borrow_mut()
805 .push((raf_id, Some(callback)));
806
807 raf_id
808 }
809
810 fn CancelAnimationFrame(&self, frame: i32) {
812 let mut list = self.raf_callback_list.borrow_mut();
813 if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
814 pair.1 = None;
815 }
816
817 let mut list = self.current_raf_callback_list.borrow_mut();
818 if let Some(pair) = list.iter_mut().find(|pair| pair.0 == frame) {
819 pair.1 = None;
820 }
821 }
822
823 fn EnvironmentBlendMode(&self) -> XREnvironmentBlendMode {
825 self.blend_mode
826 }
827
828 fn VisibilityState(&self) -> XRVisibilityState {
830 self.visibility_state.get()
831 }
832
833 fn RequestReferenceSpace(
835 &self,
836 cx: &mut CurrentRealm,
837 ty: XRReferenceSpaceType,
838 ) -> Rc<Promise> {
839 let p = Promise::new_in_realm(cx);
840
841 if !self.is_immersive() &&
847 (ty == XRReferenceSpaceType::Bounded_floor || ty == XRReferenceSpaceType::Unbounded)
848 {
849 p.reject_error(cx, Error::NotSupported(None));
850 return p;
851 }
852
853 match ty {
854 XRReferenceSpaceType::Unbounded => {
855 p.reject_error(cx, Error::NotSupported(None))
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(cx, Error::NotSupported(None));
871 return p;
872 }
873 }
874 if ty == XRReferenceSpaceType::Bounded_floor {
875 let space = XRBoundedReferenceSpace::new(cx, self.global().as_window(), self);
876 self.reference_spaces
877 .borrow_mut()
878 .push(Dom::from_ref(space.reference_space()));
879 p.resolve_native(cx, &space);
880 } else {
881 let space = XRReferenceSpace::new(cx, self.global().as_window(), self, ty);
882 self.reference_spaces
883 .borrow_mut()
884 .push(Dom::from_ref(&*space));
885 p.resolve_native(cx, &space);
886 }
887 },
888 }
889 p
890 }
891
892 fn InputSources(&self) -> DomRoot<XRInputSourceArray> {
894 DomRoot::from_ref(&*self.input_sources)
895 }
896
897 fn End(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
899 let p = Promise::new_in_realm(cx);
900 if self.ended.get() && self.end_promises.borrow().is_empty() {
901 p.resolve_native(cx, &());
911 return p;
912 }
913 self.end_promises.borrow_mut().push(p.clone());
914 self.ended.set(true);
918 self.global()
919 .as_window()
920 .Navigator(cx)
921 .Xr(cx)
922 .end_session(self);
923 self.session.borrow_mut().end_session();
924 for source in 0..self.input_sources.Length() {
926 self.input_sources
927 .remove_input_source(cx, self, InputId(source));
928 }
929 p
930 }
931
932 fn RequestHitTestSource(
934 &self,
935 cx: &mut CurrentRealm,
936 options: &XRHitTestOptionsInit,
937 ) -> Rc<Promise> {
938 let p = Promise::new_in_realm(cx);
939
940 if !self
941 .session
942 .borrow()
943 .granted_features()
944 .iter()
945 .any(|f| f == "hit-test")
946 {
947 p.reject_error(cx, Error::NotSupported(None));
948 return p;
949 }
950
951 let id = self.next_hit_test_id.get();
952 self.next_hit_test_id.set(HitTestId(id.0 + 1));
953
954 let space = options.space.space();
955 let ray = if let Some(ref ray) = options.offsetRay {
956 ray.ray()
957 } else {
958 Ray {
959 origin: Vector3D::new(0., 0., 0.),
960 direction: Vector3D::new(0., 0., -1.),
961 }
962 };
963
964 let mut types = EntityTypes::default();
965
966 if let Some(ref tys) = options.entityTypes {
967 for ty in tys {
968 match ty {
969 XRHitTestTrackableType::Point => types.point = true,
970 XRHitTestTrackableType::Plane => types.plane = true,
971 XRHitTestTrackableType::Mesh => types.mesh = true,
972 }
973 }
974 } else {
975 types.plane = true;
976 }
977
978 let source = HitTestSource {
979 id,
980 space,
981 ray,
982 types,
983 };
984 self.pending_hit_test_promises
985 .borrow_mut()
986 .insert(id, p.clone());
987
988 self.session.borrow().request_hit_test(source);
989
990 p
991 }
992
993 fn InteractionMode(&self) -> XRInteractionMode {
995 XRInteractionMode::World_space
998 }
999
1000 fn GetFrameRate(&self) -> Option<Finite<f32>> {
1002 let session = self.session.borrow();
1003 if self.mode == XRSessionMode::Inline || session.supported_frame_rates().is_empty() {
1004 None
1005 } else {
1006 Finite::new(self.framerate.get())
1007 }
1008 }
1009
1010 fn GetSupportedFrameRates(
1012 &self,
1013 cx: &mut JSContext,
1014 ) -> Option<RootedTraceableBox<HeapFloat32Array>> {
1015 let session = self.session.borrow();
1016 if self.mode == XRSessionMode::Inline || session.supported_frame_rates().is_empty() {
1017 None
1018 } else {
1019 let framerates = session.supported_frame_rates();
1020 rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
1021 Some(
1022 create_buffer_source(cx, framerates, array.handle_mut())
1023 .expect("Failed to construct supported frame rates array"),
1024 )
1025 }
1026 }
1027
1028 fn EnabledFeatures(&self, cx: &mut JSContext, retval: MutableHandleValue) {
1030 let session = self.session.borrow();
1031 let features = session.granted_features();
1032 to_frozen_array(cx, features, retval)
1033 }
1034
1035 fn IsSystemKeyboardSupported(&self) -> bool {
1037 false
1040 }
1041
1042 fn UpdateTargetFrameRate(&self, cx: &mut CurrentRealm, rate: Finite<f32>) -> Rc<Promise> {
1044 let promise = Promise::new_in_realm(cx);
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(cx, Error::InvalidState(None));
1054 return promise;
1055 }
1056
1057 if !supported_frame_rates.contains(&*rate) {
1058 promise.reject_error(cx, Error::Type(c"Provided framerate not supported".into()));
1059 return promise;
1060 }
1061 }
1062
1063 *self.update_framerate_promise.borrow_mut() = Some(promise.clone());
1064
1065 let this = Trusted::new(self);
1066 let global = self.global();
1067 let task_source = global
1068 .task_manager()
1069 .dom_manipulation_task_source()
1070 .to_sendable();
1071
1072 let callback =
1073 ProfileGenericCallback::new(global.time_profiler_chan().clone(), move |message| {
1074 let this = this.clone();
1075 task_source.queue(task!(update_session_framerate: move |cx| {
1076 let session = this.root();
1077 session.apply_nominal_framerate(cx, message.unwrap());
1078 if let Some(promise) = session.update_framerate_promise.borrow_mut().take() {
1079 promise.resolve_native(cx, &());
1080 };
1081 }));
1082 })
1083 .expect("Could not create callback");
1084
1085 self.session.borrow_mut().update_frame_rate(*rate, callback);
1086
1087 promise
1088 }
1089}
1090
1091pub(crate) type ApiPose = RigidTransform3D<f32, ApiSpace, webxr_api::Native>;
1093pub(crate) type ApiRigidTransform = RigidTransform3D<f32, ApiSpace, ApiSpace>;
1095
1096#[derive(Clone, Copy)]
1097pub(crate) struct BaseSpace;
1098
1099pub(crate) type BaseTransform = RigidTransform3D<f32, webxr_api::Native, BaseSpace>;
1100
1101#[expect(unsafe_code)]
1102pub(crate) fn cast_transform<T, U, V, W>(
1103 transform: RigidTransform3D<f32, T, U>,
1104) -> RigidTransform3D<f32, V, W> {
1105 unsafe { mem::transmute(transform) }
1106}
1107
1108impl Convert<XREnvironmentBlendMode> for EnvironmentBlendMode {
1109 fn convert(self) -> XREnvironmentBlendMode {
1110 match self {
1111 EnvironmentBlendMode::Opaque => XREnvironmentBlendMode::Opaque,
1112 EnvironmentBlendMode::AlphaBlend => XREnvironmentBlendMode::Alpha_blend,
1113 EnvironmentBlendMode::Additive => XREnvironmentBlendMode::Additive,
1114 }
1115 }
1116}