1use std::array::from_ref;
6use std::cell::Cell;
7use std::f64::consts::PI;
8use std::mem;
9use std::time::{Duration, Instant};
10
11use embedder_traits::{
12 Cursor, EmbedderMsg, ImeEvent, InputEvent, InputEventId, InputEventOutcome, InputEventResult,
13 KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction, MouseButtonEvent,
14 MouseLeftViewportEvent, MouseMoveEvent, TouchEvent as EmbedderTouchEvent, TouchEventType,
15 TouchId, TouchPointerType, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
16};
17#[cfg(feature = "gamepad")]
18use embedder_traits::{
19 GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
20};
21use euclid::{Point2D, Vector2D};
22use js::context::{JSContext, NoGC};
23use keyboard_types::{
24 Code, CompositionState, Key, KeyState, KeyboardEvent as KeyboardTypesEvent, Modifiers, NamedKey,
25};
26use layout_api::{HitTestFlags, ScrollContainerQueryFlags, node_id_from_scroll_id};
27use rustc_hash::FxHashMap;
28use script_bindings::cell::DomRefCell;
29use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
30use script_bindings::codegen::GenericBindings::ElementBinding::ScrollLogicalPosition;
31use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
32use script_bindings::codegen::GenericBindings::HTMLElementBinding::HTMLElementMethods;
33use script_bindings::codegen::GenericBindings::HTMLLabelElementBinding::HTMLLabelElementMethods;
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::{
44 KeyboardScroll, LoadData, NavigationHistoryBehavior, ScriptToConstellationMessage,
45};
46use servo_url::ServoUrl;
47use style::Atom;
48use style_traits::CSSPixel;
49use webrender_api::ExternalScrollId;
50
51#[cfg(feature = "gamepad")]
52use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::PermissionName;
53use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
54use crate::dom::bindings::refcounted::Trusted;
55use crate::dom::bindings::root::MutNullableDom;
56use crate::dom::bindings::trace::NoTrace;
57use crate::dom::document::FireMouseEventType;
58use crate::dom::document::editing::{
59 editing_action_from_composition_event, editing_action_from_keyboard_event,
60};
61use crate::dom::document::focus::FocusableArea;
62use crate::dom::document::interactive_element_command::InteractiveElementCommand;
63use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
64#[cfg(feature = "gamepad")]
65use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
66#[cfg(feature = "gamepad")]
67use crate::dom::gamepad::gamepadevent::GamepadEventType;
68use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
69use crate::dom::inputevent::HitTestResult;
70use crate::dom::iterators::ShadowIncluding;
71use crate::dom::keyboardevent::KeyboardEvent;
72use crate::dom::links::htmlhyperlinkelementutils::HyperlinkElementTraits;
73use crate::dom::node::focus::FocusTrigger;
74use crate::dom::node::{self, Node, NodeTraits};
75use crate::dom::pointerevent::{PointerEvent, PointerId};
76use crate::dom::types::{
77 CompositionEvent, Element, Event, EventTarget, GlobalScope, HTMLAnchorElement, HTMLAreaElement,
78 HTMLElement, HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList, WheelEvent, Window,
79};
80use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
81use crate::drag::drag_gesture::DragGesture;
82use crate::navigation::navigate;
83use crate::realms::enter_auto_realm;
84
85#[derive(Default, JSTraceable, MallocSizeOf)]
96struct ButtonClickState {
97 #[no_trace]
98 down_point: Option<Point2D<f32, CSSPixel>>,
99 last_click_time: Option<Instant>,
100 #[no_trace]
101 last_click_point: Option<Point2D<f32, CSSPixel>>,
102 click_count: usize,
103}
104
105impl ButtonClickState {
106 fn reset_click_count(&mut self) {
107 self.click_count = 0;
108 self.last_click_time = None;
109 self.last_click_point = None;
110 }
111
112 fn increment_click_count(&mut self, point: Point2D<f32, CSSPixel>) -> usize {
113 self.last_click_time = Some(Instant::now());
114 self.last_click_point = Some(point);
115 self.click_count += 1;
116 self.click_count
117 }
118}
119
120#[derive(Default, JSTraceable, MallocSizeOf)]
121struct MouseClickTracker {
122 state: FxHashMap<NoTrace<MouseButton>, ButtonClickState>,
123}
124
125impl MouseClickTracker {
126 fn on_mouse_down(&mut self, button: MouseButton, point_in_frame: Point2D<f32, CSSPixel>) {
127 for (state_button, state) in &mut self.state {
128 if state_button.0 != button {
129 state.reset_click_count();
130 }
131 }
132
133 let state = self.state.entry(NoTrace(button)).or_default();
134 state.down_point = Some(point_in_frame);
135
136 let (Some(previous_point), Some(previous_time)) =
137 (state.last_click_point, state.last_click_time)
138 else {
139 return;
140 };
141
142 let double_click_timeout =
143 Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
144 let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
145
146 let line = point_in_frame - previous_point;
148 let distance = (line.dot(line) as f64).sqrt();
149 if Instant::now().duration_since(previous_time) > double_click_timeout ||
150 distance > double_click_distance_threshold as f64
151 {
152 state.reset_click_count();
153 }
154 }
155
156 fn on_mouse_up(
157 &mut self,
158 button: MouseButton,
159 point_in_frame: Point2D<f32, CSSPixel>,
160 ) -> usize {
161 self.state
162 .entry(NoTrace(button))
163 .or_default()
164 .increment_click_count(point_in_frame)
165 }
166
167 fn click_count_for(&self, button: MouseButton) -> usize {
168 self.state
169 .get(&NoTrace(button))
170 .map_or(0, |state| state.click_count)
171 }
172
173 fn take_last_mouse_button_down_point(
174 &mut self,
175 mouse_button: MouseButton,
176 ) -> Option<Point2D<f32, CSSPixel>> {
177 self.state
178 .get_mut(&NoTrace(mouse_button))
179 .and_then(|state| state.down_point.take())
180 }
181
182 fn reset_all(&mut self) {
183 for state in self.state.values_mut() {
184 state.reset_click_count();
185 }
186 }
187}
188
189#[derive(JSTraceable, MallocSizeOf)]
193#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
194pub(crate) struct DocumentEventHandler {
195 window: Dom<Window>,
197 #[no_trace]
199 pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
200 mouse_move_event_index: DomRefCell<Option<usize>>,
202 #[no_trace]
204 coalesced_mouse_move_event_ids: DomRefCell<Vec<InputEventId>>,
205 wheel_event_index: DomRefCell<Option<usize>>,
210 #[no_trace]
212 coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
213 button_click_state: DomRefCell<MouseClickTracker>,
215 #[no_trace]
219 mouse_button_state: Cell<MouseButtons>,
220 current_hover_target: MutNullableDom<Element>,
222 current_active_element: MutNullableDom<Element>,
225 most_recently_clicked_element: MutNullableDom<Element>,
227 #[no_trace]
229 most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
230 #[no_trace]
233 current_cursor: Cell<Option<Cursor>>,
234 active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
236 touch_sequence_click_allowed: Cell<bool>,
238 #[no_trace]
240 active_keyboard_modifiers: Cell<Modifiers>,
241 active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
243 next_touch_pointer_id: Cell<i32>,
245 access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
247 pending_pointer_capture: DomRefCell<FxHashMap<i32, Dom<Element>>>,
251 pointer_capture_target: DomRefCell<FxHashMap<i32, Dom<Element>>>,
254 drag_gesture: DomRefCell<Option<DragGesture>>,
257}
258
259impl DocumentEventHandler {
260 pub(crate) fn new(window: &Window) -> Self {
261 Self {
262 window: Dom::from_ref(window),
263 pending_input_events: Default::default(),
264 mouse_move_event_index: Default::default(),
265 coalesced_mouse_move_event_ids: Default::default(),
266 wheel_event_index: Default::default(),
267 coalesced_wheel_event_ids: Default::default(),
268 button_click_state: Default::default(),
269 mouse_button_state: Cell::new(MouseButtons::empty()),
270 current_hover_target: Default::default(),
271 current_active_element: Default::default(),
272 most_recently_clicked_element: Default::default(),
273 most_recent_mousemove_point: Default::default(),
274 current_cursor: Default::default(),
275 active_touch_points: Default::default(),
276 touch_sequence_click_allowed: Cell::new(true),
277 active_keyboard_modifiers: Default::default(),
278 active_pointer_ids: Default::default(),
279 next_touch_pointer_id: Cell::new(1),
280 access_key_handlers: Default::default(),
281 pending_pointer_capture: Default::default(),
282 pointer_capture_target: Default::default(),
283 drag_gesture: Default::default(),
284 }
285 }
286
287 pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
289 let mut pending_input_events = self.pending_input_events.borrow_mut();
290 if matches!(event.event.event, InputEvent::MouseMove(..)) {
291 if let Some(mouse_move_event) = self
293 .mouse_move_event_index
294 .borrow()
295 .and_then(|index| pending_input_events.get_mut(index))
296 {
297 self.coalesced_mouse_move_event_ids
298 .borrow_mut()
299 .push(mouse_move_event.event.id);
300 *mouse_move_event = event;
301 return;
302 }
303
304 *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
305 }
306
307 if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
308 if let Some(existing_constellation_wheel_event) = self
310 .wheel_event_index
311 .borrow()
312 .and_then(|index| pending_input_events.get_mut(index)) &&
313 let InputEvent::Wheel(ref mut existing_wheel_event) =
314 existing_constellation_wheel_event.event.event &&
315 existing_wheel_event.delta.mode == new_wheel_event.delta.mode
316 {
317 self.coalesced_wheel_event_ids
318 .borrow_mut()
319 .push(existing_constellation_wheel_event.event.id);
320 existing_wheel_event.delta.x += new_wheel_event.delta.x;
321 existing_wheel_event.delta.y += new_wheel_event.delta.y;
322 existing_wheel_event.delta.z += new_wheel_event.delta.z;
323 existing_wheel_event.point = new_wheel_event.point;
324 existing_constellation_wheel_event.event.id = event.event.id;
325 return;
326 }
327
328 *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
329 }
330
331 pending_input_events.push(event);
332 }
333
334 pub(crate) fn has_pending_input_events(&self) -> bool {
337 !self.pending_input_events.borrow().is_empty()
338 }
339
340 pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
341 #[cfg(target_os = "macos")]
342 {
343 self.active_keyboard_modifiers
344 .get()
345 .contains(Modifiers::META)
346 }
347 #[cfg(not(target_os = "macos"))]
348 {
349 self.active_keyboard_modifiers
350 .get()
351 .contains(Modifiers::CONTROL)
352 }
353 }
354
355 pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
356 debug_assert!(
357 !self.pending_input_events.borrow().is_empty(),
358 "handle_pending_input_events called with no events"
359 );
360 let mut realm = enter_auto_realm(cx, &*self.window);
361 let cx = &mut realm.current_realm();
362
363 *self.mouse_move_event_index.safe_borrow_mut(cx.no_gc()) = None;
365 *self.wheel_event_index.safe_borrow_mut(cx.no_gc()) = None;
366 let pending_input_events =
367 mem::take(&mut *self.pending_input_events.safe_borrow_mut(cx.no_gc()));
368 let mut coalesced_mouse_move_event_ids = mem::take(
369 &mut *self
370 .coalesced_mouse_move_event_ids
371 .safe_borrow_mut(cx.no_gc()),
372 );
373 let mut coalesced_wheel_event_ids =
374 mem::take(&mut *self.coalesced_wheel_event_ids.safe_borrow_mut(cx.no_gc()));
375
376 let mut input_event_outcomes = Vec::with_capacity(
377 pending_input_events.len() +
378 coalesced_mouse_move_event_ids.len() +
379 coalesced_wheel_event_ids.len(),
380 );
381 for event in pending_input_events {
387 self.active_keyboard_modifiers
388 .set(event.active_keyboard_modifiers);
389 let result = match event.event.event {
390 InputEvent::MouseButton(mouse_button_event) => {
391 self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
392 InputEventResult::default()
393 },
394 InputEvent::MouseMove(_) => {
395 self.handle_native_mouse_move_event(cx, &event);
396 input_event_outcomes.extend(
397 mem::take(&mut coalesced_mouse_move_event_ids)
398 .into_iter()
399 .map(|id| InputEventOutcome {
400 id,
401 result: InputEventResult::default(),
402 }),
403 );
404 InputEventResult::default()
405 },
406 InputEvent::MouseLeftViewport(mouse_leave_event) => {
407 self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
408 InputEventResult::default()
409 },
410 InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
411 InputEvent::Wheel(wheel_event) => {
412 let result = self.handle_wheel_event(cx, wheel_event, &event);
413 input_event_outcomes.extend(
414 mem::take(&mut coalesced_wheel_event_ids)
415 .into_iter()
416 .map(|id| InputEventOutcome { id, result }),
417 );
418 result
419 },
420 InputEvent::Keyboard(keyboard_event) => {
421 self.handle_keyboard_event(cx, keyboard_event)
422 },
423 InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
424 #[cfg(feature = "gamepad")]
425 InputEvent::Gamepad(gamepad_event) => {
426 self.handle_gamepad_event(gamepad_event);
427 InputEventResult::default()
428 },
429 InputEvent::EditingAction(clipboard_action) => {
430 let document = self.window.Document();
431 let focused_node = document
432 .focus_handler()
433 .focused_area()
434 .dom_anchor(&document);
435 let editing_context = document.editing_context(cx.no_gc(), &focused_node);
436 document.handle_clipboard_action(cx, &editing_context, clipboard_action)
437 },
438 };
439
440 input_event_outcomes.push(InputEventOutcome {
441 id: event.event.id,
442 result,
443 });
444 }
445
446 self.notify_embedder_that_events_were_handled(input_event_outcomes);
447 }
448
449 fn notify_embedder_that_events_were_handled(
450 &self,
451 input_event_outcomes: Vec<InputEventOutcome>,
452 ) {
453 let trusted_window = Trusted::new(&*self.window);
456 self.window
457 .as_global_scope()
458 .task_manager()
459 .dom_manipulation_task_source()
460 .queue_unconditionally(task!(notify_webdriver_input_event_completed: move || {
461 let window = trusted_window.root();
462 window.send_to_embedder(
463 EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
464 }));
465 }
466
467 pub(crate) fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
480 let document = self.window.Document();
481 match &*document.focus_handler().focused_area() {
482 FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
483 FocusableArea::IFrameViewport { iframe_element, .. } => {
484 DomRoot::from_ref(iframe_element.upcast())
485 },
486 FocusableArea::Viewport => document
487 .GetBody()
488 .map(DomRoot::upcast)
489 .or_else(|| document.GetDocumentElement().map(DomRoot::upcast))
490 .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
491 }
492 }
493
494 pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
495 if cursor == self.current_cursor.get() {
496 return;
497 }
498 self.current_cursor.set(cursor);
499 self.window.send_to_embedder(EmbedderMsg::SetCursor(
500 self.window.webview_id(),
501 cursor.unwrap_or_default(),
502 ));
503 }
504
505 fn handle_mouse_left_viewport_event(
506 &self,
507 cx: &mut JSContext,
508 input_event: &ConstellationInputEvent,
509 mouse_leave_event: &MouseLeftViewportEvent,
510 ) {
511 if let Some(current_hover_target) = self.current_hover_target.get() {
512 let current_hover_target = current_hover_target.upcast::<Node>();
513 for element in current_hover_target
514 .inclusive_ancestors(ShadowIncluding::Yes)
515 .filter_map(DomRoot::downcast::<Element>)
516 {
517 element.set_hover_state(cx.no_gc(), false);
518 }
519
520 if let Some(hit_test_result) =
521 self.most_recent_mousemove_point.get().and_then(|point| {
522 self.window
523 .hit_test_from_point_in_viewport(HitTestFlags::empty(), point)
524 })
525 {
526 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
527 cx,
528 &self.window,
529 FireMouseEventType::Out,
530 &hit_test_result,
531 input_event,
532 );
533
534 mouse_out_event
536 .to_pointer_hover_event(cx, "pointerout")
537 .upcast::<Event>()
538 .fire(cx, current_hover_target.upcast());
539
540 mouse_out_event
541 .upcast::<Event>()
542 .fire(cx, current_hover_target.upcast());
543
544 self.handle_mouse_enter_leave_event(
545 cx,
546 current_hover_target,
547 None,
548 FireMouseEventType::Leave,
549 &hit_test_result,
550 input_event,
551 );
552 }
553 }
554
555 if !mouse_leave_event.focus_moving_to_another_iframe {
560 self.window
564 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
565 self.set_cursor(None);
566 } else {
567 self.current_cursor.set(None);
568 }
569
570 self.current_hover_target.set(None);
571 self.most_recent_mousemove_point.set(None);
572 }
573
574 fn handle_mouse_enter_leave_event(
575 &self,
576 cx: &mut JSContext,
577 event_target: &Node,
578 related_target: Option<&Node>,
579 event_type: FireMouseEventType,
580 hit_test_result: &HitTestResult,
581 input_event: &ConstellationInputEvent,
582 ) {
583 assert!(matches!(
584 event_type,
585 FireMouseEventType::Enter | FireMouseEventType::Leave
586 ));
587
588 let is_compatibility_event_for_touch = input_event.is_compatibility_event_for_touch();
589
590 let common_ancestor = match related_target.as_ref() {
591 Some(related_target) => event_target
592 .common_ancestor_in_flat_tree(cx.no_gc(), related_target)
593 .unwrap_or_else(|| DomRoot::from_ref(event_target)),
594 None => DomRoot::from_ref(event_target),
595 };
596
597 let mut targets: Vec<_> = event_target
600 .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
601 .take_while(|node| *node != *common_ancestor)
602 .map(|node| node.as_rooted())
603 .collect();
604
605 if event_type == FireMouseEventType::Enter {
608 targets.reverse();
609 }
610
611 let pointer_event_name = match event_type {
612 FireMouseEventType::Enter => "pointerenter",
613 FireMouseEventType::Leave => "pointerleave",
614 _ => unreachable!(),
615 };
616
617 for target in targets {
618 let mouse_event = MouseEvent::new_for_platform_motion_event(
619 cx,
620 &self.window,
621 event_type,
622 hit_test_result,
623 input_event,
624 );
625 mouse_event
626 .upcast::<Event>()
627 .set_related_target(related_target.as_ref().map(|target| target.upcast()));
628
629 if !is_compatibility_event_for_touch {
631 mouse_event
632 .to_pointer_hover_event(cx, pointer_event_name)
633 .upcast::<Event>()
634 .fire(cx, target.upcast());
635 }
636
637 mouse_event.upcast::<Event>().fire(cx, target.upcast());
639 }
640 }
641
642 fn handle_native_mouse_move_event(
644 &self,
645 cx: &mut JSContext,
646 input_event: &ConstellationInputEvent,
647 ) {
648 let is_compatibility_event_for_touch = input_event.is_compatibility_event_for_touch();
649
650 let pointer_id = PointerId::Mouse as i32;
653 let released_disconnected = !is_compatibility_event_for_touch &&
654 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
655
656 let hit_test_flags = if self
657 .drag_gesture
658 .borrow()
659 .as_ref()
660 .is_some_and(DragGesture::need_dom_position_from_hit_test)
661 {
662 HitTestFlags::IncludeDomPosition
663 } else {
664 HitTestFlags::empty()
665 };
666
667 let Some(hit_test_result) = self
672 .window
673 .hit_test_from_input_event(hit_test_flags, input_event)
674 else {
675 return;
676 };
677
678 {
679 let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
680 if maybe_drag_gesture.as_mut().is_some_and(|drag_gesture| {
681 !drag_gesture.handle_mouse_move_event(cx, input_event, &hit_test_result)
682 }) {
683 *maybe_drag_gesture = None;
684 }
685 }
686
687 let old_mouse_move_point = self
688 .most_recent_mousemove_point
689 .replace(Some(hit_test_result.point_in_frame));
690 if !is_compatibility_event_for_touch &&
693 old_mouse_move_point == Some(hit_test_result.point_in_frame)
694 {
695 return;
696 }
697
698 self.set_cursor(Some(hit_test_result.cursor));
700
701 let Some(new_target) = hit_test_result
702 .node
703 .inclusive_ancestors(ShadowIncluding::Yes)
704 .find_map(DomRoot::downcast::<Element>)
705 else {
706 return;
707 };
708
709 let capture_is_active = !is_compatibility_event_for_touch &&
710 self.get_pointer_capture_target(pointer_id).is_some();
711 let old_hover_target = self.current_hover_target.get();
712 let target_has_changed = old_hover_target
713 .as_ref()
714 .is_none_or(|old_target| *old_target != new_target);
715
716 if target_has_changed {
719 if let Some(old_target) = self.current_hover_target.get() {
721 let old_target_is_ancestor_of_new_target = old_target
722 .upcast::<Node>()
723 .is_ancestor_of(new_target.upcast::<Node>());
724
725 if !old_target_is_ancestor_of_new_target {
728 for element in old_target
729 .upcast::<Node>()
730 .inclusive_ancestors(ShadowIncluding::Yes)
731 .filter_map(DomRoot::downcast::<Element>)
732 {
733 element.set_hover_state(cx.no_gc(), false);
734 }
735 }
736
737 if !capture_is_active {
738 let mouse_out_event = MouseEvent::new_for_platform_motion_event(
739 cx,
740 &self.window,
741 FireMouseEventType::Out,
742 &hit_test_result,
743 input_event,
744 );
745 mouse_out_event
746 .upcast::<Event>()
747 .set_related_target(Some(new_target.upcast()));
748
749 if !is_compatibility_event_for_touch {
751 mouse_out_event
752 .to_pointer_hover_event(cx, "pointerout")
753 .upcast::<Event>()
754 .fire(cx, old_target.upcast());
755 }
756
757 mouse_out_event
758 .upcast::<Event>()
759 .fire(cx, old_target.upcast());
760
761 if !old_target_is_ancestor_of_new_target {
762 let event_target = old_target.upcast::<Node>();
763 let moving_into = Some(new_target.upcast::<Node>());
764 self.handle_mouse_enter_leave_event(
765 cx,
766 event_target,
767 moving_into,
768 FireMouseEventType::Leave,
769 &hit_test_result,
770 input_event,
771 );
772 }
773 }
774 }
775
776 for element in new_target
778 .upcast::<Node>()
779 .inclusive_ancestors(ShadowIncluding::Yes)
780 .filter_map(DomRoot::downcast::<Element>)
781 {
782 element.set_hover_state(cx.no_gc(), true);
783 }
784
785 if !capture_is_active {
786 let mouse_over_event = MouseEvent::new_for_platform_motion_event(
787 cx,
788 &self.window,
789 FireMouseEventType::Over,
790 &hit_test_result,
791 input_event,
792 );
793 mouse_over_event
794 .upcast::<Event>()
795 .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
796
797 if !is_compatibility_event_for_touch {
799 mouse_over_event
800 .to_pointer_hover_event(cx, "pointerover")
801 .upcast::<Event>()
802 .dispatch(cx, new_target.upcast(), false);
803 }
804
805 mouse_over_event
806 .upcast::<Event>()
807 .dispatch(cx, new_target.upcast(), false);
808
809 let moving_from = old_hover_target
810 .as_ref()
811 .map(|old_target| old_target.upcast::<Node>());
812 let event_target = new_target.upcast::<Node>();
813 self.handle_mouse_enter_leave_event(
814 cx,
815 event_target,
816 moving_from,
817 FireMouseEventType::Enter,
818 &hit_test_result,
819 input_event,
820 );
821 }
822 }
823
824 let mouse_event = MouseEvent::new_for_platform_motion_event(
827 cx,
828 &self.window,
829 FireMouseEventType::Move,
830 &hit_test_result,
831 input_event,
832 );
833
834 let pointer_target = if is_compatibility_event_for_touch {
840 None
841 } else {
842 self.get_pointer_capture_target(pointer_id)
843 }
844 .map(DomRoot::upcast::<EventTarget>)
845 .unwrap_or_else(|| DomRoot::from_ref(new_target.upcast::<EventTarget>()));
846
847 if !is_compatibility_event_for_touch {
848 let pointer_event = mouse_event.to_pointer_event(cx, Atom::from("pointermove"));
849 pointer_event.upcast::<Event>().set_composed(true);
850 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
851
852 if !released_disconnected {
856 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
857 }
858 }
859
860 mouse_event.upcast::<Event>().fire(cx, &pointer_target);
862
863 self.update_current_hover_target_and_status(Some(new_target));
864 }
865
866 fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
867 let current_hover_target = self.current_hover_target.get();
868 if current_hover_target == new_hover_target {
869 return;
870 }
871
872 let previous_hover_target = self.current_hover_target.get();
873 self.current_hover_target.set(new_hover_target.as_deref());
874
875 if let Some(target) = self.current_hover_target.get() &&
878 let Some(anchor) = target
879 .upcast::<Node>()
880 .inclusive_ancestors(ShadowIncluding::Yes)
881 .find_map(DomRoot::downcast::<HTMLAnchorElement>)
882 {
883 let status = anchor
884 .full_href_url_for_user_interface()
885 .map(|url| url.to_string());
886 self.window
887 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
888 return;
889 }
890
891 if previous_hover_target.is_none_or(|previous_hover_target| {
896 previous_hover_target
897 .upcast::<Node>()
898 .inclusive_ancestors(ShadowIncluding::Yes)
899 .any(|node| node.is::<HTMLAnchorElement>())
900 }) {
901 self.window
902 .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
903 }
904 }
905
906 pub(crate) fn handle_refresh_cursor(&self) {
907 let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
908 return;
909 };
910
911 let Some(hit_test_result) = self
912 .window
913 .hit_test_from_point_in_viewport(HitTestFlags::empty(), most_recent_mousemove_point)
914 else {
915 return;
916 };
917
918 self.set_cursor(Some(hit_test_result.cursor));
919 }
920
921 fn set_active_element(&self, no_gc: &NoGC, original_target: &Element) {
922 let find_element_for_activation = |element: &Element| {
923 let node: &Node = element.upcast();
924 if node.is_in_ua_widget() &&
925 let Some(containing_shadow_root) = node.containing_shadow_root()
926 {
927 return containing_shadow_root.Host();
928 }
929
930 if node.type_id() ==
932 NodeTypeId::Element(ElementTypeId::HTMLElement(
933 HTMLElementTypeId::HTMLLabelElement,
934 ))
935 {
936 let label = element.downcast::<HTMLLabelElement>().unwrap();
937 if let Some(control) = label.GetControl(no_gc) {
938 return DomRoot::from_ref(control.upcast::<Element>());
939 }
940 }
941
942 DomRoot::from_ref(element)
943 };
944 let element_for_activation = find_element_for_activation(original_target);
945
946 if let Some(currently_active_element) = self.current_active_element.get() {
949 if currently_active_element == element_for_activation {
950 return;
951 }
952 self.unset_active_element(no_gc);
953 }
954
955 element_for_activation.set_active_state(no_gc, true);
956 self.current_active_element
957 .set(Some(&*element_for_activation));
958 }
959
960 fn unset_active_element(&self, no_gc: &NoGC) {
961 if let Some(active_element) = self.current_active_element.take() {
962 active_element.set_active_state(no_gc, false);
963 }
964 }
965
966 fn handle_native_mouse_button_event(
969 &self,
970 cx: &mut JSContext,
971 mouse_button_event: MouseButtonEvent,
972 input_event: &ConstellationInputEvent,
973 ) {
974 let is_compatibility_event_for_touch = input_event.is_compatibility_event_for_touch();
975
976 {
977 let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
978 if maybe_drag_gesture
979 .as_mut()
980 .is_some_and(|drag_gesture| !drag_gesture.handle_mouse_button_event(input_event))
981 {
982 *maybe_drag_gesture = None;
983 }
984 }
985
986 let flags = if input_event.primary_button_is_pressed() ||
987 input_event.auxiliary_button_is_pressed()
988 {
989 HitTestFlags::IncludeDomPosition
990 } else {
991 HitTestFlags::empty()
992 };
993 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
995 else {
996 return;
997 };
998
999 debug!(
1000 "{:?}: at {:?}",
1001 mouse_button_event.action, hit_test_result.point_in_frame
1002 );
1003
1004 let document = self.window.Document();
1007 if mouse_button_event.action == MouseButtonAction::Down {
1008 document
1009 .focus_handler()
1010 .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
1011 }
1012
1013 let Some(element) = hit_test_result
1014 .node
1015 .inclusive_ancestors(ShadowIncluding::Yes)
1016 .find_map(DomRoot::downcast::<Element>)
1017 else {
1018 return;
1019 };
1020
1021 let node = element.upcast::<Node>();
1022 debug!("{:?} on {:?}", mouse_button_event.action, node.debug_str());
1023
1024 if mouse_button_event.button == MouseButton::Primary {
1029 if mouse_button_event.action == MouseButtonAction::Down {
1030 self.set_active_element(cx.no_gc(), &element);
1031 }
1032 if mouse_button_event.action == MouseButtonAction::Up {
1033 self.unset_active_element(cx.no_gc());
1034 }
1035 }
1036
1037 if element.is_actually_disabled() {
1040 return;
1041 }
1042
1043 let mouse_event_type = match mouse_button_event.action {
1044 embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
1045 embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
1046 };
1047
1048 let mouse_button = mouse_button_event.button;
1049 if mouse_button_event.action == MouseButtonAction::Down {
1050 self.button_click_state
1051 .safe_borrow_mut(cx.no_gc())
1052 .on_mouse_down(mouse_button, hit_test_result.point_in_frame);
1053 }
1054
1055 let click_count = self
1062 .button_click_state
1063 .borrow()
1064 .click_count_for(mouse_button) +
1065 1;
1066
1067 let mouse_event = MouseEvent::for_platform_button_event(
1068 cx,
1069 mouse_event_type,
1070 mouse_button_event,
1071 input_event.pressed_mouse_buttons,
1072 &self.window,
1073 &hit_test_result,
1074 input_event.active_keyboard_modifiers,
1075 click_count,
1076 );
1077
1078 match mouse_button_event.action {
1079 MouseButtonAction::Down => {
1080 let pointer_event_result = if is_compatibility_event_for_touch {
1082 true
1083 } else {
1084 let pointer_event_name = if self.mouse_button_state.get().is_empty() {
1085 "pointerdown".into()
1090 } else {
1091 "pointermove".into()
1097 };
1098 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1099
1100 let pointer_id = PointerId::Mouse as i32;
1102
1103 let released_disconnected =
1105 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1106
1107 let pointer_target = self
1109 .get_pointer_capture_target(pointer_id)
1110 .map(DomRoot::upcast::<EventTarget>)
1111 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1112
1113 self.mouse_button_state
1115 .set(input_event.pressed_mouse_buttons);
1116
1117 let pointer_event_result =
1118 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1119
1120 if !released_disconnected {
1124 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1125 }
1126 pointer_event_result
1127 };
1128
1129 let result = mouse_event
1131 .upcast::<Event>()
1132 .dispatch(cx, node.upcast(), false);
1133
1134 if result &&
1138 pointer_event_result &&
1139 let Some(node) = mouse_event
1140 .upcast::<Event>()
1141 .GetTarget()
1142 .and_then(DomRoot::downcast::<Node>)
1143 {
1144 let editing_context = document.editing_context(cx, &node);
1145 editing_context.handle_mousedown_event(cx, &mouse_event, &hit_test_result);
1146 }
1147
1148 if result {
1151 document
1155 .focus_handler()
1156 .focus(cx, &node.find_click_focusable_area(cx));
1157 }
1158
1159 if let MouseButton::Secondary = mouse_button_event.button {
1162 self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
1163 }
1164 },
1165 MouseButtonAction::Up => {
1167 if !is_compatibility_event_for_touch {
1168 let mouse_button_state = self.mouse_button_state.get();
1170 let exactly_one_button = mouse_button_state.exactly_one_button_pressed();
1171 let pointer_event_name = if exactly_one_button {
1172 "pointerup".into()
1177 } else {
1178 "pointermove".into()
1184 };
1185 let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1186
1187 let pointer_id = PointerId::Mouse as i32;
1189
1190 let released_disconnected =
1192 self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1193
1194 let pointer_target = self
1196 .get_pointer_capture_target(pointer_id)
1197 .map(DomRoot::upcast::<EventTarget>)
1198 .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1199
1200 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1201
1202 self.mouse_button_state
1205 .set(input_event.pressed_mouse_buttons);
1206
1207 if !released_disconnected {
1211 self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1212 }
1213
1214 if exactly_one_button {
1216 self.implicit_release_pointer_capture(cx, pointer_id, "mouse", true);
1217 }
1218 }
1219
1220 mouse_event
1222 .upcast::<Event>()
1223 .dispatch(cx, node.upcast(), false);
1224
1225 self.button_click_state
1229 .safe_borrow_mut(cx.no_gc())
1230 .on_mouse_up(mouse_button_event.button, hit_test_result.point_in_frame);
1231
1232 self.maybe_trigger_click_for_mouse_button_down_event(
1233 cx,
1234 mouse_button_event,
1235 input_event,
1236 &hit_test_result,
1237 &element,
1238 );
1239 },
1240 }
1241 }
1242
1243 fn maybe_trigger_click_for_mouse_button_down_event(
1246 &self,
1247 cx: &mut JSContext,
1248 event: MouseButtonEvent,
1249 input_event: &ConstellationInputEvent,
1250 hit_test_result: &HitTestResult,
1251 element: &Element,
1252 ) {
1253 let Some(last_mouse_button_down_point) = self
1254 .button_click_state
1255 .safe_borrow_mut(cx.no_gc())
1256 .take_last_mouse_button_down_point(event.button)
1257 else {
1258 return;
1259 };
1260
1261 let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1262 let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1263 if distance > maximum_click_distance {
1264 return;
1265 }
1266
1267 let mut dispatch_button_event = |event_name: Atom, click_count: usize| {
1268 let mouse_event = MouseEvent::for_platform_button_event(
1269 cx,
1270 event_name,
1271 event,
1272 input_event.pressed_mouse_buttons,
1273 &self.window,
1274 hit_test_result,
1275 input_event.active_keyboard_modifiers,
1276 click_count,
1277 );
1278 let event = mouse_event.upcast::<Event>();
1279 event.dispatch(cx, element.upcast(), false);
1280 event.flags()
1281 };
1282
1283 let click_count = self
1284 .button_click_state
1285 .borrow()
1286 .click_count_for(event.button);
1287 match event.button {
1288 MouseButton::Primary => {
1289 self.most_recently_clicked_element.set(Some(element));
1293
1294 element.set_click_in_progress(true);
1295 dispatch_button_event(atom!("click"), click_count);
1296 element.set_click_in_progress(false);
1297
1298 if click_count.is_multiple_of(2) {
1307 dispatch_button_event(Atom::from("dblclick"), 2);
1308 }
1309 },
1310 MouseButton::Auxiliary |
1319 MouseButton::Back |
1320 MouseButton::Forward |
1321 MouseButton::Other(..) => {
1322 let event_flags = dispatch_button_event(Atom::from("auxclick"), click_count);
1323 if !event_flags.intersects(EventFlags::Canceled | EventFlags::Handled) &&
1324 event.button == MouseButton::Auxiliary
1325 {
1326 self.maybe_activate_link_due_to_auxiliary_click(cx, element.upcast());
1327 }
1328 },
1329 _ => {},
1330 }
1331 }
1332
1333 fn maybe_show_context_menu(
1335 &self,
1336 cx: &mut JSContext,
1337 target: &EventTarget,
1338 hit_test_result: &HitTestResult,
1339 input_event: &ConstellationInputEvent,
1340 ) {
1341 let menu_event = PointerEvent::new(
1343 cx,
1344 &self.window, "contextmenu".into(), EventBubbles::Bubbles, EventCancelable::Cancelable, Some(&self.window), 0, hit_test_result.point_in_frame.to_i32(),
1351 hit_test_result.point_in_frame.to_i32(),
1352 hit_test_result
1353 .point_relative_to_initial_containing_block
1354 .to_i32(),
1355 input_event.active_keyboard_modifiers,
1356 MouseButton::Secondary,
1357 input_event.pressed_mouse_buttons,
1358 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![], );
1375 menu_event.upcast::<Event>().set_composed(true);
1376
1377 let result = menu_event.upcast::<Event>().fire(cx, target);
1379
1380 if result {
1382 self.window
1383 .Document()
1384 .embedder_controls()
1385 .show_context_menu(cx, hit_test_result);
1386 };
1387 }
1388
1389 fn handle_touch_event(
1390 &self,
1391 cx: &mut JSContext,
1392 event: EmbedderTouchEvent,
1393 input_event: &ConstellationInputEvent,
1394 ) -> InputEventResult {
1395 let flags = HitTestFlags::empty();
1396 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1398 else {
1399 self.update_active_touch_points_when_early_return(event);
1400 return Default::default();
1401 };
1402
1403 let TouchId(identifier) = event.touch_id;
1404
1405 let Some(element) = hit_test_result
1406 .node
1407 .inclusive_ancestors(ShadowIncluding::Yes)
1408 .find_map(DomRoot::downcast::<Element>)
1409 else {
1410 self.update_active_touch_points_when_early_return(event);
1411 return Default::default();
1412 };
1413
1414 let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1415 let window = &*self.window;
1416
1417 let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1418 let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1419 let page_x =
1420 Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1421 let page_y =
1422 Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1423
1424 let pointer_touch = Touch::new(
1426 cx,
1427 window,
1428 identifier,
1429 ¤t_target,
1430 client_x,
1431 client_y, client_x,
1433 client_y,
1434 page_x,
1435 page_y,
1436 );
1437
1438 let pointer_event_name = match event.event_type {
1440 TouchEventType::Down => "pointerdown",
1441 TouchEventType::Move => "pointermove",
1442 TouchEventType::Up => "pointerup",
1443 TouchEventType::Cancel => "pointercancel",
1444 };
1445
1446 let pointer_type = match event.pointer_type {
1448 TouchPointerType::Pen => "pen",
1449 TouchPointerType::Touch => "touch",
1450 };
1451
1452 let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1454 let is_primary = self.is_primary_pointer(pointer_id);
1455
1456 if matches!(event.event_type, TouchEventType::Down) {
1459 let pointer_over = pointer_touch.to_pointer_event(
1461 cx,
1462 window,
1463 "pointerover",
1464 pointer_id,
1465 is_primary,
1466 pointer_type,
1467 input_event.active_keyboard_modifiers,
1468 true, Some(hit_test_result.point_in_node),
1470 );
1471 pointer_over.upcast::<Event>().fire(cx, ¤t_target);
1472
1473 self.fire_pointer_event_for_touch(
1475 cx,
1476 &element,
1477 &pointer_touch,
1478 pointer_id,
1479 "pointerenter",
1480 is_primary,
1481 pointer_type,
1482 input_event,
1483 &hit_test_result,
1484 );
1485 }
1486
1487 let released_disconnected = if matches!(event.event_type, TouchEventType::Cancel) {
1491 false
1492 } else {
1493 self.release_disconnected_pointer_capture(cx, pointer_id, pointer_type, is_primary)
1494 };
1495
1496 let pointer_target = self
1498 .get_pointer_capture_target(pointer_id)
1499 .map(DomRoot::upcast::<EventTarget>)
1500 .unwrap_or_else(|| current_target.clone());
1501
1502 let pointer_event = pointer_touch.to_pointer_event(
1503 cx,
1504 window,
1505 pointer_event_name,
1506 pointer_id,
1507 is_primary,
1508 pointer_type,
1509 input_event.active_keyboard_modifiers,
1510 event.is_cancelable(),
1511 Some(hit_test_result.point_in_node),
1512 );
1513 pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1514
1515 if !released_disconnected && !matches!(event.event_type, TouchEventType::Cancel) {
1521 self.process_pending_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1522 }
1523
1524 if matches!(
1527 event.event_type,
1528 TouchEventType::Up | TouchEventType::Cancel
1529 ) {
1530 self.implicit_release_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1531 }
1532
1533 if matches!(
1536 event.event_type,
1537 TouchEventType::Up | TouchEventType::Cancel
1538 ) {
1539 let pointer_out = pointer_touch.to_pointer_event(
1541 cx,
1542 window,
1543 "pointerout",
1544 pointer_id,
1545 is_primary,
1546 pointer_type,
1547 input_event.active_keyboard_modifiers,
1548 true, Some(hit_test_result.point_in_node),
1550 );
1551 pointer_out.upcast::<Event>().fire(cx, ¤t_target);
1552
1553 self.fire_pointer_event_for_touch(
1555 cx,
1556 &element,
1557 &pointer_touch,
1558 pointer_id,
1559 "pointerleave",
1560 is_primary,
1561 pointer_type,
1562 input_event,
1563 &hit_test_result,
1564 );
1565 }
1566
1567 let (touch_dispatch_target, changed_touch) = match event.event_type {
1568 TouchEventType::Down => {
1569 self.touch_sequence_click_allowed.set(true);
1570 self.active_touch_points
1572 .safe_borrow_mut(cx.no_gc())
1573 .push(Dom::from_ref(&*pointer_touch));
1574 self.set_active_element(cx.no_gc(), &element);
1575 (current_target, pointer_touch)
1576 },
1577 _ => {
1578 let active_touch_points = self.active_touch_points.borrow();
1584 let Some(index) = active_touch_points
1585 .iter()
1586 .position(|point| point.Identifier() == identifier)
1587 else {
1588 warn!("No active touch point for {:?}", event.event_type);
1589 return Default::default();
1590 };
1591 let original_target = active_touch_points[index].Target();
1593 drop(active_touch_points);
1594
1595 let touch_with_touchstart_target = Touch::new(
1596 cx,
1597 window,
1598 identifier,
1599 &original_target,
1600 client_x,
1601 client_y,
1602 client_x,
1603 client_y,
1604 page_x,
1605 page_y,
1606 );
1607
1608 let mut active_touch_points = self.active_touch_points.safe_borrow_mut(cx.no_gc());
1609 match event.event_type {
1611 TouchEventType::Move => {
1612 active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1613 },
1614 TouchEventType::Up | TouchEventType::Cancel => {
1615 active_touch_points.swap_remove(index);
1616 self.remove_pointer_id_for_touch(identifier);
1617 self.unset_active_element(cx.no_gc());
1618 },
1619 TouchEventType::Down => unreachable!("Should have been handled above"),
1620 }
1621 (original_target, touch_with_touchstart_target)
1622 },
1623 };
1624
1625 rooted_vec!(let mut target_touches);
1626 target_touches.extend(
1627 self.active_touch_points
1628 .borrow()
1629 .iter()
1630 .filter(|touch| touch.Target() == touch_dispatch_target)
1631 .cloned(),
1632 );
1633
1634 let event_name = match event.event_type {
1635 TouchEventType::Down => "touchstart",
1636 TouchEventType::Move => "touchmove",
1637 TouchEventType::Up => "touchend",
1638 TouchEventType::Cancel => "touchcancel",
1639 };
1640
1641 let touches = TouchList::new(cx, window, self.active_touch_points.borrow().r());
1642 let changed_touches = TouchList::new(cx, window, from_ref(&&*changed_touch));
1643 let target_touches = TouchList::new(cx, window, target_touches.r());
1644
1645 let touch_event = TouchEvent::new(
1646 cx,
1647 window,
1648 event_name.into(),
1649 EventBubbles::Bubbles,
1650 EventCancelable::from(event.is_cancelable()),
1651 EventComposed::Composed,
1652 Some(window),
1653 0i32,
1654 &touches,
1655 &changed_touches,
1656 &target_touches,
1657 false,
1659 false,
1660 false,
1661 false,
1662 );
1663 let dom_event = touch_event.upcast::<Event>();
1664 dom_event.fire(cx, &touch_dispatch_target);
1665 let flags = dom_event.flags();
1666
1667 if flags.contains(EventFlags::Canceled) &&
1669 matches!(event.event_type, TouchEventType::Down | TouchEventType::Up)
1670 {
1671 self.touch_sequence_click_allowed.set(false);
1672 }
1673
1674 let synthesizes_mouse_events = matches!(event.event_type, TouchEventType::Up) &&
1677 event.can_synthesize_mouse_events &&
1678 self.touch_sequence_click_allowed.get() &&
1679 !flags.contains(EventFlags::Canceled) &&
1680 self.active_touch_points.borrow().is_empty();
1681
1682 if synthesizes_mouse_events {
1683 self.synthesize_compatibility_mouse_events(cx, &event, input_event);
1684 } else if matches!(
1685 event.event_type,
1686 TouchEventType::Up | TouchEventType::Cancel
1687 ) {
1688 self.button_click_state
1692 .safe_borrow_mut(cx.no_gc())
1693 .reset_all();
1694 }
1695
1696 flags.into()
1697 }
1698
1699 fn synthesize_compatibility_mouse_events(
1703 &self,
1704 cx: &mut JSContext,
1705 event: &EmbedderTouchEvent,
1706 input_event: &ConstellationInputEvent,
1707 ) {
1708 let make_input_event =
1710 |input_event_to_send: InputEvent, pressed_mouse_buttons: MouseButtons| {
1711 ConstellationInputEvent {
1712 hit_test_result: input_event.hit_test_result.clone(),
1713 pressed_mouse_buttons,
1714 active_keyboard_modifiers: input_event.active_keyboard_modifiers,
1715 event: input_event_to_send.into(),
1716 }
1717 };
1718
1719 let point = event.point;
1720 let mouse_move_input_event = make_input_event(
1721 InputEvent::MouseMove(MouseMoveEvent::new_compatibility_for_touch(point)),
1722 MouseButtons::empty(),
1723 );
1724 self.handle_native_mouse_move_event(cx, &mouse_move_input_event);
1725
1726 for (action, pressed_mouse_buttons) in [
1727 (MouseButtonAction::Down, MouseButtons::Primary),
1728 (MouseButtonAction::Up, MouseButtons::empty()),
1729 ] {
1730 let button_event =
1731 MouseButtonEvent::new_compatibility_for_touch(action, MouseButton::Primary, point);
1732 self.handle_native_mouse_button_event(
1733 cx,
1734 button_event,
1735 &make_input_event(InputEvent::MouseButton(button_event), pressed_mouse_buttons),
1736 );
1737 }
1738 }
1739
1740 fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1749 match event.event_type {
1750 TouchEventType::Down | TouchEventType::Move => {},
1751 TouchEventType::Up | TouchEventType::Cancel => {
1752 let mut active_touch_points = self.active_touch_points.borrow_mut();
1753 if let Some(index) = active_touch_points
1754 .iter()
1755 .position(|t| t.Identifier() == event.touch_id.0)
1756 {
1757 active_touch_points.swap_remove(index);
1758 self.remove_pointer_id_for_touch(event.touch_id.0);
1759 } else {
1760 warn!(
1761 "Received {:?} for a non-active touch point {}",
1762 event.event_type, event.touch_id.0
1763 );
1764 }
1765 },
1766 }
1767 }
1768
1769 fn handle_keyboard_event(
1771 &self,
1772 cx: &mut JSContext,
1773 keyboard_event: EmbedderKeyboardEvent,
1774 ) -> InputEventResult {
1775 let target = &self.target_for_events_following_focus();
1776 let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1777 cx,
1778 &self.window,
1779 keyboard_event.event.state.event_type().into(),
1780 &keyboard_event.event,
1781 );
1782
1783 let event = keyevent.upcast::<Event>();
1784 event.set_composed(true);
1785 event.fire(cx, target);
1786
1787 let mut flags = event.flags();
1792 if keyboard_event.event.state != KeyState::Down || flags.contains(EventFlags::Canceled) {
1793 return flags.into();
1794 }
1795
1796 let fires_keypress_event = keyboard_event_fires_keypress_event(&keyboard_event);
1800 if fires_keypress_event {
1801 let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1802 cx,
1803 &self.window,
1804 atom!("keypress"),
1805 &keyboard_event.event,
1806 );
1807 keypress_event.upcast::<Event>().set_composed(true);
1808 let event = keypress_event.upcast::<Event>();
1809 event.fire(cx, target);
1810 flags |= event.flags();
1811 }
1812
1813 if flags.intersects(EventFlags::Handled | EventFlags::Canceled) {
1816 return flags.into();
1817 }
1818
1819 self.run_default_keydown_handler(cx, target, &keyboard_event, flags.into())
1820 }
1821
1822 fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1823 let document = self.window.Document();
1824 let composition_event = match event {
1825 ImeEvent::Dismissed => {
1826 document.focus_handler().focus(cx, &FocusableArea::Viewport);
1827 return Default::default();
1828 },
1829 ImeEvent::Composition(composition_event) => composition_event,
1830 };
1831
1832 let focused_area = document.focus_handler().focused_area();
1837 let Some(focused_element) = focused_area.element() else {
1838 return Default::default();
1840 };
1841
1842 let cancelable = composition_event.state == CompositionState::Start;
1843 let event = CompositionEvent::new(
1844 cx,
1845 &self.window,
1846 composition_event.state.event_type().into(),
1847 true,
1848 cancelable,
1849 Some(&self.window),
1850 0,
1851 DOMString::from(composition_event.data.as_str()),
1852 );
1853
1854 let event = event.upcast::<Event>();
1855 event.fire(cx, focused_element.upcast());
1856 let composition_event_result: InputEventResult = event.flags().into();
1857
1858 if event
1859 .flags()
1860 .intersects(EventFlags::Canceled | EventFlags::Handled)
1861 {
1862 return composition_event_result;
1863 }
1864
1865 let editing_action = editing_action_from_composition_event(&composition_event);
1866 let editing_host = document.editing_context(cx.no_gc(), focused_element.upcast());
1867 if editing_host.perform_editing_action(cx, editing_action) {
1868 return composition_event_result | InputEventResult::Consumed;
1869 }
1870
1871 composition_event_result
1872 }
1873
1874 fn handle_wheel_event(
1875 &self,
1876 cx: &mut JSContext,
1877 event: EmbedderWheelEvent,
1878 input_event: &ConstellationInputEvent,
1879 ) -> InputEventResult {
1880 let flags = HitTestFlags::empty();
1882 let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1883 else {
1884 return Default::default();
1885 };
1886
1887 let Some(el) = hit_test_result
1888 .node
1889 .inclusive_ancestors(ShadowIncluding::Yes)
1890 .find_map(DomRoot::downcast::<Element>)
1891 else {
1892 return Default::default();
1893 };
1894
1895 let node = el.upcast::<Node>();
1896 debug!(
1897 "wheel: on {:?} at {:?}",
1898 node.debug_str(),
1899 hit_test_result.point_in_frame
1900 );
1901
1902 let event_type = "wheel".into();
1903
1904 let cancelable = EventCancelable::from(
1905 self.window
1906 .upcast::<EventTarget>()
1907 .has_non_passive_listener(&event_type) ||
1908 node.inclusive_ancestors(ShadowIncluding::Yes)
1909 .any(|target| {
1910 target
1911 .upcast::<EventTarget>()
1912 .has_non_passive_listener(&event_type)
1913 }),
1914 );
1915 let dom_event = WheelEvent::new(
1917 cx,
1918 &self.window,
1919 event_type,
1920 EventBubbles::Bubbles,
1921 cancelable,
1922 Some(&self.window),
1923 0i32,
1924 hit_test_result.point_in_frame.to_i32(),
1925 hit_test_result.point_in_frame.to_i32(),
1926 hit_test_result
1927 .point_relative_to_initial_containing_block
1928 .to_i32(),
1929 input_event.active_keyboard_modifiers,
1930 MouseButton::Primary,
1931 input_event.pressed_mouse_buttons,
1932 None,
1933 None,
1934 Finite::wrap(-event.delta.x),
1939 Finite::wrap(-event.delta.y),
1940 Finite::wrap(-event.delta.z),
1941 event.delta.mode as u32,
1942 );
1943
1944 let dom_event = dom_event.upcast::<Event>();
1945 dom_event.set_trusted(true);
1946 dom_event.set_composed(true);
1947 dom_event.fire(cx, node.upcast());
1948
1949 dom_event.flags().into()
1950 }
1951
1952 #[cfg(feature = "gamepad")]
1953 fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1954 match gamepad_event {
1955 EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1956 self.handle_gamepad_connect(
1957 index.0,
1958 name,
1959 bounds.axis_bounds,
1960 bounds.button_bounds,
1961 supported_haptic_effects,
1962 );
1963 },
1964 EmbedderGamepadEvent::Disconnected(index) => {
1965 self.handle_gamepad_disconnect(index.0);
1966 },
1967 EmbedderGamepadEvent::Updated(index, update_type) => {
1968 self.receive_new_gamepad_button_or_axis(index.0, update_type);
1969 },
1970 };
1971 }
1972
1973 #[cfg(feature = "gamepad")]
1975 fn handle_gamepad_connect(
1976 &self,
1977 _index: usize,
1981 name: String,
1982 axis_bounds: (f64, f64),
1983 button_bounds: (f64, f64),
1984 supported_haptic_effects: GamepadSupportedHapticEffects,
1985 ) {
1986 let doc = self.window.Document();
1988
1989 if !doc.allowed_to_use_feature(PermissionName::Gamepad) {
1992 return;
1993 }
1994
1995 let trusted_window = Trusted::new(&*self.window);
1996
1997 self.window
2000 .upcast::<GlobalScope>()
2001 .task_manager()
2002 .gamepad_task_source()
2003 .queue(task!(gamepad_connected: move |cx| {
2004 let window = trusted_window.root();
2005
2006 let navigator = window.Navigator(cx);
2009 let selected_index = navigator.select_gamepad_index();
2010 let gamepad = Gamepad::new(
2011 cx,
2012 &window,
2013 selected_index,
2014 name,
2015 "standard".into(),
2016 axis_bounds,
2017 button_bounds,
2018 supported_haptic_effects,
2019 false,
2020 );
2021
2022 navigator.set_gamepad(selected_index as usize, Some(&gamepad));
2024
2025 if navigator.has_gamepad_gesture() {
2027 gamepad.set_exposed(true);
2029 if window.Document().is_fully_active() {
2034 gamepad.notify_event(cx, GamepadEventType::Connected);
2035 }
2036 }
2037 }));
2038 }
2039
2040 #[cfg(feature = "gamepad")]
2042 fn handle_gamepad_disconnect(&self, index: usize) {
2043 let trusted_window = Trusted::new(&*self.window);
2047 self.window
2048 .upcast::<GlobalScope>()
2049 .task_manager()
2050 .gamepad_task_source()
2051 .queue(task!(gamepad_disconnected: move |cx| {
2052 let window = trusted_window.root();
2053 let navigator = window.Navigator(cx);
2054
2055 if let Some(gamepad) = navigator.get_gamepad(index) {
2056 gamepad.update_connected(false);
2058 if gamepad.exposed() && window.Document().is_fully_active() {
2066 gamepad.notify_event(cx, GamepadEventType::Disconnected);
2067 }
2068 }
2069
2070 navigator.set_gamepad(index, None);
2074 navigator.shrink_gamepads_list();
2077 }));
2078 }
2079
2080 #[cfg(feature = "gamepad")]
2082 fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
2083 let trusted_window = Trusted::new(&*self.window);
2086
2087 self.window
2090 .upcast::<GlobalScope>()
2091 .task_manager()
2092 .gamepad_task_source()
2093 .queue(task!(update_gamepad_state: move |cx| {
2094 let window = trusted_window.root();
2095 let document = window.Document();
2096 document.event_handler().update_gamepad_state(cx, index, update_type);
2097 }));
2098 }
2099
2100 #[cfg(feature = "gamepad")]
2102 fn update_gamepad_state(
2103 &self,
2104 cx: &mut JSContext,
2105 gamepad_index: usize,
2106 update_type: GamepadUpdateType,
2107 ) {
2108 use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
2109 let now = *self.window.Performance(cx).Now();
2112
2113 let navigator = self.window.Navigator(cx);
2116
2117 if let Some(gamepad) = navigator.get_gamepad(gamepad_index) {
2118 gamepad.update_timestamp(now);
2120 match update_type {
2123 GamepadUpdateType::Axis(axis_index, value) => {
2124 gamepad.map_and_normalize_axes(axis_index, value);
2125 },
2126 GamepadUpdateType::Button(button_index, value) => {
2127 gamepad.map_and_normalize_buttons(button_index, value);
2128 },
2129 };
2130 if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
2135 navigator.set_has_gamepad_gesture(true);
2137 navigator
2139 .get_connected_gamepad()
2140 .iter()
2141 .for_each(|connected_gamepad| {
2142 connected_gamepad.set_exposed(true);
2144 connected_gamepad.update_timestamp(now);
2146 let trusted_gamepad = Trusted::new(&**connected_gamepad);
2153 if self.window.Document().is_fully_active() {
2154 self.window
2155 .upcast::<GlobalScope>()
2156 .task_manager()
2157 .gamepad_task_source()
2158 .queue(task!(fire_gamepad_connected: move |cx| {
2159 let gamepad = trusted_gamepad.root();
2160 gamepad.notify_event(cx, GamepadEventType::Connected);
2161 }));
2162 }
2163 });
2164 }
2165 }
2166 }
2167
2168 #[expect(unsafe_code)]
2170 pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
2171 let document = self.window.Document();
2173 if scrolled_node.is_root() {
2174 document.handle_viewport_scroll_event();
2175 } else {
2176 let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
2180 let node = unsafe {
2181 node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
2182 };
2183 let Some(element) = node
2184 .inclusive_ancestors(ShadowIncluding::Yes)
2185 .find_map(DomRoot::downcast::<Element>)
2186 else {
2187 return;
2188 };
2189
2190 element.handle_scroll_event();
2191 }
2192 }
2193
2194 pub(crate) fn maybe_dispatch_simulated_click(
2200 &self,
2201 cx: &mut JSContext,
2202 node: &Node,
2203 event: &KeyboardEvent,
2204 ) {
2205 if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
2206 {
2207 return;
2208 }
2209
2210 if node
2218 .downcast::<Element>()
2219 .and_then(Element::as_maybe_activatable)
2220 .is_none() ||
2221 node.downcast::<HTMLInputElement>()
2222 .is_some_and(|input| input.is_textual_or_password())
2223 {
2224 return;
2225 }
2226
2227 node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
2228 event.upcast::<Event>().mark_as_handled();
2229 }
2230
2231 pub(crate) fn run_default_keydown_handler(
2232 &self,
2233 cx: &mut JSContext,
2234 event_target: &EventTarget,
2235 event: &EmbedderKeyboardEvent,
2236 input_event_result: InputEventResult,
2237 ) -> InputEventResult {
2238 let document = self.window.Document();
2239 let node = event_target
2240 .downcast::<Node>()
2241 .unwrap_or_else(|| document.upcast());
2242
2243 let event = &event.event;
2244 if let Some(editing_action) = editing_action_from_keyboard_event(event) {
2245 let editing_host = document.editing_context(cx.no_gc(), node);
2246 if editing_host.perform_editing_action(cx, editing_action) {
2247 return input_event_result | InputEventResult::Consumed;
2248 }
2249 }
2250
2251 if self.maybe_handle_accesskey(cx, event) {
2252 return input_event_result | InputEventResult::Consumed;
2253 }
2254
2255 let mut is_space = false;
2256 let scroll = match &event.key {
2257 Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
2258 Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
2259 Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
2260 Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
2261 Key::Named(NamedKey::End) => KeyboardScroll::End,
2262 Key::Named(NamedKey::Home) => KeyboardScroll::Home,
2263 Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
2264 Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
2265 Key::Character(string) if string == " " => {
2266 is_space = true;
2267 if event.modifiers.contains(Modifiers::SHIFT) {
2268 KeyboardScroll::PageUp
2269 } else {
2270 KeyboardScroll::PageDown
2271 }
2272 },
2273 Key::Named(NamedKey::Tab) => {
2274 self.window
2280 .Document()
2281 .focus_handler()
2282 .sequential_focus_navigation_via_keyboard_event(cx, event);
2283 return input_event_result | InputEventResult::Consumed;
2284 },
2285 _ => return input_event_result,
2286 };
2287
2288 if !event.modifiers.is_empty() && !is_space {
2289 return input_event_result;
2290 }
2291
2292 self.do_keyboard_scroll(cx, scroll);
2293 input_event_result | InputEventResult::Consumed
2294 }
2295
2296 pub(crate) fn do_keyboard_scroll(&self, cx: &mut JSContext, scroll: KeyboardScroll) {
2297 let scroll_axis = match scroll {
2298 KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2299 _ => ScrollingBoxAxis::Y,
2300 };
2301
2302 let document = self.window.Document();
2303 let mut scrolling_box = document
2304 .focus_handler()
2305 .focused_area()
2306 .element()
2307 .or(self.most_recently_clicked_element.get().as_deref())
2308 .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2309 .unwrap_or_else(|| {
2310 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2311 });
2312
2313 while !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2314 if scrolling_box.is_viewport() {
2316 break;
2317 }
2318 let parent = scrolling_box.parent().unwrap_or_else(|| {
2319 document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2320 });
2321 scrolling_box = parent;
2322 }
2323
2324 let calculate_current_scroll_offset_and_delta = || {
2325 const LINE_HEIGHT: f32 = 76.0;
2326 const LINE_WIDTH: f32 = 76.0;
2327
2328 let current_scroll_offset = scrolling_box.scroll_position();
2329 (
2330 current_scroll_offset,
2331 match scroll {
2332 KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2333 KeyboardScroll::End => Vector2D::new(
2334 0.0,
2335 -current_scroll_offset.y + scrolling_box.content_size().height -
2336 scrolling_box.size(cx.no_gc()).height,
2337 ),
2338 KeyboardScroll::PageDown => Vector2D::new(
2339 0.0,
2340 scrolling_box.size(cx.no_gc()).height - 2.0 * LINE_HEIGHT,
2341 ),
2342 KeyboardScroll::PageUp => Vector2D::new(
2343 0.0,
2344 2.0 * LINE_HEIGHT - scrolling_box.size(cx.no_gc()).height,
2345 ),
2346 KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2347 KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2348 KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2349 KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2350 },
2351 )
2352 };
2353
2354 let parent_pipeline = self.window.parent_info();
2358 if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2359 let (_, delta) = calculate_current_scroll_offset_and_delta();
2360 self.window
2361 .paint_api()
2362 .scroll_viewport_by_delta(self.window.webview_id(), delta);
2363 }
2364
2365 if !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2368 assert!(scrolling_box.is_viewport());
2369
2370 let window_proxy = document.window().window_proxy();
2371 if let Some(iframe) = window_proxy.frame_element() {
2372 let iframe_window = iframe.owner_window();
2375 let mut realm = enter_auto_realm(cx, &*iframe_window);
2376 let cx = &mut realm;
2377 iframe_window
2378 .Document()
2379 .event_handler()
2380 .do_keyboard_scroll(cx, scroll);
2381 } else if let Some(parent_pipeline) = parent_pipeline {
2382 document.window().send_to_constellation(
2386 ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2387 );
2388 };
2389 return;
2390 }
2391
2392 let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2393 scrolling_box.scroll_to(cx, delta + current_scroll_offset, ScrollBehavior::Auto);
2394 }
2395
2396 fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2399 let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2400
2401 if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2402 return pointer_id;
2403 }
2404
2405 let pointer_id = self.next_touch_pointer_id.get();
2406 active_pointer_ids.insert(touch_id, pointer_id);
2407 self.next_touch_pointer_id.set(pointer_id + 1);
2408 pointer_id
2409 }
2410
2411 fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2413 self.active_pointer_ids.borrow_mut().remove(&touch_id);
2414 }
2415
2416 fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2419 self.active_pointer_ids
2422 .borrow()
2423 .values()
2424 .min()
2425 .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2426 }
2427
2428 #[expect(clippy::too_many_arguments)]
2432 fn fire_pointer_event_for_touch(
2433 &self,
2434 cx: &mut JSContext,
2435 target_element: &Element,
2436 touch: &Touch,
2437 pointer_id: i32,
2438 event_name: &str,
2439 is_primary: bool,
2440 pointer_type: &str,
2441 input_event: &ConstellationInputEvent,
2442 hit_test_result: &HitTestResult,
2443 ) {
2444 let mut targets: Vec<_> = target_element
2445 .upcast::<Node>()
2446 .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
2447 .map(|node| node.as_rooted())
2448 .collect();
2449
2450 if event_name == "pointerenter" {
2452 targets.reverse();
2453 }
2454
2455 for target in targets {
2456 let pointer_event = touch.to_pointer_event(
2457 cx,
2458 &self.window,
2459 event_name,
2460 pointer_id,
2461 is_primary,
2462 pointer_type,
2463 input_event.active_keyboard_modifiers,
2464 false,
2465 Some(hit_test_result.point_in_node),
2466 );
2467 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2468 }
2469 }
2470
2471 pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2472 self.access_key_handlers
2473 .borrow()
2474 .values()
2475 .any(|value| &**value == element)
2476 }
2477
2478 pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2479 self.access_key_handlers
2480 .borrow_mut()
2481 .retain(|_, value| &**value != element)
2482 }
2483
2484 pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2485 let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2486 access_key_handlers
2488 .entry(code.into())
2489 .or_insert(Dom::from_ref(element));
2490 }
2491
2492 fn maybe_handle_accesskey(&self, cx: &mut JSContext, event: &KeyboardTypesEvent) -> bool {
2493 #[cfg(target_os = "macos")]
2494 let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2495 #[cfg(not(target_os = "macos"))]
2496 let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2497
2498 if event.modifiers != access_key_modifiers {
2499 return false;
2500 }
2501
2502 let Some(html_element) = self
2503 .access_key_handlers
2504 .borrow()
2505 .get(&event.code.into())
2506 .map(|html_element| html_element.as_rooted())
2507 else {
2508 return false;
2509 };
2510
2511 let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2519 return false;
2520 };
2521
2522 if command.disabled() || command.hidden() {
2523 return false;
2524 }
2525
2526 let node = html_element.upcast::<Node>();
2527 if !node.is_connected() {
2528 return false;
2529 }
2530
2531 for node in node.inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::Yes) {
2532 if node
2533 .downcast::<HTMLElement>()
2534 .is_some_and(|html_element| html_element.Hidden())
2535 {
2536 return false;
2537 }
2538 }
2539
2540 self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2543 command.perform_action(cx);
2544 true
2545 }
2546
2547 pub(crate) fn focus_and_scroll_to_element_for_key_event(
2548 &self,
2549 cx: &mut JSContext,
2550 element: &Element,
2551 ) {
2552 element
2553 .upcast::<Node>()
2554 .run_the_focusing_steps(cx, None, FocusTrigger::Other);
2555 let scroll_axis = ScrollAxisState {
2556 position: ScrollLogicalPosition::Center,
2557 requirement: ScrollRequirement::IfNotVisible,
2558 };
2559 element.scroll_into_view_with_options(
2560 cx,
2561 ScrollBehavior::Auto,
2562 scroll_axis,
2563 scroll_axis,
2564 None,
2565 None,
2566 );
2567 }
2568
2569 pub(crate) fn is_active_pointer(&self, pointer_id: i32) -> bool {
2572 if pointer_id == PointerId::Mouse as i32 {
2573 !self.mouse_button_state.get().is_empty()
2575 } else {
2576 self.active_pointer_ids
2578 .borrow()
2579 .values()
2580 .any(|&id| id == pointer_id)
2581 }
2582 }
2583
2584 pub(crate) fn set_pending_pointer_capture(&self, pointer_id: i32, element: &Element) {
2587 self.pending_pointer_capture
2588 .borrow_mut()
2589 .insert(pointer_id, Dom::from_ref(element));
2590 }
2591
2592 pub(crate) fn clear_pending_pointer_capture(&self, pointer_id: i32) {
2595 self.pending_pointer_capture
2596 .borrow_mut()
2597 .remove(&pointer_id);
2598 }
2599
2600 pub(crate) fn has_pointer_capture(&self, pointer_id: i32, element: &Element) -> bool {
2603 self.pending_pointer_capture
2604 .borrow()
2605 .get(&pointer_id)
2606 .is_some_and(|el| &**el == element)
2607 }
2608
2609 fn get_pointer_capture_target(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2614 self.pointer_capture_target
2615 .borrow()
2616 .get(&pointer_id)
2617 .map(|el| DomRoot::from_ref(&**el))
2618 .filter(|el| el.upcast::<Node>().is_connected())
2619 }
2620
2621 fn release_disconnected_pointer_capture(
2627 &self,
2628 cx: &mut JSContext,
2629 pointer_id: i32,
2630 pointer_type: &str,
2631 is_primary: bool,
2632 ) -> bool {
2633 let capture_target = self
2634 .pointer_capture_target
2635 .borrow()
2636 .get(&pointer_id)
2637 .map(|el| DomRoot::from_ref(&**el));
2638 if let Some(capture_element) = capture_target &&
2639 !capture_element.upcast::<Node>().is_connected()
2640 {
2641 let document = self.window.Document();
2643 self.fire_pointer_capture_event_at_target(
2644 cx,
2645 "lostpointercapture",
2646 pointer_id,
2647 pointer_type,
2648 is_primary,
2649 document.upcast::<EventTarget>(),
2650 );
2651 self.pending_pointer_capture
2653 .safe_borrow_mut(cx.no_gc())
2654 .remove(&pointer_id);
2655 self.pointer_capture_target
2656 .safe_borrow_mut(cx.no_gc())
2657 .remove(&pointer_id);
2658 return true;
2659 }
2660 false
2661 }
2662
2663 fn fire_pointer_capture_event(
2666 &self,
2667 cx: &mut JSContext,
2668 event_type: &str,
2669 pointer_id: i32,
2670 pointer_type: &str,
2671 is_primary: bool,
2672 target: &Element,
2673 ) {
2674 self.fire_pointer_capture_event_at_target(
2675 cx,
2676 event_type,
2677 pointer_id,
2678 pointer_type,
2679 is_primary,
2680 target.upcast::<EventTarget>(),
2681 );
2682 }
2683
2684 fn fire_pointer_capture_event_at_target(
2686 &self,
2687 cx: &mut JSContext,
2688 event_type: &str,
2689 pointer_id: i32,
2690 pointer_type: &str,
2691 is_primary: bool,
2692 target: &EventTarget,
2693 ) {
2694 let pointer_event = PointerEvent::new(
2695 cx,
2696 &self.window,
2697 event_type.into(),
2698 EventBubbles::Bubbles,
2699 EventCancelable::NotCancelable,
2700 Some(&self.window),
2701 0,
2702 Point2D::new(0, 0),
2703 Point2D::new(0, 0),
2704 Point2D::new(0, 0),
2705 Modifiers::empty(),
2706 MouseButton::Primary,
2707 MouseButtons::empty(),
2708 None,
2709 None,
2710 pointer_id,
2711 1,
2712 1,
2713 0.0,
2714 0.0,
2715 0,
2716 0,
2717 0,
2718 PI / 2.0,
2719 0.0,
2720 DOMString::from(pointer_type),
2721 is_primary,
2722 vec![],
2723 vec![],
2724 );
2725 pointer_event.upcast::<Event>().set_composed(true);
2726 pointer_event.upcast::<Event>().fire(cx, target);
2727 }
2728
2729 #[expect(clippy::too_many_arguments)]
2733 fn fire_pointer_boundary_event(
2734 &self,
2735 cx: &mut JSContext,
2736 event_type: &str,
2737 bubbles: EventBubbles,
2738 pointer_id: i32,
2739 pointer_type: &str,
2740 is_primary: bool,
2741 target: &Element,
2742 related_target: Option<&Element>,
2743 ) {
2744 let pointer_event = PointerEvent::new(
2745 cx,
2746 &self.window,
2747 event_type.into(),
2748 bubbles,
2749 EventCancelable::NotCancelable,
2750 Some(&self.window),
2751 0,
2752 Point2D::new(0, 0),
2753 Point2D::new(0, 0),
2754 Point2D::new(0, 0),
2755 Modifiers::empty(),
2756 MouseButton::None,
2757 self.mouse_button_state.get(),
2758 related_target.map(|el| el.upcast::<EventTarget>()),
2759 None,
2760 pointer_id,
2761 1,
2762 1,
2763 0.0,
2764 0.0,
2765 0,
2766 0,
2767 0,
2768 PI / 2.0,
2769 0.0,
2770 DOMString::from(pointer_type),
2771 is_primary,
2772 vec![],
2773 vec![],
2774 );
2775 pointer_event.upcast::<Event>().set_composed(true);
2776 pointer_event.upcast::<Event>().fire(cx, target.upcast());
2777 }
2778
2779 fn fire_pointer_capture_boundary_transition(
2784 &self,
2785 cx: &mut JSContext,
2786 pointer_id: i32,
2787 pointer_type: &str,
2788 is_primary: bool,
2789 old_target: &Element,
2790 new_target: &Element,
2791 ) {
2792 if pointer_type != "mouse" {
2793 return;
2794 }
2795 if old_target == new_target {
2796 return;
2797 }
2798 self.fire_pointer_boundary_event(
2799 cx,
2800 "pointerout",
2801 EventBubbles::Bubbles,
2802 pointer_id,
2803 pointer_type,
2804 is_primary,
2805 old_target,
2806 Some(new_target),
2807 );
2808 self.fire_pointer_boundary_event(
2809 cx,
2810 "pointerleave",
2811 EventBubbles::DoesNotBubble,
2812 pointer_id,
2813 pointer_type,
2814 is_primary,
2815 old_target,
2816 Some(new_target),
2817 );
2818 self.fire_pointer_boundary_event(
2819 cx,
2820 "pointerover",
2821 EventBubbles::Bubbles,
2822 pointer_id,
2823 pointer_type,
2824 is_primary,
2825 new_target,
2826 Some(old_target),
2827 );
2828 self.fire_pointer_boundary_event(
2829 cx,
2830 "pointerenter",
2831 EventBubbles::DoesNotBubble,
2832 pointer_id,
2833 pointer_type,
2834 is_primary,
2835 new_target,
2836 Some(old_target),
2837 );
2838 }
2839
2840 fn implicit_release_pointer_capture(
2843 &self,
2844 cx: &mut JSContext,
2845 pointer_id: i32,
2846 pointer_type: &str,
2847 is_primary: bool,
2848 ) {
2849 let capture_element = self
2850 .pointer_capture_target
2851 .borrow()
2852 .get(&pointer_id)
2853 .map(|el| DomRoot::from_ref(&**el));
2854 if let Some(capture_element) = capture_element {
2855 if capture_element.upcast::<Node>().is_connected() {
2856 self.fire_pointer_capture_event(
2857 cx,
2858 "lostpointercapture",
2859 pointer_id,
2860 pointer_type,
2861 is_primary,
2862 &capture_element,
2863 );
2864 if let Some(hover_target) = self.current_hover_target.get() {
2867 self.fire_pointer_capture_boundary_transition(
2868 cx,
2869 pointer_id,
2870 pointer_type,
2871 is_primary,
2872 &capture_element,
2873 &hover_target,
2874 );
2875 }
2876 } else {
2877 let document = self.window.Document();
2878 self.fire_pointer_capture_event_at_target(
2879 cx,
2880 "lostpointercapture",
2881 pointer_id,
2882 pointer_type,
2883 is_primary,
2884 document.upcast::<EventTarget>(),
2885 );
2886 }
2887 }
2888 self.pending_pointer_capture
2889 .safe_borrow_mut(cx.no_gc())
2890 .remove(&pointer_id);
2891 self.pointer_capture_target
2892 .safe_borrow_mut(cx.no_gc())
2893 .remove(&pointer_id);
2894 }
2895
2896 fn process_pending_pointer_capture(
2900 &self,
2901 cx: &mut JSContext,
2902 pointer_id: i32,
2903 pointer_type: &str,
2904 is_primary: bool,
2905 ) {
2906 let pending = self
2907 .pending_pointer_capture
2908 .borrow()
2909 .get(&pointer_id)
2910 .map(|el| DomRoot::from_ref(&**el));
2911 let current = self
2912 .pointer_capture_target
2913 .borrow()
2914 .get(&pointer_id)
2915 .map(|el| DomRoot::from_ref(&**el));
2916
2917 let pending_connected = pending
2920 .as_ref()
2921 .is_some_and(|el| el.upcast::<Node>().is_connected());
2922
2923 let pointer_is_active = self.is_active_pointer(pointer_id);
2926
2927 match (&pending, ¤t) {
2928 (Some(pending_el), None) if pending_connected => {
2929 if pointer_is_active {
2930 if let Some(hover_target) = self.current_hover_target.get() {
2933 self.fire_pointer_capture_boundary_transition(
2934 cx,
2935 pointer_id,
2936 pointer_type,
2937 is_primary,
2938 &hover_target,
2939 pending_el,
2940 );
2941 }
2942 self.fire_pointer_capture_event(
2943 cx,
2944 "gotpointercapture",
2945 pointer_id,
2946 pointer_type,
2947 is_primary,
2948 pending_el,
2949 );
2950 self.pointer_capture_target
2951 .safe_borrow_mut(cx.no_gc())
2952 .insert(pointer_id, Dom::from_ref(pending_el));
2953 } else {
2954 self.pending_pointer_capture
2955 .safe_borrow_mut(cx.no_gc())
2956 .remove(&pointer_id);
2957 }
2958 },
2959 (Some(pending_el), Some(current_el))
2960 if pending_connected && pending_el != current_el =>
2961 {
2962 self.fire_pointer_capture_event(
2963 cx,
2964 "lostpointercapture",
2965 pointer_id,
2966 pointer_type,
2967 is_primary,
2968 current_el,
2969 );
2970 if pointer_is_active {
2971 self.fire_pointer_capture_boundary_transition(
2972 cx,
2973 pointer_id,
2974 pointer_type,
2975 is_primary,
2976 current_el,
2977 pending_el,
2978 );
2979 self.fire_pointer_capture_event(
2980 cx,
2981 "gotpointercapture",
2982 pointer_id,
2983 pointer_type,
2984 is_primary,
2985 pending_el,
2986 );
2987 self.pointer_capture_target
2988 .safe_borrow_mut(cx.no_gc())
2989 .insert(pointer_id, Dom::from_ref(pending_el));
2990 } else {
2991 self.pending_pointer_capture
2992 .safe_borrow_mut(cx.no_gc())
2993 .remove(&pointer_id);
2994 self.pointer_capture_target
2995 .safe_borrow_mut(cx.no_gc())
2996 .remove(&pointer_id);
2997 }
2998 },
2999 (None, Some(current_el)) | (Some(_), Some(current_el)) if !pending_connected => {
3000 self.fire_pointer_capture_event(
3001 cx,
3002 "lostpointercapture",
3003 pointer_id,
3004 pointer_type,
3005 is_primary,
3006 current_el,
3007 );
3008 if let Some(hover_target) = self.current_hover_target.get() {
3011 self.fire_pointer_capture_boundary_transition(
3012 cx,
3013 pointer_id,
3014 pointer_type,
3015 is_primary,
3016 current_el,
3017 &hover_target,
3018 );
3019 }
3020 self.pointer_capture_target
3021 .safe_borrow_mut(cx.no_gc())
3022 .remove(&pointer_id);
3023 if !pending_connected {
3024 self.pending_pointer_capture
3025 .safe_borrow_mut(cx.no_gc())
3026 .remove(&pointer_id);
3027 }
3028 },
3029 _ => {},
3030 }
3031 }
3032
3033 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
3034 pub(crate) fn install_drag_gesture(&self, drag_gesture: DragGesture) {
3035 *self.drag_gesture.borrow_mut() = Some(drag_gesture);
3036 }
3037
3038 fn maybe_activate_link_due_to_auxiliary_click(&self, cx: &mut JSContext, node: &Node) {
3041 let no_gc = cx.no_gc();
3042 let Some(url) = node
3043 .inclusive_ancestors_unrooted(no_gc, ShadowIncluding::Yes)
3044 .find_map(|node| {
3045 if let Some(anchor) = node.downcast::<HTMLAnchorElement>() {
3046 return anchor.full_href_url_for_user_interface();
3047 }
3048 if let Some(area) = node.downcast::<HTMLAreaElement>() {
3049 return area.full_href_url_for_user_interface();
3050 }
3051 None
3052 })
3053 else {
3054 return;
3055 };
3056 self.open_url_in_new_webview(cx, url);
3057 }
3058
3059 pub(crate) fn open_url_in_new_webview(&self, cx: &mut JSContext, url: ServoUrl) {
3063 let Some(browsing_context) = self.window.document_unrooted(cx.no_gc()).browsing_context()
3064 else {
3065 return;
3066 };
3067 let (browsing_context, new) = browsing_context.choose_a_navigable(
3068 cx,
3069 DOMString::from_static("_blank"),
3070 true, );
3072 let Some(browsing_context) = browsing_context else {
3073 return;
3074 };
3075 assert!(new);
3076 let Some(target_document) = browsing_context.document() else {
3077 return;
3078 };
3079
3080 let target_window = target_document.window();
3081 let target = Trusted::new(target_window);
3082 let load_data = LoadData::new_for_new_unrelated_webview(url);
3083 let task = task!(open_link_in_new_webview: move |cx| {
3084 navigate(cx, &target.root(), NavigationHistoryBehavior::Replace, false, load_data);
3085 });
3086 target_document
3087 .owner_global()
3088 .task_manager()
3089 .dom_manipulation_task_source()
3090 .queue(task);
3091 }
3092}
3093
3094fn keyboard_event_fires_keypress_event(keyboard_event: &EmbedderKeyboardEvent) -> bool {
3102 let is_character_value_key = matches!(
3103 keyboard_event.event.key,
3104 Key::Character(_) | Key::Named(NamedKey::Enter)
3105 );
3106 keyboard_event.event.state == KeyState::Down &&
3107 is_character_value_key &&
3108 !keyboard_event.event.is_composing
3109}
3110
3111pub(crate) fn character_to_code(character: char) -> Option<Code> {
3112 Some(match character.to_ascii_lowercase() {
3113 '`' => Code::Backquote,
3114 '\\' => Code::Backslash,
3115 '[' | '{' => Code::BracketLeft,
3116 ']' | '}' => Code::BracketRight,
3117 ',' | '<' => Code::Comma,
3118 '0' => Code::Digit0,
3119 '1' => Code::Digit1,
3120 '2' => Code::Digit2,
3121 '3' => Code::Digit3,
3122 '4' => Code::Digit4,
3123 '5' => Code::Digit5,
3124 '6' => Code::Digit6,
3125 '7' => Code::Digit7,
3126 '8' => Code::Digit8,
3127 '9' => Code::Digit9,
3128 '=' => Code::Equal,
3129 'a' => Code::KeyA,
3130 'b' => Code::KeyB,
3131 'c' => Code::KeyC,
3132 'd' => Code::KeyD,
3133 'e' => Code::KeyE,
3134 'f' => Code::KeyF,
3135 'g' => Code::KeyG,
3136 'h' => Code::KeyH,
3137 'i' => Code::KeyI,
3138 'j' => Code::KeyJ,
3139 'k' => Code::KeyK,
3140 'l' => Code::KeyL,
3141 'm' => Code::KeyM,
3142 'n' => Code::KeyN,
3143 'o' => Code::KeyO,
3144 'p' => Code::KeyP,
3145 'q' => Code::KeyQ,
3146 'r' => Code::KeyR,
3147 's' => Code::KeyS,
3148 't' => Code::KeyT,
3149 'u' => Code::KeyU,
3150 'v' => Code::KeyV,
3151 'w' => Code::KeyW,
3152 'x' => Code::KeyX,
3153 'y' => Code::KeyY,
3154 'z' => Code::KeyZ,
3155 '-' => Code::Minus,
3156 '.' => Code::Period,
3157 '\'' | '"' => Code::Quote,
3158 ';' => Code::Semicolon,
3159 '/' => Code::Slash,
3160 ' ' => Code::Space,
3161 _ => return None,
3162 })
3163}