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