1use std::array::from_ref;
6use std::cell::Cell;
7use std::f64::consts::PI;
8use std::mem;
9use std::str::FromStr;
10use std::time::{Duration, Instant};
11
12use embedder_traits::{
13 Cursor, EmbedderMsg, ImeEvent, InputEvent, InputEventId, InputEventOutcome, InputEventResult,
14 KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction, MouseButtonEvent,
15 MouseLeftViewportEvent, TouchEvent as EmbedderTouchEvent, TouchEventType, TouchId,
16 TouchPointerType, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
17};
18#[cfg(feature = "gamepad")]
19use embedder_traits::{
20 GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
21};
22use euclid::{Point2D, Vector2D};
23use js::context::{JSContext, NoGC};
24use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
25use layout_api::{HitTestFlags, ScrollContainerQueryFlags, node_id_from_scroll_id};
26use rustc_hash::FxHashMap;
27use script_bindings::cell::DomRefCell;
28use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
29use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
30use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
31use script_bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElementMethods;
32use script_bindings::codegen::GenericBindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
33use script_bindings::codegen::GenericBindings::KeyboardEventBinding::KeyboardEventMethods;
34use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
35use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
36use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
37use script_bindings::inheritance::Castable;
38use script_bindings::num::Finite;
39use script_bindings::root::{Dom, DomRoot, DomSlice};
40use script_bindings::str::DOMString;
41use script_traits::{ConstellationInputEvent, MouseButtons};
42use servo_config::pref;
43use servo_constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
44use style::Atom;
45use style_traits::CSSPixel;
46use webrender_api::ExternalScrollId;
47
48#[cfg(feature = "gamepad")]
49use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionName;
50use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
51use crate::dom::bindings::refcounted::Trusted;
52use crate::dom::bindings::root::MutNullableDom;
53use crate::dom::bindings::trace::NoTrace;
54use crate::dom::document::FireMouseEventType;
55use crate::dom::document::focus::FocusableArea;
56use crate::dom::document::interactive_element_command::InteractiveElementCommand;
57use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
58#[cfg(feature = "gamepad")]
59use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
60#[cfg(feature = "gamepad")]
61use crate::dom::gamepad::gamepadevent::GamepadEventType;
62use crate::dom::inputevent::HitTestResult;
63use crate::dom::iterators::ShadowIncluding;
64use crate::dom::keyboardevent::KeyboardEvent;
65use crate::dom::node::focus::FocusTrigger;
66use crate::dom::node::{self, Node, NodeTraits};
67use crate::dom::pointerevent::{PointerEvent, PointerId};
68use crate::dom::types::{
69 CompositionEvent, Element, Event, EventTarget, GlobalScope, HTMLAnchorElement, HTMLElement,
70 HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList, WheelEvent, Window,
71};
72use crate::dom::virtualmethods::vtable_for;
73use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
74use crate::drag::drag_gesture::DragGesture;
75use crate::realms::enter_auto_realm;
76
77#[derive(Default, JSTraceable, MallocSizeOf)]
87struct ClickCountingInfo {
88 time: Option<Instant>,
89 #[no_trace]
90 point: Option<Point2D<f32, CSSPixel>>,
91 #[no_trace]
92 button: Option<MouseButton>,
93 count: usize,
94}
95
96impl ClickCountingInfo {
97 fn reset_click_count_if_necessary(
98 &mut self,
99 button: MouseButton,
100 point_in_frame: Point2D<f32, CSSPixel>,
101 ) {
102 let (Some(previous_button), Some(previous_point), Some(previous_time)) =
103 (self.button, self.point, self.time)
104 else {
105 assert_eq!(self.count, 0);
106 return;
107 };
108
109 let double_click_timeout =
110 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
111 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
112
113 let line = point_in_frame - previous_point;
115 let distance = (line.dot(line) as f64).sqrt();
116 if previous_button != button ||
117 Instant::now().duration_since(previous_time) > double_click_timeout ||
118 distance > double_click_distance_threshold as f64
119 {
120 self.count = 0;
121 self.time = None;
122 self.point = None;
123 }
124 }
125
126 fn increment_click_count(
127 &mut self,
128 button: MouseButton,
129 point: Point2D<f32, CSSPixel>,
130 ) -> usize {
131 self.time = Some(Instant::now());
132 self.point = Some(point);
133 self.button = Some(button);
134 self.count += 1;
135 self.count
136 }
137}
138
139#[derive(JSTraceable, MallocSizeOf)]
143#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
144pub(crate) struct DocumentEventHandler {
145 window: Dom<Window>,
147 #[no_trace]
149 #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
150 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
151 mouse_move_event_index: DomRefCell<Option<usize>>,
153 #[no_trace]
155 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
156 coalesced_mouse_move_event_ids: DomRefCell<Vec<InputEventId>>,
157 wheel_event_index: DomRefCell<Option<usize>>,
162 #[no_trace]
164 #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
165 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
166 click_counting_info: DomRefCell<ClickCountingInfo>,
168 #[no_trace]
169 last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
170 #[no_trace]
174 mouse_button_state: Cell<MouseButtons>,
175 current_hover_target: MutNullableDom<Element>,
177 current_active_element: MutNullableDom<Element>,
180 most_recently_clicked_element: MutNullableDom<Element>,
182 #[no_trace]
184 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
185 #[no_trace]
188 current_cursor: Cell<Option<Cursor>>,
189 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
191 #[no_trace]
193 active_keyboard_modifiers: Cell<Modifiers>,
194 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
196 next_touch_pointer_id: Cell<i32>,
198 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
200 pending_pointer_capture: DomRefCell<FxHashMap<i32, Dom<Element>>>,
204 pointer_capture_target: DomRefCell<FxHashMap<i32, Dom<Element>>>,
207 drag_gesture: DomRefCell<Option<DragGesture>>,
210}
211
212impl DocumentEventHandler {
213 pub(crate) fn new(window: &Window) -> Self {
214 Self {
215 window: Dom::from_ref(window),
216 pending_input_events: Default::default(),
217 mouse_move_event_index: Default::default(),
218 coalesced_mouse_move_event_ids: Default::default(),
219 wheel_event_index: Default::default(),
220 coalesced_wheel_event_ids: Default::default(),
221 click_counting_info: Default::default(),
222 last_mouse_button_down_point: Default::default(),
223 mouse_button_state: Cell::new(MouseButtons::empty()),
224 current_hover_target: Default::default(),
225 current_active_element: Default::default(),
226 most_recently_clicked_element: Default::default(),
227 most_recent_mousemove_point: Default::default(),
228 current_cursor: Default::default(),
229 active_touch_points: Default::default(),
230 active_keyboard_modifiers: Default::default(),
231 active_pointer_ids: Default::default(),
232 next_touch_pointer_id: Cell::new(1),
233 access_key_handlers: Default::default(),
234 pending_pointer_capture: Default::default(),
235 pointer_capture_target: Default::default(),
236 drag_gesture: Default::default(),
237 }
238 }
239
240 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
242 let mut pending_input_events = self.pending_input_events.borrow_mut();
243 if matches!(event.event.event, InputEvent::MouseMove(..)) {
244 if let Some(mouse_move_event) = self
246 .mouse_move_event_index
247 .borrow()
248 .and_then(|index| pending_input_events.get_mut(index))
249 {
250 self.coalesced_mouse_move_event_ids
251 .borrow_mut()
252 .push(mouse_move_event.event.id);
253 *mouse_move_event = event;
254 return;
255 }
256
257 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
258 }
259
260 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
261 if let Some(existing_constellation_wheel_event) = self
263 .wheel_event_index
264 .borrow()
265 .and_then(|index| pending_input_events.get_mut(index)) &&
266 let InputEvent::Wheel(ref mut existing_wheel_event) =
267 existing_constellation_wheel_event.event.event &&
268 existing_wheel_event.delta.mode == new_wheel_event.delta.mode
269 {
270 self.coalesced_wheel_event_ids
271 .borrow_mut()
272 .push(existing_constellation_wheel_event.event.id);
273 existing_wheel_event.delta.x += new_wheel_event.delta.x;
274 existing_wheel_event.delta.y += new_wheel_event.delta.y;
275 existing_wheel_event.delta.z += new_wheel_event.delta.z;
276 existing_wheel_event.point = new_wheel_event.point;
277 existing_constellation_wheel_event.event.id = event.event.id;
278 return;
279 }
280
281 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
282 }
283
284 pending_input_events.push(event);
285 }
286
287 pub(crate) fn has_pending_input_events(&self) -> bool {
290 !self.pending_input_events.borrow().is_empty()
291 }
292
293 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
294 #[cfg(target_os = "macos")]
295 {
296 self.active_keyboard_modifiers
297 .get()
298 .contains(Modifiers::META)
299 }
300 #[cfg(not(target_os = "macos"))]
301 {
302 self.active_keyboard_modifiers
303 .get()
304 .contains(Modifiers::CONTROL)
305 }
306 }
307
308 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
309 debug_assert!(
310 !self.pending_input_events.borrow().is_empty(),
311 "handle_pending_input_events called with no events"
312 );
313 let mut realm = enter_auto_realm(cx, &*self.window);
314 let cx = &mut realm.current_realm();
315
316 *self.mouse_move_event_index.safe_borrow_mut(cx.no_gc()) = None;
318 *self.wheel_event_index.safe_borrow_mut(cx.no_gc()) = None;
319 let pending_input_events =
320 mem::take(&mut *self.pending_input_events.safe_borrow_mut(cx.no_gc()));
321 let mut coalesced_mouse_move_event_ids = mem::take(
322 &mut *self
323 .coalesced_mouse_move_event_ids
324 .safe_borrow_mut(cx.no_gc()),
325 );
326 let mut coalesced_wheel_event_ids =
327 mem::take(&mut *self.coalesced_wheel_event_ids.safe_borrow_mut(cx.no_gc()));
328
329 let mut input_event_outcomes = Vec::with_capacity(
330 pending_input_events.len() +
331 coalesced_mouse_move_event_ids.len() +
332 coalesced_wheel_event_ids.len(),
333 );
334 for event in pending_input_events {
340 self.active_keyboard_modifiers
341 .set(event.active_keyboard_modifiers);
342 let result = match event.event.event {
343 InputEvent::MouseButton(mouse_button_event) => {
344 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
345 InputEventResult::default()
346 },
347 InputEvent::MouseMove(_) => {
348 self.handle_native_mouse_move_event(cx, &event);
349 input_event_outcomes.extend(
350 mem::take(&mut coalesced_mouse_move_event_ids)
351 .into_iter()
352 .map(|id| InputEventOutcome {
353 id,
354 result: InputEventResult::default(),
355 }),
356 );
357 InputEventResult::default()
358 },
359 InputEvent::MouseLeftViewport(mouse_leave_event) => {
360 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
361 InputEventResult::default()
362 },
363 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
364 InputEvent::Wheel(wheel_event) => {
365 let result = self.handle_wheel_event(cx, wheel_event, &event);
366 input_event_outcomes.extend(
367 mem::take(&mut coalesced_wheel_event_ids)
368 .into_iter()
369 .map(|id| InputEventOutcome { id, result }),
370 );
371 result
372 },
373 InputEvent::Keyboard(keyboard_event) => {
374 self.handle_keyboard_event(cx, keyboard_event)
375 },
376 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
377 #[cfg(feature = "gamepad")]
378 InputEvent::Gamepad(gamepad_event) => {
379 self.handle_gamepad_event(gamepad_event);
380 InputEventResult::default()
381 },
382 InputEvent::EditingAction(editing_action_event) => {
383 let document = self.window.Document();
384 let focused_node = document
385 .focus_handler()
386 .focused_area()
387 .dom_anchor(&document);
388 document.handle_editing_action(cx, &focused_node, editing_action_event)
389 },
390 };
391
392 input_event_outcomes.push(InputEventOutcome {
393 id: event.event.id,
394 result,
395 });
396 }
397
398 self.notify_embedder_that_events_were_handled(input_event_outcomes);
399 }
400
401 fn notify_embedder_that_events_were_handled(
402 &self,
403 input_event_outcomes: Vec<InputEventOutcome>,
404 ) {
405 let trusted_window = Trusted::new(&*self.window);
408 self.window
409 .as_global_scope()
410 .task_manager()
411 .dom_manipulation_task_source()
412 .queue(task!(notify_webdriver_input_event_completed: move || {
413 let window = trusted_window.root();
414 window.send_to_embedder(
415 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
416 }));
417 }
418
419 pub(crate) fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
423 let document = self.window.Document();
424 match &*document.focus_handler().focused_area() {
425 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
426 FocusableArea::IFrameViewport { iframe_element, .. } => {
427 DomRoot::from_ref(iframe_element.upcast())
428 },
429 FocusableArea::Viewport => document
430 .GetBody()
431 .map(DomRoot::upcast)
432 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
433 }
434 }
435
436 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
437 if cursor == self.current_cursor.get() {
438 return;
439 }
440 self.current_cursor.set(cursor);
441 self.window.send_to_embedder(EmbedderMsg::SetCursor(
442 self.window.webview_id(),
443 cursor.unwrap_or_default(),
444 ));
445 }
446
447 fn handle_mouse_left_viewport_event(
448 &self,
449 cx: &mut JSContext,
450 input_event: &ConstellationInputEvent,
451 mouse_leave_event: &MouseLeftViewportEvent,
452 ) {
453 if let Some(current_hover_target) = self.current_hover_target.get() {
454 let current_hover_target = current_hover_target.upcast::<Node>();
455 for element in current_hover_target
456 .inclusive_ancestors(ShadowIncluding::Yes)
457 .filter_map(DomRoot::downcast::<Element>)
458 {
459 element.set_hover_state(false);
460 }
461
462 if let Some(hit_test_result) =
463 self.most_recent_mousemove_point.get().and_then(|point| {
464 self.window
465 .hit_test_from_point_in_viewport(HitTestFlags::empty(), point)
466 })
467 {
468 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
469 cx,
470 &self.window,
471 FireMouseEventType::Out,
472 &hit_test_result,
473 input_event,
474 );
475
476 mouse_out_event
478 .to_pointer_hover_event(cx, "pointerout")
479 .upcast::<Event>()
480 .fire(cx, current_hover_target.upcast());
481
482 mouse_out_event
483 .upcast::<Event>()
484 .fire(cx, current_hover_target.upcast());
485
486 self.handle_mouse_enter_leave_event(
487 cx,
488 current_hover_target,
489 None,
490 FireMouseEventType::Leave,
491 &hit_test_result,
492 input_event,
493 );
494 }
495 }
496
497 if !mouse_leave_event.focus_moving_to_another_iframe {
502 self.window
506 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
507 self.set_cursor(None);
508 } else {
509 self.current_cursor.set(None);
510 }
511
512 self.current_hover_target.set(None);
513 self.most_recent_mousemove_point.set(None);
514 }
515
516 fn handle_mouse_enter_leave_event(
517 &self,
518 cx: &mut JSContext,
519 event_target: &Node,
520 related_target: Option<&Node>,
521 event_type: FireMouseEventType,
522 hit_test_result: &HitTestResult,
523 input_event: &ConstellationInputEvent,
524 ) {
525 assert!(matches!(
526 event_type,
527 FireMouseEventType::Enter | FireMouseEventType::Leave
528 ));
529
530 let common_ancestor = match related_target.as_ref() {
531 Some(related_target) => event_target
532 .common_ancestor_in_flat_tree(cx.no_gc(), related_target)
533 .unwrap_or_else(|| DomRoot::from_ref(event_target)),
534 None => DomRoot::from_ref(event_target),
535 };
536
537 let mut targets: Vec<_> = event_target
540 .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
541 .take_while(|node| *node != *common_ancestor)
542 .map(|node| node.as_rooted())
543 .collect();
544
545 if event_type == FireMouseEventType::Enter {
548 targets.reverse();
549 }
550
551 let pointer_event_name = match event_type {
552 FireMouseEventType::Enter => "pointerenter",
553 FireMouseEventType::Leave => "pointerleave",
554 _ => unreachable!(),
555 };
556
557 for target in targets {
558 let mouse_event = MouseEvent::new_for_platform_motion_event(
559 cx,
560 &self.window,
561 event_type,
562 hit_test_result,
563 input_event,
564 );
565 mouse_event
566 .upcast::<Event>()
567 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
568
569 mouse_event
571 .to_pointer_hover_event(cx, pointer_event_name)
572 .upcast::<Event>()
573 .fire(cx, target.upcast());
574
575 mouse_event.upcast::<Event>().fire(cx, target.upcast());
577 }
578 }
579
580 fn handle_native_mouse_move_event(
582 &self,
583 cx: &mut JSContext,
584 input_event: &ConstellationInputEvent,
585 ) {
586 let pointer_id = PointerId::Mouse as i32;
589 let released_disconnected =
590 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
591
592 let hit_test_flags = if self
593 .drag_gesture
594 .borrow()
595 .as_ref()
596 .is_some_and(DragGesture::need_dom_position_from_hit_test)
597 {
598 HitTestFlags::IncludeDomPosition
599 } else {
600 HitTestFlags::empty()
601 };
602
603 let Some(hit_test_result) = self
608 .window
609 .hit_test_from_input_event(hit_test_flags, input_event)
610 else {
611 return;
612 };
613
614 {
615 let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
616 if maybe_drag_gesture.as_mut().is_some_and(|drag_gesture| {
617 !drag_gesture.handle_mouse_move_event(cx, input_event, &hit_test_result)
618 }) {
619 *maybe_drag_gesture = None;
620 }
621 }
622
623 let old_mouse_move_point = self
624 .most_recent_mousemove_point
625 .replace(Some(hit_test_result.point_in_frame));
626 if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
627 return;
628 }
629
630 self.set_cursor(Some(hit_test_result.cursor));
632
633 let Some(new_target) = hit_test_result
634 .node
635 .inclusive_ancestors(ShadowIncluding::Yes)
636 .find_map(DomRoot::downcast::<Element>)
637 else {
638 return;
639 };
640
641 let capture_is_active = self.get_pointer_capture_target(pointer_id).is_some();
642 let old_hover_target = self.current_hover_target.get();
643 let target_has_changed = old_hover_target
644 .as_ref()
645 .is_none_or(|old_target| *old_target != new_target);
646
647 if target_has_changed {
650 if let Some(old_target) = self.current_hover_target.get() {
652 let old_target_is_ancestor_of_new_target = old_target
653 .upcast::<Node>()
654 .is_ancestor_of(new_target.upcast::<Node>());
655
656 if !old_target_is_ancestor_of_new_target {
659 for element in old_target
660 .upcast::<Node>()
661 .inclusive_ancestors(ShadowIncluding::Yes)
662 .filter_map(DomRoot::downcast::<Element>)
663 {
664 element.set_hover_state(false);
665 }
666 }
667
668 if !capture_is_active {
669 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
670 cx,
671 &self.window,
672 FireMouseEventType::Out,
673 &hit_test_result,
674 input_event,
675 );
676 mouse_out_event
677 .upcast::<Event>()
678 .set_related_target(Some(new_target.upcast()));
679
680 mouse_out_event
682 .to_pointer_hover_event(cx, "pointerout")
683 .upcast::<Event>()
684 .fire(cx, old_target.upcast());
685
686 mouse_out_event
687 .upcast::<Event>()
688 .fire(cx, old_target.upcast());
689
690 if !old_target_is_ancestor_of_new_target {
691 let event_target = old_target.upcast::<Node>();
692 let moving_into = Some(new_target.upcast::<Node>());
693 self.handle_mouse_enter_leave_event(
694 cx,
695 event_target,
696 moving_into,
697 FireMouseEventType::Leave,
698 &hit_test_result,
699 input_event,
700 );
701 }
702 }
703 }
704
705 for element in new_target
707 .upcast::<Node>()
708 .inclusive_ancestors(ShadowIncluding::Yes)
709 .filter_map(DomRoot::downcast::<Element>)
710 {
711 element.set_hover_state(true);
712 }
713
714 if !capture_is_active {
715 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
716 cx,
717 &self.window,
718 FireMouseEventType::Over,
719 &hit_test_result,
720 input_event,
721 );
722 mouse_over_event
723 .upcast::<Event>()
724 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
725
726 mouse_over_event
728 .to_pointer_hover_event(cx, "pointerover")
729 .upcast::<Event>()
730 .dispatch(cx, new_target.upcast(), false);
731
732 mouse_over_event
733 .upcast::<Event>()
734 .dispatch(cx, new_target.upcast(), false);
735
736 let moving_from = old_hover_target
737 .as_ref()
738 .map(|old_target| old_target.upcast::<Node>());
739 let event_target = new_target.upcast::<Node>();
740 self.handle_mouse_enter_leave_event(
741 cx,
742 event_target,
743 moving_from,
744 FireMouseEventType::Enter,
745 &hit_test_result,
746 input_event,
747 );
748 }
749 }
750
751 let mouse_event = MouseEvent::new_for_platform_motion_event(
754 cx,
755 &self.window,
756 FireMouseEventType::Move,
757 &hit_test_result,
758 input_event,
759 );
760
761 let pointer_target = self
766 .get_pointer_capture_target(pointer_id)
767 .map(DomRoot::upcast::<EventTarget>)
768 .unwrap_or_else(|| DomRoot::from_ref(new_target.upcast::<EventTarget>()));
769
770 let pointer_event = mouse_event.to_pointer_event(cx, Atom::from("pointermove"));
771 pointer_event.upcast::<Event>().set_composed(true);
772 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
773
774 if !released_disconnected {
778 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
779 }
780
781 mouse_event.upcast::<Event>().fire(cx, &pointer_target);
783
784 self.update_current_hover_target_and_status(cx.no_gc(), Some(new_target));
785 }
786
787 fn update_current_hover_target_and_status(
788 &self,
789 no_gc: &NoGC,
790 new_hover_target: Option<DomRoot<Element>>,
791 ) {
792 let current_hover_target = self.current_hover_target.get();
793 if current_hover_target == new_hover_target {
794 return;
795 }
796
797 let previous_hover_target = self.current_hover_target.get();
798 self.current_hover_target.set(new_hover_target.as_deref());
799
800 if let Some(target) = self.current_hover_target.get() &&
803 let Some(anchor) = target
804 .upcast::<Node>()
805 .inclusive_ancestors(ShadowIncluding::Yes)
806 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
807 {
808 let status = anchor
809 .full_href_url_for_user_interface(no_gc)
810 .map(|url| url.to_string());
811 self.window
812 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
813 return;
814 }
815
816 if previous_hover_target.is_none_or(|previous_hover_target| {
821 previous_hover_target
822 .upcast::<Node>()
823 .inclusive_ancestors(ShadowIncluding::Yes)
824 .any(|node| node.is::<HTMLAnchorElement>())
825 }) {
826 self.window
827 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
828 }
829 }
830
831 pub(crate) fn handle_refresh_cursor(&self) {
832 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
833 return;
834 };
835
836 let Some(hit_test_result) = self
837 .window
838 .hit_test_from_point_in_viewport(HitTestFlags::empty(), most_recent_mousemove_point)
839 else {
840 return;
841 };
842
843 self.set_cursor(Some(hit_test_result.cursor));
844 }
845
846 fn set_active_element(&self, original_target: &Element) {
847 let find_element_for_activation = |element: &Element| {
848 let node: &Node = element.upcast();
849 if node.is_in_ua_widget() &&
850 let Some(containing_shadow_root) = node.containing_shadow_root()
851 {
852 return containing_shadow_root.Host();
853 }
854
855 if node.type_id() ==
857 NodeTypeId::Element(ElementTypeId::HTMLElement(
858 HTMLElementTypeId::HTMLLabelElement,
859 ))
860 {
861 let label = element.downcast::<HTMLLabelElement>().unwrap();
862 if let Some(control) = label.GetControl() {
863 return DomRoot::from_ref(control.upcast::<Element>());
864 }
865 }
866
867 DomRoot::from_ref(element)
868 };
869 let element_for_activation = find_element_for_activation(original_target);
870
871 if let Some(currently_active_element) = self.current_active_element.get() {
874 if currently_active_element == element_for_activation {
875 return;
876 }
877 self.unset_active_element();
878 }
879
880 element_for_activation.set_active_state(true);
881 self.current_active_element
882 .set(Some(&*element_for_activation));
883 }
884
885 fn unset_active_element(&self) {
886 if let Some(active_element) = self.current_active_element.take() {
887 active_element.set_active_state(false);
888 }
889 }
890
891 fn handle_native_mouse_button_event(
894 &self,
895 cx: &mut JSContext,
896 mouse_button_event: MouseButtonEvent,
897 input_event: &ConstellationInputEvent,
898 ) {
899 {
900 let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
901 if maybe_drag_gesture
902 .as_mut()
903 .is_some_and(|drag_gesture| !drag_gesture.handle_mouse_button_event(input_event))
904 {
905 *maybe_drag_gesture = None;
906 }
907 }
908
909 let flags = if input_event.primary_button_is_pressed() ||
910 input_event.auxiliary_button_is_pressed()
911 {
912 HitTestFlags::IncludeDomPosition
913 } else {
914 HitTestFlags::empty()
915 };
916 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
918 else {
919 return;
920 };
921
922 debug!(
923 "{:?}: at {:?}",
924 mouse_button_event.action, hit_test_result.point_in_frame
925 );
926
927 let document = self.window.Document();
930 if mouse_button_event.action == MouseButtonAction::Down {
931 document
932 .focus_handler()
933 .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
934 }
935
936 let Some(element) = hit_test_result
937 .node
938 .inclusive_ancestors(ShadowIncluding::Yes)
939 .find_map(DomRoot::downcast::<Element>)
940 else {
941 return;
942 };
943
944 let node = element.upcast::<Node>();
945 debug!("{:?} on {:?}", mouse_button_event.action, node.debug_str());
946
947 if mouse_button_event.button == MouseButton::Primary {
952 if mouse_button_event.action == MouseButtonAction::Down {
953 self.set_active_element(&element);
954 }
955 if mouse_button_event.action == MouseButtonAction::Up {
956 self.unset_active_element();
957 }
958 }
959
960 if element.is_actually_disabled() {
964 return;
965 }
966
967 let mouse_event_type = match mouse_button_event.action {
968 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
969 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
970 };
971
972 if mouse_button_event.action == MouseButtonAction::Down {
979 self.click_counting_info
980 .safe_borrow_mut(cx.no_gc())
981 .reset_click_count_if_necessary(
982 mouse_button_event.button,
983 hit_test_result.point_in_frame,
984 );
985 }
986
987 let mouse_event = MouseEvent::for_platform_button_event(
988 cx,
989 mouse_event_type,
990 mouse_button_event,
991 input_event.pressed_mouse_buttons,
992 &self.window,
993 &hit_test_result,
994 input_event.active_keyboard_modifiers,
995 self.click_counting_info.borrow().count + 1,
996 );
997
998 match mouse_button_event.action {
999 MouseButtonAction::Down => {
1000 self.last_mouse_button_down_point
1001 .set(Some(hit_test_result.point_in_frame));
1002
1003 let pointer_event_name = if self.mouse_button_state.get().is_empty() {
1005 "pointerdown".into()
1010 } else {
1011 "pointermove".into()
1017 };
1018 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1019
1020 let pointer_id = PointerId::Mouse as i32;
1022
1023 let released_disconnected =
1025 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1026
1027 let pointer_target = self
1029 .get_pointer_capture_target(pointer_id)
1030 .map(DomRoot::upcast::<EventTarget>)
1031 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1032
1033 self.mouse_button_state
1035 .set(input_event.pressed_mouse_buttons);
1036
1037 let pointer_event_result =
1038 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1039
1040 if !released_disconnected {
1044 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1045 }
1046
1047 let result = mouse_event
1049 .upcast::<Event>()
1050 .dispatch(cx, node.upcast(), false);
1051
1052 if result &&
1056 pointer_event_result &&
1057 let Some(node) = mouse_event
1058 .upcast::<Event>()
1059 .GetTarget()
1060 .and_then(DomRoot::downcast::<Node>)
1061 {
1062 vtable_for(&node).handle_mousedown_event(cx, &mouse_event, &hit_test_result);
1063 }
1064
1065 if result {
1068 document
1072 .focus_handler()
1073 .focus(cx, &node.find_click_focusable_area(cx));
1074 }
1075
1076 if let MouseButton::Secondary = mouse_button_event.button {
1079 self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
1080 }
1081 },
1082 MouseButtonAction::Up => {
1084 let mouse_button_state = self.mouse_button_state.get();
1086 let exactly_one_button = mouse_button_state.exactly_one_button_pressed();
1087 let pointer_event_name = if exactly_one_button {
1088 "pointerup".into()
1093 } else {
1094 "pointermove".into()
1100 };
1101 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1102
1103 let pointer_id = PointerId::Mouse as i32;
1105
1106 let released_disconnected =
1108 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1109
1110 let pointer_target = self
1112 .get_pointer_capture_target(pointer_id)
1113 .map(DomRoot::upcast::<EventTarget>)
1114 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1115
1116 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1117
1118 self.mouse_button_state
1121 .set(input_event.pressed_mouse_buttons);
1122
1123 if !released_disconnected {
1127 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1128 }
1129
1130 if exactly_one_button {
1132 self.implicit_release_pointer_capture(cx, pointer_id, "mouse", true);
1133 }
1134
1135 mouse_event
1137 .upcast::<Event>()
1138 .dispatch(cx, node.upcast(), false);
1139
1140 self.click_counting_info
1144 .safe_borrow_mut(cx.no_gc())
1145 .increment_click_count(
1146 mouse_button_event.button,
1147 hit_test_result.point_in_frame,
1148 );
1149
1150 self.maybe_trigger_click_for_mouse_button_down_event(
1151 cx,
1152 mouse_button_event,
1153 input_event,
1154 &hit_test_result,
1155 &element,
1156 );
1157 },
1158 }
1159 }
1160
1161 fn maybe_trigger_click_for_mouse_button_down_event(
1164 &self,
1165 cx: &mut JSContext,
1166 event: MouseButtonEvent,
1167 input_event: &ConstellationInputEvent,
1168 hit_test_result: &HitTestResult,
1169 element: &Element,
1170 ) {
1171 if event.button != MouseButton::Primary {
1172 return;
1173 }
1174
1175 let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1176 return;
1177 };
1178
1179 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1180 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1181 if distance > maximum_click_distance {
1182 return;
1183 }
1184
1185 let element = &element.inclusive_ancestor_element_in_non_ua_shadow_root();
1189 self.most_recently_clicked_element.set(Some(element));
1190
1191 let click_count = self.click_counting_info.borrow().count;
1192 element.set_click_in_progress(true);
1193 MouseEvent::for_platform_button_event(
1194 cx,
1195 atom!("click"),
1196 event,
1197 input_event.pressed_mouse_buttons,
1198 &self.window,
1199 hit_test_result,
1200 input_event.active_keyboard_modifiers,
1201 click_count,
1202 )
1203 .upcast::<Event>()
1204 .dispatch(cx, element.upcast(), false);
1205 element.set_click_in_progress(false);
1206
1207 if click_count.is_multiple_of(2) {
1216 MouseEvent::for_platform_button_event(
1217 cx,
1218 Atom::from("dblclick"),
1219 event,
1220 input_event.pressed_mouse_buttons,
1221 &self.window,
1222 hit_test_result,
1223 input_event.active_keyboard_modifiers,
1224 2,
1225 )
1226 .upcast::<Event>()
1227 .dispatch(cx, element.upcast(), false);
1228 }
1229 }
1230
1231 fn maybe_show_context_menu(
1233 &self,
1234 cx: &mut js::context::JSContext,
1235 target: &EventTarget,
1236 hit_test_result: &HitTestResult,
1237 input_event: &ConstellationInputEvent,
1238 ) {
1239 let menu_event = PointerEvent::new(
1241 cx,
1242 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1249 hit_test_result.point_in_frame.to_i32(),
1250 hit_test_result
1251 .point_relative_to_initial_containing_block
1252 .to_i32(),
1253 input_event.active_keyboard_modifiers,
1254 MouseButton::Secondary,
1255 input_event.pressed_mouse_buttons,
1256 None, None, PointerId::Mouse as i32, 1, 1, 0.5, 0.0, 0, 0, 0, PI / 2.0, 0.0, DOMString::from_static("mouse"), true, vec![], vec![], );
1273 menu_event.upcast::<Event>().set_composed(true);
1274
1275 let result = menu_event.upcast::<Event>().fire(cx, target);
1277
1278 if result {
1280 self.window
1281 .Document()
1282 .embedder_controls()
1283 .show_context_menu(cx.no_gc(), hit_test_result);
1284 };
1285 }
1286
1287 fn handle_touch_event(
1288 &self,
1289 cx: &mut JSContext,
1290 event: EmbedderTouchEvent,
1291 input_event: &ConstellationInputEvent,
1292 ) -> InputEventResult {
1293 let flags = HitTestFlags::empty();
1294 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1296 else {
1297 self.update_active_touch_points_when_early_return(event);
1298 return Default::default();
1299 };
1300
1301 let TouchId(identifier) = event.touch_id;
1302
1303 let Some(element) = hit_test_result
1304 .node
1305 .inclusive_ancestors(ShadowIncluding::Yes)
1306 .find_map(DomRoot::downcast::<Element>)
1307 else {
1308 self.update_active_touch_points_when_early_return(event);
1309 return Default::default();
1310 };
1311
1312 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1313 let window = &*self.window;
1314
1315 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1316 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1317 let page_x =
1318 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1319 let page_y =
1320 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1321
1322 let pointer_touch = Touch::new(
1324 cx,
1325 window,
1326 identifier,
1327 ¤t_target,
1328 client_x,
1329 client_y, client_x,
1331 client_y,
1332 page_x,
1333 page_y,
1334 );
1335
1336 let pointer_event_name = match event.event_type {
1338 TouchEventType::Down => "pointerdown",
1339 TouchEventType::Move => "pointermove",
1340 TouchEventType::Up => "pointerup",
1341 TouchEventType::Cancel => "pointercancel",
1342 };
1343
1344 let pointer_type = match event.pointer_type {
1346 TouchPointerType::Pen => "pen",
1347 TouchPointerType::Touch => "touch",
1348 };
1349
1350 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1352 let is_primary = self.is_primary_pointer(pointer_id);
1353
1354 if matches!(event.event_type, TouchEventType::Down) {
1357 let pointer_over = pointer_touch.to_pointer_event(
1359 cx,
1360 window,
1361 "pointerover",
1362 pointer_id,
1363 is_primary,
1364 pointer_type,
1365 input_event.active_keyboard_modifiers,
1366 true, Some(hit_test_result.point_in_node),
1368 );
1369 pointer_over.upcast::<Event>().fire(cx, ¤t_target);
1370
1371 self.fire_pointer_event_for_touch(
1373 cx,
1374 &element,
1375 &pointer_touch,
1376 pointer_id,
1377 "pointerenter",
1378 is_primary,
1379 pointer_type,
1380 input_event,
1381 &hit_test_result,
1382 );
1383 }
1384
1385 let released_disconnected = if matches!(event.event_type, TouchEventType::Cancel) {
1389 false
1390 } else {
1391 self.release_disconnected_pointer_capture(cx, pointer_id, pointer_type, is_primary)
1392 };
1393
1394 let pointer_target = self
1396 .get_pointer_capture_target(pointer_id)
1397 .map(DomRoot::upcast::<EventTarget>)
1398 .unwrap_or_else(|| current_target.clone());
1399
1400 let pointer_event = pointer_touch.to_pointer_event(
1401 cx,
1402 window,
1403 pointer_event_name,
1404 pointer_id,
1405 is_primary,
1406 pointer_type,
1407 input_event.active_keyboard_modifiers,
1408 event.is_cancelable(),
1409 Some(hit_test_result.point_in_node),
1410 );
1411 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1412
1413 if !released_disconnected && !matches!(event.event_type, TouchEventType::Cancel) {
1419 self.process_pending_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1420 }
1421
1422 if matches!(
1425 event.event_type,
1426 TouchEventType::Up | TouchEventType::Cancel
1427 ) {
1428 self.implicit_release_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1429 }
1430
1431 if matches!(
1434 event.event_type,
1435 TouchEventType::Up | TouchEventType::Cancel
1436 ) {
1437 let pointer_out = pointer_touch.to_pointer_event(
1439 cx,
1440 window,
1441 "pointerout",
1442 pointer_id,
1443 is_primary,
1444 pointer_type,
1445 input_event.active_keyboard_modifiers,
1446 true, Some(hit_test_result.point_in_node),
1448 );
1449 pointer_out.upcast::<Event>().fire(cx, ¤t_target);
1450
1451 self.fire_pointer_event_for_touch(
1453 cx,
1454 &element,
1455 &pointer_touch,
1456 pointer_id,
1457 "pointerleave",
1458 is_primary,
1459 pointer_type,
1460 input_event,
1461 &hit_test_result,
1462 );
1463 }
1464
1465 let (touch_dispatch_target, changed_touch) = match event.event_type {
1466 TouchEventType::Down => {
1467 self.active_touch_points
1469 .safe_borrow_mut(cx.no_gc())
1470 .push(Dom::from_ref(&*pointer_touch));
1471 self.set_active_element(&element);
1472 (current_target, pointer_touch)
1473 },
1474 _ => {
1475 let active_touch_points = self.active_touch_points.borrow();
1481 let Some(index) = active_touch_points
1482 .iter()
1483 .position(|point| point.Identifier() == identifier)
1484 else {
1485 warn!("No active touch point for {:?}", event.event_type);
1486 return Default::default();
1487 };
1488 let original_target = active_touch_points[index].Target();
1490 drop(active_touch_points);
1491
1492 let touch_with_touchstart_target = Touch::new(
1493 cx,
1494 window,
1495 identifier,
1496 &original_target,
1497 client_x,
1498 client_y,
1499 client_x,
1500 client_y,
1501 page_x,
1502 page_y,
1503 );
1504
1505 let mut active_touch_points = self.active_touch_points.safe_borrow_mut(cx.no_gc());
1506 match event.event_type {
1508 TouchEventType::Move => {
1509 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1510 },
1511 TouchEventType::Up | TouchEventType::Cancel => {
1512 active_touch_points.swap_remove(index);
1513 self.remove_pointer_id_for_touch(identifier);
1514 self.unset_active_element();
1515 },
1516 TouchEventType::Down => unreachable!("Should have been handled above"),
1517 }
1518 (original_target, touch_with_touchstart_target)
1519 },
1520 };
1521
1522 rooted_vec!(let mut target_touches);
1523 target_touches.extend(
1524 self.active_touch_points
1525 .borrow()
1526 .iter()
1527 .filter(|touch| touch.Target() == touch_dispatch_target)
1528 .cloned(),
1529 );
1530
1531 let event_name = match event.event_type {
1532 TouchEventType::Down => "touchstart",
1533 TouchEventType::Move => "touchmove",
1534 TouchEventType::Up => "touchend",
1535 TouchEventType::Cancel => "touchcancel",
1536 };
1537
1538 let touches = TouchList::new(cx, window, self.active_touch_points.borrow().r());
1539 let changed_touches = TouchList::new(cx, window, from_ref(&&*changed_touch));
1540 let target_touches = TouchList::new(cx, window, target_touches.r());
1541
1542 let touch_event = TouchEvent::new(
1543 cx,
1544 window,
1545 event_name.into(),
1546 EventBubbles::Bubbles,
1547 EventCancelable::from(event.is_cancelable()),
1548 EventComposed::Composed,
1549 Some(window),
1550 0i32,
1551 &touches,
1552 &changed_touches,
1553 &target_touches,
1554 false,
1556 false,
1557 false,
1558 false,
1559 );
1560 let event = touch_event.upcast::<Event>();
1561 event.fire(cx, &touch_dispatch_target);
1562 event.flags().into()
1563 }
1564
1565 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1574 match event.event_type {
1575 TouchEventType::Down | TouchEventType::Move => {},
1576 TouchEventType::Up | TouchEventType::Cancel => {
1577 let mut active_touch_points = self.active_touch_points.borrow_mut();
1578 if let Some(index) = active_touch_points
1579 .iter()
1580 .position(|t| t.Identifier() == event.touch_id.0)
1581 {
1582 active_touch_points.swap_remove(index);
1583 self.remove_pointer_id_for_touch(event.touch_id.0);
1584 } else {
1585 warn!(
1586 "Received {:?} for a non-active touch point {}",
1587 event.event_type, event.touch_id.0
1588 );
1589 }
1590 },
1591 }
1592 }
1593
1594 fn handle_keyboard_event(
1596 &self,
1597 cx: &mut JSContext,
1598 keyboard_event: EmbedderKeyboardEvent,
1599 ) -> InputEventResult {
1600 let target = &self.target_for_events_following_focus();
1601 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1602 cx,
1603 &self.window,
1604 keyboard_event.event.state.event_type().into(),
1605 &keyboard_event.event,
1606 );
1607
1608 let event = keyevent.upcast::<Event>();
1609
1610 event.set_composed(true);
1611
1612 event.fire(cx, target);
1613
1614 let mut flags = event.flags();
1615 if flags.contains(EventFlags::Canceled) {
1616 return flags.into();
1617 }
1618
1619 let is_character_value_key = matches!(
1625 keyboard_event.event.key,
1626 Key::Character(_) | Key::Named(NamedKey::Enter)
1627 );
1628 if keyboard_event.event.state == KeyState::Down &&
1629 is_character_value_key &&
1630 !keyboard_event.event.is_composing
1631 {
1632 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1634 cx,
1635 &self.window,
1636 atom!("keypress"),
1637 &keyboard_event.event,
1638 );
1639 keypress_event.upcast::<Event>().set_composed(true);
1640 let event = keypress_event.upcast::<Event>();
1641 event.fire(cx, target);
1642 flags = event.flags();
1643 }
1644
1645 flags.into()
1646 }
1647
1648 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1649 let document = self.window.Document();
1650 let composition_event = match event {
1651 ImeEvent::Dismissed => {
1652 document.focus_handler().focus(cx, &FocusableArea::Viewport);
1653 return Default::default();
1654 },
1655 ImeEvent::Composition(composition_event) => composition_event,
1656 };
1657
1658 let focused_area = document.focus_handler().focused_area();
1663 let Some(focused_element) = focused_area.element() else {
1664 return Default::default();
1666 };
1667
1668 let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1669 let event = CompositionEvent::new(
1670 cx,
1671 &self.window,
1672 composition_event.state.event_type().into(),
1673 true,
1674 cancelable,
1675 Some(&self.window),
1676 0,
1677 DOMString::from(composition_event.data),
1678 );
1679
1680 let event = event.upcast::<Event>();
1681 event.fire(cx, focused_element.upcast());
1682 event.flags().into()
1683 }
1684
1685 fn handle_wheel_event(
1686 &self,
1687 cx: &mut JSContext,
1688 event: EmbedderWheelEvent,
1689 input_event: &ConstellationInputEvent,
1690 ) -> InputEventResult {
1691 let flags = HitTestFlags::empty();
1693 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1694 else {
1695 return Default::default();
1696 };
1697
1698 let Some(el) = hit_test_result
1699 .node
1700 .inclusive_ancestors(ShadowIncluding::Yes)
1701 .find_map(DomRoot::downcast::<Element>)
1702 else {
1703 return Default::default();
1704 };
1705
1706 let node = el.upcast::<Node>();
1707 debug!(
1708 "wheel: on {:?} at {:?}",
1709 node.debug_str(),
1710 hit_test_result.point_in_frame
1711 );
1712
1713 let event_type = "wheel".into();
1714
1715 let cancelable = EventCancelable::from(
1716 self.window
1717 .upcast::<EventTarget>()
1718 .has_non_passive_listener(&event_type) ||
1719 node.inclusive_ancestors(ShadowIncluding::Yes)
1720 .any(|target| {
1721 target
1722 .upcast::<EventTarget>()
1723 .has_non_passive_listener(&event_type)
1724 }),
1725 );
1726 let dom_event = WheelEvent::new(
1728 cx,
1729 &self.window,
1730 event_type,
1731 EventBubbles::Bubbles,
1732 cancelable,
1733 Some(&self.window),
1734 0i32,
1735 hit_test_result.point_in_frame.to_i32(),
1736 hit_test_result.point_in_frame.to_i32(),
1737 hit_test_result
1738 .point_relative_to_initial_containing_block
1739 .to_i32(),
1740 input_event.active_keyboard_modifiers,
1741 MouseButton::Primary,
1742 input_event.pressed_mouse_buttons,
1743 None,
1744 None,
1745 Finite::wrap(-event.delta.x),
1750 Finite::wrap(-event.delta.y),
1751 Finite::wrap(-event.delta.z),
1752 event.delta.mode as u32,
1753 );
1754
1755 let dom_event = dom_event.upcast::<Event>();
1756 dom_event.set_trusted(true);
1757 dom_event.set_composed(true);
1758 dom_event.fire(cx, node.upcast());
1759
1760 dom_event.flags().into()
1761 }
1762
1763 #[cfg(feature = "gamepad")]
1764 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1765 match gamepad_event {
1766 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1767 self.handle_gamepad_connect(
1768 index.0,
1769 name,
1770 bounds.axis_bounds,
1771 bounds.button_bounds,
1772 supported_haptic_effects,
1773 );
1774 },
1775 EmbedderGamepadEvent::Disconnected(index) => {
1776 self.handle_gamepad_disconnect(index.0);
1777 },
1778 EmbedderGamepadEvent::Updated(index, update_type) => {
1779 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1780 },
1781 };
1782 }
1783
1784 #[cfg(feature = "gamepad")]
1786 fn handle_gamepad_connect(
1787 &self,
1788 _index: usize,
1792 name: String,
1793 axis_bounds: (f64, f64),
1794 button_bounds: (f64, f64),
1795 supported_haptic_effects: GamepadSupportedHapticEffects,
1796 ) {
1797 let doc = self.window.Document();
1799
1800 if !doc.allowed_to_use_feature(PermissionName::Gamepad) {
1803 return;
1804 }
1805
1806 let trusted_window = Trusted::new(&*self.window);
1807
1808 self.window
1811 .upcast::<GlobalScope>()
1812 .task_manager()
1813 .gamepad_task_source()
1814 .queue(task!(gamepad_connected: move |cx| {
1815 let window = trusted_window.root();
1816
1817 let navigator = window.Navigator(cx);
1820 let selected_index = navigator.select_gamepad_index();
1821 let gamepad = Gamepad::new(
1822 cx,
1823 &window,
1824 selected_index,
1825 name,
1826 "standard".into(),
1827 axis_bounds,
1828 button_bounds,
1829 supported_haptic_effects,
1830 false,
1831 );
1832
1833 navigator.set_gamepad(selected_index as usize, Some(&gamepad));
1835
1836 if navigator.has_gamepad_gesture() {
1838 gamepad.set_exposed(true);
1840 if window.Document().is_fully_active() {
1845 gamepad.notify_event(cx, GamepadEventType::Connected);
1846 }
1847 }
1848 }));
1849 }
1850
1851 #[cfg(feature = "gamepad")]
1853 fn handle_gamepad_disconnect(&self, index: usize) {
1854 let trusted_window = Trusted::new(&*self.window);
1858 self.window
1859 .upcast::<GlobalScope>()
1860 .task_manager()
1861 .gamepad_task_source()
1862 .queue(task!(gamepad_disconnected: move |cx| {
1863 let window = trusted_window.root();
1864 let navigator = window.Navigator(cx);
1865
1866 if let Some(gamepad) = navigator.get_gamepad(index) {
1867 gamepad.update_connected(false);
1869 if gamepad.exposed() && window.Document().is_fully_active() {
1877 gamepad.notify_event(cx, GamepadEventType::Disconnected);
1878 }
1879 }
1880
1881 navigator.set_gamepad(index, None);
1885 navigator.shrink_gamepads_list();
1888 }));
1889 }
1890
1891 #[cfg(feature = "gamepad")]
1893 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1894 let trusted_window = Trusted::new(&*self.window);
1897
1898 self.window
1901 .upcast::<GlobalScope>()
1902 .task_manager()
1903 .gamepad_task_source()
1904 .queue(task!(update_gamepad_state: move |cx| {
1905 let window = trusted_window.root();
1906 let document = window.Document();
1907 document.event_handler().update_gamepad_state(cx, index, update_type);
1908 }));
1909 }
1910
1911 #[cfg(feature = "gamepad")]
1913 fn update_gamepad_state(
1914 &self,
1915 cx: &mut JSContext,
1916 gamepad_index: usize,
1917 update_type: GamepadUpdateType,
1918 ) {
1919 use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
1920 let now = *self.window.Performance(cx).Now();
1923
1924 let navigator = self.window.Navigator(cx);
1927
1928 if let Some(gamepad) = navigator.get_gamepad(gamepad_index) {
1929 gamepad.update_timestamp(now);
1931 match update_type {
1934 GamepadUpdateType::Axis(axis_index, value) => {
1935 gamepad.map_and_normalize_axes(axis_index, value);
1936 },
1937 GamepadUpdateType::Button(button_index, value) => {
1938 gamepad.map_and_normalize_buttons(button_index, value);
1939 },
1940 };
1941 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1946 navigator.set_has_gamepad_gesture(true);
1948 navigator
1950 .get_connected_gamepad()
1951 .iter()
1952 .for_each(|connected_gamepad| {
1953 connected_gamepad.set_exposed(true);
1955 connected_gamepad.update_timestamp(now);
1957 let trusted_gamepad = Trusted::new(&**connected_gamepad);
1964 if self.window.Document().is_fully_active() {
1965 self.window
1966 .upcast::<GlobalScope>()
1967 .task_manager()
1968 .gamepad_task_source()
1969 .queue(task!(fire_gamepad_connected: move |cx| {
1970 let gamepad = trusted_gamepad.root();
1971 gamepad.notify_event(cx, GamepadEventType::Connected);
1972 }));
1973 }
1974 });
1975 }
1976 }
1977 }
1978
1979 #[expect(unsafe_code)]
1981 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
1982 let document = self.window.Document();
1984 if scrolled_node.is_root() {
1985 document.handle_viewport_scroll_event();
1986 } else {
1987 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
1991 let node = unsafe {
1992 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
1993 };
1994 let Some(element) = node
1995 .inclusive_ancestors(ShadowIncluding::Yes)
1996 .find_map(DomRoot::downcast::<Element>)
1997 else {
1998 return;
1999 };
2000
2001 element.handle_scroll_event();
2002 }
2003 }
2004
2005 pub(crate) fn maybe_dispatch_simulated_click(
2011 &self,
2012 cx: &mut JSContext,
2013 node: &Node,
2014 event: &KeyboardEvent,
2015 ) -> bool {
2016 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
2017 {
2018 return false;
2019 }
2020
2021 if node
2025 .downcast::<Element>()
2026 .and_then(Element::as_maybe_activatable)
2027 .is_none()
2028 {
2029 return false;
2030 }
2031
2032 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
2033 true
2034 }
2035
2036 pub(crate) fn run_default_keyboard_event_handler(
2037 &self,
2038 cx: &mut js::context::JSContext,
2039 node: &Node,
2040 event: &KeyboardEvent,
2041 ) {
2042 if event.upcast::<Event>().type_() != atom!("keydown") {
2043 return;
2044 }
2045
2046 let document = self.window.Document();
2047 if document.maybe_perform_editing_command(cx, event) {
2048 return;
2049 }
2050
2051 if self.maybe_dispatch_simulated_click(cx, node, event) {
2052 return;
2053 }
2054
2055 if self.maybe_handle_accesskey(cx, event) {
2056 return;
2057 }
2058
2059 let mut is_space = false;
2060 let scroll = match event.key() {
2061 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
2062 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
2063 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
2064 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
2065 Key::Named(NamedKey::End) => KeyboardScroll::End,
2066 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
2067 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
2068 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
2069 Key::Character(string) if &string == " " => {
2070 is_space = true;
2071 if event.modifiers().contains(Modifiers::SHIFT) {
2072 KeyboardScroll::PageUp
2073 } else {
2074 KeyboardScroll::PageDown
2075 }
2076 },
2077 Key::Named(NamedKey::Tab) => {
2078 document
2084 .focus_handler()
2085 .sequential_focus_navigation_via_keyboard_event(cx, event);
2086 return;
2087 },
2088 _ => return,
2089 };
2090
2091 if !event.modifiers().is_empty() && !is_space {
2092 return;
2093 }
2094
2095 self.do_keyboard_scroll(cx, scroll);
2096 }
2097
2098 pub(crate) fn do_keyboard_scroll(&self, cx: &mut JSContext, scroll: KeyboardScroll) {
2099 let scroll_axis = match scroll {
2100 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2101 _ => ScrollingBoxAxis::Y,
2102 };
2103
2104 let document = self.window.Document();
2105 let mut scrolling_box = document
2106 .focus_handler()
2107 .focused_area()
2108 .element()
2109 .or(self.most_recently_clicked_element.get().as_deref())
2110 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2111 .unwrap_or_else(|| {
2112 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2113 });
2114
2115 while !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2116 if scrolling_box.is_viewport() {
2118 break;
2119 }
2120 let parent = scrolling_box.parent().unwrap_or_else(|| {
2121 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2122 });
2123 scrolling_box = parent;
2124 }
2125
2126 let calculate_current_scroll_offset_and_delta = || {
2127 const LINE_HEIGHT: f32 = 76.0;
2128 const LINE_WIDTH: f32 = 76.0;
2129
2130 let current_scroll_offset = scrolling_box.scroll_position();
2131 (
2132 current_scroll_offset,
2133 match scroll {
2134 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2135 KeyboardScroll::End => Vector2D::new(
2136 0.0,
2137 -current_scroll_offset.y + scrolling_box.content_size().height -
2138 scrolling_box.size(cx.no_gc()).height,
2139 ),
2140 KeyboardScroll::PageDown => Vector2D::new(
2141 0.0,
2142 scrolling_box.size(cx.no_gc()).height - 2.0 * LINE_HEIGHT,
2143 ),
2144 KeyboardScroll::PageUp => Vector2D::new(
2145 0.0,
2146 2.0 * LINE_HEIGHT - scrolling_box.size(cx.no_gc()).height,
2147 ),
2148 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2149 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2150 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2151 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2152 },
2153 )
2154 };
2155
2156 let parent_pipeline = self.window.parent_info();
2160 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2161 let (_, delta) = calculate_current_scroll_offset_and_delta();
2162 self.window
2163 .paint_api()
2164 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2165 }
2166
2167 if !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2170 assert!(scrolling_box.is_viewport());
2171
2172 let window_proxy = document.window().window_proxy();
2173 if let Some(iframe) = window_proxy.frame_element() {
2174 let iframe_window = iframe.owner_window();
2177 let mut realm = enter_auto_realm(cx, &*iframe_window);
2178 let cx = &mut realm;
2179 iframe_window
2180 .Document()
2181 .event_handler()
2182 .do_keyboard_scroll(cx, scroll);
2183 } else if let Some(parent_pipeline) = parent_pipeline {
2184 document.window().send_to_constellation(
2188 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2189 );
2190 };
2191 return;
2192 }
2193
2194 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2195 scrolling_box.scroll_to(cx, delta + current_scroll_offset, ScrollBehavior::Auto);
2196 }
2197
2198 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2201 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2202
2203 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2204 return pointer_id;
2205 }
2206
2207 let pointer_id = self.next_touch_pointer_id.get();
2208 active_pointer_ids.insert(touch_id, pointer_id);
2209 self.next_touch_pointer_id.set(pointer_id + 1);
2210 pointer_id
2211 }
2212
2213 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2215 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2216 }
2217
2218 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2221 self.active_pointer_ids
2224 .borrow()
2225 .values()
2226 .min()
2227 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2228 }
2229
2230 #[allow(clippy::too_many_arguments)]
2234 fn fire_pointer_event_for_touch(
2235 &self,
2236 cx: &mut js::context::JSContext,
2237 target_element: &Element,
2238 touch: &Touch,
2239 pointer_id: i32,
2240 event_name: &str,
2241 is_primary: bool,
2242 pointer_type: &str,
2243 input_event: &ConstellationInputEvent,
2244 hit_test_result: &HitTestResult,
2245 ) {
2246 let mut targets: Vec<_> = target_element
2247 .upcast::<Node>()
2248 .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
2249 .map(|node| node.as_rooted())
2250 .collect();
2251
2252 if event_name == "pointerenter" {
2254 targets.reverse();
2255 }
2256
2257 for target in targets {
2258 let pointer_event = touch.to_pointer_event(
2259 cx,
2260 &self.window,
2261 event_name,
2262 pointer_id,
2263 is_primary,
2264 pointer_type,
2265 input_event.active_keyboard_modifiers,
2266 false,
2267 Some(hit_test_result.point_in_node),
2268 );
2269 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2270 }
2271 }
2272
2273 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2274 self.access_key_handlers
2275 .borrow()
2276 .values()
2277 .any(|value| &**value == element)
2278 }
2279
2280 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2281 self.access_key_handlers
2282 .borrow_mut()
2283 .retain(|_, value| &**value != element)
2284 }
2285
2286 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2287 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2288 access_key_handlers
2290 .entry(code.into())
2291 .or_insert(Dom::from_ref(element));
2292 }
2293
2294 fn maybe_handle_accesskey(
2295 &self,
2296 cx: &mut js::context::JSContext,
2297 event: &KeyboardEvent,
2298 ) -> bool {
2299 #[cfg(target_os = "macos")]
2300 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2301 #[cfg(not(target_os = "macos"))]
2302 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2303
2304 if event.modifiers() != access_key_modifiers {
2305 return false;
2306 }
2307
2308 let Ok(code) = Code::from_str(&event.Code().str()) else {
2309 return false;
2310 };
2311
2312 let Some(html_element) = self
2313 .access_key_handlers
2314 .borrow()
2315 .get(&code.into())
2316 .map(|html_element| html_element.as_rooted())
2317 else {
2318 return false;
2319 };
2320
2321 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2329 return false;
2330 };
2331
2332 if command.disabled() || command.hidden() {
2333 return false;
2334 }
2335
2336 let node = html_element.upcast::<Node>();
2337 if !node.is_connected() {
2338 return false;
2339 }
2340
2341 for node in node.inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::Yes) {
2342 if node
2343 .downcast::<HTMLElement>()
2344 .is_some_and(|html_element| html_element.Hidden())
2345 {
2346 return false;
2347 }
2348 }
2349
2350 self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2353 command.perform_action(cx);
2354 true
2355 }
2356
2357 pub(crate) fn focus_and_scroll_to_element_for_key_event(
2358 &self,
2359 cx: &mut JSContext,
2360 element: &Element,
2361 ) {
2362 element
2363 .upcast::<Node>()
2364 .run_the_focusing_steps(cx, None, FocusTrigger::Other);
2365 let scroll_axis = ScrollAxisState {
2366 position: ScrollLogicalPosition::Center,
2367 requirement: ScrollRequirement::IfNotVisible,
2368 };
2369 element.scroll_into_view_with_options(
2370 cx,
2371 ScrollBehavior::Auto,
2372 scroll_axis,
2373 scroll_axis,
2374 None,
2375 None,
2376 );
2377 }
2378
2379 pub(crate) fn is_active_pointer(&self, pointer_id: i32) -> bool {
2382 if pointer_id == PointerId::Mouse as i32 {
2383 !self.mouse_button_state.get().is_empty()
2385 } else {
2386 self.active_pointer_ids
2388 .borrow()
2389 .values()
2390 .any(|&id| id == pointer_id)
2391 }
2392 }
2393
2394 pub(crate) fn set_pending_pointer_capture(&self, pointer_id: i32, element: &Element) {
2397 self.pending_pointer_capture
2398 .borrow_mut()
2399 .insert(pointer_id, Dom::from_ref(element));
2400 }
2401
2402 pub(crate) fn clear_pending_pointer_capture(&self, pointer_id: i32) {
2405 self.pending_pointer_capture
2406 .borrow_mut()
2407 .remove(&pointer_id);
2408 }
2409
2410 pub(crate) fn has_pointer_capture(&self, pointer_id: i32, element: &Element) -> bool {
2413 self.pending_pointer_capture
2414 .borrow()
2415 .get(&pointer_id)
2416 .is_some_and(|el| &**el == element)
2417 }
2418
2419 fn get_pointer_capture_target(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2424 self.pointer_capture_target
2425 .borrow()
2426 .get(&pointer_id)
2427 .map(|el| DomRoot::from_ref(&**el))
2428 .filter(|el| el.upcast::<Node>().is_connected())
2429 }
2430
2431 fn release_disconnected_pointer_capture(
2437 &self,
2438 cx: &mut JSContext,
2439 pointer_id: i32,
2440 pointer_type: &str,
2441 is_primary: bool,
2442 ) -> bool {
2443 let capture_target = self
2444 .pointer_capture_target
2445 .borrow()
2446 .get(&pointer_id)
2447 .map(|el| DomRoot::from_ref(&**el));
2448 if let Some(capture_element) = capture_target &&
2449 !capture_element.upcast::<Node>().is_connected()
2450 {
2451 let document = self.window.Document();
2453 self.fire_pointer_capture_event_at_target(
2454 cx,
2455 "lostpointercapture",
2456 pointer_id,
2457 pointer_type,
2458 is_primary,
2459 document.upcast::<EventTarget>(),
2460 );
2461 self.pending_pointer_capture
2463 .safe_borrow_mut(cx.no_gc())
2464 .remove(&pointer_id);
2465 self.pointer_capture_target
2466 .safe_borrow_mut(cx.no_gc())
2467 .remove(&pointer_id);
2468 return true;
2469 }
2470 false
2471 }
2472
2473 fn fire_pointer_capture_event(
2476 &self,
2477 cx: &mut JSContext,
2478 event_type: &str,
2479 pointer_id: i32,
2480 pointer_type: &str,
2481 is_primary: bool,
2482 target: &Element,
2483 ) {
2484 self.fire_pointer_capture_event_at_target(
2485 cx,
2486 event_type,
2487 pointer_id,
2488 pointer_type,
2489 is_primary,
2490 target.upcast::<EventTarget>(),
2491 );
2492 }
2493
2494 fn fire_pointer_capture_event_at_target(
2496 &self,
2497 cx: &mut JSContext,
2498 event_type: &str,
2499 pointer_id: i32,
2500 pointer_type: &str,
2501 is_primary: bool,
2502 target: &EventTarget,
2503 ) {
2504 let pointer_event = PointerEvent::new(
2505 cx,
2506 &self.window,
2507 event_type.into(),
2508 EventBubbles::Bubbles,
2509 EventCancelable::NotCancelable,
2510 Some(&self.window),
2511 0,
2512 Point2D::new(0, 0),
2513 Point2D::new(0, 0),
2514 Point2D::new(0, 0),
2515 Modifiers::empty(),
2516 MouseButton::Primary,
2517 MouseButtons::empty(),
2518 None,
2519 None,
2520 pointer_id,
2521 1,
2522 1,
2523 0.0,
2524 0.0,
2525 0,
2526 0,
2527 0,
2528 PI / 2.0,
2529 0.0,
2530 DOMString::from(pointer_type),
2531 is_primary,
2532 vec![],
2533 vec![],
2534 );
2535 pointer_event.upcast::<Event>().set_composed(true);
2536 pointer_event.upcast::<Event>().fire(cx, target);
2537 }
2538
2539 #[expect(clippy::too_many_arguments)]
2543 fn fire_pointer_boundary_event(
2544 &self,
2545 cx: &mut JSContext,
2546 event_type: &str,
2547 bubbles: EventBubbles,
2548 pointer_id: i32,
2549 pointer_type: &str,
2550 is_primary: bool,
2551 target: &Element,
2552 related_target: Option<&Element>,
2553 ) {
2554 let pointer_event = PointerEvent::new(
2555 cx,
2556 &self.window,
2557 event_type.into(),
2558 bubbles,
2559 EventCancelable::NotCancelable,
2560 Some(&self.window),
2561 0,
2562 Point2D::new(0, 0),
2563 Point2D::new(0, 0),
2564 Point2D::new(0, 0),
2565 Modifiers::empty(),
2566 MouseButton::None,
2567 self.mouse_button_state.get(),
2568 related_target.map(|el| el.upcast::<EventTarget>()),
2569 None,
2570 pointer_id,
2571 1,
2572 1,
2573 0.0,
2574 0.0,
2575 0,
2576 0,
2577 0,
2578 PI / 2.0,
2579 0.0,
2580 DOMString::from(pointer_type),
2581 is_primary,
2582 vec![],
2583 vec![],
2584 );
2585 pointer_event.upcast::<Event>().set_composed(true);
2586 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2587 }
2588
2589 fn fire_pointer_capture_boundary_transition(
2594 &self,
2595 cx: &mut JSContext,
2596 pointer_id: i32,
2597 pointer_type: &str,
2598 is_primary: bool,
2599 old_target: &Element,
2600 new_target: &Element,
2601 ) {
2602 if pointer_type != "mouse" {
2603 return;
2604 }
2605 if old_target == new_target {
2606 return;
2607 }
2608 self.fire_pointer_boundary_event(
2609 cx,
2610 "pointerout",
2611 EventBubbles::Bubbles,
2612 pointer_id,
2613 pointer_type,
2614 is_primary,
2615 old_target,
2616 Some(new_target),
2617 );
2618 self.fire_pointer_boundary_event(
2619 cx,
2620 "pointerleave",
2621 EventBubbles::DoesNotBubble,
2622 pointer_id,
2623 pointer_type,
2624 is_primary,
2625 old_target,
2626 Some(new_target),
2627 );
2628 self.fire_pointer_boundary_event(
2629 cx,
2630 "pointerover",
2631 EventBubbles::Bubbles,
2632 pointer_id,
2633 pointer_type,
2634 is_primary,
2635 new_target,
2636 Some(old_target),
2637 );
2638 self.fire_pointer_boundary_event(
2639 cx,
2640 "pointerenter",
2641 EventBubbles::DoesNotBubble,
2642 pointer_id,
2643 pointer_type,
2644 is_primary,
2645 new_target,
2646 Some(old_target),
2647 );
2648 }
2649
2650 fn implicit_release_pointer_capture(
2653 &self,
2654 cx: &mut JSContext,
2655 pointer_id: i32,
2656 pointer_type: &str,
2657 is_primary: bool,
2658 ) {
2659 let capture_element = self
2660 .pointer_capture_target
2661 .borrow()
2662 .get(&pointer_id)
2663 .map(|el| DomRoot::from_ref(&**el));
2664 if let Some(capture_element) = capture_element {
2665 if capture_element.upcast::<Node>().is_connected() {
2666 self.fire_pointer_capture_event(
2667 cx,
2668 "lostpointercapture",
2669 pointer_id,
2670 pointer_type,
2671 is_primary,
2672 &capture_element,
2673 );
2674 if let Some(hover_target) = self.current_hover_target.get() {
2677 self.fire_pointer_capture_boundary_transition(
2678 cx,
2679 pointer_id,
2680 pointer_type,
2681 is_primary,
2682 &capture_element,
2683 &hover_target,
2684 );
2685 }
2686 } else {
2687 let document = self.window.Document();
2688 self.fire_pointer_capture_event_at_target(
2689 cx,
2690 "lostpointercapture",
2691 pointer_id,
2692 pointer_type,
2693 is_primary,
2694 document.upcast::<EventTarget>(),
2695 );
2696 }
2697 }
2698 self.pending_pointer_capture
2699 .safe_borrow_mut(cx.no_gc())
2700 .remove(&pointer_id);
2701 self.pointer_capture_target
2702 .safe_borrow_mut(cx.no_gc())
2703 .remove(&pointer_id);
2704 }
2705
2706 fn process_pending_pointer_capture(
2710 &self,
2711 cx: &mut JSContext,
2712 pointer_id: i32,
2713 pointer_type: &str,
2714 is_primary: bool,
2715 ) {
2716 let pending = self
2717 .pending_pointer_capture
2718 .borrow()
2719 .get(&pointer_id)
2720 .map(|el| DomRoot::from_ref(&**el));
2721 let current = self
2722 .pointer_capture_target
2723 .borrow()
2724 .get(&pointer_id)
2725 .map(|el| DomRoot::from_ref(&**el));
2726
2727 let pending_connected = pending
2730 .as_ref()
2731 .is_some_and(|el| el.upcast::<Node>().is_connected());
2732
2733 let pointer_is_active = self.is_active_pointer(pointer_id);
2736
2737 match (&pending, ¤t) {
2738 (Some(pending_el), None) if pending_connected => {
2739 if pointer_is_active {
2740 if let Some(hover_target) = self.current_hover_target.get() {
2743 self.fire_pointer_capture_boundary_transition(
2744 cx,
2745 pointer_id,
2746 pointer_type,
2747 is_primary,
2748 &hover_target,
2749 pending_el,
2750 );
2751 }
2752 self.fire_pointer_capture_event(
2753 cx,
2754 "gotpointercapture",
2755 pointer_id,
2756 pointer_type,
2757 is_primary,
2758 pending_el,
2759 );
2760 self.pointer_capture_target
2761 .safe_borrow_mut(cx.no_gc())
2762 .insert(pointer_id, Dom::from_ref(pending_el));
2763 } else {
2764 self.pending_pointer_capture
2765 .safe_borrow_mut(cx.no_gc())
2766 .remove(&pointer_id);
2767 }
2768 },
2769 (Some(pending_el), Some(current_el))
2770 if pending_connected && pending_el != current_el =>
2771 {
2772 self.fire_pointer_capture_event(
2773 cx,
2774 "lostpointercapture",
2775 pointer_id,
2776 pointer_type,
2777 is_primary,
2778 current_el,
2779 );
2780 if pointer_is_active {
2781 self.fire_pointer_capture_boundary_transition(
2782 cx,
2783 pointer_id,
2784 pointer_type,
2785 is_primary,
2786 current_el,
2787 pending_el,
2788 );
2789 self.fire_pointer_capture_event(
2790 cx,
2791 "gotpointercapture",
2792 pointer_id,
2793 pointer_type,
2794 is_primary,
2795 pending_el,
2796 );
2797 self.pointer_capture_target
2798 .safe_borrow_mut(cx.no_gc())
2799 .insert(pointer_id, Dom::from_ref(pending_el));
2800 } else {
2801 self.pending_pointer_capture
2802 .safe_borrow_mut(cx.no_gc())
2803 .remove(&pointer_id);
2804 self.pointer_capture_target
2805 .safe_borrow_mut(cx.no_gc())
2806 .remove(&pointer_id);
2807 }
2808 },
2809 (None, Some(current_el)) | (Some(_), Some(current_el)) if !pending_connected => {
2810 self.fire_pointer_capture_event(
2811 cx,
2812 "lostpointercapture",
2813 pointer_id,
2814 pointer_type,
2815 is_primary,
2816 current_el,
2817 );
2818 if let Some(hover_target) = self.current_hover_target.get() {
2821 self.fire_pointer_capture_boundary_transition(
2822 cx,
2823 pointer_id,
2824 pointer_type,
2825 is_primary,
2826 current_el,
2827 &hover_target,
2828 );
2829 }
2830 self.pointer_capture_target
2831 .safe_borrow_mut(cx.no_gc())
2832 .remove(&pointer_id);
2833 if !pending_connected {
2834 self.pending_pointer_capture
2835 .safe_borrow_mut(cx.no_gc())
2836 .remove(&pointer_id);
2837 }
2838 },
2839 _ => {},
2840 }
2841 }
2842
2843 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
2844 pub(crate) fn install_drag_gesture(&self, drag_gesture: DragGesture) {
2845 *self.drag_gesture.borrow_mut() = Some(drag_gesture);
2846 }
2847}
2848
2849pub(crate) fn character_to_code(character: char) -> Option<Code> {
2850 Some(match character.to_ascii_lowercase() {
2851 '`' => Code::Backquote,
2852 '\\' => Code::Backslash,
2853 '[' | '{' => Code::BracketLeft,
2854 ']' | '}' => Code::BracketRight,
2855 ',' | '<' => Code::Comma,
2856 '0' => Code::Digit0,
2857 '1' => Code::Digit1,
2858 '2' => Code::Digit2,
2859 '3' => Code::Digit3,
2860 '4' => Code::Digit4,
2861 '5' => Code::Digit5,
2862 '6' => Code::Digit6,
2863 '7' => Code::Digit7,
2864 '8' => Code::Digit8,
2865 '9' => Code::Digit9,
2866 '=' => Code::Equal,
2867 'a' => Code::KeyA,
2868 'b' => Code::KeyB,
2869 'c' => Code::KeyC,
2870 'd' => Code::KeyD,
2871 'e' => Code::KeyE,
2872 'f' => Code::KeyF,
2873 'g' => Code::KeyG,
2874 'h' => Code::KeyH,
2875 'i' => Code::KeyI,
2876 'j' => Code::KeyJ,
2877 'k' => Code::KeyK,
2878 'l' => Code::KeyL,
2879 'm' => Code::KeyM,
2880 'n' => Code::KeyN,
2881 'o' => Code::KeyO,
2882 'p' => Code::KeyP,
2883 'q' => Code::KeyQ,
2884 'r' => Code::KeyR,
2885 's' => Code::KeyS,
2886 't' => Code::KeyT,
2887 'u' => Code::KeyU,
2888 'v' => Code::KeyV,
2889 'w' => Code::KeyW,
2890 'x' => Code::KeyX,
2891 'y' => Code::KeyY,
2892 'z' => Code::KeyZ,
2893 '-' => Code::Minus,
2894 '.' => Code::Period,
2895 '\'' | '"' => Code::Quote,
2896 ';' => Code::Semicolon,
2897 '/' => Code::Slash,
2898 ' ' => Code::Space,
2899 _ => return None,
2900 })
2901}
2902
2903impl Element {
2904 fn inclusive_ancestor_element_in_non_ua_shadow_root(&self) -> DomRoot<Element> {
2906 if !self.upcast::<Node>().is_in_ua_widget() {
2907 return DomRoot::from_ref(self);
2908 }
2909 let Some(shadow_root) = self.containing_shadow_root() else {
2910 return DomRoot::from_ref(self);
2911 };
2912 shadow_root
2913 .Host()
2914 .inclusive_ancestor_element_in_non_ua_shadow_root()
2915 }
2916}