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