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