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_traits::CSSPixel;
17
18use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
19use crate::dom::bindings::codegen::Bindings::MouseEventBinding;
20use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
21use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods;
22use crate::dom::bindings::error::Fallible;
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object_with_proto};
25use crate::dom::bindings::root::DomRoot;
26use crate::dom::bindings::str::DOMString;
27use crate::dom::document::FireMouseEventType;
28use crate::dom::event::{Event, EventBubbles, EventCancelable};
29use crate::dom::eventtarget::EventTarget;
30use crate::dom::inputevent::HitTestResult;
31use crate::dom::node::Node;
32use crate::dom::pointerevent::{PointerEvent, PointerId};
33use crate::dom::uievent::UIEvent;
34use crate::dom::window::Window;
35use crate::script_runtime::CanGc;
36
37#[dom_struct]
39pub(crate) struct MouseEvent {
40 uievent: UIEvent,
41
42 #[no_trace]
49 screen_point: Cell<Point2D<i32, CSSPixel>>,
50
51 #[no_trace]
58 client_point: Cell<Point2D<i32, CSSPixel>>,
59
60 #[no_trace]
67 page_point: Cell<Point2D<i32, CSSPixel>>,
68
69 #[no_trace]
71 modifiers: Cell<Modifiers>,
72
73 button: Cell<i16>,
75
76 buttons: Cell<u16>,
78
79 #[no_trace]
80 point_in_target: Cell<Option<Point2D<f32, CSSPixel>>>,
81}
82
83impl MouseEvent {
84 pub(crate) fn new_inherited() -> MouseEvent {
85 MouseEvent {
86 uievent: UIEvent::new_inherited(),
87 screen_point: Cell::new(Default::default()),
88 client_point: Cell::new(Default::default()),
89 page_point: Cell::new(Default::default()),
90 modifiers: Cell::new(Modifiers::empty()),
91 button: Cell::new(0),
92 buttons: Cell::new(0),
93 point_in_target: Cell::new(None),
94 }
95 }
96
97 pub(crate) fn new_uninitialized(window: &Window, can_gc: CanGc) -> DomRoot<MouseEvent> {
98 Self::new_uninitialized_with_proto(window, None, can_gc)
99 }
100
101 fn new_uninitialized_with_proto(
102 window: &Window,
103 proto: Option<HandleObject>,
104 can_gc: CanGc,
105 ) -> DomRoot<MouseEvent> {
106 reflect_dom_object_with_proto(Box::new(MouseEvent::new_inherited()), window, proto, can_gc)
107 }
108
109 #[allow(clippy::too_many_arguments)]
110 pub(crate) fn new(
111 window: &Window,
112 type_: DOMString,
113 can_bubble: EventBubbles,
114 cancelable: EventCancelable,
115 view: Option<&Window>,
116 detail: i32,
117 screen_point: Point2D<i32, CSSPixel>,
118 client_point: Point2D<i32, CSSPixel>,
119 page_point: Point2D<i32, CSSPixel>,
120 modifiers: Modifiers,
121 button: i16,
122 buttons: u16,
123 related_target: Option<&EventTarget>,
124 point_in_target: Option<Point2D<f32, CSSPixel>>,
125 can_gc: CanGc,
126 ) -> DomRoot<MouseEvent> {
127 Self::new_with_proto(
128 window,
129 None,
130 type_,
131 can_bubble,
132 cancelable,
133 view,
134 detail,
135 screen_point,
136 client_point,
137 page_point,
138 modifiers,
139 button,
140 buttons,
141 related_target,
142 point_in_target,
143 can_gc,
144 )
145 }
146
147 #[allow(clippy::too_many_arguments)]
148 fn new_with_proto(
149 window: &Window,
150 proto: Option<HandleObject>,
151 type_: DOMString,
152 can_bubble: EventBubbles,
153 cancelable: EventCancelable,
154 view: Option<&Window>,
155 detail: i32,
156 screen_point: Point2D<i32, CSSPixel>,
157 client_point: Point2D<i32, CSSPixel>,
158 page_point: Point2D<i32, CSSPixel>,
159 modifiers: Modifiers,
160 button: i16,
161 buttons: u16,
162 related_target: Option<&EventTarget>,
163 point_in_target: Option<Point2D<f32, CSSPixel>>,
164 can_gc: CanGc,
165 ) -> DomRoot<MouseEvent> {
166 let ev = MouseEvent::new_uninitialized_with_proto(window, proto, can_gc);
167 ev.initialize_mouse_event(
168 type_,
169 can_bubble,
170 cancelable,
171 view,
172 detail,
173 screen_point,
174 client_point,
175 page_point,
176 modifiers,
177 button,
178 buttons,
179 related_target,
180 point_in_target,
181 );
182 ev
183 }
184
185 #[expect(clippy::too_many_arguments)]
187 pub(crate) fn initialize_mouse_event(
188 &self,
189 type_: DOMString,
190 can_bubble: EventBubbles,
191 cancelable: EventCancelable,
192 view: Option<&Window>,
193 detail: i32,
194 screen_point: Point2D<i32, CSSPixel>,
195 client_point: Point2D<i32, CSSPixel>,
196 page_point: Point2D<i32, CSSPixel>,
197 modifiers: Modifiers,
198 button: i16,
199 buttons: u16,
200 related_target: Option<&EventTarget>,
201 point_in_target: Option<Point2D<f32, CSSPixel>>,
202 ) {
203 self.uievent.initialize_ui_event(
204 type_,
205 view.map(|window| window.upcast::<EventTarget>()),
206 can_bubble,
207 cancelable,
208 );
209
210 self.uievent.set_detail(detail);
211 self.screen_point.set(screen_point);
212 self.client_point.set(client_point);
213 self.page_point.set(page_point);
214 self.modifiers.set(modifiers);
215 self.button.set(button);
216 self.buttons.set(buttons);
217 self.upcast::<Event>().set_related_target(related_target);
218 self.point_in_target.set(point_in_target);
219 let w = if button >= 0 { (button as u32) + 1 } else { 0 };
221 self.uievent.set_which(w);
222 }
223
224 pub(crate) fn new_for_platform_motion_event(
225 window: &Window,
226 event_name: FireMouseEventType,
227 hit_test_result: &HitTestResult,
228 input_event: &ConstellationInputEvent,
229 can_gc: CanGc,
230 ) -> DomRoot<Self> {
231 let (bubbles, cancelable, composed) = match event_name {
234 FireMouseEventType::Move | FireMouseEventType::Over | FireMouseEventType::Out => {
235 (EventBubbles::Bubbles, EventCancelable::Cancelable, true)
236 },
237 FireMouseEventType::Enter | FireMouseEventType::Leave => (
238 EventBubbles::DoesNotBubble,
239 EventCancelable::NotCancelable,
240 false,
241 ),
242 };
243
244 let mouse_event = Self::new(
245 window,
246 DOMString::from(event_name.as_str()),
247 bubbles,
248 cancelable,
249 Some(window),
250 0i32,
251 hit_test_result.point_in_frame.to_i32(),
252 hit_test_result.point_in_frame.to_i32(),
253 hit_test_result
254 .point_relative_to_initial_containing_block
255 .to_i32(),
256 input_event.active_keyboard_modifiers,
257 0i16,
258 input_event.pressed_mouse_buttons,
259 None,
260 None,
261 can_gc,
262 );
263
264 let event = mouse_event.upcast::<Event>();
265 event.set_composed(composed);
266 event.set_trusted(true);
267
268 mouse_event
269 }
270
271 #[expect(clippy::too_many_arguments)]
274 pub(crate) fn for_platform_button_event(
275 event_type_string: &'static str,
276 event: embedder_traits::MouseButtonEvent,
277 pressed_mouse_buttons: u16,
278 window: &Window,
279 hit_test_result: &HitTestResult,
280 modifiers: Modifiers,
281 click_count: usize,
282 can_gc: CanGc,
283 ) -> DomRoot<Self> {
284 let client_point = hit_test_result.point_in_frame.to_i32();
285 let page_point = hit_test_result
286 .point_relative_to_initial_containing_block
287 .to_i32();
288
289 let mouse_event = Self::new(
290 window,
291 event_type_string.into(),
292 EventBubbles::Bubbles,
293 EventCancelable::Cancelable,
294 Some(window),
295 click_count as i32,
296 client_point, client_point,
298 page_point,
299 modifiers,
300 event.button.into(),
301 pressed_mouse_buttons,
302 None,
303 Some(hit_test_result.point_in_node),
304 can_gc,
305 );
306
307 mouse_event.upcast::<Event>().set_trusted(true);
308 mouse_event.upcast::<Event>().set_composed(true);
309
310 mouse_event
311 }
312
313 pub(crate) fn point_in_viewport(&self) -> Option<Point2D<f32, CSSPixel>> {
314 Some(self.client_point.get().to_f32())
315 }
316
317 pub(crate) fn to_pointer_event(
321 &self,
322 event_type: &str,
323 can_gc: CanGc,
324 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
325 let pressure = if event_type == "pointerdown" ||
327 (event_type == "pointermove" && self.Buttons() != 0)
328 {
329 0.5
330 } else {
331 0.0
332 };
333
334 let button = if event_type == "pointerdown" || event_type == "pointerup" {
335 self.Button()
336 } else {
337 -1
338 };
339
340 let window = self.global();
341 let window = window.as_window();
342
343 PointerEvent::new(
344 window,
345 DOMString::from(event_type),
346 EventBubbles::from(self.upcast::<Event>().Bubbles()),
347 EventCancelable::from(self.upcast::<Event>().Cancelable()),
348 self.uievent.GetView().as_deref(),
349 self.uievent.Detail(),
350 Point2D::new(self.ScreenX(), self.ScreenY()),
351 Point2D::new(self.ClientX(), self.ClientY()),
352 Point2D::new(self.PageX(), self.PageY()),
353 self.modifiers.get(),
354 button,
355 self.Buttons(),
356 self.GetRelatedTarget().as_deref(),
357 self.point_in_target.get(),
358 PointerId::Mouse as i32, 1, 1, pressure,
362 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"),
369 true, vec![], vec![], can_gc,
373 )
374 }
375}
376
377impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
378 fn Constructor(
380 window: &Window,
381 proto: Option<HandleObject>,
382 can_gc: CanGc,
383 type_: DOMString,
384 init: &MouseEventBinding::MouseEventInit,
385 ) -> Fallible<DomRoot<MouseEvent>> {
386 let bubbles = EventBubbles::from(init.parent.parent.parent.bubbles);
387 let cancelable = EventCancelable::from(init.parent.parent.parent.cancelable);
388 let scroll_offset = window.scroll_offset();
389 let page_point = Point2D::new(
390 scroll_offset.x as i32 + init.clientX,
391 scroll_offset.y as i32 + init.clientY,
392 );
393 let event = MouseEvent::new_with_proto(
394 window,
395 proto,
396 type_,
397 bubbles,
398 cancelable,
399 init.parent.parent.view.as_deref(),
400 init.parent.parent.detail,
401 Point2D::new(init.screenX, init.screenY),
402 Point2D::new(init.clientX, init.clientY),
403 page_point,
404 init.parent.modifiers(),
405 init.button,
406 init.buttons,
407 init.relatedTarget.as_deref(),
408 None,
409 can_gc,
410 );
411 event
412 .upcast::<Event>()
413 .set_composed(init.parent.parent.parent.composed);
414 Ok(event)
415 }
416
417 fn ScreenX(&self) -> i32 {
419 self.screen_point.get().x
420 }
421
422 fn ScreenY(&self) -> i32 {
424 self.screen_point.get().y
425 }
426
427 fn ClientX(&self) -> i32 {
429 self.client_point.get().x
430 }
431
432 fn ClientY(&self) -> i32 {
434 self.client_point.get().y
435 }
436
437 fn PageX(&self) -> i32 {
439 if self.upcast::<Event>().dispatching() {
444 return self.page_point.get().x;
445 }
446
447 self.global().as_window().ScrollX() + self.ClientX()
451 }
452
453 fn PageY(&self) -> i32 {
455 if self.upcast::<Event>().dispatching() {
460 return self.page_point.get().y;
461 }
462
463 self.global().as_window().ScrollY() + self.ClientY()
467 }
468
469 fn X(&self) -> i32 {
471 self.ClientX()
472 }
473
474 fn Y(&self) -> i32 {
476 self.ClientY()
477 }
478
479 fn OffsetX(&self) -> i32 {
481 let event = self.upcast::<Event>();
487 if event.dispatching() {
488 let Some(target) = event.GetTarget() else {
489 return 0;
490 };
491 let Some(node) = target.downcast::<Node>() else {
492 return 0;
493 };
494 return self.ClientX() - node.client_rect().origin.x;
495 }
496
497 self.PageX()
499 }
500
501 fn OffsetY(&self) -> i32 {
503 let event = self.upcast::<Event>();
509 if event.dispatching() {
510 let Some(target) = event.GetTarget() else {
511 return 0;
512 };
513 let Some(node) = target.downcast::<Node>() else {
514 return 0;
515 };
516 return self.ClientY() - node.client_rect().origin.y;
517 }
518
519 self.PageY()
521 }
522
523 fn CtrlKey(&self) -> bool {
525 self.modifiers.get().contains(Modifiers::CONTROL)
526 }
527
528 fn ShiftKey(&self) -> bool {
530 self.modifiers.get().contains(Modifiers::SHIFT)
531 }
532
533 fn AltKey(&self) -> bool {
535 self.modifiers.get().contains(Modifiers::ALT)
536 }
537
538 fn MetaKey(&self) -> bool {
540 self.modifiers.get().contains(Modifiers::META)
541 }
542
543 fn Button(&self) -> i16 {
545 self.button.get()
546 }
547
548 fn Buttons(&self) -> u16 {
550 self.buttons.get()
551 }
552
553 fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> {
555 self.upcast::<Event>().related_target()
556 }
557
558 fn InitMouseEvent(
560 &self,
561 type_arg: DOMString,
562 can_bubble_arg: bool,
563 cancelable_arg: bool,
564 view_arg: Option<&Window>,
565 detail_arg: i32,
566 screen_x_arg: i32,
567 screen_y_arg: i32,
568 client_x_arg: i32,
569 client_y_arg: i32,
570 ctrl_key_arg: bool,
571 alt_key_arg: bool,
572 shift_key_arg: bool,
573 meta_key_arg: bool,
574 button_arg: i16,
575 related_target_arg: Option<&EventTarget>,
576 ) {
577 if self.upcast::<Event>().dispatching() {
578 return;
579 }
580
581 self.upcast::<UIEvent>().InitUIEvent(
582 type_arg,
583 can_bubble_arg,
584 cancelable_arg,
585 view_arg,
586 detail_arg,
587 );
588 self.screen_point
589 .set(Point2D::new(screen_x_arg, screen_y_arg));
590 self.client_point
591 .set(Point2D::new(client_x_arg, client_y_arg));
592
593 let global = self.global();
594 let scroll_offset = global.as_window().scroll_offset();
595 self.page_point.set(Point2D::new(
596 scroll_offset.x as i32 + client_x_arg,
597 scroll_offset.y as i32 + client_y_arg,
598 ));
599
600 let mut modifiers = Modifiers::empty();
601 if ctrl_key_arg {
602 modifiers.insert(Modifiers::CONTROL);
603 }
604 if alt_key_arg {
605 modifiers.insert(Modifiers::ALT);
606 }
607 if shift_key_arg {
608 modifiers.insert(Modifiers::SHIFT);
609 }
610 if meta_key_arg {
611 modifiers.insert(Modifiers::META);
612 }
613 self.modifiers.set(modifiers);
614
615 self.button.set(button_arg);
616 self.upcast::<Event>()
617 .set_related_target(related_target_arg);
618
619 let w = if button_arg >= 0 {
621 (button_arg as u32) + 1
622 } else {
623 0
624 };
625 self.uievent.set_which(w);
626 }
627
628 fn IsTrusted(&self) -> bool {
630 self.uievent.IsTrusted()
631 }
632
633 fn GetModifierState(&self, key_arg: DOMString) -> bool {
635 self.modifiers
636 .get()
637 .contains(match_domstring_ascii!(key_arg,
638 "Alt" => Modifiers::ALT,
639 "AltGraph" => Modifiers::ALT_GRAPH,
640 "CapsLock" => Modifiers::CAPS_LOCK,
641 "Control" => Modifiers::CONTROL,
642 "Fn" => Modifiers::FN,
643 "FnLock" => Modifiers::FN_LOCK,
644 "Meta" => Modifiers::META,
645 "NumLock" => Modifiers::NUM_LOCK,
646 "ScrollLock" => Modifiers::SCROLL_LOCK,
647 "Shift" => Modifiers::SHIFT,
648 "Symbol" => Modifiers::SYMBOL,
649 "SymbolLock" => Modifiers::SYMBOL_LOCK,
650 _ => { return false; },
651 ))
652 }
653}