1use 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#[dom_struct]
41pub(crate) struct MouseEvent {
42 uievent: UIEvent,
43
44 #[no_trace]
51 screen_point: Cell<Point2D<i32, CSSPixel>>,
52
53 #[no_trace]
60 client_point: Cell<Point2D<i32, CSSPixel>>,
61
62 #[no_trace]
69 page_point: Cell<Point2D<i32, CSSPixel>>,
70
71 #[no_trace]
73 modifiers: Cell<Modifiers>,
74
75 button: Cell<i16>,
77
78 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 #[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 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 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 #[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, 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 pub(crate) fn to_pointer_event(
329 &self,
330 event_type: Atom,
331 can_gc: CanGc,
332 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
333 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 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 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, 1, 1, pressure,
378 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"),
385 true, vec![], vec![], can_gc,
389 );
390
391 pointer_event.upcast::<Event>().set_composed(composed);
392
393 pointer_event
394 }
395
396 pub(crate) fn to_pointer_hover_event(
400 &self,
401 event_type: &str,
402 can_gc: CanGc,
403 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
404 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, self.Buttons(),
431 self.GetRelatedTarget().as_deref(),
432 self.point_in_target.get(),
433 PointerId::Mouse as i32, 1, 1, 0.0, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"),
444 true, vec![], vec![], can_gc,
448 );
449
450 let composed = !matches!(event_type, "pointerenter" | "pointerleave");
454 pointer_event.upcast::<Event>().set_composed(composed);
455
456 pointer_event
458 .upcast::<Event>()
459 .set_trusted(self.IsTrusted());
460
461 pointer_event
462 }
463}
464
465impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
466 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 fn ScreenX(&self) -> i32 {
507 self.screen_point.get().x
508 }
509
510 fn ScreenY(&self) -> i32 {
512 self.screen_point.get().y
513 }
514
515 fn ClientX(&self) -> i32 {
517 self.client_point.get().x
518 }
519
520 fn ClientY(&self) -> i32 {
522 self.client_point.get().y
523 }
524
525 fn PageX(&self) -> i32 {
527 if self.upcast::<Event>().dispatching() {
532 return self.page_point.get().x;
533 }
534
535 self.global().as_window().ScrollX() + self.ClientX()
539 }
540
541 fn PageY(&self) -> i32 {
543 if self.upcast::<Event>().dispatching() {
548 return self.page_point.get().y;
549 }
550
551 self.global().as_window().ScrollY() + self.ClientY()
555 }
556
557 fn X(&self) -> i32 {
559 self.ClientX()
560 }
561
562 fn Y(&self) -> i32 {
564 self.ClientY()
565 }
566
567 fn OffsetX(&self) -> i32 {
569 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 self.PageX()
587 }
588
589 fn OffsetY(&self) -> i32 {
591 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 self.PageY()
609 }
610
611 fn CtrlKey(&self) -> bool {
613 self.modifiers.get().contains(Modifiers::CONTROL)
614 }
615
616 fn ShiftKey(&self) -> bool {
618 self.modifiers.get().contains(Modifiers::SHIFT)
619 }
620
621 fn AltKey(&self) -> bool {
623 self.modifiers.get().contains(Modifiers::ALT)
624 }
625
626 fn MetaKey(&self) -> bool {
628 self.modifiers.get().contains(Modifiers::META)
629 }
630
631 fn Button(&self) -> i16 {
633 self.button.get()
634 }
635
636 fn Buttons(&self) -> u16 {
638 self.buttons.get()
639 }
640
641 fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> {
643 self.upcast::<Event>().related_target()
644 }
645
646 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 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 fn IsTrusted(&self) -> bool {
718 self.uievent.IsTrusted()
719 }
720
721 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}