Skip to main content

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