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