Skip to main content

script/dom/touch/
touch.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 std::f64::consts::PI;
6
7use dom_struct::dom_struct;
8use euclid::Point2D;
9use js::context::JSContext;
10use js::rust::HandleObject;
11use script_bindings::inheritance::Castable;
12use script_bindings::reflector::{
13    Reflector, reflect_dom_object_with_cx, reflect_dom_object_with_proto,
14};
15
16use crate::dom::bindings::codegen::Bindings::TouchBinding::{TouchInit, TouchMethods};
17use crate::dom::bindings::num::Finite;
18use crate::dom::bindings::root::{DomRoot, MutDom};
19use crate::dom::event::{Event, EventBubbles, EventCancelable};
20use crate::dom::eventtarget::EventTarget;
21use crate::dom::pointerevent::PointerEvent;
22use crate::dom::window::Window;
23
24#[dom_struct]
25pub(crate) struct Touch {
26    reflector_: Reflector,
27    identifier: i32,
28    target: MutDom<EventTarget>,
29    screen_x: f64,
30    screen_y: f64,
31    client_x: f64,
32    client_y: f64,
33    page_x: f64,
34    page_y: f64,
35}
36
37impl Touch {
38    #[allow(clippy::too_many_arguments)]
39    fn new_inherited(
40        identifier: i32,
41        target: &EventTarget,
42        screen_x: Finite<f64>,
43        screen_y: Finite<f64>,
44        client_x: Finite<f64>,
45        client_y: Finite<f64>,
46        page_x: Finite<f64>,
47        page_y: Finite<f64>,
48    ) -> Touch {
49        Touch {
50            reflector_: Reflector::new(),
51            identifier,
52            target: MutDom::new(target),
53            screen_x: *screen_x,
54            screen_y: *screen_y,
55            client_x: *client_x,
56            client_y: *client_y,
57            page_x: *page_x,
58            page_y: *page_y,
59        }
60    }
61
62    #[allow(clippy::too_many_arguments)]
63    pub(crate) fn new(
64        cx: &mut JSContext,
65        window: &Window,
66        identifier: i32,
67        target: &EventTarget,
68        screen_x: Finite<f64>,
69        screen_y: Finite<f64>,
70        client_x: Finite<f64>,
71        client_y: Finite<f64>,
72        page_x: Finite<f64>,
73        page_y: Finite<f64>,
74    ) -> DomRoot<Touch> {
75        reflect_dom_object_with_cx(
76            Box::new(Touch::new_inherited(
77                identifier, target, screen_x, screen_y, client_x, client_y, page_x, page_y,
78            )),
79            window,
80            cx,
81        )
82    }
83
84    #[allow(clippy::too_many_arguments)]
85    fn new_with_proto(
86        cx: &mut JSContext,
87        window: &Window,
88        proto: Option<HandleObject>,
89        identifier: i32,
90        target: &EventTarget,
91        screen_x: Finite<f64>,
92        screen_y: Finite<f64>,
93        client_x: Finite<f64>,
94        client_y: Finite<f64>,
95        page_x: Finite<f64>,
96        page_y: Finite<f64>,
97    ) -> DomRoot<Touch> {
98        reflect_dom_object_with_proto(
99            cx,
100            Box::new(Touch::new_inherited(
101                identifier, target, screen_x, screen_y, client_x, client_y, page_x, page_y,
102            )),
103            window,
104            proto,
105        )
106    }
107
108    /// Create a PointerEvent from this Touch.
109    /// <https://w3c.github.io/pointerevents/#the-primary-pointer>
110    #[allow(clippy::too_many_arguments)]
111    pub(crate) fn to_pointer_event(
112        &self,
113        cx: &mut JSContext,
114        window: &Window,
115        event_type: &str,
116        pointer_id: i32,
117        is_primary: bool,
118        pointer_type: &str,
119        modifiers: keyboard_types::Modifiers,
120        is_cancelable: bool,
121        point_in_node: Option<euclid::Point2D<f32, style_traits::CSSPixel>>,
122    ) -> DomRoot<PointerEvent> {
123        // Pressure is 0.5 for active touches, 0.0 for up/cancel/out/leave
124        // <https://w3c.github.io/pointerevents/#dom-pointerevent-pressure>
125        // TODO: add proper force support.
126        let pressure = if event_type == "pointerup" ||
127            event_type == "pointercancel" ||
128            event_type == "pointerout" ||
129            event_type == "pointerleave"
130        {
131            0.0
132        } else {
133            0.5
134        };
135
136        // <https://w3c.github.io/pointerevents/#the-button-property>
137        // For pointermove, pointerover, pointerenter, pointerout, pointerleave: button is -1
138        // For pointerdown, pointerup, pointercancel: button is 0 (primary button)
139        let button = if event_type == "pointermove" ||
140            event_type == "pointerover" ||
141            event_type == "pointerenter" ||
142            event_type == "pointerout" ||
143            event_type == "pointerleave"
144        {
145            -1
146        } else {
147            0
148        };
149
150        // Buttons: 1 if a button is pressed during the event, 0 otherwise
151        // For touch: button is pressed during over/enter/down/move, not during up/cancel/out/leave
152        let buttons = if event_type == "pointermove" ||
153            event_type == "pointerover" ||
154            event_type == "pointerenter" ||
155            event_type == "pointerdown"
156        {
157            1
158        } else {
159            0
160        };
161
162        // For enter/leave events, they don't bubble and are not cancelable
163        let (bubbles, cancelable) = match event_type {
164            "pointerenter" | "pointerleave" => {
165                (EventBubbles::DoesNotBubble, EventCancelable::NotCancelable)
166            },
167            _ => (EventBubbles::Bubbles, EventCancelable::from(is_cancelable)),
168        };
169
170        let pointer_event = PointerEvent::new(
171            cx,
172            window,
173            event_type.into(),
174            bubbles,
175            cancelable,
176            Some(window),
177            0, // detail
178            Point2D::new(*self.ScreenX() as i32, *self.ScreenY() as i32),
179            Point2D::new(*self.ClientX() as i32, *self.ClientY() as i32),
180            Point2D::new(*self.PageX() as i32, *self.PageY() as i32),
181            modifiers,
182            button,
183            buttons,
184            None, // related_target
185            point_in_node,
186            pointer_id,
187            1, // width (TODO: could get from touch if available)
188            1, // height (TODO: could get from touch if available)
189            pressure,
190            0.0,      // tangential_pressure
191            0,        // tilt_x
192            0,        // tilt_y
193            0,        // twist
194            PI / 2.0, // altitude_angle (perpendicular to surface)
195            0.0,      // azimuth_angle
196            pointer_type.into(),
197            is_primary,
198            vec![], // coalesced_events
199            vec![], // predicted_events
200        );
201
202        // https://w3c.github.io/pointerevents/#dfn-attributes-and-default-actions
203        // For pointerenter and pointerleave events, the composed [DOM] attribute SHOULD be false;
204        // for all other pointer events in the table above, the attribute SHOULD be true.
205        let composed = !matches!(event_type, "pointerenter" | "pointerleave");
206        pointer_event.upcast::<Event>().set_composed(composed);
207
208        pointer_event
209    }
210}
211
212impl TouchMethods<crate::DomTypeHolder> for Touch {
213    /// <https://w3c.github.io/touch-events/#dom-touch-constructor>
214    #[allow(clippy::too_many_arguments)]
215    fn Constructor(
216        cx: &mut JSContext,
217        window: &Window,
218        proto: Option<HandleObject>,
219        touch_init_dict: &TouchInit,
220    ) -> DomRoot<Touch> {
221        Touch::new_with_proto(
222            cx,
223            window,
224            proto,
225            touch_init_dict.identifier,
226            &touch_init_dict.target,
227            touch_init_dict.screenX,
228            touch_init_dict.screenY,
229            touch_init_dict.clientX,
230            touch_init_dict.clientY,
231            touch_init_dict.pageX,
232            touch_init_dict.pageY,
233        )
234    }
235
236    /// <https://w3c.github.io/touch-events/#widl-Touch-identifier>
237    fn Identifier(&self) -> i32 {
238        self.identifier
239    }
240
241    /// <https://w3c.github.io/touch-events/#widl-Touch-target>
242    fn Target(&self) -> DomRoot<EventTarget> {
243        self.target.get()
244    }
245
246    /// <https://w3c.github.io/touch-events/#widl-Touch-screenX>
247    fn ScreenX(&self) -> Finite<f64> {
248        Finite::wrap(self.screen_x)
249    }
250
251    /// <https://w3c.github.io/touch-events/#widl-Touch-screenY>
252    fn ScreenY(&self) -> Finite<f64> {
253        Finite::wrap(self.screen_y)
254    }
255
256    /// <https://w3c.github.io/touch-events/#widl-Touch-clientX>
257    fn ClientX(&self) -> Finite<f64> {
258        Finite::wrap(self.client_x)
259    }
260
261    /// <https://w3c.github.io/touch-events/#widl-Touch-clientY>
262    fn ClientY(&self) -> Finite<f64> {
263        Finite::wrap(self.client_y)
264    }
265
266    /// <https://w3c.github.io/touch-events/#widl-Touch-clientX>
267    fn PageX(&self) -> Finite<f64> {
268        Finite::wrap(self.page_x)
269    }
270
271    /// <https://w3c.github.io/touch-events/#widl-Touch-clientY>
272    fn PageY(&self) -> Finite<f64> {
273        Finite::wrap(self.page_y)
274    }
275}