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::dom::MutNullableDom;
16use script_bindings::match_domstring_ascii;
17use script_bindings::reflector::reflect_dom_object_with_proto;
18use script_traits::ConstellationInputEvent;
19use servo_base::text::Utf32CodeUnits;
20use style::Atom;
21use style_traits::CSSPixel;
22
23use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
24use crate::dom::bindings::codegen::Bindings::MouseEventBinding;
25use crate::dom::bindings::codegen::Bindings::MouseEventBinding::MouseEventMethods;
26use crate::dom::bindings::codegen::Bindings::UIEventBinding::UIEventMethods;
27use crate::dom::bindings::error::Fallible;
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::reflector::DomGlobal;
30use crate::dom::bindings::root::DomRoot;
31use crate::dom::bindings::str::DOMString;
32use crate::dom::document::FireMouseEventType;
33use crate::dom::event::{Event, EventBubbles, EventCancelable};
34use crate::dom::eventtarget::EventTarget;
35use crate::dom::inputevent::HitTestResult;
36use crate::dom::node::Node;
37use crate::dom::pointerevent::{PointerEvent, PointerId};
38use crate::dom::uievent::UIEvent;
39use crate::dom::window::Window;
40
41#[dom_struct]
43pub(crate) struct MouseEvent {
44 uievent: UIEvent,
45
46 #[no_trace]
53 screen_point: Cell<Point2D<i32, CSSPixel>>,
54
55 #[no_trace]
62 client_point: Cell<Point2D<i32, CSSPixel>>,
63
64 #[no_trace]
71 page_point: Cell<Point2D<i32, CSSPixel>>,
72
73 #[no_trace]
75 modifiers: Cell<Modifiers>,
76
77 button: Cell<i16>,
79
80 buttons: Cell<u16>,
82
83 #[no_trace]
84 point_in_target: Cell<Option<Point2D<f32, CSSPixel>>>,
85
86 dom_position_container: MutNullableDom<Node>,
89
90 #[no_trace]
93 dom_position_offset: Cell<Option<Utf32CodeUnits>>,
94}
95
96impl MouseEvent {
97 pub(crate) fn new_inherited() -> MouseEvent {
98 MouseEvent {
99 uievent: UIEvent::new_inherited(),
100 screen_point: Cell::new(Default::default()),
101 client_point: Cell::new(Default::default()),
102 page_point: Cell::new(Default::default()),
103 modifiers: Cell::new(Modifiers::empty()),
104 button: Cell::new(0),
105 buttons: Cell::new(0),
106 point_in_target: Cell::new(None),
107 dom_position_container: MutNullableDom::default(),
108 dom_position_offset: Cell::new(None),
109 }
110 }
111
112 pub(crate) fn new_uninitialized(cx: &mut JSContext, window: &Window) -> DomRoot<MouseEvent> {
113 Self::new_uninitialized_with_proto(cx, window, None)
114 }
115
116 fn new_uninitialized_with_proto(
117 cx: &mut JSContext,
118 window: &Window,
119 proto: Option<HandleObject>,
120 ) -> DomRoot<MouseEvent> {
121 reflect_dom_object_with_proto(cx, Box::new(MouseEvent::new_inherited()), window, proto)
122 }
123
124 #[allow(clippy::too_many_arguments)]
125 pub(crate) fn new(
126 cx: &mut JSContext,
127 window: &Window,
128 event_type: Atom,
129 can_bubble: EventBubbles,
130 cancelable: EventCancelable,
131 view: Option<&Window>,
132 detail: i32,
133 screen_point: Point2D<i32, CSSPixel>,
134 client_point: Point2D<i32, CSSPixel>,
135 page_point: Point2D<i32, CSSPixel>,
136 modifiers: Modifiers,
137 button: i16,
138 buttons: u16,
139 related_target: Option<&EventTarget>,
140 point_in_target: Option<Point2D<f32, CSSPixel>>,
141 dom_position_for_selection: Option<&(DomRoot<Node>, Utf32CodeUnits)>,
142 ) -> DomRoot<MouseEvent> {
143 Self::new_with_proto(
144 cx,
145 window,
146 None,
147 event_type,
148 can_bubble,
149 cancelable,
150 view,
151 detail,
152 screen_point,
153 client_point,
154 page_point,
155 modifiers,
156 button,
157 buttons,
158 related_target,
159 point_in_target,
160 dom_position_for_selection,
161 )
162 }
163
164 #[allow(clippy::too_many_arguments)]
165 fn new_with_proto(
166 cx: &mut JSContext,
167 window: &Window,
168 proto: Option<HandleObject>,
169 event_type: Atom,
170 can_bubble: EventBubbles,
171 cancelable: EventCancelable,
172 view: Option<&Window>,
173 detail: i32,
174 screen_point: Point2D<i32, CSSPixel>,
175 client_point: Point2D<i32, CSSPixel>,
176 page_point: Point2D<i32, CSSPixel>,
177 modifiers: Modifiers,
178 button: i16,
179 buttons: u16,
180 related_target: Option<&EventTarget>,
181 point_in_target: Option<Point2D<f32, CSSPixel>>,
182 dom_position_for_selection: Option<&(DomRoot<Node>, Utf32CodeUnits)>,
183 ) -> DomRoot<MouseEvent> {
184 let ev = MouseEvent::new_uninitialized_with_proto(cx, window, proto);
185 ev.initialize_mouse_event(
186 event_type,
187 can_bubble,
188 cancelable,
189 view,
190 detail,
191 screen_point,
192 client_point,
193 page_point,
194 modifiers,
195 button,
196 buttons,
197 related_target,
198 point_in_target,
199 dom_position_for_selection,
200 );
201 ev
202 }
203
204 #[expect(clippy::too_many_arguments)]
206 pub(crate) fn initialize_mouse_event(
207 &self,
208 event_type: Atom,
209 can_bubble: EventBubbles,
210 cancelable: EventCancelable,
211 view: Option<&Window>,
212 detail: i32,
213 screen_point: Point2D<i32, CSSPixel>,
214 client_point: Point2D<i32, CSSPixel>,
215 page_point: Point2D<i32, CSSPixel>,
216 modifiers: Modifiers,
217 button: i16,
218 buttons: u16,
219 related_target: Option<&EventTarget>,
220 point_in_target: Option<Point2D<f32, CSSPixel>>,
221 dom_position_for_selection: Option<&(DomRoot<Node>, Utf32CodeUnits)>,
222 ) {
223 self.uievent.initialize_ui_event(
224 event_type,
225 view.map(|window| window.upcast::<EventTarget>()),
226 can_bubble,
227 cancelable,
228 );
229
230 self.uievent.set_detail(detail);
231 self.screen_point.set(screen_point);
232 self.client_point.set(client_point);
233 self.page_point.set(page_point);
234 self.modifiers.set(modifiers);
235 self.button.set(button);
236 self.buttons.set(buttons);
237 self.upcast::<Event>().set_related_target(related_target);
238 self.point_in_target.set(point_in_target);
239 let w = if button >= 0 { (button as u32) + 1 } else { 0 };
241 self.uievent.set_which(w);
242 if let Some((node, offset)) = dom_position_for_selection {
243 self.dom_position_container.set(Some(node));
244 self.dom_position_offset.set(Some(*offset));
245 }
246 }
247
248 pub(crate) fn new_for_platform_motion_event(
249 cx: &mut JSContext,
250 window: &Window,
251 event_name: FireMouseEventType,
252 hit_test_result: &HitTestResult,
253 input_event: &ConstellationInputEvent,
254 ) -> DomRoot<Self> {
255 let (bubbles, cancelable, composed) = match event_name {
258 FireMouseEventType::Move | FireMouseEventType::Over | FireMouseEventType::Out => {
259 (EventBubbles::Bubbles, EventCancelable::Cancelable, true)
260 },
261 FireMouseEventType::Enter | FireMouseEventType::Leave => (
262 EventBubbles::DoesNotBubble,
263 EventCancelable::NotCancelable,
264 false,
265 ),
266 };
267
268 let mouse_event = Self::new(
269 cx,
270 window,
271 Atom::from(event_name.as_str()),
272 bubbles,
273 cancelable,
274 Some(window),
275 0i32,
276 hit_test_result.point_in_frame.to_i32(),
277 hit_test_result.point_in_frame.to_i32(),
278 hit_test_result
279 .point_relative_to_initial_containing_block
280 .to_i32(),
281 input_event.active_keyboard_modifiers,
282 0i16,
283 input_event.pressed_mouse_buttons,
284 None,
285 None,
286 hit_test_result.dom_position_for_selection.as_ref(),
287 );
288
289 let event = mouse_event.upcast::<Event>();
290 event.set_composed(composed);
291 event.set_trusted(true);
292
293 mouse_event
294 }
295
296 pub(crate) fn dom_offset_for_selection(&self) -> Option<Utf32CodeUnits> {
297 self.dom_position_offset.get()
298 }
299
300 #[expect(clippy::too_many_arguments)]
304 pub(crate) fn for_platform_button_event(
305 cx: &mut JSContext,
306 event_type: Atom,
307 event: embedder_traits::MouseButtonEvent,
308 pressed_mouse_buttons: u16,
309 window: &Window,
310 hit_test_result: &HitTestResult,
311 modifiers: Modifiers,
312 click_count: usize,
313 ) -> DomRoot<Self> {
314 let client_point = hit_test_result.point_in_frame.to_i32();
315 let page_point = hit_test_result
316 .point_relative_to_initial_containing_block
317 .to_i32();
318
319 let mouse_event = Self::new(
320 cx,
321 window,
322 event_type,
323 EventBubbles::Bubbles,
324 EventCancelable::Cancelable,
325 Some(window),
326 click_count as i32,
327 client_point, client_point,
329 page_point,
330 modifiers,
331 event.button.into(),
332 pressed_mouse_buttons,
333 None,
334 Some(hit_test_result.point_in_node),
335 hit_test_result.dom_position_for_selection.as_ref(),
336 );
337
338 mouse_event.upcast::<Event>().set_trusted(true);
339 mouse_event.upcast::<Event>().set_composed(true);
340
341 mouse_event
342 }
343
344 pub(crate) fn to_pointer_event(
348 &self,
349 cx: &mut JSContext,
350 event_type: Atom,
351 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
352 let is_pointer_down = &*event_type == "pointerdown";
354 let is_pointer_move = &*event_type == "pointermove";
355 let is_pointer_up = &*event_type == "pointerup";
356
357 let pressure = if is_pointer_down || (is_pointer_move && self.Buttons() != 0) {
359 0.5
360 } else {
361 0.0
362 };
363
364 let button = if is_pointer_down || is_pointer_up {
365 self.Button()
366 } else {
367 -1
368 };
369
370 let composed = !matches!(&*event_type, "pointerenter" | "pointerleave");
374
375 let window = self.global();
376 let window = window.as_window();
377
378 let pointer_event = PointerEvent::new(
379 cx,
380 window,
381 event_type,
382 EventBubbles::from(self.upcast::<Event>().Bubbles()),
383 EventCancelable::from(self.upcast::<Event>().Cancelable()),
384 self.uievent.GetView().as_deref(),
385 self.uievent.Detail(),
386 Point2D::new(self.ScreenX(), self.ScreenY()),
387 Point2D::new(self.ClientX(), self.ClientY()),
388 Point2D::new(self.PageX(), self.PageY()),
389 self.modifiers.get(),
390 button,
391 self.Buttons(),
392 self.GetRelatedTarget().as_deref(),
393 self.point_in_target.get(),
394 PointerId::Mouse as i32, 1, 1, pressure,
398 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"),
405 true, vec![], vec![], );
409
410 pointer_event.upcast::<Event>().set_composed(composed);
411
412 pointer_event
413 }
414
415 pub(crate) fn to_pointer_hover_event(
419 &self,
420 cx: &mut JSContext,
421 event_type: &str,
422 ) -> DomRoot<crate::dom::pointerevent::PointerEvent> {
423 let (bubbles, cancelable) = match event_type {
427 "pointerover" | "pointerout" => (EventBubbles::Bubbles, EventCancelable::Cancelable),
428 "pointerenter" | "pointerleave" => {
429 (EventBubbles::DoesNotBubble, EventCancelable::NotCancelable)
430 },
431 _ => (EventBubbles::Bubbles, EventCancelable::Cancelable),
432 };
433
434 let window = self.global();
435 let window = window.as_window();
436
437 let pointer_event = PointerEvent::new(
438 cx,
439 window,
440 event_type.into(),
441 bubbles,
442 cancelable,
443 self.uievent.GetView().as_deref(),
444 self.uievent.Detail(),
445 Point2D::new(self.ScreenX(), self.ScreenY()),
446 Point2D::new(self.ClientX(), self.ClientY()),
447 Point2D::new(self.PageX(), self.PageY()),
448 self.modifiers.get(),
449 -1, self.Buttons(),
451 self.GetRelatedTarget().as_deref(),
452 self.point_in_target.get(),
453 PointerId::Mouse as i32, 1, 1, 0.0, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from("mouse"),
464 true, vec![], vec![], );
468
469 let composed = !matches!(event_type, "pointerenter" | "pointerleave");
473 pointer_event.upcast::<Event>().set_composed(composed);
474
475 pointer_event
477 .upcast::<Event>()
478 .set_trusted(self.IsTrusted());
479
480 pointer_event
481 }
482}
483
484impl MouseEventMethods<crate::DomTypeHolder> for MouseEvent {
485 fn Constructor(
487 cx: &mut JSContext,
488 window: &Window,
489 proto: Option<HandleObject>,
490 event_type: DOMString,
491 init: &MouseEventBinding::MouseEventInit,
492 ) -> Fallible<DomRoot<MouseEvent>> {
493 let bubbles = EventBubbles::from(init.parent.parent.parent.bubbles);
494 let cancelable = EventCancelable::from(init.parent.parent.parent.cancelable);
495 let scroll_offset = window.scroll_offset();
496 let page_point = Point2D::new(
497 scroll_offset.x as i32 + init.clientX,
498 scroll_offset.y as i32 + init.clientY,
499 );
500 let event = MouseEvent::new_with_proto(
501 cx,
502 window,
503 proto,
504 event_type.into(),
505 bubbles,
506 cancelable,
507 init.parent.parent.view.as_deref(),
508 init.parent.parent.detail,
509 Point2D::new(init.screenX, init.screenY),
510 Point2D::new(init.clientX, init.clientY),
511 page_point,
512 init.parent.modifiers(),
513 init.button,
514 init.buttons,
515 init.relatedTarget.as_deref(),
516 None,
517 None,
518 );
519 event
520 .upcast::<Event>()
521 .set_composed(init.parent.parent.parent.composed);
522 Ok(event)
523 }
524
525 fn ScreenX(&self) -> i32 {
527 self.screen_point.get().x
528 }
529
530 fn ScreenY(&self) -> i32 {
532 self.screen_point.get().y
533 }
534
535 fn ClientX(&self) -> i32 {
537 self.client_point.get().x
538 }
539
540 fn ClientY(&self) -> i32 {
542 self.client_point.get().y
543 }
544
545 fn PageX(&self) -> i32 {
547 if self.upcast::<Event>().dispatching() {
552 return self.page_point.get().x;
553 }
554
555 self.global().as_window().ScrollX() + self.ClientX()
559 }
560
561 fn PageY(&self) -> i32 {
563 if self.upcast::<Event>().dispatching() {
568 return self.page_point.get().y;
569 }
570
571 self.global().as_window().ScrollY() + self.ClientY()
575 }
576
577 fn X(&self) -> i32 {
579 self.ClientX()
580 }
581
582 fn Y(&self) -> i32 {
584 self.ClientY()
585 }
586
587 fn OffsetX(&self) -> i32 {
589 let event = self.upcast::<Event>();
595 if event.dispatching() {
596 let Some(target) = event.GetTarget() else {
597 return 0;
598 };
599 let Some(node) = target.downcast::<Node>() else {
600 return 0;
601 };
602 return self.ClientX() - node.client_rect().origin.x;
603 }
604
605 self.PageX()
607 }
608
609 fn OffsetY(&self) -> i32 {
611 let event = self.upcast::<Event>();
617 if event.dispatching() {
618 let Some(target) = event.GetTarget() else {
619 return 0;
620 };
621 let Some(node) = target.downcast::<Node>() else {
622 return 0;
623 };
624 return self.ClientY() - node.client_rect().origin.y;
625 }
626
627 self.PageY()
629 }
630
631 fn CtrlKey(&self) -> bool {
633 self.modifiers.get().contains(Modifiers::CONTROL)
634 }
635
636 fn ShiftKey(&self) -> bool {
638 self.modifiers.get().contains(Modifiers::SHIFT)
639 }
640
641 fn AltKey(&self) -> bool {
643 self.modifiers.get().contains(Modifiers::ALT)
644 }
645
646 fn MetaKey(&self) -> bool {
648 self.modifiers.get().contains(Modifiers::META)
649 }
650
651 fn Button(&self) -> i16 {
653 self.button.get()
654 }
655
656 fn Buttons(&self) -> u16 {
658 self.buttons.get()
659 }
660
661 fn GetRelatedTarget(&self) -> Option<DomRoot<EventTarget>> {
663 self.upcast::<Event>().related_target()
664 }
665
666 fn InitMouseEvent(
668 &self,
669 type_arg: DOMString,
670 can_bubble_arg: bool,
671 cancelable_arg: bool,
672 view_arg: Option<&Window>,
673 detail_arg: i32,
674 screen_x_arg: i32,
675 screen_y_arg: i32,
676 client_x_arg: i32,
677 client_y_arg: i32,
678 ctrl_key_arg: bool,
679 alt_key_arg: bool,
680 shift_key_arg: bool,
681 meta_key_arg: bool,
682 button_arg: i16,
683 related_target_arg: Option<&EventTarget>,
684 ) {
685 if self.upcast::<Event>().dispatching() {
686 return;
687 }
688
689 self.upcast::<UIEvent>().InitUIEvent(
690 type_arg,
691 can_bubble_arg,
692 cancelable_arg,
693 view_arg,
694 detail_arg,
695 );
696 self.screen_point
697 .set(Point2D::new(screen_x_arg, screen_y_arg));
698 self.client_point
699 .set(Point2D::new(client_x_arg, client_y_arg));
700
701 let global = self.global();
702 let scroll_offset = global.as_window().scroll_offset();
703 self.page_point.set(Point2D::new(
704 scroll_offset.x as i32 + client_x_arg,
705 scroll_offset.y as i32 + client_y_arg,
706 ));
707
708 let mut modifiers = Modifiers::empty();
709 if ctrl_key_arg {
710 modifiers.insert(Modifiers::CONTROL);
711 }
712 if alt_key_arg {
713 modifiers.insert(Modifiers::ALT);
714 }
715 if shift_key_arg {
716 modifiers.insert(Modifiers::SHIFT);
717 }
718 if meta_key_arg {
719 modifiers.insert(Modifiers::META);
720 }
721 self.modifiers.set(modifiers);
722
723 self.button.set(button_arg);
724 self.upcast::<Event>()
725 .set_related_target(related_target_arg);
726
727 let w = if button_arg >= 0 {
729 (button_arg as u32) + 1
730 } else {
731 0
732 };
733 self.uievent.set_which(w);
734 }
735
736 fn IsTrusted(&self) -> bool {
738 self.uievent.IsTrusted()
739 }
740
741 fn GetModifierState(&self, key_arg: DOMString) -> bool {
743 self.modifiers
744 .get()
745 .contains(match_domstring_ascii!(key_arg,
746 "Alt" => Modifiers::ALT,
747 "AltGraph" => Modifiers::ALT_GRAPH,
748 "CapsLock" => Modifiers::CAPS_LOCK,
749 "Control" => Modifiers::CONTROL,
750 "Fn" => Modifiers::FN,
751 "FnLock" => Modifiers::FN_LOCK,
752 "Meta" => Modifiers::META,
753 "NumLock" => Modifiers::NUM_LOCK,
754 "ScrollLock" => Modifiers::SCROLL_LOCK,
755 "Shift" => Modifiers::SHIFT,
756 "Symbol" => Modifiers::SYMBOL,
757 "SymbolLock" => Modifiers::SYMBOL_LOCK,
758 _ => { return false; },
759 ))
760 }
761}