script/dom/webxr/
xrinputsource.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use dom_struct::dom_struct;
6use embedder_traits::GamepadSupportedHapticEffects;
7use js::jsapi::Heap;
8use js::jsval::{JSVal, UndefinedValue};
9use js::rust::MutableHandleValue;
10use script_bindings::conversions::SafeToJSValConvertible;
11use webxr_api::{Handedness, InputFrame, InputId, InputSource, TargetRayMode};
12
13use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
14    XRHandedness, XRInputSourceMethods, XRTargetRayMode,
15};
16use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
17use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
18use crate::dom::gamepad::Gamepad;
19use crate::dom::globalscope::GlobalScope;
20use crate::dom::window::Window;
21use crate::dom::xrhand::XRHand;
22use crate::dom::xrsession::XRSession;
23use crate::dom::xrspace::XRSpace;
24use crate::realms::enter_realm;
25use crate::script_runtime::{CanGc, JSContext};
26
27#[dom_struct]
28pub(crate) struct XRInputSource {
29    reflector: Reflector,
30    session: Dom<XRSession>,
31    #[no_trace]
32    info: InputSource,
33    target_ray_space: MutNullableDom<XRSpace>,
34    grip_space: MutNullableDom<XRSpace>,
35    hand: MutNullableDom<XRHand>,
36    #[ignore_malloc_size_of = "mozjs"]
37    profiles: Heap<JSVal>,
38    gamepad: DomRoot<Gamepad>,
39}
40
41impl XRInputSource {
42    pub(crate) fn new_inherited(
43        window: &Window,
44        session: &XRSession,
45        info: InputSource,
46        can_gc: CanGc,
47    ) -> XRInputSource {
48        // <https://www.w3.org/TR/webxr-gamepads-module-1/#gamepad-differences>
49        let gamepad = Gamepad::new(
50            window,
51            0,
52            "".into(),
53            "xr-standard".into(),
54            (-1.0, 1.0),
55            (0.0, 1.0),
56            GamepadSupportedHapticEffects {
57                supports_dual_rumble: false,
58                supports_trigger_rumble: false,
59            },
60            true,
61            can_gc,
62        );
63        XRInputSource {
64            reflector: Reflector::new(),
65            session: Dom::from_ref(session),
66            info,
67            target_ray_space: Default::default(),
68            grip_space: Default::default(),
69            hand: Default::default(),
70            profiles: Heap::default(),
71            gamepad,
72        }
73    }
74
75    pub(crate) fn new(
76        window: &Window,
77        session: &XRSession,
78        info: InputSource,
79        can_gc: CanGc,
80    ) -> DomRoot<XRInputSource> {
81        let source = reflect_dom_object(
82            Box::new(XRInputSource::new_inherited(window, session, info, can_gc)),
83            window,
84            can_gc,
85        );
86
87        let _ac = enter_realm(window);
88        let cx = GlobalScope::get_cx();
89        rooted!(in(*cx) let mut profiles = UndefinedValue());
90        source
91            .info
92            .profiles
93            .safe_to_jsval(cx, profiles.handle_mut(), can_gc);
94        source.profiles.set(profiles.get());
95        source
96    }
97
98    pub(crate) fn id(&self) -> InputId {
99        self.info.id
100    }
101
102    pub(crate) fn session(&self) -> &XRSession {
103        &self.session
104    }
105
106    pub(crate) fn update_gamepad_state(&self, frame: InputFrame) {
107        frame
108            .button_values
109            .iter()
110            .enumerate()
111            .for_each(|(i, value)| {
112                self.gamepad.map_and_normalize_buttons(i, *value as f64);
113            });
114        frame.axis_values.iter().enumerate().for_each(|(i, value)| {
115            self.gamepad.map_and_normalize_axes(i, *value as f64);
116        });
117    }
118
119    pub(crate) fn gamepad(&self) -> &DomRoot<Gamepad> {
120        &self.gamepad
121    }
122}
123
124impl XRInputSourceMethods<crate::DomTypeHolder> for XRInputSource {
125    /// <https://immersive-web.github.io/webxr/#dom-xrinputsource-handedness>
126    fn Handedness(&self) -> XRHandedness {
127        match self.info.handedness {
128            Handedness::None => XRHandedness::None,
129            Handedness::Left => XRHandedness::Left,
130            Handedness::Right => XRHandedness::Right,
131        }
132    }
133
134    /// <https://immersive-web.github.io/webxr/#dom-xrinputsource-targetraymode>
135    fn TargetRayMode(&self) -> XRTargetRayMode {
136        match self.info.target_ray_mode {
137            TargetRayMode::Gaze => XRTargetRayMode::Gaze,
138            TargetRayMode::TrackedPointer => XRTargetRayMode::Tracked_pointer,
139            TargetRayMode::Screen => XRTargetRayMode::Screen,
140            TargetRayMode::TransientPointer => XRTargetRayMode::Transient_pointer,
141        }
142    }
143
144    /// <https://immersive-web.github.io/webxr/#dom-xrinputsource-targetrayspace>
145    fn TargetRaySpace(&self) -> DomRoot<XRSpace> {
146        self.target_ray_space.or_init(|| {
147            let global = self.global();
148            XRSpace::new_inputspace(&global, &self.session, self, false, CanGc::note())
149        })
150    }
151
152    /// <https://immersive-web.github.io/webxr/#dom-xrinputsource-gripspace>
153    fn GetGripSpace(&self) -> Option<DomRoot<XRSpace>> {
154        if self.info.supports_grip {
155            Some(self.grip_space.or_init(|| {
156                let global = self.global();
157                XRSpace::new_inputspace(&global, &self.session, self, true, CanGc::note())
158            }))
159        } else {
160            None
161        }
162    }
163    /// <https://immersive-web.github.io/webxr/#dom-xrinputsource-profiles>
164    fn Profiles(&self, _cx: JSContext, mut retval: MutableHandleValue) {
165        retval.set(self.profiles.get())
166    }
167
168    /// <https://www.w3.org/TR/webxr/#dom-xrinputsource-skiprendering>
169    fn SkipRendering(&self) -> bool {
170        // Servo is not currently supported anywhere that would allow for skipped
171        // controller rendering.
172        false
173    }
174
175    /// <https://www.w3.org/TR/webxr-gamepads-module-1/#xrinputsource-interface>
176    fn GetGamepad(&self) -> Option<DomRoot<Gamepad>> {
177        Some(DomRoot::from_ref(&*self.gamepad))
178    }
179
180    /// <https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md>
181    fn GetHand(&self) -> Option<DomRoot<XRHand>> {
182        self.info.hand_support.as_ref().map(|hand| {
183            self.hand
184                .or_init(|| XRHand::new(&self.global(), self, hand.clone(), CanGc::note()))
185        })
186    }
187}