Skip to main content

script/dom/document/
document_event_handler.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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::{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::editing::{
56    editing_action_from_composition_event, editing_action_from_keyboard_event,
57};
58use crate::dom::document::focus::FocusableArea;
59use crate::dom::document::interactive_element_command::InteractiveElementCommand;
60use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
61#[cfg(feature = "gamepad")]
62use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
63#[cfg(feature = "gamepad")]
64use crate::dom::gamepad::gamepadevent::GamepadEventType;
65use crate::dom::html::form_controls::htmlinputelement::HTMLInputElement;
66use crate::dom::inputevent::HitTestResult;
67use crate::dom::iterators::ShadowIncluding;
68use crate::dom::keyboardevent::KeyboardEvent;
69use crate::dom::node::focus::FocusTrigger;
70use crate::dom::node::{self, Node, NodeTraits};
71use crate::dom::pointerevent::{PointerEvent, PointerId};
72use crate::dom::types::{
73    CompositionEvent, Element, Event, EventTarget, GlobalScope, HTMLAnchorElement, HTMLElement,
74    HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList, WheelEvent, Window,
75};
76use crate::dom::window::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
77use crate::drag::drag_gesture::DragGesture;
78use crate::realms::enter_auto_realm;
79
80/// A data structure used for tracking the current click count. This can be
81/// reset to 0 if a mouse button event happens at a sufficient distance or time
82/// from the previous one.
83///
84/// From <https://w3c.github.io/uievents/#current-click-count>:
85/// > Implementations MUST maintain the current click count when generating mouse
86/// > events. This MUST be a non-negative integer indicating the number of consecutive
87/// > clicks of a pointing device button within a specific time. The delay after which
88/// > the count resets is specific to the environment configuration.
89#[derive(Default, JSTraceable, MallocSizeOf)]
90struct ClickCountingInfo {
91    time: Option<Instant>,
92    #[no_trace]
93    point: Option<Point2D<f32, CSSPixel>>,
94    #[no_trace]
95    button: Option<MouseButton>,
96    count: usize,
97}
98
99impl ClickCountingInfo {
100    fn reset_click_count_if_necessary(
101        &mut self,
102        button: MouseButton,
103        point_in_frame: Point2D<f32, CSSPixel>,
104    ) {
105        let (Some(previous_button), Some(previous_point), Some(previous_time)) =
106            (self.button, self.point, self.time)
107        else {
108            assert_eq!(self.count, 0);
109            return;
110        };
111
112        let double_click_timeout =
113            Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
114        let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
115
116        // Calculate distance between this click and the previous click.
117        let line = point_in_frame - previous_point;
118        let distance = (line.dot(line) as f64).sqrt();
119        if previous_button != button ||
120            Instant::now().duration_since(previous_time) > double_click_timeout ||
121            distance > double_click_distance_threshold as f64
122        {
123            self.reset_click_count();
124        }
125    }
126
127    fn increment_click_count(
128        &mut self,
129        button: MouseButton,
130        point: Point2D<f32, CSSPixel>,
131    ) -> usize {
132        self.time = Some(Instant::now());
133        self.point = Some(point);
134        self.button = Some(button);
135        self.count += 1;
136        self.count
137    }
138
139    fn reset_click_count(&mut self) {
140        self.count = 0;
141        self.time = None;
142        self.point = None;
143    }
144}
145
146/// The [`DocumentEventHandler`] is a structure responsible for handling input events for
147/// the [`crate::Document`] and storing data related to event handling. It exists to
148/// decrease the size of the [`crate::Document`] structure.
149#[derive(JSTraceable, MallocSizeOf)]
150#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
151pub(crate) struct DocumentEventHandler {
152    /// The [`Window`] element for this [`DocumentEventHandler`].
153    window: Dom<Window>,
154    /// Pending input events, to be handled at the next rendering opportunity.
155    #[no_trace]
156    #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
157    pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
158    /// The index of the last mouse move event in the pending input events queue.
159    mouse_move_event_index: DomRefCell<Option<usize>>,
160    /// The [`InputEventId`]s of mousemove events that have been coalesced.
161    #[no_trace]
162    #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
163    coalesced_mouse_move_event_ids: DomRefCell<Vec<InputEventId>>,
164    /// The index of the last wheel event in the pending input events queue.
165    /// This is non-standard behaviour.
166    /// According to <https://www.w3.org/TR/pointerevents/#dfn-coalesced-events>,
167    /// we should only coalesce `pointermove` events.
168    wheel_event_index: DomRefCell<Option<usize>>,
169    /// The [`InputEventId`]s of wheel events that have been coalesced.
170    #[no_trace]
171    #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
172    coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
173    /// <https://w3c.github.io/uievents/#event-type-dblclick>
174    click_counting_info: DomRefCell<ClickCountingInfo>,
175    #[no_trace]
176    last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
177    /// The current button state of the mouse. This is used to ensure that
178    /// `pointerup` and `pointerdown` events are only sent when transitioning from
179    /// having no mouse buttons pressed to having any and vice-versa.
180    #[no_trace]
181    mouse_button_state: Cell<MouseButtons>,
182    /// The element that is currently hovered by the cursor.
183    current_hover_target: MutNullableDom<Element>,
184    /// The element that was most recently activated during a mouse button press or touch
185    /// event.
186    current_active_element: MutNullableDom<Element>,
187    /// The element that was most recently clicked.
188    most_recently_clicked_element: MutNullableDom<Element>,
189    /// The most recent mouse movement point, used for processing `mouseleave` events.
190    #[no_trace]
191    most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
192    /// The currently set [`Cursor`] or `None` if the `Document` isn't being hovered
193    /// by the cursor.
194    #[no_trace]
195    current_cursor: Cell<Option<Cursor>>,
196    /// <http://w3c.github.io/touch-events/#dfn-active-touch-point>
197    active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
198    /// Whether the current touch sequence may still generate compatibility mouse events.
199    touch_sequence_click_allowed: Cell<bool>,
200    /// The active keyboard modifiers for the WebView. This is updated when receiving any input event.
201    #[no_trace]
202    active_keyboard_modifiers: Cell<Modifiers>,
203    /// Map from touch identifier to pointer ID for active touch points
204    active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
205    /// Counter for generating unique pointer IDs for touch inputs
206    next_touch_pointer_id: Cell<i32>,
207    /// A map holding information about currently registered access key handlers.
208    access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
209    /// Map from pointer ID to pending pointer capture target override element.
210    /// This is set by setPointerCapture and cleared by releasePointerCapture.
211    /// <https://w3c.github.io/pointerevents/#pointer-capture>
212    pending_pointer_capture: DomRefCell<FxHashMap<i32, Dom<Element>>>,
213    /// Map from pointer ID to the actual/current pointer capture target.
214    /// Updated during process_pending_pointer_capture when events are dispatched.
215    pointer_capture_target: DomRefCell<FxHashMap<i32, Dom<Element>>>,
216    /// The current drag gesture, if one exists. Events that affect this drag
217    /// gesture will be forwarded to it.
218    drag_gesture: DomRefCell<Option<DragGesture>>,
219}
220
221impl DocumentEventHandler {
222    pub(crate) fn new(window: &Window) -> Self {
223        Self {
224            window: Dom::from_ref(window),
225            pending_input_events: Default::default(),
226            mouse_move_event_index: Default::default(),
227            coalesced_mouse_move_event_ids: Default::default(),
228            wheel_event_index: Default::default(),
229            coalesced_wheel_event_ids: Default::default(),
230            click_counting_info: Default::default(),
231            last_mouse_button_down_point: Default::default(),
232            mouse_button_state: Cell::new(MouseButtons::empty()),
233            current_hover_target: Default::default(),
234            current_active_element: Default::default(),
235            most_recently_clicked_element: Default::default(),
236            most_recent_mousemove_point: Default::default(),
237            current_cursor: Default::default(),
238            active_touch_points: Default::default(),
239            touch_sequence_click_allowed: Cell::new(true),
240            active_keyboard_modifiers: Default::default(),
241            active_pointer_ids: Default::default(),
242            next_touch_pointer_id: Cell::new(1),
243            access_key_handlers: Default::default(),
244            pending_pointer_capture: Default::default(),
245            pointer_capture_target: Default::default(),
246            drag_gesture: Default::default(),
247        }
248    }
249
250    /// Note a pending input event, to be processed at the next `update_the_rendering` task.
251    pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
252        let mut pending_input_events = self.pending_input_events.borrow_mut();
253        if matches!(event.event.event, InputEvent::MouseMove(..)) {
254            // First try to replace any existing mouse move event.
255            if let Some(mouse_move_event) = self
256                .mouse_move_event_index
257                .borrow()
258                .and_then(|index| pending_input_events.get_mut(index))
259            {
260                self.coalesced_mouse_move_event_ids
261                    .borrow_mut()
262                    .push(mouse_move_event.event.id);
263                *mouse_move_event = event;
264                return;
265            }
266
267            *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
268        }
269
270        if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
271            // Coalesce with any existing pending wheel event by summing deltas.
272            if let Some(existing_constellation_wheel_event) = self
273                .wheel_event_index
274                .borrow()
275                .and_then(|index| pending_input_events.get_mut(index)) &&
276                let InputEvent::Wheel(ref mut existing_wheel_event) =
277                    existing_constellation_wheel_event.event.event &&
278                existing_wheel_event.delta.mode == new_wheel_event.delta.mode
279            {
280                self.coalesced_wheel_event_ids
281                    .borrow_mut()
282                    .push(existing_constellation_wheel_event.event.id);
283                existing_wheel_event.delta.x += new_wheel_event.delta.x;
284                existing_wheel_event.delta.y += new_wheel_event.delta.y;
285                existing_wheel_event.delta.z += new_wheel_event.delta.z;
286                existing_wheel_event.point = new_wheel_event.point;
287                existing_constellation_wheel_event.event.id = event.event.id;
288                return;
289            }
290
291            *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
292        }
293
294        pending_input_events.push(event);
295    }
296
297    /// Whether or not this [`Document`] has any pending input events to be processed during
298    /// "update the rendering."
299    pub(crate) fn has_pending_input_events(&self) -> bool {
300        !self.pending_input_events.borrow().is_empty()
301    }
302
303    pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
304        #[cfg(target_os = "macos")]
305        {
306            self.active_keyboard_modifiers
307                .get()
308                .contains(Modifiers::META)
309        }
310        #[cfg(not(target_os = "macos"))]
311        {
312            self.active_keyboard_modifiers
313                .get()
314                .contains(Modifiers::CONTROL)
315        }
316    }
317
318    pub(crate) fn handle_pending_input_events(&self, cx: &mut JSContext) {
319        debug_assert!(
320            !self.pending_input_events.borrow().is_empty(),
321            "handle_pending_input_events called with no events"
322        );
323        let mut realm = enter_auto_realm(cx, &*self.window);
324        let cx = &mut realm.current_realm();
325
326        // Reset the mouse and wheel event indices.
327        *self.mouse_move_event_index.safe_borrow_mut(cx.no_gc()) = None;
328        *self.wheel_event_index.safe_borrow_mut(cx.no_gc()) = None;
329        let pending_input_events =
330            mem::take(&mut *self.pending_input_events.safe_borrow_mut(cx.no_gc()));
331        let mut coalesced_mouse_move_event_ids = mem::take(
332            &mut *self
333                .coalesced_mouse_move_event_ids
334                .safe_borrow_mut(cx.no_gc()),
335        );
336        let mut coalesced_wheel_event_ids =
337            mem::take(&mut *self.coalesced_wheel_event_ids.safe_borrow_mut(cx.no_gc()));
338
339        let mut input_event_outcomes = Vec::with_capacity(
340            pending_input_events.len() +
341                coalesced_mouse_move_event_ids.len() +
342                coalesced_wheel_event_ids.len(),
343        );
344        // TODO: For some of these we still aren't properly calculating whether or not
345        // the event was handled or if `preventDefault()` was called on it. Each of
346        // these cases needs to be examined and some of them either fire more than one
347        // event or fire events later. We have to make a good decision about what to
348        // return to the embedder when that happens.
349        for event in pending_input_events {
350            self.active_keyboard_modifiers
351                .set(event.active_keyboard_modifiers);
352            let result = match event.event.event {
353                InputEvent::MouseButton(mouse_button_event) => {
354                    self.handle_native_mouse_button_event(cx, mouse_button_event, &event);
355                    InputEventResult::default()
356                },
357                InputEvent::MouseMove(_) => {
358                    self.handle_native_mouse_move_event(cx, &event);
359                    input_event_outcomes.extend(
360                        mem::take(&mut coalesced_mouse_move_event_ids)
361                            .into_iter()
362                            .map(|id| InputEventOutcome {
363                                id,
364                                result: InputEventResult::default(),
365                            }),
366                    );
367                    InputEventResult::default()
368                },
369                InputEvent::MouseLeftViewport(mouse_leave_event) => {
370                    self.handle_mouse_left_viewport_event(cx, &event, &mouse_leave_event);
371                    InputEventResult::default()
372                },
373                InputEvent::Touch(touch_event) => self.handle_touch_event(cx, touch_event, &event),
374                InputEvent::Wheel(wheel_event) => {
375                    let result = self.handle_wheel_event(cx, wheel_event, &event);
376                    input_event_outcomes.extend(
377                        mem::take(&mut coalesced_wheel_event_ids)
378                            .into_iter()
379                            .map(|id| InputEventOutcome { id, result }),
380                    );
381                    result
382                },
383                InputEvent::Keyboard(keyboard_event) => {
384                    self.handle_keyboard_event(cx, keyboard_event)
385                },
386                InputEvent::Ime(ime_event) => self.handle_ime_event(cx, ime_event),
387                #[cfg(feature = "gamepad")]
388                InputEvent::Gamepad(gamepad_event) => {
389                    self.handle_gamepad_event(gamepad_event);
390                    InputEventResult::default()
391                },
392                InputEvent::EditingAction(clipboard_action) => {
393                    let document = self.window.Document();
394                    let focused_node = document
395                        .focus_handler()
396                        .focused_area()
397                        .dom_anchor(&document);
398                    let editing_context = document.editing_context(cx.no_gc(), &focused_node);
399                    document.handle_clipboard_action(cx, &editing_context, clipboard_action)
400                },
401            };
402
403            input_event_outcomes.push(InputEventOutcome {
404                id: event.event.id,
405                result,
406            });
407        }
408
409        self.notify_embedder_that_events_were_handled(input_event_outcomes);
410    }
411
412    fn notify_embedder_that_events_were_handled(
413        &self,
414        input_event_outcomes: Vec<InputEventOutcome>,
415    ) {
416        // Wait to to notify the embedder that the event was handled until all pending DOM
417        // event processing is finished.
418        let trusted_window = Trusted::new(&*self.window);
419        self.window
420            .as_global_scope()
421            .task_manager()
422            .dom_manipulation_task_source()
423            .queue_unconditionally(task!(notify_webdriver_input_event_completed: move || {
424                let window = trusted_window.root();
425                window.send_to_embedder(
426                    EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
427            }));
428    }
429
430    /// When an event should be fired on the element that has focus, this returns the target. If
431    /// there is no associated element with the focused area (such as when the viewport is focused),
432    /// then the body is returned. If no body is returned then the `Window` is returned.
433    ///
434    /// From <https://w3c.github.io/uievents/#events-keyboard-event-order>:
435    /// > The event target of a key event is the currently focused element which is
436    /// > processing the keyboard activity. This is often an HTML input element or a textual
437    /// > element which is editable, but MAY be an element defined by the host language to
438    /// > accept keyboard input for non-text purposes, such as the activation of an
439    /// > accelerator key or trigger of some other behavior. If no suitable element is in
440    /// > focus, the event target will be the HTML body element if available, otherwise the
441    /// > root element.
442    pub(crate) fn target_for_events_following_focus(&self) -> DomRoot<EventTarget> {
443        let document = self.window.Document();
444        match &*document.focus_handler().focused_area() {
445            FocusableArea::Node { node, .. } => DomRoot::from_ref(node.upcast()),
446            FocusableArea::IFrameViewport { iframe_element, .. } => {
447                DomRoot::from_ref(iframe_element.upcast())
448            },
449            FocusableArea::Viewport => document
450                .GetBody()
451                .map(DomRoot::upcast)
452                .or_else(|| document.GetDocumentElement().map(DomRoot::upcast))
453                .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast())),
454        }
455    }
456
457    pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
458        if cursor == self.current_cursor.get() {
459            return;
460        }
461        self.current_cursor.set(cursor);
462        self.window.send_to_embedder(EmbedderMsg::SetCursor(
463            self.window.webview_id(),
464            cursor.unwrap_or_default(),
465        ));
466    }
467
468    fn handle_mouse_left_viewport_event(
469        &self,
470        cx: &mut JSContext,
471        input_event: &ConstellationInputEvent,
472        mouse_leave_event: &MouseLeftViewportEvent,
473    ) {
474        if let Some(current_hover_target) = self.current_hover_target.get() {
475            let current_hover_target = current_hover_target.upcast::<Node>();
476            for element in current_hover_target
477                .inclusive_ancestors(ShadowIncluding::Yes)
478                .filter_map(DomRoot::downcast::<Element>)
479            {
480                element.set_hover_state(false);
481            }
482
483            if let Some(hit_test_result) =
484                self.most_recent_mousemove_point.get().and_then(|point| {
485                    self.window
486                        .hit_test_from_point_in_viewport(HitTestFlags::empty(), point)
487                })
488            {
489                let mouse_out_event = MouseEvent::new_for_platform_motion_event(
490                    cx,
491                    &self.window,
492                    FireMouseEventType::Out,
493                    &hit_test_result,
494                    input_event,
495                );
496
497                // Fire pointerout before mouseout
498                mouse_out_event
499                    .to_pointer_hover_event(cx, "pointerout")
500                    .upcast::<Event>()
501                    .fire(cx, current_hover_target.upcast());
502
503                mouse_out_event
504                    .upcast::<Event>()
505                    .fire(cx, current_hover_target.upcast());
506
507                self.handle_mouse_enter_leave_event(
508                    cx,
509                    current_hover_target,
510                    None,
511                    FireMouseEventType::Leave,
512                    &hit_test_result,
513                    input_event,
514                );
515            }
516        }
517
518        // We do not want to always inform the embedder that cursor has been set to the
519        // default cursor, in order to avoid a timing issue when moving between `<iframe>`
520        // elements. There is currently no way to control which `SetCursor` message will
521        // reach the embedder first. This is safer when leaving the `WebView` entirely.
522        if !mouse_leave_event.focus_moving_to_another_iframe {
523            // If focus is moving to another frame, it will decide what the new status
524            // text is, but if this mouse leave event is leaving the WebView entirely,
525            // then clear it.
526            self.window
527                .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
528            self.set_cursor(None);
529        } else {
530            self.current_cursor.set(None);
531        }
532
533        self.current_hover_target.set(None);
534        self.most_recent_mousemove_point.set(None);
535    }
536
537    fn handle_mouse_enter_leave_event(
538        &self,
539        cx: &mut JSContext,
540        event_target: &Node,
541        related_target: Option<&Node>,
542        event_type: FireMouseEventType,
543        hit_test_result: &HitTestResult,
544        input_event: &ConstellationInputEvent,
545    ) {
546        assert!(matches!(
547            event_type,
548            FireMouseEventType::Enter | FireMouseEventType::Leave
549        ));
550
551        let common_ancestor = match related_target.as_ref() {
552            Some(related_target) => event_target
553                .common_ancestor_in_flat_tree(cx.no_gc(), related_target)
554                .unwrap_or_else(|| DomRoot::from_ref(event_target)),
555            None => DomRoot::from_ref(event_target),
556        };
557
558        // We need to create a target chain in case the event target shares
559        // its boundaries with its ancestors.
560        let mut targets: Vec<_> = event_target
561            .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
562            .take_while(|node| *node != *common_ancestor)
563            .map(|node| node.as_rooted())
564            .collect();
565
566        // The order for dispatching mouseenter/pointerenter events starts from the topmost
567        // common ancestor of the event target and the related target.
568        if event_type == FireMouseEventType::Enter {
569            targets.reverse();
570        }
571
572        let pointer_event_name = match event_type {
573            FireMouseEventType::Enter => "pointerenter",
574            FireMouseEventType::Leave => "pointerleave",
575            _ => unreachable!(),
576        };
577
578        for target in targets {
579            let mouse_event = MouseEvent::new_for_platform_motion_event(
580                cx,
581                &self.window,
582                event_type,
583                hit_test_result,
584                input_event,
585            );
586            mouse_event
587                .upcast::<Event>()
588                .set_related_target(related_target.as_ref().map(|target| target.upcast()));
589
590            // Fire pointer event before mouse event
591            mouse_event
592                .to_pointer_hover_event(cx, pointer_event_name)
593                .upcast::<Event>()
594                .fire(cx, target.upcast());
595
596            // Fire mouse event
597            mouse_event.upcast::<Event>().fire(cx, target.upcast());
598        }
599    }
600
601    /// <https://w3c.github.io/uievents/#handle-native-mouse-move>
602    fn handle_native_mouse_move_event(
603        &self,
604        cx: &mut JSContext,
605        input_event: &ConstellationInputEvent,
606    ) {
607        // First check if the capture target is disconnected and release it if so.
608        // This must happen before any pointer event fires.
609        let pointer_id = PointerId::Mouse as i32;
610        let released_disconnected =
611            self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
612
613        let hit_test_flags = if self
614            .drag_gesture
615            .borrow()
616            .as_ref()
617            .is_some_and(DragGesture::need_dom_position_from_hit_test)
618        {
619            HitTestFlags::IncludeDomPosition
620        } else {
621            HitTestFlags::empty()
622        };
623
624        // Always do full hit test so we can keep `current_hover_target` in sync
625        // with the actual element under the pointer. Boundary events for hover
626        // transitions are suppressed while pointer capture is active: per spec,
627        // pointer events are retargeted to the capture element.
628        let Some(hit_test_result) = self
629            .window
630            .hit_test_from_input_event(hit_test_flags, input_event)
631        else {
632            return;
633        };
634
635        {
636            let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
637            if maybe_drag_gesture.as_mut().is_some_and(|drag_gesture| {
638                !drag_gesture.handle_mouse_move_event(cx, input_event, &hit_test_result)
639            }) {
640                *maybe_drag_gesture = None;
641            }
642        }
643
644        let old_mouse_move_point = self
645            .most_recent_mousemove_point
646            .replace(Some(hit_test_result.point_in_frame));
647        // Always dispatch compatibility `mousemove` events, even at the same
648        // point as the previous one.
649        let is_compatibility_event_for_touch = matches!(
650            input_event.event.event,
651            InputEvent::MouseMove(ref event) if event.is_compatibility_event_for_touch
652        );
653        if !is_compatibility_event_for_touch &&
654            old_mouse_move_point == Some(hit_test_result.point_in_frame)
655        {
656            return;
657        }
658
659        // Update the cursor when the mouse moves, if it has changed.
660        self.set_cursor(Some(hit_test_result.cursor));
661
662        let Some(new_target) = hit_test_result
663            .node
664            .inclusive_ancestors(ShadowIncluding::Yes)
665            .find_map(DomRoot::downcast::<Element>)
666        else {
667            return;
668        };
669
670        let capture_is_active = self.get_pointer_capture_target(pointer_id).is_some();
671        let old_hover_target = self.current_hover_target.get();
672        let target_has_changed = old_hover_target
673            .as_ref()
674            .is_none_or(|old_target| *old_target != new_target);
675
676        // Here we know the target has changed, so we must update the state,
677        // dispatch mouseout to the previous one, mouseover to the new one.
678        if target_has_changed {
679            // Dispatch pointerout/mouseout and pointerleave/mouseleave to previous target.
680            if let Some(old_target) = self.current_hover_target.get() {
681                let old_target_is_ancestor_of_new_target = old_target
682                    .upcast::<Node>()
683                    .is_ancestor_of(new_target.upcast::<Node>());
684
685                // If the old target is an ancestor of the new target, this can be skipped
686                // completely, since the node's hover state will be reset below.
687                if !old_target_is_ancestor_of_new_target {
688                    for element in old_target
689                        .upcast::<Node>()
690                        .inclusive_ancestors(ShadowIncluding::Yes)
691                        .filter_map(DomRoot::downcast::<Element>)
692                    {
693                        element.set_hover_state(false);
694                    }
695                }
696
697                if !capture_is_active {
698                    let mouse_out_event = MouseEvent::new_for_platform_motion_event(
699                        cx,
700                        &self.window,
701                        FireMouseEventType::Out,
702                        &hit_test_result,
703                        input_event,
704                    );
705                    mouse_out_event
706                        .upcast::<Event>()
707                        .set_related_target(Some(new_target.upcast()));
708
709                    // Fire pointerout before mouseout
710                    mouse_out_event
711                        .to_pointer_hover_event(cx, "pointerout")
712                        .upcast::<Event>()
713                        .fire(cx, old_target.upcast());
714
715                    mouse_out_event
716                        .upcast::<Event>()
717                        .fire(cx, old_target.upcast());
718
719                    if !old_target_is_ancestor_of_new_target {
720                        let event_target = old_target.upcast::<Node>();
721                        let moving_into = Some(new_target.upcast::<Node>());
722                        self.handle_mouse_enter_leave_event(
723                            cx,
724                            event_target,
725                            moving_into,
726                            FireMouseEventType::Leave,
727                            &hit_test_result,
728                            input_event,
729                        );
730                    }
731                }
732            }
733
734            // Dispatch pointerover/mouseover and pointerenter/mouseenter to new target.
735            for element in new_target
736                .upcast::<Node>()
737                .inclusive_ancestors(ShadowIncluding::Yes)
738                .filter_map(DomRoot::downcast::<Element>)
739            {
740                element.set_hover_state(true);
741            }
742
743            if !capture_is_active {
744                let mouse_over_event = MouseEvent::new_for_platform_motion_event(
745                    cx,
746                    &self.window,
747                    FireMouseEventType::Over,
748                    &hit_test_result,
749                    input_event,
750                );
751                mouse_over_event
752                    .upcast::<Event>()
753                    .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
754
755                // Fire pointerover before mouseover
756                mouse_over_event
757                    .to_pointer_hover_event(cx, "pointerover")
758                    .upcast::<Event>()
759                    .dispatch(cx, new_target.upcast(), false);
760
761                mouse_over_event
762                    .upcast::<Event>()
763                    .dispatch(cx, new_target.upcast(), false);
764
765                let moving_from = old_hover_target
766                    .as_ref()
767                    .map(|old_target| old_target.upcast::<Node>());
768                let event_target = new_target.upcast::<Node>();
769                self.handle_mouse_enter_leave_event(
770                    cx,
771                    event_target,
772                    moving_from,
773                    FireMouseEventType::Enter,
774                    &hit_test_result,
775                    input_event,
776                );
777            }
778        }
779
780        // Send mousemove event to topmost target, unless it's an iframe, in which case
781        // `Paint` should have also sent an event to the inner document.
782        let mouse_event = MouseEvent::new_for_platform_motion_event(
783            cx,
784            &self.window,
785            FireMouseEventType::Move,
786            &hit_test_result,
787            input_event,
788        );
789
790        // Send pointermove event before mousemove.
791        // If pointer capture is active, retarget the pointer/mouse events to
792        // the capture element. Boundary events (pointerover/out/enter/leave)
793        // already fired above use the actual hit-test target.
794        let pointer_target = self
795            .get_pointer_capture_target(pointer_id)
796            .map(DomRoot::upcast::<EventTarget>)
797            .unwrap_or_else(|| DomRoot::from_ref(new_target.upcast::<EventTarget>()));
798
799        let pointer_event = mouse_event.to_pointer_event(cx, Atom::from("pointermove"));
800        pointer_event.upcast::<Event>().set_composed(true);
801        pointer_event.upcast::<Event>().fire(cx, &pointer_target);
802
803        // Process pending pointer capture after firing event, but skip if we just
804        // released a disconnected capture to avoid immediately re-capturing.
805        // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
806        if !released_disconnected {
807            self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
808        }
809
810        // Send mousemove event. Routed to the capture target when capture is active.
811        mouse_event.upcast::<Event>().fire(cx, &pointer_target);
812
813        self.update_current_hover_target_and_status(cx.no_gc(), Some(new_target));
814    }
815
816    fn update_current_hover_target_and_status(
817        &self,
818        no_gc: &NoGC,
819        new_hover_target: Option<DomRoot<Element>>,
820    ) {
821        let current_hover_target = self.current_hover_target.get();
822        if current_hover_target == new_hover_target {
823            return;
824        }
825
826        let previous_hover_target = self.current_hover_target.get();
827        self.current_hover_target.set(new_hover_target.as_deref());
828
829        // If the new hover target is an anchor with a status value, inform the embedder
830        // of the new value.
831        if let Some(target) = self.current_hover_target.get() &&
832            let Some(anchor) = target
833                .upcast::<Node>()
834                .inclusive_ancestors(ShadowIncluding::Yes)
835                .find_map(DomRoot::downcast::<HTMLAnchorElement>)
836        {
837            let status = anchor
838                .full_href_url_for_user_interface(no_gc)
839                .map(|url| url.to_string());
840            self.window
841                .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
842            return;
843        }
844
845        // No state was set above, which means that the new value of the status in the embedder
846        // should be `None`. Set that now. If `previous_hover_target` is `None` that means this
847        // is the first mouse move event we are seeing after getting the cursor. In that case,
848        // we also clear the status.
849        if previous_hover_target.is_none_or(|previous_hover_target| {
850            previous_hover_target
851                .upcast::<Node>()
852                .inclusive_ancestors(ShadowIncluding::Yes)
853                .any(|node| node.is::<HTMLAnchorElement>())
854        }) {
855            self.window
856                .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
857        }
858    }
859
860    pub(crate) fn handle_refresh_cursor(&self) {
861        let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
862            return;
863        };
864
865        let Some(hit_test_result) = self
866            .window
867            .hit_test_from_point_in_viewport(HitTestFlags::empty(), most_recent_mousemove_point)
868        else {
869            return;
870        };
871
872        self.set_cursor(Some(hit_test_result.cursor));
873    }
874
875    fn set_active_element(&self, no_gc: &NoGC, original_target: &Element) {
876        let find_element_for_activation = |element: &Element| {
877            let node: &Node = element.upcast();
878            if node.is_in_ua_widget() &&
879                let Some(containing_shadow_root) = node.containing_shadow_root()
880            {
881                return containing_shadow_root.Host();
882            }
883
884            // If the element is a label, the activable element is the control element.
885            if node.type_id() ==
886                NodeTypeId::Element(ElementTypeId::HTMLElement(
887                    HTMLElementTypeId::HTMLLabelElement,
888                ))
889            {
890                let label = element.downcast::<HTMLLabelElement>().unwrap();
891                if let Some(control) = label.GetControl(no_gc) {
892                    return DomRoot::from_ref(control.upcast::<Element>());
893                }
894            }
895
896            DomRoot::from_ref(element)
897        };
898        let element_for_activation = find_element_for_activation(original_target);
899
900        // This might happen if the user is alternating between different pointing devices
901        // such as two mice or a mouse and touch events. Only keep the latest activated.
902        if let Some(currently_active_element) = self.current_active_element.get() {
903            if currently_active_element == element_for_activation {
904                return;
905            }
906            self.unset_active_element();
907        }
908
909        element_for_activation.set_active_state(true);
910        self.current_active_element
911            .set(Some(&*element_for_activation));
912    }
913
914    fn unset_active_element(&self) {
915        if let Some(active_element) = self.current_active_element.take() {
916            active_element.set_active_state(false);
917        }
918    }
919
920    /// <https://w3c.github.io/uievents/#mouseevent-algorithms>
921    /// Handles native mouse down, mouse up, mouse click.
922    fn handle_native_mouse_button_event(
923        &self,
924        cx: &mut JSContext,
925        mouse_button_event: MouseButtonEvent,
926        input_event: &ConstellationInputEvent,
927    ) {
928        {
929            let mut maybe_drag_gesture = self.drag_gesture.borrow_mut();
930            if maybe_drag_gesture
931                .as_mut()
932                .is_some_and(|drag_gesture| !drag_gesture.handle_mouse_button_event(input_event))
933            {
934                *maybe_drag_gesture = None;
935            }
936        }
937
938        let flags = if input_event.primary_button_is_pressed() ||
939            input_event.auxiliary_button_is_pressed()
940        {
941            HitTestFlags::IncludeDomPosition
942        } else {
943            HitTestFlags::empty()
944        };
945        // Ignore all incoming events without a hit test.
946        let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
947        else {
948            return;
949        };
950
951        debug!(
952            "{:?}: at {:?}",
953            mouse_button_event.action, hit_test_result.point_in_frame
954        );
955
956        // Set the sequential focus navigation starting point for any mouse button down event, no
957        // matter if the target is not a node.
958        let document = self.window.Document();
959        if mouse_button_event.action == MouseButtonAction::Down {
960            document
961                .focus_handler()
962                .set_sequential_focus_navigation_starting_point(&hit_test_result.node);
963        }
964
965        let Some(element) = hit_test_result
966            .node
967            .inclusive_ancestors(ShadowIncluding::Yes)
968            .find_map(DomRoot::downcast::<Element>)
969        else {
970            return;
971        };
972
973        let node = element.upcast::<Node>();
974        debug!("{:?} on {:?}", mouse_button_event.action, node.debug_str());
975
976        // <https://html.spec.whatwg.org/multipage/#selector-active>
977        // > If the element is being actively pointed at the element is being activated.
978        // Disabled elements can also be activated, so this must happen before the
979        // early return below.
980        if mouse_button_event.button == MouseButton::Primary {
981            if mouse_button_event.action == MouseButtonAction::Down {
982                self.set_active_element(cx.no_gc(), &element);
983            }
984            if mouse_button_event.action == MouseButtonAction::Up {
985                self.unset_active_element();
986            }
987        }
988
989        // https://w3c.github.io/uievents/#hit-test
990        // Prevent mouse event if element is disabled.
991        // TODO: also inert.
992        if element.is_actually_disabled() {
993            return;
994        }
995
996        let mouse_event_type = match mouse_button_event.action {
997            embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
998            embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
999        };
1000
1001        // From <https://w3c.github.io/pointerevents/#dfn-mousedown>
1002        // and <https://w3c.github.io/pointerevents/#mouseup>:
1003        //
1004        // UIEvent.detail: indicates the current click count incremented by one. For
1005        // example, if no click happened before the mousedown, detail will contain
1006        // the value 1
1007        if mouse_button_event.action == MouseButtonAction::Down {
1008            self.click_counting_info
1009                .safe_borrow_mut(cx.no_gc())
1010                .reset_click_count_if_necessary(
1011                    mouse_button_event.button,
1012                    hit_test_result.point_in_frame,
1013                );
1014        }
1015
1016        let mouse_event = MouseEvent::for_platform_button_event(
1017            cx,
1018            mouse_event_type,
1019            mouse_button_event,
1020            input_event.pressed_mouse_buttons,
1021            &self.window,
1022            &hit_test_result,
1023            input_event.active_keyboard_modifiers,
1024            self.click_counting_info.borrow().count + 1,
1025        );
1026
1027        match mouse_button_event.action {
1028            MouseButtonAction::Down => {
1029                self.last_mouse_button_down_point
1030                    .set(Some(hit_test_result.point_in_frame));
1031
1032                // Step 6. Dispatch pointerdown event.
1033                let pointer_event_name = if self.mouse_button_state.get().is_empty() {
1034                    // From <https://w3c.github.io/pointerevents/#dfn-pointerdown>
1035                    // > The user agent MUST fire a pointer event named pointerdown when a pointer enters
1036                    // > the active buttons state. For mouse, this is when the device transitions from no
1037                    // > buttons depressed to at least one button depressed.
1038                    "pointerdown".into()
1039                } else {
1040                    // From <https://w3c.github.io/pointerevents/#dfn-pointermove>:
1041                    // > The user agent MUST fire a pointer event named pointermove when a pointer
1042                    // > changes any properties that don't fire pointerdown or pointerup events. This
1043                    // > includes any changes to coordinates, pressure, tangential pressure, tilt, twist,
1044                    // > contact geometry (width and height) or chorded buttons.
1045                    "pointermove".into()
1046                };
1047                let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1048
1049                // Check for pointer capture target for mouse events
1050                let pointer_id = PointerId::Mouse as i32;
1051
1052                // Release any disconnected capture target before firing pointer events
1053                let released_disconnected =
1054                    self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1055
1056                // Get the current capture target (before processing pending changes)
1057                let pointer_target = self
1058                    .get_pointer_capture_target(pointer_id)
1059                    .map(DomRoot::upcast::<EventTarget>)
1060                    .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1061
1062                // Update button state before firing so setPointerCapture works in handler.
1063                self.mouse_button_state
1064                    .set(input_event.pressed_mouse_buttons);
1065
1066                let pointer_event_result =
1067                    pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1068
1069                // Process pending pointer capture after firing event, but skip if we just
1070                // released a disconnected capture to avoid immediately re-capturing.
1071                // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
1072                if !released_disconnected {
1073                    self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1074                }
1075
1076                // Step 7. Let result = dispatch event at target
1077                let result = mouse_event
1078                    .upcast::<Event>()
1079                    .dispatch(cx, node.upcast(), false);
1080
1081                // If neither the `mousedown` nor the `pointerdown` event had `preventDefault()`
1082                // called on them, call the default mousdown handler on retargeted node
1083                // (`mouse_event.target` is mutated by `dispatch` above).
1084                if result &&
1085                    pointer_event_result &&
1086                    let Some(node) = mouse_event
1087                        .upcast::<Event>()
1088                        .GetTarget()
1089                        .and_then(DomRoot::downcast::<Node>)
1090                {
1091                    let editing_context = document.editing_context(cx, &node);
1092                    editing_context.handle_mousedown_event(cx, &mouse_event, &hit_test_result);
1093                }
1094
1095                // Step 8. If result is true and target is a focusable area
1096                // that is click focusable, then Run the focusing steps at target.
1097                if result {
1098                    // Note that this differs from the specification, because we are going to look
1099                    // for the first inclusive ancestor that is click focusable and then focus it.
1100                    // See documentation for [`Node::find_click_focusable_area`].
1101                    document
1102                        .focus_handler()
1103                        .focus(cx, &node.find_click_focusable_area(cx));
1104                }
1105
1106                // Step 9. If mbutton is the secondary mouse button, then
1107                // Maybe show context menu with native, target.
1108                if let MouseButton::Secondary = mouse_button_event.button {
1109                    self.maybe_show_context_menu(cx, node.upcast(), &hit_test_result, input_event);
1110                }
1111            },
1112            // https://w3c.github.io/pointerevents/#dfn-handle-native-mouse-up
1113            MouseButtonAction::Up => {
1114                // Step 6. Dispatch pointerup event.
1115                let mouse_button_state = self.mouse_button_state.get();
1116                let exactly_one_button = mouse_button_state.exactly_one_button_pressed();
1117                let pointer_event_name = if exactly_one_button {
1118                    // From <https://w3c.github.io/pointerevents/#dfn-pointerup>:
1119                    // > The user agent MUST fire a pointer event named pointerup when a pointer leaves
1120                    // > the active buttons state. For mouse, this is when the device transitions from at
1121                    // > least one button depressed to no buttons depressed.
1122                    "pointerup".into()
1123                } else {
1124                    // From <https://w3c.github.io/pointerevents/#dfn-pointermove>:
1125                    // > The user agent MUST fire a pointer event named pointermove when a pointer
1126                    // > changes any properties that don't fire pointerdown or pointerup events. This
1127                    // > includes any changes to coordinates, pressure, tangential pressure, tilt, twist,
1128                    // > contact geometry (width and height) or chorded buttons.
1129                    "pointermove".into()
1130                };
1131                let pointer_event = mouse_event.to_pointer_event(cx, pointer_event_name);
1132
1133                // Check for pointer capture target for mouse events
1134                let pointer_id = PointerId::Mouse as i32;
1135
1136                // Release any disconnected capture target before firing pointer events
1137                let released_disconnected =
1138                    self.release_disconnected_pointer_capture(cx, pointer_id, "mouse", true);
1139
1140                // Get the current capture target (before any state changes)
1141                let pointer_target = self
1142                    .get_pointer_capture_target(pointer_id)
1143                    .map(DomRoot::upcast::<EventTarget>)
1144                    .unwrap_or_else(|| DomRoot::from_ref(node.upcast::<EventTarget>()));
1145
1146                pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1147
1148                // Update button state after firing event, so setPointerCapture/releasePointerCapture
1149                // work during the pointerup handler (pointer is still "active").
1150                self.mouse_button_state
1151                    .set(input_event.pressed_mouse_buttons);
1152
1153                // Process pending pointer capture after decrementing button count, but skip
1154                // if we just released a disconnected capture to avoid immediately re-capturing.
1155                // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
1156                if !released_disconnected {
1157                    self.process_pending_pointer_capture(cx, pointer_id, "mouse", true);
1158                }
1159
1160                // Implicitly release pointer capture when last button was released
1161                if exactly_one_button {
1162                    self.implicit_release_pointer_capture(cx, pointer_id, "mouse", true);
1163                }
1164
1165                // Step 7. dispatch event at target.
1166                mouse_event
1167                    .upcast::<Event>()
1168                    .dispatch(cx, node.upcast(), false);
1169
1170                // Click counts should still work for other buttons even though they
1171                // do not trigger "click" and "dblclick" events, so we increment
1172                // even when those events are not fired.
1173                self.click_counting_info
1174                    .safe_borrow_mut(cx.no_gc())
1175                    .increment_click_count(
1176                        mouse_button_event.button,
1177                        hit_test_result.point_in_frame,
1178                    );
1179
1180                self.maybe_trigger_click_for_mouse_button_down_event(
1181                    cx,
1182                    mouse_button_event,
1183                    input_event,
1184                    &hit_test_result,
1185                    &element,
1186                );
1187            },
1188        }
1189    }
1190
1191    /// <https://w3c.github.io/pointerevents/#handle-native-mouse-click>
1192    /// <https://w3c.github.io/pointerevents/#handle-native-mouse-double-click>
1193    fn maybe_trigger_click_for_mouse_button_down_event(
1194        &self,
1195        cx: &mut JSContext,
1196        event: MouseButtonEvent,
1197        input_event: &ConstellationInputEvent,
1198        hit_test_result: &HitTestResult,
1199        element: &Element,
1200    ) {
1201        if event.button != MouseButton::Primary {
1202            return;
1203        }
1204
1205        let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
1206            return;
1207        };
1208
1209        let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
1210        let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
1211        if distance > maximum_click_distance {
1212            return;
1213        }
1214
1215        // From <https://w3c.github.io/pointerevents/#click>
1216        // > The click event type MUST be dispatched on the topmost event target indicated by the
1217        // > pointer, when the user presses down and releases the primary pointer button.
1218        self.most_recently_clicked_element.set(Some(element));
1219
1220        let click_count = self.click_counting_info.borrow().count;
1221        element.set_click_in_progress(true);
1222        MouseEvent::for_platform_button_event(
1223            cx,
1224            atom!("click"),
1225            event,
1226            input_event.pressed_mouse_buttons,
1227            &self.window,
1228            hit_test_result,
1229            input_event.active_keyboard_modifiers,
1230            click_count,
1231        )
1232        .upcast::<Event>()
1233        .dispatch(cx, element.upcast(), false);
1234        element.set_click_in_progress(false);
1235
1236        // The firing of "dbclick" events is dependent on the platform, so we have
1237        // some flexibility here. Some browsers on some platforms only fire a
1238        // "dbclick" when the click count is 2 and others essentially fire one for
1239        // every 2 clicks in a sequence. In all cases, browsers set the click count
1240        // `detail` property to 2.
1241        //
1242        // We follow the latter approach here, considering that every sequence of
1243        // even numbered clicks is a series of double clicks.
1244        if click_count.is_multiple_of(2) {
1245            MouseEvent::for_platform_button_event(
1246                cx,
1247                Atom::from("dblclick"),
1248                event,
1249                input_event.pressed_mouse_buttons,
1250                &self.window,
1251                hit_test_result,
1252                input_event.active_keyboard_modifiers,
1253                2,
1254            )
1255            .upcast::<Event>()
1256            .dispatch(cx, element.upcast(), false);
1257        }
1258    }
1259
1260    /// <https://www.w3.org/TR/pointerevents4/#maybe-show-context-menu>
1261    fn maybe_show_context_menu(
1262        &self,
1263        cx: &mut JSContext,
1264        target: &EventTarget,
1265        hit_test_result: &HitTestResult,
1266        input_event: &ConstellationInputEvent,
1267    ) {
1268        // <https://w3c.github.io/pointerevents/#contextmenu>
1269        let menu_event = PointerEvent::new(
1270            cx,
1271            &self.window,                // window
1272            "contextmenu".into(),        // type
1273            EventBubbles::Bubbles,       // can_bubble
1274            EventCancelable::Cancelable, // cancelable
1275            Some(&self.window),          // view
1276            0,                           // detail
1277            hit_test_result.point_in_frame.to_i32(),
1278            hit_test_result.point_in_frame.to_i32(),
1279            hit_test_result
1280                .point_relative_to_initial_containing_block
1281                .to_i32(),
1282            input_event.active_keyboard_modifiers,
1283            MouseButton::Secondary,
1284            input_event.pressed_mouse_buttons,
1285            None,                            // related_target
1286            None,                            // point_in_target
1287            PointerId::Mouse as i32,         // pointer_id
1288            1,                               // width
1289            1,                               // height
1290            0.5,                             // pressure
1291            0.0,                             // tangential_pressure
1292            0,                               // tilt_x
1293            0,                               // tilt_y
1294            0,                               // twist
1295            PI / 2.0,                        // altitude_angle
1296            0.0,                             // azimuth_angle
1297            DOMString::from_static("mouse"), // pointer_type
1298            true,                            // is_primary
1299            vec![],                          // coalesced_events
1300            vec![],                          // predicted_events
1301        );
1302        menu_event.upcast::<Event>().set_composed(true);
1303
1304        // Step 3. Let result = dispatch menuevent at target.
1305        let result = menu_event.upcast::<Event>().fire(cx, target);
1306
1307        // Step 4. If result is true, then show the UA context menu
1308        if result {
1309            self.window
1310                .Document()
1311                .embedder_controls()
1312                .show_context_menu(cx.no_gc(), hit_test_result);
1313        };
1314    }
1315
1316    fn handle_touch_event(
1317        &self,
1318        cx: &mut JSContext,
1319        event: EmbedderTouchEvent,
1320        input_event: &ConstellationInputEvent,
1321    ) -> InputEventResult {
1322        let flags = HitTestFlags::empty();
1323        // Ignore all incoming events without a hit test.
1324        let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1325        else {
1326            self.update_active_touch_points_when_early_return(event);
1327            return Default::default();
1328        };
1329
1330        let TouchId(identifier) = event.touch_id;
1331
1332        let Some(element) = hit_test_result
1333            .node
1334            .inclusive_ancestors(ShadowIncluding::Yes)
1335            .find_map(DomRoot::downcast::<Element>)
1336        else {
1337            self.update_active_touch_points_when_early_return(event);
1338            return Default::default();
1339        };
1340
1341        let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1342        let window = &*self.window;
1343
1344        let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1345        let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1346        let page_x =
1347            Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1348        let page_y =
1349            Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1350
1351        // This is used to construct pointerevent and touchdown event.
1352        let pointer_touch = Touch::new(
1353            cx,
1354            window,
1355            identifier,
1356            &current_target,
1357            client_x,
1358            client_y, // TODO: Get real screen coordinates?
1359            client_x,
1360            client_y,
1361            page_x,
1362            page_y,
1363        );
1364
1365        // Dispatch pointer event before updating active touch points and before touch event.
1366        let pointer_event_name = match event.event_type {
1367            TouchEventType::Down => "pointerdown",
1368            TouchEventType::Move => "pointermove",
1369            TouchEventType::Up => "pointerup",
1370            TouchEventType::Cancel => "pointercancel",
1371        };
1372
1373        // Map the embedder-side subtype to the spec `pointerType` string.
1374        let pointer_type = match event.pointer_type {
1375            TouchPointerType::Pen => "pen",
1376            TouchPointerType::Touch => "touch",
1377        };
1378
1379        // Get or create pointer ID for this touch
1380        let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1381        let is_primary = self.is_primary_pointer(pointer_id);
1382
1383        // For touch devices (which don't support hover), fire pointerover/pointerenter
1384        // <https://w3c.github.io/pointerevents/#mapping-for-devices-that-do-not-support-hover>
1385        if matches!(event.event_type, TouchEventType::Down) {
1386            // Fire pointerover
1387            let pointer_over = pointer_touch.to_pointer_event(
1388                cx,
1389                window,
1390                "pointerover",
1391                pointer_id,
1392                is_primary,
1393                pointer_type,
1394                input_event.active_keyboard_modifiers,
1395                true, // cancelable
1396                Some(hit_test_result.point_in_node),
1397            );
1398            pointer_over.upcast::<Event>().fire(cx, &current_target);
1399
1400            // Fire pointerenter hierarchically (from topmost ancestor to target)
1401            self.fire_pointer_event_for_touch(
1402                cx,
1403                &element,
1404                &pointer_touch,
1405                pointer_id,
1406                "pointerenter",
1407                is_primary,
1408                pointer_type,
1409                input_event,
1410                &hit_test_result,
1411            );
1412        }
1413
1414        // Release any disconnected capture target before firing pointer events,
1415        // but not for pointercancel: let implicit_release handle that so
1416        // lostpointercapture fires after pointercancel per spec.
1417        let released_disconnected = if matches!(event.event_type, TouchEventType::Cancel) {
1418            false
1419        } else {
1420            self.release_disconnected_pointer_capture(cx, pointer_id, pointer_type, is_primary)
1421        };
1422
1423        // Get the current capture target (before processing pending changes)
1424        let pointer_target = self
1425            .get_pointer_capture_target(pointer_id)
1426            .map(DomRoot::upcast::<EventTarget>)
1427            .unwrap_or_else(|| current_target.clone());
1428
1429        let pointer_event = pointer_touch.to_pointer_event(
1430            cx,
1431            window,
1432            pointer_event_name,
1433            pointer_id,
1434            is_primary,
1435            pointer_type,
1436            input_event.active_keyboard_modifiers,
1437            event.is_cancelable(),
1438            Some(hit_test_result.point_in_node),
1439        );
1440        pointer_event.upcast::<Event>().fire(cx, &pointer_target);
1441
1442        // Process pending pointer capture after firing event, but skip if we just
1443        // released a disconnected capture (to avoid immediately re-capturing).
1444        // Also skip for pointercancel: per spec, process pending only runs for
1445        // pointerdown, pointermove, and pointerup, not pointercancel.
1446        // https://w3c.github.io/pointerevents/#process-pending-pointer-capture
1447        if !released_disconnected && !matches!(event.event_type, TouchEventType::Cancel) {
1448            self.process_pending_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1449        }
1450
1451        // Implicitly release pointer capture on pointerup or pointercancel
1452        // For pointercancel, this fires lostpointercapture after the pointercancel event.
1453        if matches!(
1454            event.event_type,
1455            TouchEventType::Up | TouchEventType::Cancel
1456        ) {
1457            self.implicit_release_pointer_capture(cx, pointer_id, pointer_type, is_primary);
1458        }
1459
1460        // For touch devices, fire pointerout/pointerleave after pointerup/pointercancel
1461        // <https://w3c.github.io/pointerevents/#mapping-for-devices-that-do-not-support-hover>
1462        if matches!(
1463            event.event_type,
1464            TouchEventType::Up | TouchEventType::Cancel
1465        ) {
1466            // Fire pointerout
1467            let pointer_out = pointer_touch.to_pointer_event(
1468                cx,
1469                window,
1470                "pointerout",
1471                pointer_id,
1472                is_primary,
1473                pointer_type,
1474                input_event.active_keyboard_modifiers,
1475                true, // cancelable
1476                Some(hit_test_result.point_in_node),
1477            );
1478            pointer_out.upcast::<Event>().fire(cx, &current_target);
1479
1480            // Fire pointerleave hierarchically (from target to topmost ancestor)
1481            self.fire_pointer_event_for_touch(
1482                cx,
1483                &element,
1484                &pointer_touch,
1485                pointer_id,
1486                "pointerleave",
1487                is_primary,
1488                pointer_type,
1489                input_event,
1490                &hit_test_result,
1491            );
1492        }
1493
1494        let (touch_dispatch_target, changed_touch) = match event.event_type {
1495            TouchEventType::Down => {
1496                self.touch_sequence_click_allowed.set(true);
1497                // Add a new touch point
1498                self.active_touch_points
1499                    .safe_borrow_mut(cx.no_gc())
1500                    .push(Dom::from_ref(&*pointer_touch));
1501                self.set_active_element(cx.no_gc(), &element);
1502                (current_target, pointer_touch)
1503            },
1504            _ => {
1505                // From <https://w3c.github.io/touch-events/#dfn-touchend>:
1506                // > For move/up/cancel:
1507                // > The target of this event must be the same Element on which the touch
1508                // > point started when it was first placed on the surface, even if the touch point
1509                // > has since moved outside the interactive area of the target element.
1510                let active_touch_points = self.active_touch_points.borrow();
1511                let Some(index) = active_touch_points
1512                    .iter()
1513                    .position(|point| point.Identifier() == identifier)
1514                else {
1515                    warn!("No active touch point for {:?}", event.event_type);
1516                    return Default::default();
1517                };
1518                // This is the original target that was selected during `touchstart` event handling.
1519                let original_target = active_touch_points[index].Target();
1520                drop(active_touch_points);
1521
1522                let touch_with_touchstart_target = Touch::new(
1523                    cx,
1524                    window,
1525                    identifier,
1526                    &original_target,
1527                    client_x,
1528                    client_y,
1529                    client_x,
1530                    client_y,
1531                    page_x,
1532                    page_y,
1533                );
1534
1535                let mut active_touch_points = self.active_touch_points.safe_borrow_mut(cx.no_gc());
1536                // Update or remove the stored touch
1537                match event.event_type {
1538                    TouchEventType::Move => {
1539                        active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1540                    },
1541                    TouchEventType::Up | TouchEventType::Cancel => {
1542                        active_touch_points.swap_remove(index);
1543                        self.remove_pointer_id_for_touch(identifier);
1544                        self.unset_active_element();
1545                    },
1546                    TouchEventType::Down => unreachable!("Should have been handled above"),
1547                }
1548                (original_target, touch_with_touchstart_target)
1549            },
1550        };
1551
1552        rooted_vec!(let mut target_touches);
1553        target_touches.extend(
1554            self.active_touch_points
1555                .borrow()
1556                .iter()
1557                .filter(|touch| touch.Target() == touch_dispatch_target)
1558                .cloned(),
1559        );
1560
1561        let event_name = match event.event_type {
1562            TouchEventType::Down => "touchstart",
1563            TouchEventType::Move => "touchmove",
1564            TouchEventType::Up => "touchend",
1565            TouchEventType::Cancel => "touchcancel",
1566        };
1567
1568        let touches = TouchList::new(cx, window, self.active_touch_points.borrow().r());
1569        let changed_touches = TouchList::new(cx, window, from_ref(&&*changed_touch));
1570        let target_touches = TouchList::new(cx, window, target_touches.r());
1571
1572        let touch_event = TouchEvent::new(
1573            cx,
1574            window,
1575            event_name.into(),
1576            EventBubbles::Bubbles,
1577            EventCancelable::from(event.is_cancelable()),
1578            EventComposed::Composed,
1579            Some(window),
1580            0i32,
1581            &touches,
1582            &changed_touches,
1583            &target_touches,
1584            // FIXME: modifier keys
1585            false,
1586            false,
1587            false,
1588            false,
1589        );
1590        let dom_event = touch_event.upcast::<Event>();
1591        dom_event.fire(cx, &touch_dispatch_target);
1592        let flags = dom_event.flags();
1593
1594        // `preventDefault()` on `touchstart` or `touchend` suppresses compat mouse events.
1595        if flags.contains(EventFlags::Canceled) &&
1596            matches!(event.event_type, TouchEventType::Down | TouchEventType::Up)
1597        {
1598            self.touch_sequence_click_allowed.set(false);
1599        }
1600
1601        // Generate the compatibility mouse events synchronously as part of
1602        // `touchend`, before the embedder is told the touch event was handled.
1603        let synthesizes_mouse_events = matches!(event.event_type, TouchEventType::Up) &&
1604            event.can_synthesize_mouse_events &&
1605            self.touch_sequence_click_allowed.get() &&
1606            !flags.contains(EventFlags::Canceled) &&
1607            self.active_touch_points.borrow().is_empty();
1608
1609        if synthesizes_mouse_events {
1610            self.synthesize_compatibility_mouse_events(cx, &event, input_event);
1611        } else if matches!(
1612            event.event_type,
1613            TouchEventType::Up | TouchEventType::Cancel
1614        ) {
1615            // A touchend that doesn't synthesize compatibility mouse events ends the
1616            // click sequence.
1617            // See https://github.com/servo/servo/issues/46497#issuecomment-6030515485>
1618            self.click_counting_info
1619                .safe_borrow_mut(cx.no_gc())
1620                .reset_click_count();
1621        }
1622
1623        flags.into()
1624    }
1625
1626    /// Generate the compatibility mouse events for a tap while handling `touchend`.
1627    ///
1628    /// <http://w3c.github.io/touch-events/#mouse-events>
1629    fn synthesize_compatibility_mouse_events(
1630        &self,
1631        cx: &mut JSContext,
1632        event: &EmbedderTouchEvent,
1633        input_event: &ConstellationInputEvent,
1634    ) {
1635        // Reuse the touch event's hit test result and keyboard modifiers.
1636        let make_input_event =
1637            |input_event_to_send: InputEvent, pressed_mouse_buttons: MouseButtons| {
1638                ConstellationInputEvent {
1639                    hit_test_result: input_event.hit_test_result.clone(),
1640                    pressed_mouse_buttons,
1641                    active_keyboard_modifiers: input_event.active_keyboard_modifiers,
1642                    event: input_event_to_send.into(),
1643                }
1644            };
1645
1646        let point = event.point;
1647        let mouse_move_input_event = make_input_event(
1648            InputEvent::MouseMove(MouseMoveEvent::new_compatibility_for_touch(point)),
1649            MouseButtons::empty(),
1650        );
1651        self.handle_native_mouse_move_event(cx, &mouse_move_input_event);
1652
1653        for (action, pressed_mouse_buttons) in [
1654            (MouseButtonAction::Down, MouseButtons::Primary),
1655            (MouseButtonAction::Up, MouseButtons::empty()),
1656        ] {
1657            let button_event = MouseButtonEvent::new(action, MouseButton::Primary, point);
1658            self.handle_native_mouse_button_event(
1659                cx,
1660                button_event,
1661                &make_input_event(InputEvent::MouseButton(button_event), pressed_mouse_buttons),
1662            );
1663        }
1664    }
1665
1666    /// Updates the active touch points when a hit test fails early.
1667    ///
1668    /// - For `Down`: No action needed; a failed down event won't create an active point.
1669    /// - For `Move`: No action needed; position information is unavailable, so we cannot update.
1670    /// - For `Up`/`Cancel`: Remove the corresponding touch point and its pointer ID mapping.
1671    ///
1672    /// When a touchup or touchcancel occurs at that touch point,
1673    /// a warning is triggered: Received touchup/touchcancel event for a non-active touch point.
1674    fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1675        match event.event_type {
1676            TouchEventType::Down | TouchEventType::Move => {},
1677            TouchEventType::Up | TouchEventType::Cancel => {
1678                let mut active_touch_points = self.active_touch_points.borrow_mut();
1679                if let Some(index) = active_touch_points
1680                    .iter()
1681                    .position(|t| t.Identifier() == event.touch_id.0)
1682                {
1683                    active_touch_points.swap_remove(index);
1684                    self.remove_pointer_id_for_touch(event.touch_id.0);
1685                } else {
1686                    warn!(
1687                        "Received {:?} for a non-active touch point {}",
1688                        event.event_type, event.touch_id.0
1689                    );
1690                }
1691            },
1692        }
1693    }
1694
1695    /// The entry point for all key processing for web content
1696    fn handle_keyboard_event(
1697        &self,
1698        cx: &mut JSContext,
1699        keyboard_event: EmbedderKeyboardEvent,
1700    ) -> InputEventResult {
1701        let target = &self.target_for_events_following_focus();
1702        let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1703            cx,
1704            &self.window,
1705            keyboard_event.event.state.event_type().into(),
1706            &keyboard_event.event,
1707        );
1708
1709        let event = keyevent.upcast::<Event>();
1710        event.set_composed(true);
1711        event.fire(cx, target);
1712
1713        // From https://w3c.github.io/uievents/#keys-cancelable-keys:
1714        // > Canceling the default action of a keydown event MUST NOT affect its
1715        // > respective keyup event, but it MUST prevent the respective beforeinput and
1716        // > input (and keypress if supported) events from being generated.
1717        let mut flags = event.flags();
1718        if keyboard_event.event.state != KeyState::Down || flags.contains(EventFlags::Canceled) {
1719            return flags.into();
1720        }
1721
1722        // From <https://w3c.github.io/uievents/#keypress-event-order>:
1723        // > The keypress event type MUST be dispatched after the keydown event and before
1724        // > the keyup event associated with the same key.
1725        let fires_keypress_event = keyboard_event_fires_keypress_event(&keyboard_event);
1726        if fires_keypress_event {
1727            let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1728                cx,
1729                &self.window,
1730                atom!("keypress"),
1731                &keyboard_event.event,
1732            );
1733            keypress_event.upcast::<Event>().set_composed(true);
1734            let event = keypress_event.upcast::<Event>();
1735            event.fire(cx, target);
1736            flags |= event.flags();
1737        }
1738
1739        // If the event was canceled or consumed during event propagation do not run the
1740        // default keydown event handler.
1741        if flags.intersects(EventFlags::Handled | EventFlags::Canceled) {
1742            return flags.into();
1743        }
1744
1745        self.run_default_keydown_handler(cx, target, &keyboard_event, flags.into())
1746    }
1747
1748    fn handle_ime_event(&self, cx: &mut JSContext, event: ImeEvent) -> InputEventResult {
1749        let document = self.window.Document();
1750        let composition_event = match event {
1751            ImeEvent::Dismissed => {
1752                document.focus_handler().focus(cx, &FocusableArea::Viewport);
1753                return Default::default();
1754            },
1755            ImeEvent::Composition(composition_event) => composition_event,
1756        };
1757
1758        // spec: https://w3c.github.io/uievents/#compositionstart
1759        // spec: https://w3c.github.io/uievents/#compositionupdate
1760        // spec: https://w3c.github.io/uievents/#compositionend
1761        // > Event.target : focused element processing the composition
1762        let focused_area = document.focus_handler().focused_area();
1763        let Some(focused_element) = focused_area.element() else {
1764            // Event is only dispatched if there is a focused element.
1765            return Default::default();
1766        };
1767
1768        let cancelable = composition_event.state == CompositionState::Start;
1769        let event = CompositionEvent::new(
1770            cx,
1771            &self.window,
1772            composition_event.state.event_type().into(),
1773            true,
1774            cancelable,
1775            Some(&self.window),
1776            0,
1777            DOMString::from(composition_event.data.as_str()),
1778        );
1779
1780        let event = event.upcast::<Event>();
1781        event.fire(cx, focused_element.upcast());
1782        let composition_event_result: InputEventResult = event.flags().into();
1783
1784        if event
1785            .flags()
1786            .intersects(EventFlags::Canceled | EventFlags::Handled)
1787        {
1788            return composition_event_result;
1789        }
1790
1791        let editing_action = editing_action_from_composition_event(&composition_event);
1792        let editing_host = document.editing_context(cx.no_gc(), focused_element.upcast());
1793        if editing_host.perform_editing_action(cx, editing_action) {
1794            return composition_event_result | InputEventResult::Consumed;
1795        }
1796
1797        composition_event_result
1798    }
1799
1800    fn handle_wheel_event(
1801        &self,
1802        cx: &mut JSContext,
1803        event: EmbedderWheelEvent,
1804        input_event: &ConstellationInputEvent,
1805    ) -> InputEventResult {
1806        // Ignore all incoming events without a hit test.
1807        let flags = HitTestFlags::empty();
1808        let Some(hit_test_result) = self.window.hit_test_from_input_event(flags, input_event)
1809        else {
1810            return Default::default();
1811        };
1812
1813        let Some(el) = hit_test_result
1814            .node
1815            .inclusive_ancestors(ShadowIncluding::Yes)
1816            .find_map(DomRoot::downcast::<Element>)
1817        else {
1818            return Default::default();
1819        };
1820
1821        let node = el.upcast::<Node>();
1822        debug!(
1823            "wheel: on {:?} at {:?}",
1824            node.debug_str(),
1825            hit_test_result.point_in_frame
1826        );
1827
1828        let event_type = "wheel".into();
1829
1830        let cancelable = EventCancelable::from(
1831            self.window
1832                .upcast::<EventTarget>()
1833                .has_non_passive_listener(&event_type) ||
1834                node.inclusive_ancestors(ShadowIncluding::Yes)
1835                    .any(|target| {
1836                        target
1837                            .upcast::<EventTarget>()
1838                            .has_non_passive_listener(&event_type)
1839                    }),
1840        );
1841        // https://w3c.github.io/uievents/#event-wheelevents
1842        let dom_event = WheelEvent::new(
1843            cx,
1844            &self.window,
1845            event_type,
1846            EventBubbles::Bubbles,
1847            cancelable,
1848            Some(&self.window),
1849            0i32,
1850            hit_test_result.point_in_frame.to_i32(),
1851            hit_test_result.point_in_frame.to_i32(),
1852            hit_test_result
1853                .point_relative_to_initial_containing_block
1854                .to_i32(),
1855            input_event.active_keyboard_modifiers,
1856            MouseButton::Primary,
1857            input_event.pressed_mouse_buttons,
1858            None,
1859            None,
1860            // winit defines positive wheel delta values as revealing more content left/up.
1861            // https://docs.rs/winit-gtk/latest/winit/event/enum.MouseScrollDelta.html
1862            // This is the opposite of wheel delta in uievents
1863            // https://w3c.github.io/uievents/#dom-wheeleventinit-deltaz
1864            Finite::wrap(-event.delta.x),
1865            Finite::wrap(-event.delta.y),
1866            Finite::wrap(-event.delta.z),
1867            event.delta.mode as u32,
1868        );
1869
1870        let dom_event = dom_event.upcast::<Event>();
1871        dom_event.set_trusted(true);
1872        dom_event.set_composed(true);
1873        dom_event.fire(cx, node.upcast());
1874
1875        dom_event.flags().into()
1876    }
1877
1878    #[cfg(feature = "gamepad")]
1879    fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1880        match gamepad_event {
1881            EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1882                self.handle_gamepad_connect(
1883                    index.0,
1884                    name,
1885                    bounds.axis_bounds,
1886                    bounds.button_bounds,
1887                    supported_haptic_effects,
1888                );
1889            },
1890            EmbedderGamepadEvent::Disconnected(index) => {
1891                self.handle_gamepad_disconnect(index.0);
1892            },
1893            EmbedderGamepadEvent::Updated(index, update_type) => {
1894                self.receive_new_gamepad_button_or_axis(index.0, update_type);
1895            },
1896        };
1897    }
1898
1899    /// <https://www.w3.org/TR/gamepad/#dfn-gamepadconnected>
1900    #[cfg(feature = "gamepad")]
1901    fn handle_gamepad_connect(
1902        &self,
1903        // As the spec actually defines how to set the gamepad index, the GilRs index
1904        // is currently unused, though in practice it will almost always be the same.
1905        // More infra is currently needed to track gamepads across windows.
1906        _index: usize,
1907        name: String,
1908        axis_bounds: (f64, f64),
1909        button_bounds: (f64, f64),
1910        supported_haptic_effects: GamepadSupportedHapticEffects,
1911    ) {
1912        // Step 1. Let document be the current global object's associated Document; otherwise null.
1913        let doc = self.window.Document();
1914
1915        // Step 2. If document is not null and is not allowed to use the "gamepad" permission,
1916        //         then abort these steps.
1917        if !doc.allowed_to_use_feature(PermissionName::Gamepad) {
1918            return;
1919        }
1920
1921        let trusted_window = Trusted::new(&*self.window);
1922
1923        // Step 3. Queue a global task on the gamepad task source with the current global object
1924        //         to perform the following steps:
1925        self.window
1926            .upcast::<GlobalScope>()
1927            .task_manager()
1928            .gamepad_task_source()
1929            .queue(task!(gamepad_connected: move |cx| {
1930                let window = trusted_window.root();
1931
1932                // Step 3.1. Let gamepad be a new Gamepad representing the gamepad.
1933                // Step 3.2. Let navigator be gamepad's relevant global object's Navigator object.
1934                let navigator = window.Navigator(cx);
1935                let selected_index = navigator.select_gamepad_index();
1936                let gamepad = Gamepad::new(
1937                    cx,
1938                    &window,
1939                    selected_index,
1940                    name,
1941                    "standard".into(),
1942                    axis_bounds,
1943                    button_bounds,
1944                    supported_haptic_effects,
1945                    false,
1946                );
1947
1948                // Step 3.3. Set navigator.[[gamepads]][gamepad.index] to gamepad.
1949                navigator.set_gamepad(selected_index as usize, Some(&gamepad));
1950
1951                // Step 3.4. If navigator.[[hasGamepadGesture]] is true:
1952                if navigator.has_gamepad_gesture() {
1953                    // Step 3.4.1. Set gamepad.[[exposed]] to true.
1954                    gamepad.set_exposed(true);
1955                    // Step 3.4.2. If document is not null and is fully active, then fire an
1956                    //            event named gamepadconnected at gamepad's relevant global
1957                    //            object using GamepadEvent with its gamepad attribute
1958                    //            initialized to gamepad.
1959                    if window.Document().is_fully_active() {
1960                        gamepad.notify_event(cx, GamepadEventType::Connected);
1961                    }
1962                }
1963            }));
1964    }
1965
1966    /// <https://www.w3.org/TR/gamepad/#dfn-gamepaddisconnected>
1967    #[cfg(feature = "gamepad")]
1968    fn handle_gamepad_disconnect(&self, index: usize) {
1969        // Step 1. Let gamepad be the Gamepad representing the unavailable device.
1970        // Step 2. Queue a global task on the gamepad task source with
1971        //         gamepad's relevant global object to perform the following steps:
1972        let trusted_window = Trusted::new(&*self.window);
1973        self.window
1974            .upcast::<GlobalScope>()
1975            .task_manager()
1976            .gamepad_task_source()
1977            .queue(task!(gamepad_disconnected: move |cx| {
1978                let window = trusted_window.root();
1979                let navigator = window.Navigator(cx);
1980
1981                if let Some(gamepad) = navigator.get_gamepad(index) {
1982                    // Step 2.1. Set gamepad.[[connected]] to false.
1983                    gamepad.update_connected(false);
1984                    // Step 2.2. Let document be gamepad's relevant global object's
1985                    //           associated Document; otherwise null.
1986                    // Step 2.3. If gamepad.[[exposed]] is true and document is not null
1987                    //           and is fully active, then fire an event named
1988                    //           gamepaddisconnected at gamepad's relevant global object
1989                    //           using GamepadEvent with its gamepad attribute
1990                    //           initialized to gamepad.
1991                    if gamepad.exposed() && window.Document().is_fully_active() {
1992                        gamepad.notify_event(cx, GamepadEventType::Disconnected);
1993                    }
1994                }
1995
1996                // Step 2.4. Let navigator be gamepad's relevant global object's
1997                //           Navigator object.
1998                // Step 2.5. Set navigator.[[gamepads]][gamepad.index] to null.
1999                navigator.set_gamepad(index, None);
2000                // Step 2.6. While navigator.[[gamepads]] is not empty and the last item of
2001                //           navigator.[[gamepads]] is null, remove the last item of navigator.[[gamepads]].
2002                navigator.shrink_gamepads_list();
2003            }));
2004    }
2005
2006    /// <https://www.w3.org/TR/gamepad/#receiving-inputs>
2007    #[cfg(feature = "gamepad")]
2008    fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
2009        // Step 1. Let gamepad be the Gamepad object representing the device that received
2010        //         new button or axis input values.
2011        let trusted_window = Trusted::new(&*self.window);
2012
2013        // Step 2. Queue a global task on the gamepad task source with gamepad's
2014        //         relevant global object to update gamepad state for gamepad.
2015        self.window
2016            .upcast::<GlobalScope>()
2017            .task_manager()
2018            .gamepad_task_source()
2019            .queue(task!(update_gamepad_state: move |cx| {
2020                let window = trusted_window.root();
2021                let document = window.Document();
2022                document.event_handler().update_gamepad_state(cx, index, update_type);
2023            }));
2024    }
2025
2026    /// <https://w3c.github.io/gamepad/#dfn-update-gamepad-state>
2027    #[cfg(feature = "gamepad")]
2028    fn update_gamepad_state(
2029        &self,
2030        cx: &mut JSContext,
2031        gamepad_index: usize,
2032        update_type: GamepadUpdateType,
2033    ) {
2034        use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
2035        // Step 1. Let now be the current high resolution time given
2036        //         gamepad's relevant global object.
2037        let now = *self.window.Performance(cx).Now();
2038
2039        // Step 6. Let navigator be gamepad's relevant global object's
2040        //         Navigator object.
2041        let navigator = self.window.Navigator(cx);
2042
2043        if let Some(gamepad) = navigator.get_gamepad(gamepad_index) {
2044            // Step 2. Set gamepad.[[timestamp]] to now.
2045            gamepad.update_timestamp(now);
2046            // Step 3. Run the steps to map and normalize axes for gamepad.
2047            // Step 4. Run the steps to map and normalize buttons for gamepad.
2048            match update_type {
2049                GamepadUpdateType::Axis(axis_index, value) => {
2050                    gamepad.map_and_normalize_axes(axis_index, value);
2051                },
2052                GamepadUpdateType::Button(button_index, value) => {
2053                    gamepad.map_and_normalize_buttons(button_index, value);
2054                },
2055            };
2056            // TODO Step 5. Run the steps to record touches for gamepad.
2057
2058            // Step 7. If navigator.[[hasGamepadGesture]] is false and
2059            //         gamepad contains a gamepad user gesture:
2060            if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
2061                // Step 7.1. Set navigator.[[hasGamepadGesture]] to true.
2062                navigator.set_has_gamepad_gesture(true);
2063                // Step 7.2. For each connectedGamepad of navigator.[[gamepads]]:
2064                navigator
2065                    .get_connected_gamepad()
2066                    .iter()
2067                    .for_each(|connected_gamepad| {
2068                        // Step 7.2.1. Set connectedGamepad.[[exposed]] to true.
2069                        connected_gamepad.set_exposed(true);
2070                        // Step 7.2.2. Set connectedGamepad.[[timestamp]] to now.
2071                        connected_gamepad.update_timestamp(now);
2072                        // Step 7.2.3. Let document be gamepad's relevant global
2073                        //             object's associated Document; otherwise null.
2074                        // Step 7.2.4. If document is not null and is fully active,
2075                        //             then queue a global task on the gamepad task
2076                        //             source to fire an event named gamepadconnected
2077                        //             at gamepad's relevant global object.
2078                        let trusted_gamepad = Trusted::new(&**connected_gamepad);
2079                        if self.window.Document().is_fully_active() {
2080                            self.window
2081                                .upcast::<GlobalScope>()
2082                                .task_manager()
2083                                .gamepad_task_source()
2084                                .queue(task!(fire_gamepad_connected: move |cx| {
2085                                    let gamepad = trusted_gamepad.root();
2086                                    gamepad.notify_event(cx, GamepadEventType::Connected);
2087                                }));
2088                        }
2089                    });
2090            }
2091        }
2092    }
2093
2094    /// <https://drafts.csswg.org/cssom-view/#scrolling-events>
2095    #[expect(unsafe_code)]
2096    pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
2097        // If it is a viewport scroll.
2098        let document = self.window.Document();
2099        if scrolled_node.is_root() {
2100            document.handle_viewport_scroll_event();
2101        } else {
2102            // Otherwise, check whether it is for a relevant element within the document. For a `::before` or `::after`
2103            // pseudo element we follow Gecko or Chromium's behavior to ensure that the event reaches the originating
2104            // node.
2105            let node_id = node_id_from_scroll_id(scrolled_node.0 as usize);
2106            let node = unsafe {
2107                node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
2108            };
2109            let Some(element) = node
2110                .inclusive_ancestors(ShadowIncluding::Yes)
2111                .find_map(DomRoot::downcast::<Element>)
2112            else {
2113                return;
2114            };
2115
2116            element.handle_scroll_event();
2117        }
2118    }
2119
2120    /// <https://w3c.github.io/uievents/#keydown>
2121    ///
2122    /// > If the key is the Enter or (Space) key and the current focus is on a state-changing element,
2123    /// > the default action MUST be to dispatch a click event, and a DOMActivate event if that event
2124    /// > type is supported by the user agent.
2125    pub(crate) fn maybe_dispatch_simulated_click(
2126        &self,
2127        cx: &mut JSContext,
2128        node: &Node,
2129        event: &KeyboardEvent,
2130    ) {
2131        if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
2132        {
2133            return;
2134        }
2135
2136        // Check whether this node is a state-changing element. Note that the specification doesn't
2137        // seem to have a good definition of what "state-changing" means, so we merely check to
2138        // see if the element is activatable here.
2139        //
2140        // However, an input that is text or password is activatable (to be able to click on it),
2141        // but hitting enter should be processed as a character rather than trigger a click. Hence
2142        // we ignore those
2143        if node
2144            .downcast::<Element>()
2145            .and_then(Element::as_maybe_activatable)
2146            .is_none() ||
2147            node.downcast::<HTMLInputElement>()
2148                .is_some_and(|input| input.is_textual_or_password())
2149        {
2150            return;
2151        }
2152
2153        node.fire_synthetic_pointer_event_not_trusted(cx, atom!("click"));
2154        event.upcast::<Event>().mark_as_handled();
2155    }
2156
2157    pub(crate) fn run_default_keydown_handler(
2158        &self,
2159        cx: &mut JSContext,
2160        event_target: &EventTarget,
2161        event: &EmbedderKeyboardEvent,
2162        input_event_result: InputEventResult,
2163    ) -> InputEventResult {
2164        let document = self.window.Document();
2165        let node = event_target
2166            .downcast::<Node>()
2167            .unwrap_or_else(|| document.upcast());
2168
2169        let event = &event.event;
2170        if let Some(editing_action) = editing_action_from_keyboard_event(event) {
2171            let editing_host = document.editing_context(cx.no_gc(), node);
2172            if editing_host.perform_editing_action(cx, editing_action) {
2173                return input_event_result | InputEventResult::Consumed;
2174            }
2175        }
2176
2177        if self.maybe_handle_accesskey(cx, event) {
2178            return input_event_result | InputEventResult::Consumed;
2179        }
2180
2181        let mut is_space = false;
2182        let scroll = match &event.key {
2183            Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
2184            Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
2185            Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
2186            Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
2187            Key::Named(NamedKey::End) => KeyboardScroll::End,
2188            Key::Named(NamedKey::Home) => KeyboardScroll::Home,
2189            Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
2190            Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
2191            Key::Character(string) if string == " " => {
2192                is_space = true;
2193                if event.modifiers.contains(Modifiers::SHIFT) {
2194                    KeyboardScroll::PageUp
2195                } else {
2196                    KeyboardScroll::PageDown
2197                }
2198            },
2199            Key::Named(NamedKey::Tab) => {
2200                // From <https://w3c.github.io/uievents/#keydown>:
2201                //
2202                // > If the key is the Tab key, the default action MUST be to shift the document focus
2203                // > from the currently focused element (if any) to the new focused element, as
2204                // > described in Focus Event Types
2205                self.window
2206                    .Document()
2207                    .focus_handler()
2208                    .sequential_focus_navigation_via_keyboard_event(cx, event);
2209                return input_event_result | InputEventResult::Consumed;
2210            },
2211            _ => return input_event_result,
2212        };
2213
2214        if !event.modifiers.is_empty() && !is_space {
2215            return input_event_result;
2216        }
2217
2218        self.do_keyboard_scroll(cx, scroll);
2219        input_event_result | InputEventResult::Consumed
2220    }
2221
2222    pub(crate) fn do_keyboard_scroll(&self, cx: &mut JSContext, scroll: KeyboardScroll) {
2223        let scroll_axis = match scroll {
2224            KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2225            _ => ScrollingBoxAxis::Y,
2226        };
2227
2228        let document = self.window.Document();
2229        let mut scrolling_box = document
2230            .focus_handler()
2231            .focused_area()
2232            .element()
2233            .or(self.most_recently_clicked_element.get().as_deref())
2234            .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2235            .unwrap_or_else(|| {
2236                document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2237            });
2238
2239        while !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2240            // Always fall back to trying to scroll the entire document.
2241            if scrolling_box.is_viewport() {
2242                break;
2243            }
2244            let parent = scrolling_box.parent().unwrap_or_else(|| {
2245                document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2246            });
2247            scrolling_box = parent;
2248        }
2249
2250        let calculate_current_scroll_offset_and_delta = || {
2251            const LINE_HEIGHT: f32 = 76.0;
2252            const LINE_WIDTH: f32 = 76.0;
2253
2254            let current_scroll_offset = scrolling_box.scroll_position();
2255            (
2256                current_scroll_offset,
2257                match scroll {
2258                    KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2259                    KeyboardScroll::End => Vector2D::new(
2260                        0.0,
2261                        -current_scroll_offset.y + scrolling_box.content_size().height -
2262                            scrolling_box.size(cx.no_gc()).height,
2263                    ),
2264                    KeyboardScroll::PageDown => Vector2D::new(
2265                        0.0,
2266                        scrolling_box.size(cx.no_gc()).height - 2.0 * LINE_HEIGHT,
2267                    ),
2268                    KeyboardScroll::PageUp => Vector2D::new(
2269                        0.0,
2270                        2.0 * LINE_HEIGHT - scrolling_box.size(cx.no_gc()).height,
2271                    ),
2272                    KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2273                    KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2274                    KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2275                    KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2276                },
2277            )
2278        };
2279
2280        // If trying to scroll the viewport of this `Window` and this is the root `Document`
2281        // of the `WebView`, then send the srolling operation to the renderer, so that it
2282        // can properly pan any pinch zoom viewport.
2283        let parent_pipeline = self.window.parent_info();
2284        if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2285            let (_, delta) = calculate_current_scroll_offset_and_delta();
2286            self.window
2287                .paint_api()
2288                .scroll_viewport_by_delta(self.window.webview_id(), delta);
2289        }
2290
2291        // If this is the viewport and we cannot scroll, try to ask a parent viewport to scroll,
2292        // if we are inside an `<iframe>`.
2293        if !scrolling_box.can_keyboard_scroll_in_axis(cx.no_gc(), scroll_axis) {
2294            assert!(scrolling_box.is_viewport());
2295
2296            let window_proxy = document.window().window_proxy();
2297            if let Some(iframe) = window_proxy.frame_element() {
2298                // When the `<iframe>` is local (in this ScriptThread), we can
2299                // synchronously chain up the keyboard scrolling event.
2300                let iframe_window = iframe.owner_window();
2301                let mut realm = enter_auto_realm(cx, &*iframe_window);
2302                let cx = &mut realm;
2303                iframe_window
2304                    .Document()
2305                    .event_handler()
2306                    .do_keyboard_scroll(cx, scroll);
2307            } else if let Some(parent_pipeline) = parent_pipeline {
2308                // Otherwise, if we have a parent (presumably from a different origin)
2309                // asynchronously ask the Constellation to forward the event to the parent
2310                // pipeline, if we have one.
2311                document.window().send_to_constellation(
2312                    ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2313                );
2314            };
2315            return;
2316        }
2317
2318        let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2319        scrolling_box.scroll_to(cx, delta + current_scroll_offset, ScrollBehavior::Auto);
2320    }
2321
2322    /// Get or create a pointer ID for the given touch identifier.
2323    /// Returns the pointer ID to use for this touch.
2324    fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2325        let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2326
2327        if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2328            return pointer_id;
2329        }
2330
2331        let pointer_id = self.next_touch_pointer_id.get();
2332        active_pointer_ids.insert(touch_id, pointer_id);
2333        self.next_touch_pointer_id.set(pointer_id + 1);
2334        pointer_id
2335    }
2336
2337    /// Remove the pointer ID mapping for the given touch identifier.
2338    fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2339        self.active_pointer_ids.borrow_mut().remove(&touch_id);
2340    }
2341
2342    /// Check if this is the primary pointer (for touch events).
2343    /// The first touch to make contact is the primary pointer.
2344    fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2345        // For touch, the primary pointer is the one with the smallest pointer ID
2346        // that is still active.
2347        self.active_pointer_ids
2348            .borrow()
2349            .values()
2350            .min()
2351            .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2352    }
2353
2354    /// Fire pointerenter events hierarchically from topmost ancestor to target element.
2355    /// Fire pointerleave events hierarchically from target element to topmost ancestor.
2356    /// Used for touch devices that don't support hover.
2357    #[expect(clippy::too_many_arguments)]
2358    fn fire_pointer_event_for_touch(
2359        &self,
2360        cx: &mut JSContext,
2361        target_element: &Element,
2362        touch: &Touch,
2363        pointer_id: i32,
2364        event_name: &str,
2365        is_primary: bool,
2366        pointer_type: &str,
2367        input_event: &ConstellationInputEvent,
2368        hit_test_result: &HitTestResult,
2369    ) {
2370        let mut targets: Vec<_> = target_element
2371            .upcast::<Node>()
2372            .inclusive_ancestors_in_flat_tree_unrooted(cx.no_gc())
2373            .map(|node| node.as_rooted())
2374            .collect();
2375
2376        // Reverse to dispatch from topmost ancestor to target
2377        if event_name == "pointerenter" {
2378            targets.reverse();
2379        }
2380
2381        for target in targets {
2382            let pointer_event = touch.to_pointer_event(
2383                cx,
2384                &self.window,
2385                event_name,
2386                pointer_id,
2387                is_primary,
2388                pointer_type,
2389                input_event.active_keyboard_modifiers,
2390                false,
2391                Some(hit_test_result.point_in_node),
2392            );
2393            pointer_event.upcast::<Event>().fire(cx, target.upcast());
2394        }
2395    }
2396
2397    pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2398        self.access_key_handlers
2399            .borrow()
2400            .values()
2401            .any(|value| &**value == element)
2402    }
2403
2404    pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2405        self.access_key_handlers
2406            .borrow_mut()
2407            .retain(|_, value| &**value != element)
2408    }
2409
2410    pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2411        let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2412        // If an element is already assigned this access key, ignore the request.
2413        access_key_handlers
2414            .entry(code.into())
2415            .or_insert(Dom::from_ref(element));
2416    }
2417
2418    fn maybe_handle_accesskey(&self, cx: &mut JSContext, event: &KeyboardTypesEvent) -> bool {
2419        #[cfg(target_os = "macos")]
2420        let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2421        #[cfg(not(target_os = "macos"))]
2422        let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2423
2424        if event.modifiers != access_key_modifiers {
2425            return false;
2426        }
2427
2428        let Some(html_element) = self
2429            .access_key_handlers
2430            .borrow()
2431            .get(&event.code.into())
2432            .map(|html_element| html_element.as_rooted())
2433        else {
2434            return false;
2435        };
2436
2437        // From <https://html.spec.whatwg.org/multipage/#the-accesskey-attribute>:
2438        // > When the user presses the key combination corresponding to the assigned access key for
2439        // > an element, if the element defines a command, the command's Hidden State facet is false
2440        // > (visible), the command's Disabled State facet is also false (enabled), the element is in
2441        // > a document that has a non-null browsing context, and neither the element nor any of its
2442        // > ancestors has a hidden attribute specified, then the user agent must trigger the Action
2443        // > of the command.
2444        let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2445            return false;
2446        };
2447
2448        if command.disabled() || command.hidden() {
2449            return false;
2450        }
2451
2452        let node = html_element.upcast::<Node>();
2453        if !node.is_connected() {
2454            return false;
2455        }
2456
2457        for node in node.inclusive_ancestors_unrooted(cx.no_gc(), ShadowIncluding::Yes) {
2458            if node
2459                .downcast::<HTMLElement>()
2460                .is_some_and(|html_element| html_element.Hidden())
2461            {
2462                return false;
2463            }
2464        }
2465
2466        // This behavior is unspecified, but all browsers do this. When activating the element it is
2467        // focused and scrolled into view.
2468        self.focus_and_scroll_to_element_for_key_event(cx, html_element.upcast());
2469        command.perform_action(cx);
2470        true
2471    }
2472
2473    pub(crate) fn focus_and_scroll_to_element_for_key_event(
2474        &self,
2475        cx: &mut JSContext,
2476        element: &Element,
2477    ) {
2478        element
2479            .upcast::<Node>()
2480            .run_the_focusing_steps(cx, None, FocusTrigger::Other);
2481        let scroll_axis = ScrollAxisState {
2482            position: ScrollLogicalPosition::Center,
2483            requirement: ScrollRequirement::IfNotVisible,
2484        };
2485        element.scroll_into_view_with_options(
2486            cx,
2487            ScrollBehavior::Auto,
2488            scroll_axis,
2489            scroll_axis,
2490            None,
2491            None,
2492        );
2493    }
2494
2495    /// Check if a pointer ID corresponds to an active pointer.
2496    /// <https://w3c.github.io/pointerevents/#dfn-active-pointer>
2497    pub(crate) fn is_active_pointer(&self, pointer_id: i32) -> bool {
2498        if pointer_id == PointerId::Mouse as i32 {
2499            // Mouse is active when buttons are down
2500            !self.mouse_button_state.get().is_empty()
2501        } else {
2502            // Touch pointers are tracked in active_pointer_ids
2503            self.active_pointer_ids
2504                .borrow()
2505                .values()
2506                .any(|&id| id == pointer_id)
2507        }
2508    }
2509
2510    /// Set the pending pointer capture target override for a pointer.
2511    /// <https://w3c.github.io/pointerevents/#setting-pointer-capture>
2512    pub(crate) fn set_pending_pointer_capture(&self, pointer_id: i32, element: &Element) {
2513        self.pending_pointer_capture
2514            .borrow_mut()
2515            .insert(pointer_id, Dom::from_ref(element));
2516    }
2517
2518    /// Clear the pending pointer capture target override for a pointer.
2519    /// <https://w3c.github.io/pointerevents/#releasing-pointer-capture>
2520    pub(crate) fn clear_pending_pointer_capture(&self, pointer_id: i32) {
2521        self.pending_pointer_capture
2522            .borrow_mut()
2523            .remove(&pointer_id);
2524    }
2525
2526    /// Check if an element has pointer capture for a given pointer ID.
2527    /// <https://w3c.github.io/pointerevents/#dom-element-haspointercapture>
2528    pub(crate) fn has_pointer_capture(&self, pointer_id: i32, element: &Element) -> bool {
2529        self.pending_pointer_capture
2530            .borrow()
2531            .get(&pointer_id)
2532            .is_some_and(|el| &**el == element)
2533    }
2534
2535    /// Get the current pointer capture target for event dispatch.
2536    /// Returns the capture target if set and connected, otherwise None.
2537    /// This returns the actual/current target (pointer_capture_target),
2538    /// not the pending target that will be applied after process_pending.
2539    fn get_pointer_capture_target(&self, pointer_id: i32) -> Option<DomRoot<Element>> {
2540        self.pointer_capture_target
2541            .borrow()
2542            .get(&pointer_id)
2543            .map(|el| DomRoot::from_ref(&**el))
2544            .filter(|el| el.upcast::<Node>().is_connected())
2545    }
2546
2547    /// Check if the capture target is disconnected and release it if so.
2548    /// This fires lostpointercapture at the document per spec.
2549    /// Must be called before firing any pointer event.
2550    /// Returns true if a disconnected capture was released.
2551    /// <https://w3c.github.io/pointerevents/#process-pending-pointer-capture>
2552    fn release_disconnected_pointer_capture(
2553        &self,
2554        cx: &mut JSContext,
2555        pointer_id: i32,
2556        pointer_type: &str,
2557        is_primary: bool,
2558    ) -> bool {
2559        let capture_target = self
2560            .pointer_capture_target
2561            .borrow()
2562            .get(&pointer_id)
2563            .map(|el| DomRoot::from_ref(&**el));
2564        if let Some(capture_element) = capture_target &&
2565            !capture_element.upcast::<Node>().is_connected()
2566        {
2567            // Fire lostpointercapture at the document, not the disconnected element.
2568            let document = self.window.Document();
2569            self.fire_pointer_capture_event_at_target(
2570                cx,
2571                "lostpointercapture",
2572                pointer_id,
2573                pointer_type,
2574                is_primary,
2575                document.upcast::<EventTarget>(),
2576            );
2577            // Clear both pending and current capture
2578            self.pending_pointer_capture
2579                .safe_borrow_mut(cx.no_gc())
2580                .remove(&pointer_id);
2581            self.pointer_capture_target
2582                .safe_borrow_mut(cx.no_gc())
2583                .remove(&pointer_id);
2584            return true;
2585        }
2586        false
2587    }
2588
2589    /// Fire a gotpointercapture or lostpointercapture event at an Element.
2590    /// <https://w3c.github.io/pointerevents/#the-gotpointercapture-and-lostpointercapture-events>
2591    fn fire_pointer_capture_event(
2592        &self,
2593        cx: &mut JSContext,
2594        event_type: &str,
2595        pointer_id: i32,
2596        pointer_type: &str,
2597        is_primary: bool,
2598        target: &Element,
2599    ) {
2600        self.fire_pointer_capture_event_at_target(
2601            cx,
2602            event_type,
2603            pointer_id,
2604            pointer_type,
2605            is_primary,
2606            target.upcast::<EventTarget>(),
2607        );
2608    }
2609
2610    /// Fire a pointer capture event at a specific EventTarget (e.g., the document).
2611    fn fire_pointer_capture_event_at_target(
2612        &self,
2613        cx: &mut JSContext,
2614        event_type: &str,
2615        pointer_id: i32,
2616        pointer_type: &str,
2617        is_primary: bool,
2618        target: &EventTarget,
2619    ) {
2620        let pointer_event = PointerEvent::new(
2621            cx,
2622            &self.window,
2623            event_type.into(),
2624            EventBubbles::Bubbles,
2625            EventCancelable::NotCancelable,
2626            Some(&self.window),
2627            0,
2628            Point2D::new(0, 0),
2629            Point2D::new(0, 0),
2630            Point2D::new(0, 0),
2631            Modifiers::empty(),
2632            MouseButton::Primary,
2633            MouseButtons::empty(),
2634            None,
2635            None,
2636            pointer_id,
2637            1,
2638            1,
2639            0.0,
2640            0.0,
2641            0,
2642            0,
2643            0,
2644            PI / 2.0,
2645            0.0,
2646            DOMString::from(pointer_type),
2647            is_primary,
2648            vec![],
2649            vec![],
2650        );
2651        pointer_event.upcast::<Event>().set_composed(true);
2652        pointer_event.upcast::<Event>().fire(cx, target);
2653    }
2654
2655    /// Fire a single boundary `PointerEvent` (`pointerout`/`pointerleave`/
2656    /// `pointerover`/`pointerenter`) at `target`, with `related_target` set to
2657    /// the element being entered from or left to.
2658    #[expect(clippy::too_many_arguments)]
2659    fn fire_pointer_boundary_event(
2660        &self,
2661        cx: &mut JSContext,
2662        event_type: &str,
2663        bubbles: EventBubbles,
2664        pointer_id: i32,
2665        pointer_type: &str,
2666        is_primary: bool,
2667        target: &Element,
2668        related_target: Option<&Element>,
2669    ) {
2670        let pointer_event = PointerEvent::new(
2671            cx,
2672            &self.window,
2673            event_type.into(),
2674            bubbles,
2675            EventCancelable::NotCancelable,
2676            Some(&self.window),
2677            0,
2678            Point2D::new(0, 0),
2679            Point2D::new(0, 0),
2680            Point2D::new(0, 0),
2681            Modifiers::empty(),
2682            MouseButton::None,
2683            self.mouse_button_state.get(),
2684            related_target.map(|el| el.upcast::<EventTarget>()),
2685            None,
2686            pointer_id,
2687            1,
2688            1,
2689            0.0,
2690            0.0,
2691            0,
2692            0,
2693            0,
2694            PI / 2.0,
2695            0.0,
2696            DOMString::from(pointer_type),
2697            is_primary,
2698            vec![],
2699            vec![],
2700        );
2701        pointer_event.upcast::<Event>().set_composed(true);
2702        pointer_event.upcast::<Event>().fire(cx, target.upcast());
2703    }
2704
2705    /// Fire the pointer boundary events that accompany a pointer-capture
2706    /// transition from `old_target` to `new_target` for hoverable pointers.
2707    /// Per spec, only fired for pointer types that support hover (mouse, pen
2708    /// with hover); touch is skipped.
2709    fn fire_pointer_capture_boundary_transition(
2710        &self,
2711        cx: &mut JSContext,
2712        pointer_id: i32,
2713        pointer_type: &str,
2714        is_primary: bool,
2715        old_target: &Element,
2716        new_target: &Element,
2717    ) {
2718        if pointer_type != "mouse" {
2719            return;
2720        }
2721        if old_target == new_target {
2722            return;
2723        }
2724        self.fire_pointer_boundary_event(
2725            cx,
2726            "pointerout",
2727            EventBubbles::Bubbles,
2728            pointer_id,
2729            pointer_type,
2730            is_primary,
2731            old_target,
2732            Some(new_target),
2733        );
2734        self.fire_pointer_boundary_event(
2735            cx,
2736            "pointerleave",
2737            EventBubbles::DoesNotBubble,
2738            pointer_id,
2739            pointer_type,
2740            is_primary,
2741            old_target,
2742            Some(new_target),
2743        );
2744        self.fire_pointer_boundary_event(
2745            cx,
2746            "pointerover",
2747            EventBubbles::Bubbles,
2748            pointer_id,
2749            pointer_type,
2750            is_primary,
2751            new_target,
2752            Some(old_target),
2753        );
2754        self.fire_pointer_boundary_event(
2755            cx,
2756            "pointerenter",
2757            EventBubbles::DoesNotBubble,
2758            pointer_id,
2759            pointer_type,
2760            is_primary,
2761            new_target,
2762            Some(old_target),
2763        );
2764    }
2765
2766    /// Implicitly release pointer capture when pointer is lifted or canceled.
2767    /// <https://w3c.github.io/pointerevents/#implicit-release-of-pointer-capture>
2768    fn implicit_release_pointer_capture(
2769        &self,
2770        cx: &mut JSContext,
2771        pointer_id: i32,
2772        pointer_type: &str,
2773        is_primary: bool,
2774    ) {
2775        let capture_element = self
2776            .pointer_capture_target
2777            .borrow()
2778            .get(&pointer_id)
2779            .map(|el| DomRoot::from_ref(&**el));
2780        if let Some(capture_element) = capture_element {
2781            if capture_element.upcast::<Node>().is_connected() {
2782                self.fire_pointer_capture_event(
2783                    cx,
2784                    "lostpointercapture",
2785                    pointer_id,
2786                    pointer_type,
2787                    is_primary,
2788                    &capture_element,
2789                );
2790                // Boundary events: transition hover from released capture
2791                // target back to the actual hover target.
2792                if let Some(hover_target) = self.current_hover_target.get() {
2793                    self.fire_pointer_capture_boundary_transition(
2794                        cx,
2795                        pointer_id,
2796                        pointer_type,
2797                        is_primary,
2798                        &capture_element,
2799                        &hover_target,
2800                    );
2801                }
2802            } else {
2803                let document = self.window.Document();
2804                self.fire_pointer_capture_event_at_target(
2805                    cx,
2806                    "lostpointercapture",
2807                    pointer_id,
2808                    pointer_type,
2809                    is_primary,
2810                    document.upcast::<EventTarget>(),
2811                );
2812            }
2813        }
2814        self.pending_pointer_capture
2815            .safe_borrow_mut(cx.no_gc())
2816            .remove(&pointer_id);
2817        self.pointer_capture_target
2818            .safe_borrow_mut(cx.no_gc())
2819            .remove(&pointer_id);
2820    }
2821
2822    /// Process pending pointer capture before dispatching a pointer event.
2823    /// Fires gotpointercapture/lostpointercapture as needed.
2824    /// <https://w3c.github.io/pointerevents/#process-pending-pointer-capture>
2825    fn process_pending_pointer_capture(
2826        &self,
2827        cx: &mut JSContext,
2828        pointer_id: i32,
2829        pointer_type: &str,
2830        is_primary: bool,
2831    ) {
2832        let pending = self
2833            .pending_pointer_capture
2834            .borrow()
2835            .get(&pointer_id)
2836            .map(|el| DomRoot::from_ref(&**el));
2837        let current = self
2838            .pointer_capture_target
2839            .borrow()
2840            .get(&pointer_id)
2841            .map(|el| DomRoot::from_ref(&**el));
2842
2843        // Disconnected capture targets are handled by release_disconnected_pointer_capture
2844        // before any pointer event fires; by the time we get here they have been released.
2845        let pending_connected = pending
2846            .as_ref()
2847            .is_some_and(|el| el.upcast::<Node>().is_connected());
2848
2849        // If the pointer is no longer active (e.g., last button just released), we should
2850        // not fire gotpointercapture for new captures.
2851        let pointer_is_active = self.is_active_pointer(pointer_id);
2852
2853        match (&pending, &current) {
2854            (Some(pending_el), None) if pending_connected => {
2855                if pointer_is_active {
2856                    // Boundary events: transition hover from current hover target
2857                    // to the new capture target, before firing gotpointercapture.
2858                    if let Some(hover_target) = self.current_hover_target.get() {
2859                        self.fire_pointer_capture_boundary_transition(
2860                            cx,
2861                            pointer_id,
2862                            pointer_type,
2863                            is_primary,
2864                            &hover_target,
2865                            pending_el,
2866                        );
2867                    }
2868                    self.fire_pointer_capture_event(
2869                        cx,
2870                        "gotpointercapture",
2871                        pointer_id,
2872                        pointer_type,
2873                        is_primary,
2874                        pending_el,
2875                    );
2876                    self.pointer_capture_target
2877                        .safe_borrow_mut(cx.no_gc())
2878                        .insert(pointer_id, Dom::from_ref(pending_el));
2879                } else {
2880                    self.pending_pointer_capture
2881                        .safe_borrow_mut(cx.no_gc())
2882                        .remove(&pointer_id);
2883                }
2884            },
2885            (Some(pending_el), Some(current_el))
2886                if pending_connected && pending_el != current_el =>
2887            {
2888                self.fire_pointer_capture_event(
2889                    cx,
2890                    "lostpointercapture",
2891                    pointer_id,
2892                    pointer_type,
2893                    is_primary,
2894                    current_el,
2895                );
2896                if pointer_is_active {
2897                    self.fire_pointer_capture_boundary_transition(
2898                        cx,
2899                        pointer_id,
2900                        pointer_type,
2901                        is_primary,
2902                        current_el,
2903                        pending_el,
2904                    );
2905                    self.fire_pointer_capture_event(
2906                        cx,
2907                        "gotpointercapture",
2908                        pointer_id,
2909                        pointer_type,
2910                        is_primary,
2911                        pending_el,
2912                    );
2913                    self.pointer_capture_target
2914                        .safe_borrow_mut(cx.no_gc())
2915                        .insert(pointer_id, Dom::from_ref(pending_el));
2916                } else {
2917                    self.pending_pointer_capture
2918                        .safe_borrow_mut(cx.no_gc())
2919                        .remove(&pointer_id);
2920                    self.pointer_capture_target
2921                        .safe_borrow_mut(cx.no_gc())
2922                        .remove(&pointer_id);
2923                }
2924            },
2925            (None, Some(current_el)) | (Some(_), Some(current_el)) if !pending_connected => {
2926                self.fire_pointer_capture_event(
2927                    cx,
2928                    "lostpointercapture",
2929                    pointer_id,
2930                    pointer_type,
2931                    is_primary,
2932                    current_el,
2933                );
2934                // Boundary events: transition hover from released capture
2935                // target back to the actual hover target.
2936                if let Some(hover_target) = self.current_hover_target.get() {
2937                    self.fire_pointer_capture_boundary_transition(
2938                        cx,
2939                        pointer_id,
2940                        pointer_type,
2941                        is_primary,
2942                        current_el,
2943                        &hover_target,
2944                    );
2945                }
2946                self.pointer_capture_target
2947                    .safe_borrow_mut(cx.no_gc())
2948                    .remove(&pointer_id);
2949                if !pending_connected {
2950                    self.pending_pointer_capture
2951                        .safe_borrow_mut(cx.no_gc())
2952                        .remove(&pointer_id);
2953                }
2954            },
2955            _ => {},
2956        }
2957    }
2958
2959    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
2960    pub(crate) fn install_drag_gesture(&self, drag_gesture: DragGesture) {
2961        *self.drag_gesture.borrow_mut() = Some(drag_gesture);
2962    }
2963}
2964
2965/// Whether a given [`EmbedderKeyboardEvent`] should trigger a `keypress` event.
2966///
2967/// From <https://w3c.github.io/uievents/#keypress>:
2968/// > If supported by a user agent, this event MUST be dispatched when a key is pressed
2969/// > down, if and only if that key normally produces a character value. The keypress event
2970/// > type is device dependent and relies on the capabilities of the input devices and how
2971/// > they are mapped in the operating system.
2972fn keyboard_event_fires_keypress_event(keyboard_event: &EmbedderKeyboardEvent) -> bool {
2973    let is_character_value_key = matches!(
2974        keyboard_event.event.key,
2975        Key::Character(_) | Key::Named(NamedKey::Enter)
2976    );
2977    keyboard_event.event.state == KeyState::Down &&
2978        is_character_value_key &&
2979        !keyboard_event.event.is_composing
2980}
2981
2982pub(crate) fn character_to_code(character: char) -> Option<Code> {
2983    Some(match character.to_ascii_lowercase() {
2984        '`' => Code::Backquote,
2985        '\\' => Code::Backslash,
2986        '[' | '{' => Code::BracketLeft,
2987        ']' | '}' => Code::BracketRight,
2988        ',' | '<' => Code::Comma,
2989        '0' => Code::Digit0,
2990        '1' => Code::Digit1,
2991        '2' => Code::Digit2,
2992        '3' => Code::Digit3,
2993        '4' => Code::Digit4,
2994        '5' => Code::Digit5,
2995        '6' => Code::Digit6,
2996        '7' => Code::Digit7,
2997        '8' => Code::Digit8,
2998        '9' => Code::Digit9,
2999        '=' => Code::Equal,
3000        'a' => Code::KeyA,
3001        'b' => Code::KeyB,
3002        'c' => Code::KeyC,
3003        'd' => Code::KeyD,
3004        'e' => Code::KeyE,
3005        'f' => Code::KeyF,
3006        'g' => Code::KeyG,
3007        'h' => Code::KeyH,
3008        'i' => Code::KeyI,
3009        'j' => Code::KeyJ,
3010        'k' => Code::KeyK,
3011        'l' => Code::KeyL,
3012        'm' => Code::KeyM,
3013        'n' => Code::KeyN,
3014        'o' => Code::KeyO,
3015        'p' => Code::KeyP,
3016        'q' => Code::KeyQ,
3017        'r' => Code::KeyR,
3018        's' => Code::KeyS,
3019        't' => Code::KeyT,
3020        'u' => Code::KeyU,
3021        'v' => Code::KeyV,
3022        'w' => Code::KeyW,
3023        'x' => Code::KeyX,
3024        'y' => Code::KeyY,
3025        'z' => Code::KeyZ,
3026        '-' => Code::Minus,
3027        '.' => Code::Period,
3028        '\'' | '"' => Code::Quote,
3029        ';' => Code::Semicolon,
3030        '/' => Code::Slash,
3031        ' ' => Code::Space,
3032        _ => return None,
3033    })
3034}