1use std::cell::Cell;
6use std::default::Default;
7use std::f64::consts::PI;
8
9use dom_struct::dom_struct;
10use embedder_traits::MouseButton;
11use euclid::Point2D;
12use js::context::JSContext;
13use js::rust::HandleObject;
14use keyboard_types::Modifiers;
15use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
16use script_bindings::match_domstring_ascii;
17use script_bindings::reflector::reflect_dom_object_with_proto;
18use script_traits::{ConstellationInputEvent, MouseButtons};
19use style::Atom;
20use style_traits::CSSPixel;
21
22use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
23use crate::dom::bindings::codegen::Bindings::MouseEventBinding;
24use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
25use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods;
26use crate::dom::bindings::error::Fallible;
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::reflector::DomGlobal;
29use crate::dom::bindings::root::DomRoot;
30use crate::dom::bindings::str::DOMString;
31use crate::dom::document::FireMouseEventType;
32use crate::dom::event::{Event, EventBubbles, EventCancelable};
33use crate::dom::eventtarget::EventTarget;
34use crate::dom::inputevent::HitTestResult;
35use crate::dom::node::Node;
36use crate::dom::pointerevent::{PointerEvent, PointerId};
37use crate::dom::uievent::UIEvent;
38use crate::dom::window::Window;
39
40#[dom_struct]
42pub(crate) struct MouseEvent {
43 uievent: UIEvent,
44
45 #[no_trace]
52 screen_point: Cell<Point2D<i32, CSSPixel>>,
53
54 #[no_trace]
61 client_point: Cell<Point2D<i32, CSSPixel>>,
62
63 #[no_trace]
70 page_point: Cell<Point2D<i32, CSSPixel>>,
71
72 #[no_trace]
74 modifiers: Cell<Modifiers>,
75
76 #[no_trace]
78 button: Cell<MouseButton>,
79
80 #[no_trace]
82 buttons: Cell<MouseButtons>,
83
84 #[no_trace]
85 point_in_target: Cell<Option<Point2D<f32, CSSPixel>>>,
86}
87
88impl MouseEvent {
89 pub(crate) fn new_inherited() -> MouseEvent {
90 MouseEvent {
91 uievent: UIEvent::new_inherited(),
92 screen_point: Cell::new(Default::default()),
93 client_point: Cell::new(Default::default()),
94 page_point: Cell::new(Default::default()),
95 modifiers: Cell::new(Modifiers::empty()),
96 button: Cell::new(MouseButton::Primary),
97 buttons: Cell::new(MouseButtons::empty()),
98 point_in_target: Cell::new(None),
99 }
100 }
101
102 pub(crate) fn new_uninitialized(cx: &mut JSContext, window: &Window) -> DomRoot<MouseEvent> {
103 Self::new_uninitialized_with_proto(cx, window, None)
104 }
105
106 fn new_uninitialized_with_proto(
107 cx: &mut JSContext,
108 window: &Window,
109 proto: Option<HandleObject>,
110 ) -> DomRoot<MouseEvent> {
111 reflect_dom_object_with_proto(cx, Box::new(MouseEvent::new_inherited()), window, proto)
112 }
113
114 #[expect(clippy::too_many_arguments)]
115 pub(crate) fn new(
116 cx: &mut JSContext,
117 window: &Window,
118 event_type: Atom,
119 can_bubble: EventBubbles,
120 cancelable: EventCancelable,
121 view: Option<&Window>,
122 detail: i32,
123 screen_point: Point2D<i32, CSSPixel>,
124 client_point: Point2D<i32, CSSPixel>,
125 page_point: Point2D<i32, CSSPixel>,
126 modifiers: Modifiers,
127 button: MouseButton,
128 buttons: MouseButtons,
129 related_target: Option<&EventTarget>,
130 point_in_target: Option<Point2D<f32, CSSPixel>>,
131 ) -> DomRoot<MouseEvent> {
132 Self::new_with_proto(
133 cx,
134 window,
135 None,
136 event_type,
137 can_bubble,
138 cancelable,
139 view,
140 detail,
141 screen_point,
142 client_point,
143 page_point,
144 modifiers,
145 button,
146 buttons,
147 related_target,
148 point_in_target,
149 )
150 }
151
152 #[expect(clippy::too_many_arguments)]
153 fn new_with_proto(
154 cx: &mut JSContext,
155 window: &Window,
156 proto: Option<HandleObject>,
157 event_type: Atom,
158 can_bubble: EventBubbles,
159 cancelable: EventCancelable,
160 view: Option<&Window>,
161 detail: i32,
162 screen_point: Point2D<i32, CSSPixel>,
163 client_point: Point2D<i32, CSSPixel>,
164 page_point: Point2D<i32, CSSPixel>,
165 modifiers: Modifiers,
166 button: MouseButton,
167 buttons: MouseButtons,
168 related_target: Option<&EventTarget>,
169 point_in_target: Option<Point2D<f32, CSSPixel>>,
170 ) -> DomRoot<MouseEvent> {
171 let ev = MouseEvent::new_uninitialized_with_proto(cx, window, proto);
172 ev.initialize_mouse_event(
173 event_type,
174 can_bubble,
175 cancelable,
176 view,
177 detail,
178 screen_point,
179 client_point,
180 page_point,
181 modifiers,
182 button,
183 buttons,
184 related_target,
185 point_in_target,
186 );
187 ev
188 }
189
190 #[expect(clippy::too_many_arguments)]
192 pub(crate) fn initialize_mouse_event(
193 &self,
194 event_type: Atom,
195 can_bubble: EventBubbles,
196 cancelable: EventCancelable,
197 view: Option<&Window>,
198 detail: i32,
199 screen_point: Point2D<i32, CSSPixel>,
200 client_point: Point2D<i32, CSSPixel>,
201 page_point: Point2D<i32, CSSPixel>,
202 modifiers: Modifiers,
203 button: MouseButton,
204 buttons: MouseButtons,
205 related_target: Option<&EventTarget>,
206 point_in_target: Option<Point2D<f32, CSSPixel>>,
207 ) {
208 self.uievent.initialize_ui_event(
209 event_type,
210 view.map(|window| window.upcast::<EventTarget>()),
211 can_bubble,
212 cancelable,
213 );
214
215 self.uievent.set_detail(detail);
216 self.screen_point.set(screen_point);
217 self.client_point.set(client_point);
218 self.page_point.set(page_point);
219 self.modifiers.set(modifiers);
220 self.button.set(button);
221 self.buttons.set(buttons);
222 self.upcast::<Event>().set_related_target(related_target);
223 self.point_in_target.set(point_in_target);
224
225 self.uievent.set_which((i16::from(button) + 1) as u32);
228 }
229
230 pub(crate) fn new_for_platform_motion_event(
231 cx: &mut JSContext,
232 window: &Window,
233 event_name: FireMouseEventType,
234 hit_test_result: &HitTestResult,
235 input_event: &ConstellationInputEvent,
236 ) -> DomRoot<Self> {
237 let (bubbles, cancelable, composed) = match event_name {
240 FireMouseEventType::Move | FireMouseEventType::Over | FireMouseEventType::Out => {
241 (EventBubbles::Bubbles, EventCancelable::Cancelable, true)
242 },
243 FireMouseEventType::Enter | FireMouseEventType::Leave => (
244 EventBubbles::DoesNotBubble,
245 EventCancelable::NotCancelable,
246 false,
247 ),
248 };
249
250 let mouse_event = Self::new(
251 cx,
252 window,
253 Atom::from(event_name.as_str()),
254 bubbles,
255 cancelable,
256 Some(window),
257 0i32,
258 hit_test_result.point_in_frame.to_i32(),
259 hit_test_result.point_in_frame.to_i32(),
260 hit_test_result
261 .point_relative_to_initial_containing_block
262 .to_i32(),
263 input_event.active_keyboard_modifiers,
264 MouseButton::Primary,
265 input_event.pressed_mouse_buttons,
266 None,
267 None,
268 );
269
270 let event = mouse_event.upcast::<Event>();
271 event.set_composed(composed);
272 event.set_trusted(true);
273
274 mouse_event
275 }
276
277 #[expect(clippy::too_many_arguments)]
281 pub(crate) fn for_platform_button_event(
282 cx: &mut JSContext,
283 event_type: Atom,
284 event: embedder_traits::MouseButtonEvent,
285 pressed_mouse_buttons: MouseButtons,
286 window: &Window,
287 hit_test_result: &HitTestResult,
288 modifiers: Modifiers,
289 click_count: usize,
290 ) -> DomRoot<Self> {
291 let client_point = hit_test_result.point_in_frame.to_i32();
292 let page_point = hit_test_result
293 .point_relative_to_initial_containing_block
294 .to_i32();
295
296 let mouse_event = Self::new(
297 cx,
298 window,
299 event_type,
300 EventBubbles::Bubbles,
301 EventCancelable::Cancelable,
302 Some(window),
303 click_count as i32,
304 client_point, client_point,
306 page_point,
307 modifiers,
308 event.button,
309 pressed_mouse_buttons,
310 None,
311 Some(hit_test_result.point_in_node),
312 );
313
314 mouse_event.upcast::<Event>().set_trusted(true);
315 mouse_event.upcast::<Event>().set_composed(true);
316
317 mouse_event
318 }
319
320 pub(crate) fn to_pointer_event(
324 &self,
325 cx: &mut JSContext,
326 event_type: Atom,
327 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
328 let is_pointer_down = &*event_type == "pointerdown";
330 let is_pointer_move = &*event_type == "pointermove";
331 let is_pointer_up = &*event_type == "pointerup";
332
333 let pressure = if is_pointer_down || (is_pointer_move && !self.buttons.get().is_empty()) {
335 0.5
336 } else {
337 0.0
338 };
339
340 let button = if is_pointer_down || is_pointer_up {
341 self.button.get()
342 } else {
343 MouseButton::None
344 };
345
346 let composed = !matches!(&*event_type, "pointerenter" | "pointerleave");
350
351 let window = self.global();
352 let window = window.as_window();
353
354 let pointer_event = PointerEvent::new(
355 cx,
356 window,
357 event_type,
358 EventBubbles::from(self.upcast::<Event>().Bubbles()),
359 EventCancelable::from(self.upcast::<Event>().Cancelable()),
360 self.uievent.GetView().as_deref(),
361 self.uievent.Detail(),
362 self.screen_point.get(),
363 self.client_point.get(),
364 self.page_point.get(),
365 self.modifiers.get(),
366 button,
367 self.buttons.get(),
368 self.GetRelatedTarget().as_deref(),
369 self.point_in_target.get(),
370 PointerId::Mouse as i32, 1, 1, pressure,
374 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from_static("mouse"),
381 true, vec![], vec![], );
385
386 pointer_event.upcast::<Event>().set_composed(composed);
387
388 pointer_event
389 }
390
391 pub(crate) fn to_pointer_hover_event(
395 &self,
396 cx: &mut JSContext,
397 event_type: &str,
398 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
399 let (bubbles, cancelable) = match event_type {
403 "pointerover" | "pointerout" => (EventBubbles::Bubbles, EventCancelable::Cancelable),
404 "pointerenter" | "pointerleave" => {
405 (EventBubbles::DoesNotBubble, EventCancelable::NotCancelable)
406 },
407 _ => (EventBubbles::Bubbles, EventCancelable::Cancelable),
408 };
409
410 let window = self.global();
411 let window = window.as_window();
412
413 let pointer_event = PointerEvent::new(
414 cx,
415 window,
416 event_type.into(),
417 bubbles,
418 cancelable,
419 self.uievent.GetView().as_deref(),
420 self.uievent.Detail(),
421 self.screen_point.get(),
422 self.client_point.get(),
423 self.page_point.get(),
424 self.modifiers.get(),
425 MouseButton::None,
426 self.buttons.get(),
427 self.GetRelatedTarget().as_deref(),
428 self.point_in_target.get(),
429 PointerId::Mouse as i32, 1, 1, 0.0, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from_static("mouse"),
440 true, vec![], vec![], );
444
445 let composed = !matches!(event_type, "pointerenter" | "pointerleave");
449 pointer_event.upcast::<Event>().set_composed(composed);
450
451 pointer_event
453 .upcast::<Event>()
454 .set_trusted(self.IsTrusted());
455
456 pointer_event
457 }
458
459 pub(crate) fn button(&self) -> MouseButton {
460 self.button.get()
461 }
462
463 pub(crate) fn client_point(&self) -> Point2D<i32, CSSPixel> {
464 self.client_point.get()
465 }
466
467 pub(crate) fn buttons(&self) -> MouseButtons {
468 self.buttons.get()
469 }
470}
471
472impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
473 fn Constructor(
475 cx: &mut JSContext,
476 window: &Window,
477 proto: Option<HandleObject>,
478 event_type: DOMString,
479 init: &MouseEventBinding::MouseEventInit,
480 ) -> Fallible<DomRoot<MouseEvent>> {
481 let bubbles = EventBubbles::from(init.parent.parent.parent.bubbles);
482 let cancelable = EventCancelable::from(init.parent.parent.parent.cancelable);
483 let scroll_offset = window.scroll_offset();
484 let page_point = Point2D::new(
485 scroll_offset.x as i32 + init.clientX,
486 scroll_offset.y as i32 + init.clientY,
487 );
488 let event = MouseEvent::new_with_proto(
489 cx,
490 window,
491 proto,
492 event_type.into(),
493 bubbles,
494 cancelable,
495 init.parent.parent.view.as_deref(),
496 init.parent.parent.detail,
497 Point2D::new(init.screenX, init.screenY),
498 Point2D::new(init.clientX, init.clientY),
499 page_point,
500 init.parent.modifiers(),
501 init.button.into(),
502 MouseButtons::from_bits_retain(init.buttons),
503 init.relatedTarget.as_deref(),
504 None,
505 );
506 event
507 .upcast::<Event>()
508 .set_composed(init.parent.parent.parent.composed);
509 Ok(event)
510 }
511
512 fn ScreenX(&self) -> i32 {
514 self.screen_point.get().x
515 }
516
517 fn ScreenY(&self) -> i32 {
519 self.screen_point.get().y
520 }
521
522 fn ClientX(&self) -> i32 {
524 self.client_point.get().x
525 }
526
527 fn ClientY(&self) -> i32 {
529 self.client_point.get().y
530 }
531
532 fn PageX(&self) -> i32 {
534 if self.upcast::<Event>().dispatching() {
539 return self.page_point.get().x;
540 }
541
542 self.global().as_window().ScrollX() + self.ClientX()
546 }
547
548 fn PageY(&self) -> i32 {
550 if self.upcast::<Event>().dispatching() {
555 return self.page_point.get().y;
556 }
557
558 self.global().as_window().ScrollY() + self.ClientY()
562 }
563
564 fn X(&self) -> i32 {
566 self.ClientX()
567 }
568
569 fn Y(&self) -> i32 {
571 self.ClientY()
572 }
573
574 fn OffsetX(&self) -> i32 {
576 let event = self.upcast::<Event>();
582 if event.dispatching() {
583 let Some(target) = event.GetTarget() else {
584 return 0;
585 };
586 let Some(node) = target.downcast::<Node>() else {
587 return 0;
588 };
589 return self.ClientX() - node.client_rect().origin.x;
590 }
591
592 self.PageX()
594 }
595
596 fn OffsetY(&self) -> i32 {
598 let event = self.upcast::<Event>();
604 if event.dispatching() {
605 let Some(target) = event.GetTarget() else {
606 return 0;
607 };
608 let Some(node) = target.downcast::<Node>() else {
609 return 0;
610 };
611 return self.ClientY() - node.client_rect().origin.y;
612 }
613
614 self.PageY()
616 }
617
618 fn CtrlKey(&self) -> bool {
620 self.modifiers.get().contains(Modifiers::CONTROL)
621 }
622
623 fn ShiftKey(&self) -> bool {
625 self.modifiers.get().contains(Modifiers::SHIFT)
626 }
627
628 fn AltKey(&self) -> bool {
630 self.modifiers.get().contains(Modifiers::ALT)
631 }
632
633 fn MetaKey(&self) -> bool {
635 self.modifiers.get().contains(Modifiers::META)
636 }
637
638 fn Button(&self) -> i16 {
640 self.button.get().into()
641 }
642
643 fn Buttons(&self) -> u16 {
645 self.buttons.get().bits()
646 }
647
648 fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> {
650 self.upcast::<Event>().related_target()
651 }
652
653 fn InitMouseEvent(
655 &self,
656 type_arg: DOMString,
657 can_bubble_arg: bool,
658 cancelable_arg: bool,
659 view_arg: Option<&Window>,
660 detail_arg: i32,
661 screen_x_arg: i32,
662 screen_y_arg: i32,
663 client_x_arg: i32,
664 client_y_arg: i32,
665 ctrl_key_arg: bool,
666 alt_key_arg: bool,
667 shift_key_arg: bool,
668 meta_key_arg: bool,
669 button_arg: i16,
670 related_target_arg: Option<&EventTarget>,
671 ) {
672 if self.upcast::<Event>().dispatching() {
673 return;
674 }
675
676 self.upcast::<UIEvent>().InitUIEvent(
677 type_arg,
678 can_bubble_arg,
679 cancelable_arg,
680 view_arg,
681 detail_arg,
682 );
683 self.screen_point
684 .set(Point2D::new(screen_x_arg, screen_y_arg));
685 self.client_point
686 .set(Point2D::new(client_x_arg, client_y_arg));
687
688 let global = self.global();
689 let scroll_offset = global.as_window().scroll_offset();
690 self.page_point.set(Point2D::new(
691 scroll_offset.x as i32 + client_x_arg,
692 scroll_offset.y as i32 + client_y_arg,
693 ));
694
695 let mut modifiers = Modifiers::empty();
696 if ctrl_key_arg {
697 modifiers.insert(Modifiers::CONTROL);
698 }
699 if alt_key_arg {
700 modifiers.insert(Modifiers::ALT);
701 }
702 if shift_key_arg {
703 modifiers.insert(Modifiers::SHIFT);
704 }
705 if meta_key_arg {
706 modifiers.insert(Modifiers::META);
707 }
708 self.modifiers.set(modifiers);
709
710 self.button.set(button_arg.into());
711 self.upcast::<Event>()
712 .set_related_target(related_target_arg);
713
714 let w = if button_arg >= 0 {
716 (button_arg as u32) + 1
717 } else {
718 0
719 };
720 self.uievent.set_which(w);
721 }
722
723 fn IsTrusted(&self) -> bool {
725 self.uievent.IsTrusted()
726 }
727
728 fn GetModifierState(&self, key_arg: DOMString) -> bool {
730 self.modifiers
731 .get()
732 .contains(match_domstring_ascii!(key_arg,
733 "Alt" => Modifiers::ALT,
734 "AltGraph" => Modifiers::ALT_GRAPH,
735 "CapsLock" => Modifiers::CAPS_LOCK,
736 "Control" => Modifiers::CONTROL,
737 "Fn" => Modifiers::FN,
738 "FnLock" => Modifiers::FN_LOCK,
739 "Meta" => Modifiers::META,
740 "NumLock" => Modifiers::NUM_LOCK,
741 "ScrollLock" => Modifiers::SCROLL_LOCK,
742 "Shift" => Modifiers::SHIFT,
743 "Symbol" => Modifiers::SYMBOL,
744 "SymbolLock" => Modifiers::SYMBOL_LOCK,
745 _ => { return false; },
746 ))
747 }
748}