1use std::cell::Cell;
6use std::default::Default;
7use std::f64::consts::PI;
8
9use dom_struct::dom_struct;
10use euclid::Point2D;
11use js::rust::HandleObject;
12use keyboard_types::Modifiers;
13use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
14use script_bindings::match_domstring_ascii;
15use script_traits::ConstellationInputEvent;
16use style::Atom;
17use style_traits::CSSPixel;
18
19use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
20use crate::dom::bindings::codegen::Bindings::MouseEventBinding;
21use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
22use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods;
23use crate::dom::bindings::error::Fallible;
24use crate::dom::bindings::inheritance::Castable;
25use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
26use crate::dom::bindings::root::DomRoot;
27use crate::dom::bindings::str::DOMString;
28use crate::dom::document::FireMouseEventType;
29use crate::dom::event::{Event, EventBubbles, EventCancelable};
30use crate::dom::eventtarget::EventTarget;
31use crate::dom::inputevent::HitTestResult;
32use crate::dom::node::Node;
33use crate::dom::pointerevent::{PointerEvent, PointerId};
34use crate::dom::uievent::UIEvent;
35use crate::dom::window::Window;
36use crate::script_runtime::CanGc;
37
38#[dom_struct]
40pub(crate) struct MouseEvent {
41 uievent: UIEvent,
42
43 #[no_trace]
50 screen_point: Cell<Point2D<i32, CSSPixel>>,
51
52 #[no_trace]
59 client_point: Cell<Point2D<i32, CSSPixel>>,
60
61 #[no_trace]
68 page_point: Cell<Point2D<i32, CSSPixel>>,
69
70 #[no_trace]
72 modifiers: Cell<Modifiers>,
73
74 button: Cell<i16>,
76
77 buttons: Cell<u16>,
79
80 #[no_trace]
81 point_in_target: Cell<Option<Point2D<f32, CSSPixel>>>,
82}
83
84impl MouseEvent {
85 pub(crate) fn new_inherited() -> MouseEvent {
86 MouseEvent {
87 uievent: UIEvent::new_inherited(),
88 screen_point: Cell::new(Default::default()),
89 client_point: Cell::new(Default::default()),
90 page_point: Cell::new(Default::default()),
91 modifiers: Cell::new(Modifiers::empty()),
92 button: Cell::new(0),
93 buttons: Cell::new(0),
94 point_in_target: Cell::new(None),
95 }
96 }
97
98 pub(crate) fn new_uninitialized(window: &Window, can_gc: CanGc) -> DomRoot<MouseEvent> {
99 Self::new_uninitialized_with_proto(window, None, can_gc)
100 }
101
102 fn new_uninitialized_with_proto(
103 window: &Window,
104 proto: Option<HandleObject>,
105 can_gc: CanGc,
106 ) -> DomRoot<MouseEvent> {
107 reflect_dom_object_with_proto(Box::new(MouseEvent::new_inherited()), window, proto, can_gc)
108 }
109
110 #[allow(clippy::too_many_arguments)]
111 pub(crate) fn new(
112 window: &Window,
113 event_type: Atom,
114 can_bubble: EventBubbles,
115 cancelable: EventCancelable,
116 view: Option<&Window>,
117 detail: i32,
118 screen_point: Point2D<i32, CSSPixel>,
119 client_point: Point2D<i32, CSSPixel>,
120 page_point: Point2D<i32, CSSPixel>,
121 modifiers: Modifiers,
122 button: i16,
123 buttons: u16,
124 related_target: Option<&EventTarget>,
125 point_in_target: Option<Point2D<f32, CSSPixel>>,
126 can_gc: CanGc,
127 ) -> DomRoot<MouseEvent> {
128 Self::new_with_proto(
129 window,
130 None,
131 event_type,
132 can_bubble,
133 cancelable,
134 view,
135 detail,
136 screen_point,
137 client_point,
138 page_point,
139 modifiers,
140 button,
141 buttons,
142 related_target,
143 point_in_target,
144 can_gc,
145 )
146 }
147
148 #[allow(clippy::too_many_arguments)]
149 fn new_with_proto(
150 window: &Window,
151 proto: Option<HandleObject>,
152 event_type: Atom,
153 can_bubble: EventBubbles,
154 cancelable: EventCancelable,
155 view: Option<&Window>,
156 detail: i32,
157 screen_point: Point2D<i32, CSSPixel>,
158 client_point: Point2D<i32, CSSPixel>,
159 page_point: Point2D<i32, CSSPixel>,
160 modifiers: Modifiers,
161 button: i16,
162 buttons: u16,
163 related_target: Option<&EventTarget>,
164 point_in_target: Option<Point2D<f32, CSSPixel>>,
165 can_gc: CanGc,
166 ) -> DomRoot<MouseEvent> {
167 let ev = MouseEvent::new_uninitialized_with_proto(window, proto, can_gc);
168 ev.initialize_mouse_event(
169 event_type,
170 can_bubble,
171 cancelable,
172 view,
173 detail,
174 screen_point,
175 client_point,
176 page_point,
177 modifiers,
178 button,
179 buttons,
180 related_target,
181 point_in_target,
182 );
183 ev
184 }
185
186 #[expect(clippy::too_many_arguments)]
188 pub(crate) fn initialize_mouse_event(
189 &self,
190 event_type: Atom,
191 can_bubble: EventBubbles,
192 cancelable: EventCancelable,
193 view: Option<&Window>,
194 detail: i32,
195 screen_point: Point2D<i32, CSSPixel>,
196 client_point: Point2D<i32, CSSPixel>,
197 page_point: Point2D<i32, CSSPixel>,
198 modifiers: Modifiers,
199 button: i16,
200 buttons: u16,
201 related_target: Option<&EventTarget>,
202 point_in_target: Option<Point2D<f32, CSSPixel>>,
203 ) {
204 self.uievent.initialize_ui_event(
205 event_type,
206 view.map(|window| window.upcast::<EventTarget>()),
207 can_bubble,
208 cancelable,
209 );
210
211 self.uievent.set_detail(detail);
212 self.screen_point.set(screen_point);
213 self.client_point.set(client_point);
214 self.page_point.set(page_point);
215 self.modifiers.set(modifiers);
216 self.button.set(button);
217 self.buttons.set(buttons);
218 self.upcast::<Event>().set_related_target(related_target);
219 self.point_in_target.set(point_in_target);
220 let w = if button >= 0 { (button as u32) + 1 } else { 0 };
222 self.uievent.set_which(w);
223 }
224
225 pub(crate) fn new_for_platform_motion_event(
226 window: &Window,
227 event_name: FireMouseEventType,
228 hit_test_result: &HitTestResult,
229 input_event: &ConstellationInputEvent,
230 can_gc: CanGc,
231 ) -> DomRoot<Self> {
232 let (bubbles, cancelable, composed) = match event_name {
235 FireMouseEventType::Move | FireMouseEventType::Over | FireMouseEventType::Out => {
236 (EventBubbles::Bubbles, EventCancelable::Cancelable, true)
237 },
238 FireMouseEventType::Enter | FireMouseEventType::Leave => (
239 EventBubbles::DoesNotBubble,
240 EventCancelable::NotCancelable,
241 false,
242 ),
243 };
244
245 let mouse_event = Self::new(
246 window,
247 Atom::from(event_name.as_str()),
248 bubbles,
249 cancelable,
250 Some(window),
251 0i32,
252 hit_test_result.point_in_frame.to_i32(),
253 hit_test_result.point_in_frame.to_i32(),
254 hit_test_result
255 .point_relative_to_initial_containing_block
256 .to_i32(),
257 input_event.active_keyboard_modifiers,
258 0i16,
259 input_event.pressed_mouse_buttons,
260 None,
261 None,
262 can_gc,
263 );
264
265 let event = mouse_event.upcast::<Event>();
266 event.set_composed(composed);
267 event.set_trusted(true);
268
269 mouse_event
270 }
271
272 #[expect(clippy::too_many_arguments)]
275 pub(crate) fn for_platform_button_event(
276 event_type: Atom,
277 event: embedder_traits::MouseButtonEvent,
278 pressed_mouse_buttons: u16,
279 window: &Window,
280 hit_test_result: &HitTestResult,
281 modifiers: Modifiers,
282 click_count: usize,
283 can_gc: CanGc,
284 ) -> DomRoot<Self> {
285 let client_point = hit_test_result.point_in_frame.to_i32();
286 let page_point = hit_test_result
287 .point_relative_to_initial_containing_block
288 .to_i32();
289
290 let mouse_event = Self::new(
291 window,
292 event_type,
293 EventBubbles::Bubbles,
294 EventCancelable::Cancelable,
295 Some(window),
296 click_count as i32,
297 client_point, client_point,
299 page_point,
300 modifiers,
301 event.button.into(),
302 pressed_mouse_buttons,
303 None,
304 Some(hit_test_result.point_in_node),
305 can_gc,
306 );
307
308 mouse_event.upcast::<Event>().set_trusted(true);
309 mouse_event.upcast::<Event>().set_composed(true);
310
311 mouse_event
312 }
313
314 pub(crate) fn point_in_viewport(&self) -> Option<Point2D<f32, CSSPixel>> {
315 Some(self.client_point.get().to_f32())
316 }
317
318 pub(crate) fn to_pointer_event(
322 &self,
323 event_type: Atom,
324 can_gc: CanGc,
325 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
326 let is_pointer_down = &*event_type == "pointerdown";
328 let is_pointer_move = &*event_type == "pointermove";
329 let is_pointer_up = &*event_type == "pointerup";
330
331 let pressure = if is_pointer_down || (is_pointer_move && self.Buttons() != 0) {
333 0.5
334 } else {
335 0.0
336 };
337
338 let button = if is_pointer_down || is_pointer_up {
339 self.Button()
340 } else {
341 -1
342 };
343
344 let window = self.global();
345 let window = window.as_window();
346
347 PointerEvent::new(
348 window,
349 event_type,
350 EventBubbles::from(self.upcast::<Event>().Bubbles()),
351 EventCancelable::from(self.upcast::<Event>().Cancelable()),
352 self.uievent.GetView().as_deref(),
353 self.uievent.Detail(),
354 Point2D::new(self.ScreenX(), self.ScreenY()),
355 Point2D::new(self.ClientX(), self.ClientY()),
356 Point2D::new(self.PageX(), self.PageY()),
357 self.modifiers.get(),
358 button,
359 self.Buttons(),
360 self.GetRelatedTarget().as_deref(),
361 self.point_in_target.get(),
362 PointerId::Mouse as i32, 1, 1, pressure,
366 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"),
373 true, vec![], vec![], can_gc,
377 )
378 }
379
380 pub(crate) fn to_pointer_hover_event(
384 &self,
385 event_type: &str,
386 can_gc: CanGc,
387 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
388 let (bubbles, cancelable) = match event_type {
392 "pointerover" | "pointerout" => (EventBubbles::Bubbles, EventCancelable::Cancelable),
393 "pointerenter" | "pointerleave" => {
394 (EventBubbles::DoesNotBubble, EventCancelable::NotCancelable)
395 },
396 _ => (EventBubbles::Bubbles, EventCancelable::Cancelable),
397 };
398
399 let window = self.global();
400 let window = window.as_window();
401
402 let pointer_event = PointerEvent::new(
403 window,
404 event_type.into(),
405 bubbles,
406 cancelable,
407 self.uievent.GetView().as_deref(),
408 self.uievent.Detail(),
409 Point2D::new(self.ScreenX(), self.ScreenY()),
410 Point2D::new(self.ClientX(), self.ClientY()),
411 Point2D::new(self.PageX(), self.PageY()),
412 self.modifiers.get(),
413 -1, self.Buttons(),
415 self.GetRelatedTarget().as_deref(),
416 self.point_in_target.get(),
417 PointerId::Mouse as i32, 1, 1, 0.0, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"),
428 true, vec![], vec![], can_gc,
432 );
433
434 pointer_event
436 .upcast::<Event>()
437 .set_trusted(self.IsTrusted());
438
439 pointer_event
440 }
441}
442
443impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
444 fn Constructor(
446 window: &Window,
447 proto: Option<HandleObject>,
448 can_gc: CanGc,
449 event_type: DOMString,
450 init: &MouseEventBinding::MouseEventInit,
451 ) -> Fallible<DomRoot<MouseEvent>> {
452 let bubbles = EventBubbles::from(init.parent.parent.parent.bubbles);
453 let cancelable = EventCancelable::from(init.parent.parent.parent.cancelable);
454 let scroll_offset = window.scroll_offset();
455 let page_point = Point2D::new(
456 scroll_offset.x as i32 + init.clientX,
457 scroll_offset.y as i32 + init.clientY,
458 );
459 let event = MouseEvent::new_with_proto(
460 window,
461 proto,
462 event_type.into(),
463 bubbles,
464 cancelable,
465 init.parent.parent.view.as_deref(),
466 init.parent.parent.detail,
467 Point2D::new(init.screenX, init.screenY),
468 Point2D::new(init.clientX, init.clientY),
469 page_point,
470 init.parent.modifiers(),
471 init.button,
472 init.buttons,
473 init.relatedTarget.as_deref(),
474 None,
475 can_gc,
476 );
477 event
478 .upcast::<Event>()
479 .set_composed(init.parent.parent.parent.composed);
480 Ok(event)
481 }
482
483 fn ScreenX(&self) -> i32 {
485 self.screen_point.get().x
486 }
487
488 fn ScreenY(&self) -> i32 {
490 self.screen_point.get().y
491 }
492
493 fn ClientX(&self) -> i32 {
495 self.client_point.get().x
496 }
497
498 fn ClientY(&self) -> i32 {
500 self.client_point.get().y
501 }
502
503 fn PageX(&self) -> i32 {
505 if self.upcast::<Event>().dispatching() {
510 return self.page_point.get().x;
511 }
512
513 self.global().as_window().ScrollX() + self.ClientX()
517 }
518
519 fn PageY(&self) -> i32 {
521 if self.upcast::<Event>().dispatching() {
526 return self.page_point.get().y;
527 }
528
529 self.global().as_window().ScrollY() + self.ClientY()
533 }
534
535 fn X(&self) -> i32 {
537 self.ClientX()
538 }
539
540 fn Y(&self) -> i32 {
542 self.ClientY()
543 }
544
545 fn OffsetX(&self) -> i32 {
547 let event = self.upcast::<Event>();
553 if event.dispatching() {
554 let Some(target) = event.GetTarget() else {
555 return 0;
556 };
557 let Some(node) = target.downcast::<Node>() else {
558 return 0;
559 };
560 return self.ClientX() - node.client_rect().origin.x;
561 }
562
563 self.PageX()
565 }
566
567 fn OffsetY(&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.ClientY() - node.client_rect().origin.y;
583 }
584
585 self.PageY()
587 }
588
589 fn CtrlKey(&self) -> bool {
591 self.modifiers.get().contains(Modifiers::CONTROL)
592 }
593
594 fn ShiftKey(&self) -> bool {
596 self.modifiers.get().contains(Modifiers::SHIFT)
597 }
598
599 fn AltKey(&self) -> bool {
601 self.modifiers.get().contains(Modifiers::ALT)
602 }
603
604 fn MetaKey(&self) -> bool {
606 self.modifiers.get().contains(Modifiers::META)
607 }
608
609 fn Button(&self) -> i16 {
611 self.button.get()
612 }
613
614 fn Buttons(&self) -> u16 {
616 self.buttons.get()
617 }
618
619 fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> {
621 self.upcast::<Event>().related_target()
622 }
623
624 fn InitMouseEvent(
626 &self,
627 type_arg: DOMString,
628 can_bubble_arg: bool,
629 cancelable_arg: bool,
630 view_arg: Option<&Window>,
631 detail_arg: i32,
632 screen_x_arg: i32,
633 screen_y_arg: i32,
634 client_x_arg: i32,
635 client_y_arg: i32,
636 ctrl_key_arg: bool,
637 alt_key_arg: bool,
638 shift_key_arg: bool,
639 meta_key_arg: bool,
640 button_arg: i16,
641 related_target_arg: Option<&EventTarget>,
642 ) {
643 if self.upcast::<Event>().dispatching() {
644 return;
645 }
646
647 self.upcast::<UIEvent>().InitUIEvent(
648 type_arg,
649 can_bubble_arg,
650 cancelable_arg,
651 view_arg,
652 detail_arg,
653 );
654 self.screen_point
655 .set(Point2D::new(screen_x_arg, screen_y_arg));
656 self.client_point
657 .set(Point2D::new(client_x_arg, client_y_arg));
658
659 let global = self.global();
660 let scroll_offset = global.as_window().scroll_offset();
661 self.page_point.set(Point2D::new(
662 scroll_offset.x as i32 + client_x_arg,
663 scroll_offset.y as i32 + client_y_arg,
664 ));
665
666 let mut modifiers = Modifiers::empty();
667 if ctrl_key_arg {
668 modifiers.insert(Modifiers::CONTROL);
669 }
670 if alt_key_arg {
671 modifiers.insert(Modifiers::ALT);
672 }
673 if shift_key_arg {
674 modifiers.insert(Modifiers::SHIFT);
675 }
676 if meta_key_arg {
677 modifiers.insert(Modifiers::META);
678 }
679 self.modifiers.set(modifiers);
680
681 self.button.set(button_arg);
682 self.upcast::<Event>()
683 .set_related_target(related_target_arg);
684
685 let w = if button_arg >= 0 {
687 (button_arg as u32) + 1
688 } else {
689 0
690 };
691 self.uievent.set_which(w);
692 }
693
694 fn IsTrusted(&self) -> bool {
696 self.uievent.IsTrusted()
697 }
698
699 fn GetModifierState(&self, key_arg: DOMString) -> bool {
701 self.modifiers
702 .get()
703 .contains(match_domstring_ascii!(key_arg,
704 "Alt" => Modifiers::ALT,
705 "AltGraph" => Modifiers::ALT_GRAPH,
706 "CapsLock" => Modifiers::CAPS_LOCK,
707 "Control" => Modifiers::CONTROL,
708 "Fn" => Modifiers::FN,
709 "FnLock" => Modifiers::FN_LOCK,
710 "Meta" => Modifiers::META,
711 "NumLock" => Modifiers::NUM_LOCK,
712 "ScrollLock" => Modifiers::SCROLL_LOCK,
713 "Shift" => Modifiers::SHIFT,
714 "Symbol" => Modifiers::SYMBOL,
715 "SymbolLock" => Modifiers::SYMBOL_LOCK,
716 _ => { return false; },
717 ))
718 }
719}