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