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_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#[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 button: Cell<i16>,
78
79 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 #[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 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 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 #[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, 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 pub(crate) fn to_pointer_event(
330 &self,
331 event_type: Atom,
332 can_gc: CanGc,
333 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
334 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 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 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, 1, 1, pressure,
379 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"),
386 true, vec![], vec![], can_gc,
390 );
391
392 pointer_event.upcast::<Event>().set_composed(composed);
393
394 pointer_event
395 }
396
397 pub(crate) fn to_pointer_hover_event(
401 &self,
402 event_type: &str,
403 can_gc: CanGc,
404 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
405 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, self.Buttons(),
432 self.GetRelatedTarget().as_deref(),
433 self.point_in_target.get(),
434 PointerId::Mouse as i32, 1, 1, 0.0, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"),
445 true, vec![], vec![], can_gc,
449 );
450
451 let composed = !matches!(event_type, "pointerenter" | "pointerleave");
455 pointer_event.upcast::<Event>().set_composed(composed);
456
457 pointer_event
459 .upcast::<Event>()
460 .set_trusted(self.IsTrusted());
461
462 pointer_event
463 }
464}
465
466impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
467 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 fn ScreenX(&self) -> i32 {
508 self.screen_point.get().x
509 }
510
511 fn ScreenY(&self) -> i32 {
513 self.screen_point.get().y
514 }
515
516 fn ClientX(&self) -> i32 {
518 self.client_point.get().x
519 }
520
521 fn ClientY(&self) -> i32 {
523 self.client_point.get().y
524 }
525
526 fn PageX(&self) -> i32 {
528 if self.upcast::<Event>().dispatching() {
533 return self.page_point.get().x;
534 }
535
536 self.global().as_window().ScrollX() + self.ClientX()
540 }
541
542 fn PageY(&self) -> i32 {
544 if self.upcast::<Event>().dispatching() {
549 return self.page_point.get().y;
550 }
551
552 self.global().as_window().ScrollY() + self.ClientY()
556 }
557
558 fn X(&self) -> i32 {
560 self.ClientX()
561 }
562
563 fn Y(&self) -> i32 {
565 self.ClientY()
566 }
567
568 fn OffsetX(&self) -> i32 {
570 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 self.PageX()
588 }
589
590 fn OffsetY(&self) -> i32 {
592 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 self.PageY()
610 }
611
612 fn CtrlKey(&self) -> bool {
614 self.modifiers.get().contains(Modifiers::CONTROL)
615 }
616
617 fn ShiftKey(&self) -> bool {
619 self.modifiers.get().contains(Modifiers::SHIFT)
620 }
621
622 fn AltKey(&self) -> bool {
624 self.modifiers.get().contains(Modifiers::ALT)
625 }
626
627 fn MetaKey(&self) -> bool {
629 self.modifiers.get().contains(Modifiers::META)
630 }
631
632 fn Button(&self) -> i16 {
634 self.button.get()
635 }
636
637 fn Buttons(&self) -> u16 {
639 self.buttons.get()
640 }
641
642 fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> {
644 self.upcast::<Event>().related_target()
645 }
646
647 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 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 fn IsTrusted(&self) -> bool {
719 self.uievent.IsTrusted()
720 }
721
722 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}