Skip to main content

script/dom/event/
mouseevent.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::cell::Cell;
6use std::default::Default;
7use std::f64::consts::PI;
8
9use dom_struct::dom_struct;
10use euclid::Point2D;
11use js::context::JSContext;
12use js::rust::HandleObject;
13use keyboard_types::Modifiers;
14use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
15use script_bindings::dom::MutNullableDom;
16use script_bindings::match_domstring_ascii;
17use script_bindings::reflector::reflect_dom_object_with_proto;
18use script_traits::ConstellationInputEvent;
19use servo_base::text::Utf32CodeUnits;
20use style::Atom;
21use style_traits::CSSPixel;
22
23use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
24use crate::dom::bindings::codegen::Bindings::MouseEventBinding;
25use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
26use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods;
27use crate::dom::bindings::error::Fallible;
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::reflector::DomGlobal;
30use crate::dom::bindings::root::DomRoot;
31use crate::dom::bindings::str::DOMString;
32use crate::dom::document::FireMouseEventType;
33use crate::dom::event::{Event, EventBubbles, EventCancelable};
34use crate::dom::eventtarget::EventTarget;
35use crate::dom::inputevent::HitTestResult;
36use crate::dom::node::Node;
37use crate::dom::pointerevent::{PointerEvent, PointerId};
38use crate::dom::uievent::UIEvent;
39use crate::dom::window::Window;
40
41/// <https://w3c.github.io/uievents/#interface-mouseevent>
42#[dom_struct]
43pub(crate) struct MouseEvent {
44    uievent: UIEvent,
45
46    /// The point on the screen of where this [`MouseEvent`] was originally triggered,
47    /// to use during the dispatch phase.
48    ///
49    /// See:
50    /// <https://w3c.github.io/uievents/#dom-mouseevent-screenx>
51    /// <https://w3c.github.io/uievents/#dom-mouseevent-screeny>
52    #[no_trace]
53    screen_point: Cell<Point2D<i32, CSSPixel>>,
54
55    /// The point in the viewport of where this [`MouseEvent`] was originally triggered,
56    /// to use during the dispatch phase.
57    ///
58    /// See:
59    /// <https://w3c.github.io/uievents/#dom-mouseevent-clientx>
60    /// <https://w3c.github.io/uievents/#dom-mouseevent-clienty>
61    #[no_trace]
62    client_point: Cell<Point2D<i32, CSSPixel>>,
63
64    /// The point in the initial containing block of where this [`MouseEvent`] was
65    /// originally triggered to use during the dispatch phase.
66    ///
67    /// See:
68    /// <https://w3c.github.io/uievents/#dom-mouseevent-pagex>
69    /// <https://w3c.github.io/uievents/#dom-mouseevent-pagey>
70    #[no_trace]
71    page_point: Cell<Point2D<i32, CSSPixel>>,
72
73    /// The keyboard modifiers that were active when this mouse event was triggered.
74    #[no_trace]
75    modifiers: Cell<Modifiers>,
76
77    /// <https://w3c.github.io/uievents/#dom-mouseevent-button>
78    button: Cell<i16>,
79
80    /// <https://w3c.github.io/uievents/#dom-mouseevent-buttons>
81    buttons: Cell<u16>,
82
83    #[no_trace]
84    point_in_target: Cell<Option<Point2D<f32, CSSPixel>>>,
85
86    /// Together with `dom_position_offset`, if both are present, a position in the DOM tree
87    /// near the cursor at time of this event.
88    dom_position_container: MutNullableDom<Node>,
89
90    /// Together with `dom_position_container`, if both are present, a position in the DOM tree
91    /// near the cursor at time of this event.
92    #[no_trace]
93    dom_position_offset: Cell<Option<Utf32CodeUnits>>,
94}
95
96impl MouseEvent {
97    pub(crate) fn new_inherited() -> MouseEvent {
98        MouseEvent {
99            uievent: UIEvent::new_inherited(),
100            screen_point: Cell::new(Default::default()),
101            client_point: Cell::new(Default::default()),
102            page_point: Cell::new(Default::default()),
103            modifiers: Cell::new(Modifiers::empty()),
104            button: Cell::new(0),
105            buttons: Cell::new(0),
106            point_in_target: Cell::new(None),
107            dom_position_container: MutNullableDom::default(),
108            dom_position_offset: Cell::new(None),
109        }
110    }
111
112    pub(crate) fn new_uninitialized(cx: &mut JSContext, window: &Window) -> DomRoot<MouseEvent> {
113        Self::new_uninitialized_with_proto(cx, window, None)
114    }
115
116    fn new_uninitialized_with_proto(
117        cx: &mut JSContext,
118        window: &Window,
119        proto: Option<HandleObject>,
120    ) -> DomRoot<MouseEvent> {
121        reflect_dom_object_with_proto(cx, Box::new(MouseEvent::new_inherited()), window, proto)
122    }
123
124    #[allow(clippy::too_many_arguments)]
125    pub(crate) fn new(
126        cx: &mut JSContext,
127        window: &Window,
128        event_type: Atom,
129        can_bubble: EventBubbles,
130        cancelable: EventCancelable,
131        view: Option<&Window>,
132        detail: i32,
133        screen_point: Point2D<i32, CSSPixel>,
134        client_point: Point2D<i32, CSSPixel>,
135        page_point: Point2D<i32, CSSPixel>,
136        modifiers: Modifiers,
137        button: i16,
138        buttons: u16,
139        related_target: Option<&EventTarget>,
140        point_in_target: Option<Point2D<f32, CSSPixel>>,
141        dom_position_for_selection: Option<&(DomRoot<Node>, Utf32CodeUnits)>,
142    ) -> DomRoot<MouseEvent> {
143        Self::new_with_proto(
144            cx,
145            window,
146            None,
147            event_type,
148            can_bubble,
149            cancelable,
150            view,
151            detail,
152            screen_point,
153            client_point,
154            page_point,
155            modifiers,
156            button,
157            buttons,
158            related_target,
159            point_in_target,
160            dom_position_for_selection,
161        )
162    }
163
164    #[allow(clippy::too_many_arguments)]
165    fn new_with_proto(
166        cx: &mut JSContext,
167        window: &Window,
168        proto: Option<HandleObject>,
169        event_type: Atom,
170        can_bubble: EventBubbles,
171        cancelable: EventCancelable,
172        view: Option<&Window>,
173        detail: i32,
174        screen_point: Point2D<i32, CSSPixel>,
175        client_point: Point2D<i32, CSSPixel>,
176        page_point: Point2D<i32, CSSPixel>,
177        modifiers: Modifiers,
178        button: i16,
179        buttons: u16,
180        related_target: Option<&EventTarget>,
181        point_in_target: Option<Point2D<f32, CSSPixel>>,
182        dom_position_for_selection: Option<&(DomRoot<Node>, Utf32CodeUnits)>,
183    ) -> DomRoot<MouseEvent> {
184        let ev = MouseEvent::new_uninitialized_with_proto(cx, window, proto);
185        ev.initialize_mouse_event(
186            event_type,
187            can_bubble,
188            cancelable,
189            view,
190            detail,
191            screen_point,
192            client_point,
193            page_point,
194            modifiers,
195            button,
196            buttons,
197            related_target,
198            point_in_target,
199            dom_position_for_selection,
200        );
201        ev
202    }
203
204    /// <https://w3c.github.io/pointerevents/#initialize-a-mouseevent>
205    #[expect(clippy::too_many_arguments)]
206    pub(crate) fn initialize_mouse_event(
207        &self,
208        event_type: Atom,
209        can_bubble: EventBubbles,
210        cancelable: EventCancelable,
211        view: Option<&Window>,
212        detail: i32,
213        screen_point: Point2D<i32, CSSPixel>,
214        client_point: Point2D<i32, CSSPixel>,
215        page_point: Point2D<i32, CSSPixel>,
216        modifiers: Modifiers,
217        button: i16,
218        buttons: u16,
219        related_target: Option<&EventTarget>,
220        point_in_target: Option<Point2D<f32, CSSPixel>>,
221        dom_position_for_selection: Option<&(DomRoot<Node>, Utf32CodeUnits)>,
222    ) {
223        self.uievent.initialize_ui_event(
224            event_type,
225            view.map(|window| window.upcast::<EventTarget>()),
226            can_bubble,
227            cancelable,
228        );
229
230        self.uievent.set_detail(detail);
231        self.screen_point.set(screen_point);
232        self.client_point.set(client_point);
233        self.page_point.set(page_point);
234        self.modifiers.set(modifiers);
235        self.button.set(button);
236        self.buttons.set(buttons);
237        self.upcast::<Event>().set_related_target(related_target);
238        self.point_in_target.set(point_in_target);
239        // Legacy mapping per spec: left/middle/right => 1/2/3 (button + 1), else 0.
240        let w = if button >= 0 { (button as u32) + 1 } else { 0 };
241        self.uievent.set_which(w);
242        if let Some((node, offset)) = dom_position_for_selection {
243            self.dom_position_container.set(Some(node));
244            self.dom_position_offset.set(Some(*offset));
245        }
246    }
247
248    pub(crate) fn new_for_platform_motion_event(
249        cx: &mut JSContext,
250        window: &Window,
251        event_name: FireMouseEventType,
252        hit_test_result: &HitTestResult,
253        input_event: &ConstellationInputEvent,
254    ) -> DomRoot<Self> {
255        // These values come from the event tables in
256        // <https://w3c.github.io/pointerevents/#mouse-event-types>.
257        let (bubbles, cancelable, composed) = match event_name {
258            FireMouseEventType::Move | FireMouseEventType::Over | FireMouseEventType::Out => {
259                (EventBubbles::Bubbles, EventCancelable::Cancelable, true)
260            },
261            FireMouseEventType::Enter | FireMouseEventType::Leave => (
262                EventBubbles::DoesNotBubble,
263                EventCancelable::NotCancelable,
264                false,
265            ),
266        };
267
268        let mouse_event = Self::new(
269            cx,
270            window,
271            Atom::from(event_name.as_str()),
272            bubbles,
273            cancelable,
274            Some(window),
275            0i32,
276            hit_test_result.point_in_frame.to_i32(),
277            hit_test_result.point_in_frame.to_i32(),
278            hit_test_result
279                .point_relative_to_initial_containing_block
280                .to_i32(),
281            input_event.active_keyboard_modifiers,
282            0i16,
283            input_event.pressed_mouse_buttons,
284            None,
285            None,
286            hit_test_result.dom_position_for_selection.as_ref(),
287        );
288
289        let event = mouse_event.upcast::<Event>();
290        event.set_composed(composed);
291        event.set_trusted(true);
292
293        mouse_event
294    }
295
296    pub(crate) fn dom_offset_for_selection(&self) -> Option<Utf32CodeUnits> {
297        self.dom_position_offset.get()
298    }
299
300    /// Create a [MouseEvent] triggered by the embedder.
301    ///
302    /// <https://w3c.github.io/pointerevents/#create-a-cancelable-mouseevent>
303    #[expect(clippy::too_many_arguments)]
304    pub(crate) fn for_platform_button_event(
305        cx: &mut JSContext,
306        event_type: Atom,
307        event: embedder_traits::MouseButtonEvent,
308        pressed_mouse_buttons: u16,
309        window: &Window,
310        hit_test_result: &HitTestResult,
311        modifiers: Modifiers,
312        click_count: usize,
313    ) -> DomRoot<Self> {
314        let client_point = hit_test_result.point_in_frame.to_i32();
315        let page_point = hit_test_result
316            .point_relative_to_initial_containing_block
317            .to_i32();
318
319        let mouse_event = Self::new(
320            cx,
321            window,
322            event_type,
323            EventBubbles::Bubbles,
324            EventCancelable::Cancelable,
325            Some(window),
326            click_count as i32,
327            client_point, // TODO: Get real screen coordinates?
328            client_point,
329            page_point,
330            modifiers,
331            event.button.into(),
332            pressed_mouse_buttons,
333            None,
334            Some(hit_test_result.point_in_node),
335            hit_test_result.dom_position_for_selection.as_ref(),
336        );
337
338        mouse_event.upcast::<Event>().set_trusted(true);
339        mouse_event.upcast::<Event>().set_composed(true);
340
341        mouse_event
342    }
343
344    /// Create a PointerEvent from this MouseEvent.
345    /// <https://w3c.github.io/pointerevents/#the-primary-pointer>
346    /// For mouse, the pointer ID is always -1, and is_primary is always true.
347    pub(crate) fn to_pointer_event(
348        &self,
349        cx: &mut JSContext,
350        event_type: Atom,
351    ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
352        // TODO: This function should almost certainly accept an enumn for the event type.
353        let is_pointer_down = &*event_type == "pointerdown";
354        let is_pointer_move = &*event_type == "pointermove";
355        let is_pointer_up = &*event_type == "pointerup";
356
357        // Pressure is 0.5 when button is down, 0.0 when up
358        let pressure = if is_pointer_down || (is_pointer_move && self.Buttons() != 0) {
359            0.5
360        } else {
361            0.0
362        };
363
364        let button = if is_pointer_down || is_pointer_up {
365            self.Button()
366        } else {
367            -1
368        };
369
370        // https://w3c.github.io/pointerevents/#dfn-attributes-and-default-actions
371        // For pointerenter and pointerleave events, the composed [DOM] attribute SHOULD be false;
372        // for all other pointer events in the table above, the attribute SHOULD be true.
373        let composed = !matches!(&*event_type, "pointerenter" | "pointerleave");
374
375        let window = self.global();
376        let window = window.as_window();
377
378        let pointer_event = PointerEvent::new(
379            cx,
380            window,
381            event_type,
382            EventBubbles::from(self.upcast::<Event>().Bubbles()),
383            EventCancelable::from(self.upcast::<Event>().Cancelable()),
384            self.uievent.GetView().as_deref(),
385            self.uievent.Detail(),
386            Point2D::new(self.ScreenX(), self.ScreenY()),
387            Point2D::new(self.ClientX(), self.ClientY()),
388            Point2D::new(self.PageX(), self.PageY()),
389            self.modifiers.get(),
390            button,
391            self.Buttons(),
392            self.GetRelatedTarget().as_deref(),
393            self.point_in_target.get(),
394            PointerId::Mouse as i32, // Mouse pointer ID is always -1
395            1,                       // width
396            1,                       // height
397            pressure,
398            0.0,      // tangential_pressure
399            0,        // tilt_x
400            0,        // tilt_y
401            0,        // twist
402            PI / 2.0, // altitude_angle (perpendicular to surface)
403            0.0,      // azimuth_angle
404            DOMString::from("mouse"),
405            true,   // is_primary (mouse is always primary)
406            vec![], // coalesced_events
407            vec![], // predicted_events
408        );
409
410        pointer_event.upcast::<Event>().set_composed(composed);
411
412        pointer_event
413    }
414
415    /// Create a PointerEvent for hover events (pointerover, pointerenter, pointerout, pointerleave).
416    /// <https://w3c.github.io/pointerevents/#the-primary-pointer>
417    /// For mouse, the pointer ID is always -1, and is_primary is always true.
418    pub(crate) fn to_pointer_hover_event(
419        &self,
420        cx: &mut JSContext,
421        event_type: &str,
422    ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
423        // Determine bubbles and cancelable based on event type
424        // pointerover/pointerout bubble and are cancelable
425        // pointerenter/pointerleave do not bubble and are not cancelable
426        let (bubbles, cancelable) = match event_type {
427            "pointerover" | "pointerout" => (EventBubbles::Bubbles, EventCancelable::Cancelable),
428            "pointerenter" | "pointerleave" => {
429                (EventBubbles::DoesNotBubble, EventCancelable::NotCancelable)
430            },
431            _ => (EventBubbles::Bubbles, EventCancelable::Cancelable),
432        };
433
434        let window = self.global();
435        let window = window.as_window();
436
437        let pointer_event = PointerEvent::new(
438            cx,
439            window,
440            event_type.into(),
441            bubbles,
442            cancelable,
443            self.uievent.GetView().as_deref(),
444            self.uievent.Detail(),
445            Point2D::new(self.ScreenX(), self.ScreenY()),
446            Point2D::new(self.ClientX(), self.ClientY()),
447            Point2D::new(self.PageX(), self.PageY()),
448            self.modifiers.get(),
449            -1, // button: -1 for hover events (no button pressed)
450            self.Buttons(),
451            self.GetRelatedTarget().as_deref(),
452            self.point_in_target.get(),
453            PointerId::Mouse as i32, // Mouse pointer ID is always -1
454            1,                       // width
455            1,                       // height
456            0.0,                     // pressure: 0.0 for hover events
457            0.0,                     // tangential_pressure
458            0,                       // tilt_x
459            0,                       // tilt_y
460            0,                       // twist
461            PI / 2.0,                // altitude_angle (perpendicular to surface)
462            0.0,                     // azimuth_angle
463            DOMString::from("mouse"),
464            true,   // is_primary (mouse is always primary)
465            vec![], // coalesced_events
466            vec![], // predicted_events
467        );
468
469        // https://w3c.github.io/pointerevents/#dfn-attributes-and-default-actions
470        // For pointerenter and pointerleave events, the composed [DOM] attribute SHOULD be false;
471        // for all other pointer events in the table above, the attribute SHOULD be true.
472        let composed = !matches!(event_type, "pointerenter" | "pointerleave");
473        pointer_event.upcast::<Event>().set_composed(composed);
474
475        // Set trusted to match the source mouse event
476        pointer_event
477            .upcast::<Event>()
478            .set_trusted(self.IsTrusted());
479
480        pointer_event
481    }
482}
483
484impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
485    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-constructor>
486    fn Constructor(
487        cx: &mut JSContext,
488        window: &Window,
489        proto: Option<HandleObject>,
490        event_type: DOMString,
491        init: &MouseEventBinding::MouseEventInit,
492    ) -> Fallible<DomRoot<MouseEvent>> {
493        let bubbles = EventBubbles::from(init.parent.parent.parent.bubbles);
494        let cancelable = EventCancelable::from(init.parent.parent.parent.cancelable);
495        let scroll_offset = window.scroll_offset();
496        let page_point = Point2D::new(
497            scroll_offset.x as i32 + init.clientX,
498            scroll_offset.y as i32 + init.clientY,
499        );
500        let event = MouseEvent::new_with_proto(
501            cx,
502            window,
503            proto,
504            event_type.into(),
505            bubbles,
506            cancelable,
507            init.parent.parent.view.as_deref(),
508            init.parent.parent.detail,
509            Point2D::new(init.screenX, init.screenY),
510            Point2D::new(init.clientX, init.clientY),
511            page_point,
512            init.parent.modifiers(),
513            init.button,
514            init.buttons,
515            init.relatedTarget.as_deref(),
516            None,
517            None,
518        );
519        event
520            .upcast::<Event>()
521            .set_composed(init.parent.parent.parent.composed);
522        Ok(event)
523    }
524
525    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-screenx>
526    fn ScreenX(&self) -> i32 {
527        self.screen_point.get().x
528    }
529
530    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-screeny>
531    fn ScreenY(&self) -> i32 {
532        self.screen_point.get().y
533    }
534
535    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-clientx>
536    fn ClientX(&self) -> i32 {
537        self.client_point.get().x
538    }
539
540    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-clienty>
541    fn ClientY(&self) -> i32 {
542        self.client_point.get().y
543    }
544
545    /// <https://drafts.csswg.org/cssom-view/#dom-mouseevent-pagex>
546    fn PageX(&self) -> i32 {
547        // The pageX attribute must follow these steps:
548        // > 1. If the event’s dispatch flag is set, return the horizontal coordinate of the
549        // > position where the event occurred relative to the origin of the initial containing
550        // > block and terminate these steps.
551        if self.upcast::<Event>().dispatching() {
552            return self.page_point.get().x;
553        }
554
555        // > 2. Let offset be the value of the scrollX attribute of the event’s associated
556        // > Window object, if there is one, or zero otherwise.
557        // > 3. Return the sum of offset and the value of the event’s clientX attribute.
558        self.global().as_window().ScrollX() + self.ClientX()
559    }
560
561    /// <https://drafts.csswg.org/cssom-view/#dom-mouseevent-pagey>
562    fn PageY(&self) -> i32 {
563        // The pageY attribute must follow these steps:
564        // > 1. If the event’s dispatch flag is set, return the vertical coordinate of the
565        // > position where the event occurred relative to the origin of the initial
566        // > containing block and terminate these steps.
567        if self.upcast::<Event>().dispatching() {
568            return self.page_point.get().y;
569        }
570
571        // > 2. Let offset be the value of the scrollY attribute of the event’s associated
572        // > Window object, if there is one, or zero otherwise.
573        // > 3. Return the sum of offset and the value of the event’s clientY attribute.
574        self.global().as_window().ScrollY() + self.ClientY()
575    }
576
577    /// <https://drafts.csswg.org/cssom-view/#dom-mouseevent-x>
578    fn X(&self) -> i32 {
579        self.ClientX()
580    }
581
582    /// <https://drafts.csswg.org/cssom-view/#dom-mouseevent-y>
583    fn Y(&self) -> i32 {
584        self.ClientY()
585    }
586
587    /// <https://drafts.csswg.org/cssom-view/#dom-mouseevent-offsetx>
588    fn OffsetX(&self) -> i32 {
589        // > The offsetX attribute must follow these steps:
590        // > 1. If the event’s dispatch flag is set, return the x-coordinate of the position
591        // >    where the event occurred relative to the origin of the padding edge of the
592        // >    target node, ignoring the transforms that apply to the element and its
593        // >    ancestors, and terminate these steps.
594        let event = self.upcast::<Event>();
595        if event.dispatching() {
596            let Some(target) = event.GetTarget() else {
597                return 0;
598            };
599            let Some(node) = target.downcast::<Node>() else {
600                return 0;
601            };
602            return self.ClientX() - node.client_rect().origin.x;
603        }
604
605        // > 2. Return the value of the event’s pageX attribute.
606        self.PageX()
607    }
608
609    /// <https://drafts.csswg.org/cssom-view/#dom-mouseevent-offsety>
610    fn OffsetY(&self) -> i32 {
611        // > The offsetY attribute must follow these steps:
612        // > 1. If the event’s dispatch flag is set, return the y-coordinate of the
613        // >    position where the event occurred relative to the origin of the padding edge of
614        // >    the target node, ignoring the transforms that apply to the element and its
615        // >    ancestors, and terminate these steps.
616        let event = self.upcast::<Event>();
617        if event.dispatching() {
618            let Some(target) = event.GetTarget() else {
619                return 0;
620            };
621            let Some(node) = target.downcast::<Node>() else {
622                return 0;
623            };
624            return self.ClientY() - node.client_rect().origin.y;
625        }
626
627        // 2. Return the value of the event’s pageY attribute.
628        self.PageY()
629    }
630
631    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-ctrlkey>
632    fn CtrlKey(&self) -> bool {
633        self.modifiers.get().contains(Modifiers::CONTROL)
634    }
635
636    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-shiftkey>
637    fn ShiftKey(&self) -> bool {
638        self.modifiers.get().contains(Modifiers::SHIFT)
639    }
640
641    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-altkey>
642    fn AltKey(&self) -> bool {
643        self.modifiers.get().contains(Modifiers::ALT)
644    }
645
646    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-metakey>
647    fn MetaKey(&self) -> bool {
648        self.modifiers.get().contains(Modifiers::META)
649    }
650
651    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-button>
652    fn Button(&self) -> i16 {
653        self.button.get()
654    }
655
656    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-buttons>
657    fn Buttons(&self) -> u16 {
658        self.buttons.get()
659    }
660
661    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-relatedtarget>
662    fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> {
663        self.upcast::<Event>().related_target()
664    }
665
666    /// <https://w3c.github.io/pointerevents/#dom-mouseevent-initmouseevent>
667    fn InitMouseEvent(
668        &self,
669        type_arg: DOMString,
670        can_bubble_arg: bool,
671        cancelable_arg: bool,
672        view_arg: Option<&Window>,
673        detail_arg: i32,
674        screen_x_arg: i32,
675        screen_y_arg: i32,
676        client_x_arg: i32,
677        client_y_arg: i32,
678        ctrl_key_arg: bool,
679        alt_key_arg: bool,
680        shift_key_arg: bool,
681        meta_key_arg: bool,
682        button_arg: i16,
683        related_target_arg: Option<&EventTarget>,
684    ) {
685        if self.upcast::<Event>().dispatching() {
686            return;
687        }
688
689        self.upcast::<UIEvent>().InitUIEvent(
690            type_arg,
691            can_bubble_arg,
692            cancelable_arg,
693            view_arg,
694            detail_arg,
695        );
696        self.screen_point
697            .set(Point2D::new(screen_x_arg, screen_y_arg));
698        self.client_point
699            .set(Point2D::new(client_x_arg, client_y_arg));
700
701        let global = self.global();
702        let scroll_offset = global.as_window().scroll_offset();
703        self.page_point.set(Point2D::new(
704            scroll_offset.x as i32 + client_x_arg,
705            scroll_offset.y as i32 + client_y_arg,
706        ));
707
708        let mut modifiers = Modifiers::empty();
709        if ctrl_key_arg {
710            modifiers.insert(Modifiers::CONTROL);
711        }
712        if alt_key_arg {
713            modifiers.insert(Modifiers::ALT);
714        }
715        if shift_key_arg {
716            modifiers.insert(Modifiers::SHIFT);
717        }
718        if meta_key_arg {
719            modifiers.insert(Modifiers::META);
720        }
721        self.modifiers.set(modifiers);
722
723        self.button.set(button_arg);
724        self.upcast::<Event>()
725            .set_related_target(related_target_arg);
726
727        // Keep UIEvent.which in sync for legacy init path too.
728        let w = if button_arg >= 0 {
729            (button_arg as u32) + 1
730        } else {
731            0
732        };
733        self.uievent.set_which(w);
734    }
735
736    /// <https://dom.spec.whatwg.org/#dom-event-istrusted>
737    fn IsTrusted(&self) -> bool {
738        self.uievent.IsTrusted()
739    }
740
741    /// <https://w3c.github.io/pointerevents/#dfn-getmodifierstate-keyarg>
742    fn GetModifierState(&self, key_arg: DOMString) -> bool {
743        self.modifiers
744            .get()
745            .contains(match_domstring_ascii!(key_arg,
746                "Alt" => Modifiers::ALT,
747                "AltGraph" => Modifiers::ALT_GRAPH,
748                "CapsLock" => Modifiers::CAPS_LOCK,
749                "Control" => Modifiers::CONTROL,
750                "Fn" => Modifiers::FN,
751                "FnLock" => Modifiers::FN_LOCK,
752                "Meta" => Modifiers::META,
753                "NumLock" => Modifiers::NUM_LOCK,
754                "ScrollLock" => Modifiers::SCROLL_LOCK,
755                "Shift" => Modifiers::SHIFT,
756                "Symbol" => Modifiers::SYMBOL,
757                "SymbolLock" => Modifiers::SYMBOL_LOCK,
758                    _ => { return false; },
759            ))
760    }
761}