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 #[allow(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 #[allow(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 Point2D::new(self.ScreenX(), self.ScreenY()),
363 Point2D::new(self.ClientX(), self.ClientY()),
364 Point2D::new(self.PageX(), self.PageY()),
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 Point2D::new(self.ScreenX(), self.ScreenY()),
422 Point2D::new(self.ClientX(), self.ClientY()),
423 Point2D::new(self.PageX(), self.PageY()),
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 buttons(&self) -> MouseButtons {
464 self.buttons.get()
465 }
466}
467
468impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
469 fn Constructor(
471 cx: &mut JSContext,
472 window: &Window,
473 proto: Option<HandleObject>,
474 event_type: DOMString,
475 init: &MouseEventBinding::MouseEventInit,
476 ) -> Fallible<DomRoot<MouseEvent>> {
477 let bubbles = EventBubbles::from(init.parent.parent.parent.bubbles);
478 let cancelable = EventCancelable::from(init.parent.parent.parent.cancelable);
479 let scroll_offset = window.scroll_offset();
480 let page_point = Point2D::new(
481 scroll_offset.x as i32 + init.clientX,
482 scroll_offset.y as i32 + init.clientY,
483 );
484 let event = MouseEvent::new_with_proto(
485 cx,
486 window,
487 proto,
488 event_type.into(),
489 bubbles,
490 cancelable,
491 init.parent.parent.view.as_deref(),
492 init.parent.parent.detail,
493 Point2D::new(init.screenX, init.screenY),
494 Point2D::new(init.clientX, init.clientY),
495 page_point,
496 init.parent.modifiers(),
497 init.button.into(),
498 MouseButtons::from_bits_retain(init.buttons),
499 init.relatedTarget.as_deref(),
500 None,
501 );
502 event
503 .upcast::<Event>()
504 .set_composed(init.parent.parent.parent.composed);
505 Ok(event)
506 }
507
508 fn ScreenX(&self) -> i32 {
510 self.screen_point.get().x
511 }
512
513 fn ScreenY(&self) -> i32 {
515 self.screen_point.get().y
516 }
517
518 fn ClientX(&self) -> i32 {
520 self.client_point.get().x
521 }
522
523 fn ClientY(&self) -> i32 {
525 self.client_point.get().y
526 }
527
528 fn PageX(&self) -> i32 {
530 if self.upcast::<Event>().dispatching() {
535 return self.page_point.get().x;
536 }
537
538 self.global().as_window().ScrollX() + self.ClientX()
542 }
543
544 fn PageY(&self) -> i32 {
546 if self.upcast::<Event>().dispatching() {
551 return self.page_point.get().y;
552 }
553
554 self.global().as_window().ScrollY() + self.ClientY()
558 }
559
560 fn X(&self) -> i32 {
562 self.ClientX()
563 }
564
565 fn Y(&self) -> i32 {
567 self.ClientY()
568 }
569
570 fn OffsetX(&self) -> i32 {
572 let event = self.upcast::<Event>();
578 if event.dispatching() {
579 let Some(target) = event.GetTarget() else {
580 return 0;
581 };
582 let Some(node) = target.downcast::<Node>() else {
583 return 0;
584 };
585 return self.ClientX() - node.client_rect().origin.x;
586 }
587
588 self.PageX()
590 }
591
592 fn OffsetY(&self) -> i32 {
594 let event = self.upcast::<Event>();
600 if event.dispatching() {
601 let Some(target) = event.GetTarget() else {
602 return 0;
603 };
604 let Some(node) = target.downcast::<Node>() else {
605 return 0;
606 };
607 return self.ClientY() - node.client_rect().origin.y;
608 }
609
610 self.PageY()
612 }
613
614 fn CtrlKey(&self) -> bool {
616 self.modifiers.get().contains(Modifiers::CONTROL)
617 }
618
619 fn ShiftKey(&self) -> bool {
621 self.modifiers.get().contains(Modifiers::SHIFT)
622 }
623
624 fn AltKey(&self) -> bool {
626 self.modifiers.get().contains(Modifiers::ALT)
627 }
628
629 fn MetaKey(&self) -> bool {
631 self.modifiers.get().contains(Modifiers::META)
632 }
633
634 fn Button(&self) -> i16 {
636 self.button.get().into()
637 }
638
639 fn Buttons(&self) -> u16 {
641 self.buttons.get().bits()
642 }
643
644 fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> {
646 self.upcast::<Event>().related_target()
647 }
648
649 fn InitMouseEvent(
651 &self,
652 type_arg: DOMString,
653 can_bubble_arg: bool,
654 cancelable_arg: bool,
655 view_arg: Option<&Window>,
656 detail_arg: i32,
657 screen_x_arg: i32,
658 screen_y_arg: i32,
659 client_x_arg: i32,
660 client_y_arg: i32,
661 ctrl_key_arg: bool,
662 alt_key_arg: bool,
663 shift_key_arg: bool,
664 meta_key_arg: bool,
665 button_arg: i16,
666 related_target_arg: Option<&EventTarget>,
667 ) {
668 if self.upcast::<Event>().dispatching() {
669 return;
670 }
671
672 self.upcast::<UIEvent>().InitUIEvent(
673 type_arg,
674 can_bubble_arg,
675 cancelable_arg,
676 view_arg,
677 detail_arg,
678 );
679 self.screen_point
680 .set(Point2D::new(screen_x_arg, screen_y_arg));
681 self.client_point
682 .set(Point2D::new(client_x_arg, client_y_arg));
683
684 let global = self.global();
685 let scroll_offset = global.as_window().scroll_offset();
686 self.page_point.set(Point2D::new(
687 scroll_offset.x as i32 + client_x_arg,
688 scroll_offset.y as i32 + client_y_arg,
689 ));
690
691 let mut modifiers = Modifiers::empty();
692 if ctrl_key_arg {
693 modifiers.insert(Modifiers::CONTROL);
694 }
695 if alt_key_arg {
696 modifiers.insert(Modifiers::ALT);
697 }
698 if shift_key_arg {
699 modifiers.insert(Modifiers::SHIFT);
700 }
701 if meta_key_arg {
702 modifiers.insert(Modifiers::META);
703 }
704 self.modifiers.set(modifiers);
705
706 self.button.set(button_arg.into());
707 self.upcast::<Event>()
708 .set_related_target(related_target_arg);
709
710 let w = if button_arg >= 0 {
712 (button_arg as u32) + 1
713 } else {
714 0
715 };
716 self.uievent.set_which(w);
717 }
718
719 fn IsTrusted(&self) -> bool {
721 self.uievent.IsTrusted()
722 }
723
724 fn GetModifierState(&self, key_arg: DOMString) -> bool {
726 self.modifiers
727 .get()
728 .contains(match_domstring_ascii!(key_arg,
729 "Alt" => Modifiers::ALT,
730 "AltGraph" => Modifiers::ALT_GRAPH,
731 "CapsLock" => Modifiers::CAPS_LOCK,
732 "Control" => Modifiers::CONTROL,
733 "Fn" => Modifiers::FN,
734 "FnLock" => Modifiers::FN_LOCK,
735 "Meta" => Modifiers::META,
736 "NumLock" => Modifiers::NUM_LOCK,
737 "ScrollLock" => Modifiers::SCROLL_LOCK,
738 "Shift" => Modifiers::SHIFT,
739 "Symbol" => Modifiers::SYMBOL,
740 "SymbolLock" => Modifiers::SYMBOL_LOCK,
741 _ => { return false; },
742 ))
743 }
744}