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, RefCell};
7use std::cmp::Ordering;
8use std::f64::consts::PI;
9use std::mem;
10use std::rc::Rc;
11use std::str::FromStr;
12use std::time::{Duration, Instant};
13
14use embedder_traits::{
15    Cursor, EditingActionEvent, EmbedderMsg, ImeEvent, InputEvent, InputEventId, InputEventOutcome,
16    InputEventResult, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction,
17    MouseButtonEvent, MouseLeftViewportEvent, TouchEvent as EmbedderTouchEvent, TouchEventType,
18    TouchId, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
19};
20#[cfg(feature = "gamepad")]
21use embedder_traits::{
22    GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
23};
24use euclid::{Point2D, Vector2D};
25use js::jsapi::JSAutoRealm;
26use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
27use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id};
28use rustc_hash::FxHashMap;
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::KeyboardEventBinding::KeyboardEventMethods;
35use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods;
36use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
37use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
38use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
39use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
40use script_bindings::inheritance::Castable;
41use script_bindings::match_domstring_ascii;
42use script_bindings::num::Finite;
43use script_bindings::reflector::DomObject;
44use script_bindings::root::{Dom, DomRoot, DomSlice};
45use script_bindings::script_runtime::CanGc;
46use script_bindings::str::DOMString;
47use script_traits::ConstellationInputEvent;
48use servo_base::generic_channel::GenericCallback;
49use servo_config::pref;
50use servo_constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
51use style::Atom;
52use style_traits::CSSPixel;
53use webrender_api::ExternalScrollId;
54
55use crate::dom::bindings::cell::DomRefCell;
56use crate::dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
57use crate::dom::bindings::refcounted::Trusted;
58use crate::dom::bindings::root::MutNullableDom;
59use crate::dom::bindings::trace::NoTrace;
60use crate::dom::clipboardevent::ClipboardEventType;
61use crate::dom::document::{FireMouseEventType, FocusInitiator};
62use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
63#[cfg(feature = "gamepad")]
64use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
65#[cfg(feature = "gamepad")]
66use crate::dom::gamepad::gamepadevent::GamepadEventType;
67use crate::dom::inputevent::HitTestResult;
68use crate::dom::interactive_element_command::InteractiveElementCommand;
69use crate::dom::keyboardevent::KeyboardEvent;
70use crate::dom::node::{self, Node, NodeTraits, ShadowIncluding};
71use crate::dom::pointerevent::{PointerEvent, PointerId};
72use crate::dom::scrolling_box::{ScrollAxisState, ScrollRequirement, ScrollingBoxAxis};
73use crate::dom::types::{
74    ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
75    HTMLAnchorElement, HTMLElement, HTMLLabelElement, MouseEvent, Touch, TouchEvent, TouchList,
76    WheelEvent, Window,
77};
78use crate::drag_data_store::{DragDataStore, Kind, Mode};
79use crate::realms::enter_realm;
80
81/// A data structure used for tracking the current click count. This can be
82/// reset to 0 if a mouse button event happens at a sufficient distance or time
83/// from the previous one.
84///
85/// From <https://w3c.github.io/uievents/#current-click-count>:
86/// > Implementations MUST maintain the current click count when generating mouse
87/// > events. This MUST be a non-negative integer indicating the number of consecutive
88/// > clicks of a pointing device button within a specific time. The delay after which
89/// > the count resets is specific to the environment configuration.
90#[derive(Default, JSTraceable, MallocSizeOf)]
91struct ClickCountingInfo {
92    time: Option<Instant>,
93    #[no_trace]
94    point: Option<Point2D<f32, CSSPixel>>,
95    #[no_trace]
96    button: Option<MouseButton>,
97    count: usize,
98}
99
100impl ClickCountingInfo {
101    fn reset_click_count_if_necessary(
102        &mut self,
103        button: MouseButton,
104        point_in_frame: Point2D<f32, CSSPixel>,
105    ) {
106        let (Some(previous_button), Some(previous_point), Some(previous_time)) =
107            (self.button, self.point, self.time)
108        else {
109            assert_eq!(self.count, 0);
110            return;
111        };
112
113        let double_click_timeout =
114            Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
115        let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
116
117        // Calculate distance between this click and the previous click.
118        let line = point_in_frame - previous_point;
119        let distance = (line.dot(line) as f64).sqrt();
120        if previous_button != button ||
121            Instant::now().duration_since(previous_time) > double_click_timeout ||
122            distance > double_click_distance_threshold as f64
123        {
124            self.count = 0;
125            self.time = None;
126            self.point = None;
127        }
128    }
129
130    fn increment_click_count(
131        &mut self,
132        button: MouseButton,
133        point: Point2D<f32, CSSPixel>,
134    ) -> usize {
135        self.time = Some(Instant::now());
136        self.point = Some(point);
137        self.button = Some(button);
138        self.count += 1;
139        self.count
140    }
141}
142
143/// The [`DocumentEventHandler`] is a structure responsible for handling input events for
144/// the [`crate::Document`] and storing data related to event handling. It exists to
145/// decrease the size of the [`crate::Document`] structure.
146#[derive(JSTraceable, MallocSizeOf)]
147#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
148pub(crate) struct DocumentEventHandler {
149    /// The [`Window`] element for this [`DocumentEventHandler`].
150    window: Dom<Window>,
151    /// Pending input events, to be handled at the next rendering opportunity.
152    #[no_trace]
153    #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
154    pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
155    /// The index of the last mouse move event in the pending input events queue.
156    mouse_move_event_index: DomRefCell<Option<usize>>,
157    /// The [`InputEventId`]s of mousemove events that have been coalesced.
158    #[no_trace]
159    #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
160    coalesced_move_event_ids: DomRefCell<Vec<InputEventId>>,
161    /// The index of the last wheel event in the pending input events queue.
162    /// This is non-standard behaviour.
163    /// According to <https://www.w3.org/TR/pointerevents/#dfn-coalesced-events>,
164    /// we should only coalesce `pointermove` events.
165    wheel_event_index: DomRefCell<Option<usize>>,
166    /// The [`InputEventId`]s of wheel events that have been coalesced.
167    #[no_trace]
168    #[ignore_malloc_size_of = "InputEventId contains data from outside crates"]
169    coalesced_wheel_event_ids: DomRefCell<Vec<InputEventId>>,
170    /// <https://w3c.github.io/uievents/#event-type-dblclick>
171    click_counting_info: DomRefCell<ClickCountingInfo>,
172    #[no_trace]
173    last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
174    /// The number of currently down buttons, used to decide which kind
175    /// of pointer event to dispatch on MouseDown/MouseUp.
176    down_button_count: Cell<u32>,
177    /// The element that is currently hovered by the cursor.
178    current_hover_target: MutNullableDom<Element>,
179    /// The element that was most recently clicked.
180    most_recently_clicked_element: MutNullableDom<Element>,
181    /// The most recent mouse movement point, used for processing `mouseleave` events.
182    #[no_trace]
183    most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
184    /// The currently set [`Cursor`] or `None` if the `Document` isn't being hovered
185    /// by the cursor.
186    #[no_trace]
187    current_cursor: Cell<Option<Cursor>>,
188    /// <http://w3c.github.io/touch-events/#dfn-active-touch-point>
189    active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
190    /// The active keyboard modifiers for the WebView. This is updated when receiving any input event.
191    #[no_trace]
192    active_keyboard_modifiers: Cell<Modifiers>,
193    /// Map from touch identifier to pointer ID for active touch points
194    active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
195    /// Counter for generating unique pointer IDs for touch inputs
196    next_touch_pointer_id: Cell<i32>,
197    /// A map holding information about currently registered access key handlers.
198    access_key_handlers: DomRefCell<FxHashMap<NoTrace<Code>, Dom<HTMLElement>>>,
199    /// <https://html.spec.whatwg.org/multipage/#sequential-focus-navigation-starting-point>
200    sequential_focus_navigation_starting_point: MutNullableDom<Node>,
201}
202
203impl DocumentEventHandler {
204    pub(crate) fn new(window: &Window) -> Self {
205        Self {
206            window: Dom::from_ref(window),
207            pending_input_events: Default::default(),
208            mouse_move_event_index: Default::default(),
209            coalesced_move_event_ids: Default::default(),
210            wheel_event_index: Default::default(),
211            coalesced_wheel_event_ids: Default::default(),
212            click_counting_info: Default::default(),
213            last_mouse_button_down_point: Default::default(),
214            down_button_count: Cell::new(0),
215            current_hover_target: Default::default(),
216            most_recently_clicked_element: Default::default(),
217            most_recent_mousemove_point: Default::default(),
218            current_cursor: Default::default(),
219            active_touch_points: Default::default(),
220            active_keyboard_modifiers: Default::default(),
221            active_pointer_ids: Default::default(),
222            next_touch_pointer_id: Cell::new(1),
223            access_key_handlers: Default::default(),
224            sequential_focus_navigation_starting_point: Default::default(),
225        }
226    }
227
228    /// Note a pending input event, to be processed at the next `update_the_rendering` task.
229    pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
230        let mut pending_input_events = self.pending_input_events.borrow_mut();
231        if matches!(event.event.event, InputEvent::MouseMove(..)) {
232            // First try to replace any existing mouse move event.
233            if let Some(mouse_move_event) = self
234                .mouse_move_event_index
235                .borrow()
236                .and_then(|index| pending_input_events.get_mut(index))
237            {
238                self.coalesced_move_event_ids
239                    .borrow_mut()
240                    .push(mouse_move_event.event.id);
241                *mouse_move_event = event;
242                return;
243            }
244
245            *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
246        }
247
248        if let InputEvent::Wheel(ref new_wheel_event) = event.event.event {
249            // Coalesce with any existing pending wheel event by summing deltas.
250            if let Some(existing_constellation_wheel_event) = self
251                .wheel_event_index
252                .borrow()
253                .and_then(|index| pending_input_events.get_mut(index))
254            {
255                if let InputEvent::Wheel(ref mut existing_wheel_event) =
256                    existing_constellation_wheel_event.event.event
257                {
258                    if existing_wheel_event.delta.mode == new_wheel_event.delta.mode {
259                        self.coalesced_wheel_event_ids
260                            .borrow_mut()
261                            .push(existing_constellation_wheel_event.event.id);
262                        existing_wheel_event.delta.x += new_wheel_event.delta.x;
263                        existing_wheel_event.delta.y += new_wheel_event.delta.y;
264                        existing_wheel_event.delta.z += new_wheel_event.delta.z;
265                        existing_wheel_event.point = new_wheel_event.point;
266                        existing_constellation_wheel_event.event.id = event.event.id;
267                        return;
268                    }
269                }
270            }
271
272            *self.wheel_event_index.borrow_mut() = Some(pending_input_events.len());
273        }
274
275        pending_input_events.push(event);
276    }
277
278    /// Whether or not this [`Document`] has any pending input events to be processed during
279    /// "update the rendering."
280    pub(crate) fn has_pending_input_events(&self) -> bool {
281        !self.pending_input_events.borrow().is_empty()
282    }
283
284    pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
285        #[cfg(target_os = "macos")]
286        {
287            self.active_keyboard_modifiers
288                .get()
289                .contains(Modifiers::META)
290        }
291        #[cfg(not(target_os = "macos"))]
292        {
293            self.active_keyboard_modifiers
294                .get()
295                .contains(Modifiers::CONTROL)
296        }
297    }
298
299    pub(crate) fn handle_pending_input_events(&self, can_gc: CanGc) {
300        debug_assert!(
301            !self.pending_input_events.borrow().is_empty(),
302            "handle_pending_input_events called with no events"
303        );
304        let _realm = enter_realm(&*self.window);
305
306        // Reset the mouse and wheel event indices.
307        *self.mouse_move_event_index.borrow_mut() = None;
308        *self.wheel_event_index.borrow_mut() = None;
309        let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
310        let mut coalesced_move_event_ids =
311            mem::take(&mut *self.coalesced_move_event_ids.borrow_mut());
312        let mut coalesced_wheel_event_ids =
313            mem::take(&mut *self.coalesced_wheel_event_ids.borrow_mut());
314
315        let mut input_event_outcomes = Vec::with_capacity(
316            pending_input_events.len() +
317                coalesced_move_event_ids.len() +
318                coalesced_wheel_event_ids.len(),
319        );
320        // TODO: For some of these we still aren't properly calculating whether or not
321        // the event was handled or if `preventDefault()` was called on it. Each of
322        // these cases needs to be examined and some of them either fire more than one
323        // event or fire events later. We have to make a good decision about what to
324        // return to the embedder when that happens.
325        for event in pending_input_events {
326            self.active_keyboard_modifiers
327                .set(event.active_keyboard_modifiers);
328            let result = match event.event.event {
329                InputEvent::MouseButton(mouse_button_event) => {
330                    self.handle_native_mouse_button_event(mouse_button_event, &event, can_gc);
331                    InputEventResult::default()
332                },
333                InputEvent::MouseMove(_) => {
334                    self.handle_native_mouse_move_event(&event, can_gc);
335                    input_event_outcomes.extend(
336                        mem::take(&mut coalesced_move_event_ids)
337                            .into_iter()
338                            .map(|id| InputEventOutcome {
339                                id,
340                                result: InputEventResult::default(),
341                            }),
342                    );
343                    InputEventResult::default()
344                },
345                InputEvent::MouseLeftViewport(mouse_leave_event) => {
346                    self.handle_mouse_left_viewport_event(&event, &mouse_leave_event, can_gc);
347                    InputEventResult::default()
348                },
349                InputEvent::Touch(touch_event) => {
350                    self.handle_touch_event(touch_event, &event, can_gc)
351                },
352                InputEvent::Wheel(wheel_event) => {
353                    let result = self.handle_wheel_event(wheel_event, &event, can_gc);
354                    input_event_outcomes.extend(
355                        mem::take(&mut coalesced_wheel_event_ids)
356                            .into_iter()
357                            .map(|id| InputEventOutcome { id, result }),
358                    );
359                    result
360                },
361                InputEvent::Keyboard(keyboard_event) => {
362                    self.handle_keyboard_event(keyboard_event, can_gc)
363                },
364                InputEvent::Ime(ime_event) => self.handle_ime_event(ime_event, can_gc),
365                #[cfg(feature = "gamepad")]
366                InputEvent::Gamepad(gamepad_event) => {
367                    self.handle_gamepad_event(gamepad_event);
368                    InputEventResult::default()
369                },
370                InputEvent::EditingAction(editing_action_event) => {
371                    self.handle_editing_action(None, editing_action_event, can_gc)
372                },
373            };
374
375            input_event_outcomes.push(InputEventOutcome {
376                id: event.event.id,
377                result,
378            });
379        }
380
381        self.notify_embedder_that_events_were_handled(input_event_outcomes);
382    }
383
384    fn notify_embedder_that_events_were_handled(
385        &self,
386        input_event_outcomes: Vec<InputEventOutcome>,
387    ) {
388        // Wait to to notify the embedder that the event was handled until all pending DOM
389        // event processing is finished.
390        let trusted_window = Trusted::new(&*self.window);
391        self.window
392            .as_global_scope()
393            .task_manager()
394            .dom_manipulation_task_source()
395            .queue(task!(notify_webdriver_input_event_completed: move || {
396                let window = trusted_window.root();
397                window.send_to_embedder(
398                    EmbedderMsg::InputEventsHandled(window.webview_id(), input_event_outcomes));
399            }));
400    }
401
402    pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
403        if cursor == self.current_cursor.get() {
404            return;
405        }
406        self.current_cursor.set(cursor);
407        self.window.send_to_embedder(EmbedderMsg::SetCursor(
408            self.window.webview_id(),
409            cursor.unwrap_or_default(),
410        ));
411    }
412
413    fn handle_mouse_left_viewport_event(
414        &self,
415        input_event: &ConstellationInputEvent,
416        mouse_leave_event: &MouseLeftViewportEvent,
417        can_gc: CanGc,
418    ) {
419        if let Some(current_hover_target) = self.current_hover_target.get() {
420            let current_hover_target = current_hover_target.upcast::<Node>();
421            for element in current_hover_target
422                .inclusive_ancestors(ShadowIncluding::Yes)
423                .filter_map(DomRoot::downcast::<Element>)
424            {
425                element.set_hover_state(false);
426                self.element_for_activation(element).set_active_state(false);
427            }
428
429            if let Some(hit_test_result) = self
430                .most_recent_mousemove_point
431                .get()
432                .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
433            {
434                let mouse_out_event = MouseEvent::new_for_platform_motion_event(
435                    &self.window,
436                    FireMouseEventType::Out,
437                    &hit_test_result,
438                    input_event,
439                    can_gc,
440                );
441
442                // Fire pointerout before mouseout
443                mouse_out_event
444                    .to_pointer_hover_event("pointerout", can_gc)
445                    .upcast::<Event>()
446                    .fire(current_hover_target.upcast(), can_gc);
447
448                mouse_out_event
449                    .upcast::<Event>()
450                    .fire(current_hover_target.upcast(), can_gc);
451
452                self.handle_mouse_enter_leave_event(
453                    DomRoot::from_ref(current_hover_target),
454                    None,
455                    FireMouseEventType::Leave,
456                    &hit_test_result,
457                    input_event,
458                    can_gc,
459                );
460            }
461        }
462
463        // We do not want to always inform the embedder that cursor has been set to the
464        // default cursor, in order to avoid a timing issue when moving between `<iframe>`
465        // elements. There is currently no way to control which `SetCursor` message will
466        // reach the embedder first. This is safer when leaving the `WebView` entirely.
467        if !mouse_leave_event.focus_moving_to_another_iframe {
468            // If focus is moving to another frame, it will decide what the new status
469            // text is, but if this mouse leave event is leaving the WebView entirely,
470            // then clear it.
471            self.window
472                .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
473            self.set_cursor(None);
474        } else {
475            self.current_cursor.set(None);
476        }
477
478        self.current_hover_target.set(None);
479        self.most_recent_mousemove_point.set(None);
480    }
481
482    fn handle_mouse_enter_leave_event(
483        &self,
484        event_target: DomRoot<Node>,
485        related_target: Option<DomRoot<Node>>,
486        event_type: FireMouseEventType,
487        hit_test_result: &HitTestResult,
488        input_event: &ConstellationInputEvent,
489        can_gc: CanGc,
490    ) {
491        assert!(matches!(
492            event_type,
493            FireMouseEventType::Enter | FireMouseEventType::Leave
494        ));
495
496        let common_ancestor = match related_target.as_ref() {
497            Some(related_target) => event_target
498                .common_ancestor_in_flat_tree(related_target)
499                .unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
500            None => DomRoot::from_ref(&*event_target),
501        };
502
503        // We need to create a target chain in case the event target shares
504        // its boundaries with its ancestors.
505        let mut targets = vec![];
506        let mut current = Some(event_target);
507        while let Some(node) = current {
508            if node == common_ancestor {
509                break;
510            }
511            current = node.parent_in_flat_tree();
512            targets.push(node);
513        }
514
515        // The order for dispatching mouseenter/pointerenter events starts from the topmost
516        // common ancestor of the event target and the related target.
517        if event_type == FireMouseEventType::Enter {
518            targets = targets.into_iter().rev().collect();
519        }
520
521        let pointer_event_name = match event_type {
522            FireMouseEventType::Enter => "pointerenter",
523            FireMouseEventType::Leave => "pointerleave",
524            _ => unreachable!(),
525        };
526
527        for target in targets {
528            let mouse_event = MouseEvent::new_for_platform_motion_event(
529                &self.window,
530                event_type,
531                hit_test_result,
532                input_event,
533                can_gc,
534            );
535            mouse_event
536                .upcast::<Event>()
537                .set_related_target(related_target.as_ref().map(|target| target.upcast()));
538
539            // Fire pointer event before mouse event
540            mouse_event
541                .to_pointer_hover_event(pointer_event_name, can_gc)
542                .upcast::<Event>()
543                .fire(target.upcast(), can_gc);
544
545            // Fire mouse event
546            mouse_event.upcast::<Event>().fire(target.upcast(), can_gc);
547        }
548    }
549
550    /// <https://w3c.github.io/uievents/#handle-native-mouse-move>
551    fn handle_native_mouse_move_event(&self, input_event: &ConstellationInputEvent, can_gc: CanGc) {
552        // Ignore all incoming events without a hit test.
553        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
554            return;
555        };
556
557        let old_mouse_move_point = self
558            .most_recent_mousemove_point
559            .replace(Some(hit_test_result.point_in_frame));
560        if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
561            return;
562        }
563
564        // Update the cursor when the mouse moves, if it has changed.
565        self.set_cursor(Some(hit_test_result.cursor));
566
567        let Some(new_target) = hit_test_result
568            .node
569            .inclusive_ancestors(ShadowIncluding::Yes)
570            .find_map(DomRoot::downcast::<Element>)
571        else {
572            return;
573        };
574
575        let old_hover_target = self.current_hover_target.get();
576        let target_has_changed = old_hover_target
577            .as_ref()
578            .is_none_or(|old_target| *old_target != new_target);
579
580        // Here we know the target has changed, so we must update the state,
581        // dispatch mouseout to the previous one, mouseover to the new one.
582        if target_has_changed {
583            // Dispatch pointerout/mouseout and pointerleave/mouseleave to previous target.
584            if let Some(old_target) = self.current_hover_target.get() {
585                let old_target_is_ancestor_of_new_target = old_target
586                    .upcast::<Node>()
587                    .is_ancestor_of(new_target.upcast::<Node>());
588
589                // If the old target is an ancestor of the new target, this can be skipped
590                // completely, since the node's hover state will be reset below.
591                if !old_target_is_ancestor_of_new_target {
592                    for element in old_target
593                        .upcast::<Node>()
594                        .inclusive_ancestors(ShadowIncluding::No)
595                        .filter_map(DomRoot::downcast::<Element>)
596                    {
597                        element.set_hover_state(false);
598                        self.element_for_activation(element).set_active_state(false);
599                    }
600                }
601
602                let mouse_out_event = MouseEvent::new_for_platform_motion_event(
603                    &self.window,
604                    FireMouseEventType::Out,
605                    &hit_test_result,
606                    input_event,
607                    can_gc,
608                );
609                mouse_out_event
610                    .upcast::<Event>()
611                    .set_related_target(Some(new_target.upcast()));
612
613                // Fire pointerout before mouseout
614                mouse_out_event
615                    .to_pointer_hover_event("pointerout", can_gc)
616                    .upcast::<Event>()
617                    .fire(old_target.upcast(), can_gc);
618
619                mouse_out_event
620                    .upcast::<Event>()
621                    .fire(old_target.upcast(), can_gc);
622
623                if !old_target_is_ancestor_of_new_target {
624                    let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
625                    let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
626                    self.handle_mouse_enter_leave_event(
627                        event_target,
628                        moving_into,
629                        FireMouseEventType::Leave,
630                        &hit_test_result,
631                        input_event,
632                        can_gc,
633                    );
634                }
635            }
636
637            // Dispatch pointerover/mouseover and pointerenter/mouseenter to new target.
638            for element in new_target
639                .upcast::<Node>()
640                .inclusive_ancestors(ShadowIncluding::Yes)
641                .filter_map(DomRoot::downcast::<Element>)
642            {
643                element.set_hover_state(true);
644            }
645
646            let mouse_over_event = MouseEvent::new_for_platform_motion_event(
647                &self.window,
648                FireMouseEventType::Over,
649                &hit_test_result,
650                input_event,
651                can_gc,
652            );
653            mouse_over_event
654                .upcast::<Event>()
655                .set_related_target(old_hover_target.as_ref().map(|target| target.upcast()));
656
657            // Fire pointerover before mouseover
658            mouse_over_event
659                .to_pointer_hover_event("pointerover", can_gc)
660                .upcast::<Event>()
661                .dispatch(new_target.upcast(), false, can_gc);
662
663            mouse_over_event
664                .upcast::<Event>()
665                .dispatch(new_target.upcast(), false, can_gc);
666
667            let moving_from =
668                old_hover_target.map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
669            let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
670            self.handle_mouse_enter_leave_event(
671                event_target,
672                moving_from,
673                FireMouseEventType::Enter,
674                &hit_test_result,
675                input_event,
676                can_gc,
677            );
678        }
679
680        // Send mousemove event to topmost target, unless it's an iframe, in which case
681        // `Paint` should have also sent an event to the inner document.
682        let mouse_event = MouseEvent::new_for_platform_motion_event(
683            &self.window,
684            FireMouseEventType::Move,
685            &hit_test_result,
686            input_event,
687            can_gc,
688        );
689
690        // Send pointermove event before mousemove.
691        let pointer_event = mouse_event.to_pointer_event(Atom::from("pointermove"), can_gc);
692        pointer_event
693            .upcast::<Event>()
694            .fire(new_target.upcast(), can_gc);
695
696        // Send mousemove event to topmost target, unless it's an iframe, in which case
697        // `Paint` should have also sent an event to the inner document.
698        mouse_event
699            .upcast::<Event>()
700            .fire(new_target.upcast(), can_gc);
701
702        self.update_current_hover_target_and_status(Some(new_target));
703    }
704
705    fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
706        let current_hover_target = self.current_hover_target.get();
707        if current_hover_target == new_hover_target {
708            return;
709        }
710
711        let previous_hover_target = self.current_hover_target.get();
712        self.current_hover_target.set(new_hover_target.as_deref());
713
714        // If the new hover target is an anchor with a status value, inform the embedder
715        // of the new value.
716        if let Some(target) = self.current_hover_target.get() {
717            if let Some(anchor) = target
718                .upcast::<Node>()
719                .inclusive_ancestors(ShadowIncluding::Yes)
720                .find_map(DomRoot::downcast::<HTMLAnchorElement>)
721            {
722                let status = anchor
723                    .full_href_url_for_user_interface()
724                    .map(|url| url.to_string());
725                self.window
726                    .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
727                return;
728            }
729        }
730
731        // No state was set above, which means that the new value of the status in the embedder
732        // should be `None`. Set that now. If `previous_hover_target` is `None` that means this
733        // is the first mouse move event we are seeing after getting the cursor. In that case,
734        // we also clear the status.
735        if previous_hover_target.is_none_or(|previous_hover_target| {
736            previous_hover_target
737                .upcast::<Node>()
738                .inclusive_ancestors(ShadowIncluding::Yes)
739                .any(|node| node.is::<HTMLAnchorElement>())
740        }) {
741            self.window
742                .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
743        }
744    }
745
746    pub(crate) fn handle_refresh_cursor(&self) {
747        let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
748            return;
749        };
750
751        let Some(hit_test_result) = self
752            .window
753            .hit_test_from_point_in_viewport(most_recent_mousemove_point)
754        else {
755            return;
756        };
757
758        self.set_cursor(Some(hit_test_result.cursor));
759    }
760
761    fn element_for_activation(&self, element: DomRoot<Element>) -> DomRoot<Element> {
762        let node: &Node = element.upcast();
763        if node.is_in_ua_widget() {
764            if let Some(containing_shadow_root) = node.containing_shadow_root() {
765                return containing_shadow_root.Host();
766            }
767        }
768
769        // If the element is a label, the activable element is the control element.
770        if node.type_id() ==
771            NodeTypeId::Element(ElementTypeId::HTMLElement(
772                HTMLElementTypeId::HTMLLabelElement,
773            ))
774        {
775            let label = element.downcast::<HTMLLabelElement>().unwrap();
776            if let Some(control) = label.GetControl() {
777                return DomRoot::from_ref(control.upcast::<Element>());
778            }
779        }
780
781        element
782    }
783
784    /// <https://w3c.github.io/uievents/#mouseevent-algorithms>
785    /// Handles native mouse down, mouse up, mouse click.
786    fn handle_native_mouse_button_event(
787        &self,
788        event: MouseButtonEvent,
789        input_event: &ConstellationInputEvent,
790        can_gc: CanGc,
791    ) {
792        // Ignore all incoming events without a hit test.
793        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
794            return;
795        };
796
797        debug!(
798            "{:?}: at {:?}",
799            event.action, hit_test_result.point_in_frame
800        );
801
802        // Set the sequential focus navigation starting point for any mouse button down event, no
803        // matter if the target is not a node.
804        if event.action == MouseButtonAction::Down {
805            self.set_sequential_focus_navigation_starting_point(&hit_test_result.node);
806        }
807
808        let Some(element) = hit_test_result
809            .node
810            .inclusive_ancestors(ShadowIncluding::Yes)
811            .find_map(DomRoot::downcast::<Element>)
812        else {
813            return;
814        };
815
816        let node = element.upcast::<Node>();
817        debug!("{:?} on {:?}", event.action, node.debug_str());
818
819        // <https://html.spec.whatwg.org/multipage/#selector-active>
820        // If the element is being actively pointed at the element is being activated.
821        // Disabled elements can also be activated.
822        if event.action == MouseButtonAction::Down {
823            self.element_for_activation(element.clone())
824                .set_active_state(true);
825        }
826        if event.action == MouseButtonAction::Up {
827            self.element_for_activation(element.clone())
828                .set_active_state(false);
829        }
830
831        // https://w3c.github.io/uievents/#hit-test
832        // Prevent mouse event if element is disabled.
833        // TODO: also inert.
834        if element.is_actually_disabled() {
835            return;
836        }
837
838        let mouse_event_type = match event.action {
839            embedder_traits::MouseButtonAction::Up => atom!("mouseup"),
840            embedder_traits::MouseButtonAction::Down => atom!("mousedown"),
841        };
842
843        // From <https://w3c.github.io/uievents/#event-type-mousedown>
844        // and <https://w3c.github.io/uievents/#event-type-mouseup>:
845        //
846        // UIEvent.detail: indicates the current click count incremented by one. For
847        // example, if no click happened before the mousedown, detail will contain
848        // the value 1
849        if event.action == MouseButtonAction::Down {
850            self.click_counting_info
851                .borrow_mut()
852                .reset_click_count_if_necessary(event.button, hit_test_result.point_in_frame);
853        }
854
855        let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_button_event(
856            mouse_event_type,
857            event,
858            input_event.pressed_mouse_buttons,
859            &self.window,
860            &hit_test_result,
861            input_event.active_keyboard_modifiers,
862            self.click_counting_info.borrow().count + 1,
863            can_gc,
864        ));
865
866        match event.action {
867            MouseButtonAction::Down => {
868                self.last_mouse_button_down_point
869                    .set(Some(hit_test_result.point_in_frame));
870
871                // Step 6. Dispatch pointerdown event.
872                let down_button_count = self.down_button_count.get();
873
874                let event_type = if down_button_count == 0 {
875                    "pointerdown"
876                } else {
877                    "pointermove"
878                };
879                let pointer_event = dom_event
880                    .downcast::<MouseEvent>()
881                    .unwrap()
882                    .to_pointer_event(event_type.into(), can_gc);
883
884                pointer_event.upcast::<Event>().fire(node.upcast(), can_gc);
885
886                self.down_button_count.set(down_button_count + 1);
887
888                // For a node within a text input UA shadow DOM,
889                // delegate the focus target into its shadow host.
890                // TODO: This focus delegation should be done
891                // with shadow DOM delegateFocus attribute.
892                let target_el = element.find_click_focusable_area();
893
894                let document = self.window.Document();
895                document.begin_focus_transaction();
896
897                // Try to focus `el`. If it's not focusable, focus the document instead.
898                document.request_focus(None, FocusInitiator::Click, can_gc);
899                document.request_focus(target_el.as_deref(), FocusInitiator::Click, can_gc);
900
901                // Step 7. Let result = dispatch event at target
902                let result = dom_event.dispatch(node.upcast(), false, can_gc);
903
904                // Step 8. If result is true and target is a focusable area
905                // that is click focusable, then Run the focusing steps at target.
906                if result && document.has_focus_transaction() {
907                    document.commit_focus_transaction(FocusInitiator::Click, can_gc);
908                }
909
910                // Step 9. If mbutton is the secondary mouse button, then
911                // Maybe show context menu with native, target.
912                if let MouseButton::Right = event.button {
913                    self.maybe_show_context_menu(
914                        node.upcast(),
915                        &hit_test_result,
916                        input_event,
917                        can_gc,
918                    );
919                }
920            },
921            // https://w3c.github.io/uievents/#handle-native-mouse-up
922            MouseButtonAction::Up => {
923                // Step 6. Dispatch pointerup event.
924                let down_button_count = self.down_button_count.get();
925
926                if down_button_count > 0 {
927                    self.down_button_count.set(down_button_count - 1);
928                }
929
930                let event_type = if down_button_count == 0 {
931                    "pointerup"
932                } else {
933                    "pointermove"
934                };
935                let pointer_event = dom_event
936                    .downcast::<MouseEvent>()
937                    .unwrap()
938                    .to_pointer_event(event_type.into(), can_gc);
939
940                pointer_event.upcast::<Event>().fire(node.upcast(), can_gc);
941
942                // Step 7. dispatch event at target.
943                dom_event.dispatch(node.upcast(), false, can_gc);
944
945                // Click counts should still work for other buttons even though they
946                // do not trigger "click" and "dblclick" events, so we increment
947                // even when those events are not fired.
948                self.click_counting_info
949                    .borrow_mut()
950                    .increment_click_count(event.button, hit_test_result.point_in_frame);
951
952                self.maybe_trigger_click_for_mouse_button_down_event(
953                    event,
954                    input_event,
955                    &hit_test_result,
956                    &element,
957                    can_gc,
958                );
959            },
960        }
961    }
962
963    /// <https://w3c.github.io/uievents/#handle-native-mouse-click>
964    /// <https://w3c.github.io/uievents/#event-type-dblclick>
965    fn maybe_trigger_click_for_mouse_button_down_event(
966        &self,
967        event: MouseButtonEvent,
968        input_event: &ConstellationInputEvent,
969        hit_test_result: &HitTestResult,
970        element: &Element,
971        can_gc: CanGc,
972    ) {
973        if event.button != MouseButton::Left {
974            return;
975        }
976
977        let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
978            return;
979        };
980
981        let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
982        let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
983        if distance > maximum_click_distance {
984            return;
985        }
986
987        // From <https://w3c.github.io/uievents/#event-type-click>
988        // > The click event type MUST be dispatched on the topmost event target indicated by the
989        // > pointer, when the user presses down and releases the primary pointer button.
990        // For nodes inside a text input UA shadow DOM, dispatch dblclick at the shadow host.
991        let delegated = element.find_click_focusable_area();
992        let element = delegated.as_deref().unwrap_or(element);
993        self.most_recently_clicked_element.set(Some(element));
994
995        let click_count = self.click_counting_info.borrow().count;
996        element.set_click_in_progress(true);
997        MouseEvent::for_platform_button_event(
998            atom!("click"),
999            event,
1000            input_event.pressed_mouse_buttons,
1001            &self.window,
1002            hit_test_result,
1003            input_event.active_keyboard_modifiers,
1004            click_count,
1005            can_gc,
1006        )
1007        .upcast::<Event>()
1008        .dispatch(element.upcast(), false, can_gc);
1009        element.set_click_in_progress(false);
1010
1011        // The firing of "dbclick" events is dependent on the platform, so we have
1012        // some flexibility here. Some browsers on some platforms only fire a
1013        // "dbclick" when the click count is 2 and others essentially fire one for
1014        // every 2 clicks in a sequence. In all cases, browsers set the click count
1015        // `detail` property to 2.
1016        //
1017        // We follow the latter approach here, considering that every sequence of
1018        // even numbered clicks is a series of double clicks.
1019        if click_count % 2 == 0 {
1020            MouseEvent::for_platform_button_event(
1021                Atom::from("dblclick"),
1022                event,
1023                input_event.pressed_mouse_buttons,
1024                &self.window,
1025                hit_test_result,
1026                input_event.active_keyboard_modifiers,
1027                2,
1028                can_gc,
1029            )
1030            .upcast::<Event>()
1031            .dispatch(element.upcast(), false, can_gc);
1032        }
1033    }
1034
1035    /// <https://www.w3.org/TR/uievents/#maybe-show-context-menu>
1036    fn maybe_show_context_menu(
1037        &self,
1038        target: &EventTarget,
1039        hit_test_result: &HitTestResult,
1040        input_event: &ConstellationInputEvent,
1041        can_gc: CanGc,
1042    ) {
1043        // <https://w3c.github.io/uievents/#contextmenu>
1044        let menu_event = PointerEvent::new(
1045            &self.window,                // window
1046            "contextmenu".into(),        // type
1047            EventBubbles::Bubbles,       // can_bubble
1048            EventCancelable::Cancelable, // cancelable
1049            Some(&self.window),          // view
1050            0,                           // detail
1051            hit_test_result.point_in_frame.to_i32(),
1052            hit_test_result.point_in_frame.to_i32(),
1053            hit_test_result
1054                .point_relative_to_initial_containing_block
1055                .to_i32(),
1056            input_event.active_keyboard_modifiers,
1057            2i16, // button, right mouse button
1058            input_event.pressed_mouse_buttons,
1059            None,                     // related_target
1060            None,                     // point_in_target
1061            PointerId::Mouse as i32,  // pointer_id
1062            1,                        // width
1063            1,                        // height
1064            0.5,                      // pressure
1065            0.0,                      // tangential_pressure
1066            0,                        // tilt_x
1067            0,                        // tilt_y
1068            0,                        // twist
1069            PI / 2.0,                 // altitude_angle
1070            0.0,                      // azimuth_angle
1071            DOMString::from("mouse"), // pointer_type
1072            true,                     // is_primary
1073            vec![],                   // coalesced_events
1074            vec![],                   // predicted_events
1075            can_gc,
1076        );
1077
1078        // Step 3. Let result = dispatch menuevent at target.
1079        let result = menu_event.upcast::<Event>().fire(target, can_gc);
1080
1081        // Step 4. If result is true, then show the UA context menu
1082        if result {
1083            self.window
1084                .Document()
1085                .embedder_controls()
1086                .show_context_menu(hit_test_result);
1087        };
1088    }
1089
1090    fn handle_touch_event(
1091        &self,
1092        event: EmbedderTouchEvent,
1093        input_event: &ConstellationInputEvent,
1094        can_gc: CanGc,
1095    ) -> InputEventResult {
1096        // Ignore all incoming events without a hit test.
1097        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1098            self.update_active_touch_points_when_early_return(event);
1099            return Default::default();
1100        };
1101
1102        let TouchId(identifier) = event.touch_id;
1103
1104        let Some(element) = hit_test_result
1105            .node
1106            .inclusive_ancestors(ShadowIncluding::Yes)
1107            .find_map(DomRoot::downcast::<Element>)
1108        else {
1109            self.update_active_touch_points_when_early_return(event);
1110            return Default::default();
1111        };
1112
1113        let current_target = DomRoot::upcast::<EventTarget>(element.clone());
1114        let window = &*self.window;
1115
1116        let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
1117        let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
1118        let page_x =
1119            Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
1120        let page_y =
1121            Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
1122
1123        // This is used to construct pointerevent and touchdown event.
1124        let pointer_touch = Touch::new(
1125            window,
1126            identifier,
1127            &current_target,
1128            client_x,
1129            client_y, // TODO: Get real screen coordinates?
1130            client_x,
1131            client_y,
1132            page_x,
1133            page_y,
1134            can_gc,
1135        );
1136
1137        // Dispatch pointer event before updating active touch points and before touch event.
1138        let pointer_event_name = match event.event_type {
1139            TouchEventType::Down => "pointerdown",
1140            TouchEventType::Move => "pointermove",
1141            TouchEventType::Up => "pointerup",
1142            TouchEventType::Cancel => "pointercancel",
1143        };
1144
1145        // Get or create pointer ID for this touch
1146        let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
1147        let is_primary = self.is_primary_pointer(pointer_id);
1148
1149        // For touch devices (which don't support hover), fire pointerover/pointerenter
1150        // <https://w3c.github.io/pointerevents/#mapping-for-devices-that-do-not-support-hover>
1151        if matches!(event.event_type, TouchEventType::Down) {
1152            // Fire pointerover
1153            let pointer_over = pointer_touch.to_pointer_event(
1154                window,
1155                "pointerover",
1156                pointer_id,
1157                is_primary,
1158                input_event.active_keyboard_modifiers,
1159                true, // cancelable
1160                Some(hit_test_result.point_in_node),
1161                can_gc,
1162            );
1163            pointer_over.upcast::<Event>().fire(&current_target, can_gc);
1164
1165            // Fire pointerenter hierarchically (from topmost ancestor to target)
1166            self.fire_pointer_event_for_touch(
1167                &element,
1168                &pointer_touch,
1169                pointer_id,
1170                "pointerenter",
1171                is_primary,
1172                input_event,
1173                &hit_test_result,
1174                can_gc,
1175            );
1176        }
1177
1178        let pointer_event = pointer_touch.to_pointer_event(
1179            window,
1180            pointer_event_name,
1181            pointer_id,
1182            is_primary,
1183            input_event.active_keyboard_modifiers,
1184            event.is_cancelable(),
1185            Some(hit_test_result.point_in_node),
1186            can_gc,
1187        );
1188        pointer_event
1189            .upcast::<Event>()
1190            .fire(&current_target, can_gc);
1191
1192        // For touch devices, fire pointerout/pointerleave after pointerup/pointercancel
1193        // <https://w3c.github.io/pointerevents/#mapping-for-devices-that-do-not-support-hover>
1194        if matches!(
1195            event.event_type,
1196            TouchEventType::Up | TouchEventType::Cancel
1197        ) {
1198            // Fire pointerout
1199            let pointer_out = pointer_touch.to_pointer_event(
1200                window,
1201                "pointerout",
1202                pointer_id,
1203                is_primary,
1204                input_event.active_keyboard_modifiers,
1205                true, // cancelable
1206                Some(hit_test_result.point_in_node),
1207                can_gc,
1208            );
1209            pointer_out.upcast::<Event>().fire(&current_target, can_gc);
1210
1211            // Fire pointerleave hierarchically (from target to topmost ancestor)
1212            self.fire_pointer_event_for_touch(
1213                &element,
1214                &pointer_touch,
1215                pointer_id,
1216                "pointerleave",
1217                is_primary,
1218                input_event,
1219                &hit_test_result,
1220                can_gc,
1221            );
1222        }
1223
1224        let (touch_dispatch_target, changed_touch) = match event.event_type {
1225            TouchEventType::Down => {
1226                // Add a new touch point
1227                self.active_touch_points
1228                    .borrow_mut()
1229                    .push(Dom::from_ref(&*pointer_touch));
1230                // <https://html.spec.whatwg.org/multipage/#selector-active>
1231                // If the element is being actively pointed at the element is being activated.
1232                self.element_for_activation(element).set_active_state(true);
1233                (current_target, pointer_touch)
1234            },
1235            _ => {
1236                // From <https://w3c.github.io/touch-events/#dfn-touchend>:
1237                // > For move/up/cancel:
1238                // > The target of this event must be the same Element on which the touch
1239                // > point started when it was first placed on the surface, even if the touch point
1240                // > has since moved outside the interactive area of the target element.
1241                let mut active_touch_points = self.active_touch_points.borrow_mut();
1242                let Some(index) = active_touch_points
1243                    .iter()
1244                    .position(|point| point.Identifier() == identifier)
1245                else {
1246                    warn!("No active touch point for {:?}", event.event_type);
1247                    return Default::default();
1248                };
1249                // This is the original target that was selected during `touchstart` event handling.
1250                let original_target = active_touch_points[index].Target();
1251
1252                let touch_with_touchstart_target = Touch::new(
1253                    window,
1254                    identifier,
1255                    &original_target,
1256                    client_x,
1257                    client_y,
1258                    client_x,
1259                    client_y,
1260                    page_x,
1261                    page_y,
1262                    can_gc,
1263                );
1264
1265                // Update or remove the stored touch
1266                match event.event_type {
1267                    TouchEventType::Move => {
1268                        active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1269                    },
1270                    TouchEventType::Up | TouchEventType::Cancel => {
1271                        active_touch_points.swap_remove(index);
1272                        self.remove_pointer_id_for_touch(identifier);
1273                        // <https://html.spec.whatwg.org/multipage/#selector-active>
1274                        // If the element is being actively pointed at the element is being activated.
1275                        self.element_for_activation(element).set_active_state(false);
1276                    },
1277                    TouchEventType::Down => unreachable!("Should have been handled above"),
1278                }
1279                (original_target, touch_with_touchstart_target)
1280            },
1281        };
1282
1283        rooted_vec!(let mut target_touches);
1284        target_touches.extend(
1285            self.active_touch_points
1286                .borrow()
1287                .iter()
1288                .filter(|touch| touch.Target() == touch_dispatch_target)
1289                .cloned(),
1290        );
1291
1292        let event_name = match event.event_type {
1293            TouchEventType::Down => "touchstart",
1294            TouchEventType::Move => "touchmove",
1295            TouchEventType::Up => "touchend",
1296            TouchEventType::Cancel => "touchcancel",
1297        };
1298
1299        let touch_event = TouchEvent::new(
1300            window,
1301            event_name.into(),
1302            EventBubbles::Bubbles,
1303            EventCancelable::from(event.is_cancelable()),
1304            EventComposed::Composed,
1305            Some(window),
1306            0i32,
1307            &TouchList::new(window, self.active_touch_points.borrow().r(), can_gc),
1308            &TouchList::new(window, from_ref(&&*changed_touch), can_gc),
1309            &TouchList::new(window, target_touches.r(), can_gc),
1310            // FIXME: modifier keys
1311            false,
1312            false,
1313            false,
1314            false,
1315            can_gc,
1316        );
1317        let event = touch_event.upcast::<Event>();
1318        event.fire(&touch_dispatch_target, can_gc);
1319        event.flags().into()
1320    }
1321
1322    /// Updates the active touch points when a hit test fails early.
1323    ///
1324    /// - For `Down`: No action needed; a failed down event won't create an active point.
1325    /// - For `Move`: No action needed; position information is unavailable, so we cannot update.
1326    /// - For `Up`/`Cancel`: Remove the corresponding touch point and its pointer ID mapping.
1327    ///
1328    /// When a touchup or touchcancel occurs at that touch point,
1329    /// a warning is triggered: Received touchup/touchcancel event for a non-active touch point.
1330    fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1331        match event.event_type {
1332            TouchEventType::Down | TouchEventType::Move => {},
1333            TouchEventType::Up | TouchEventType::Cancel => {
1334                let mut active_touch_points = self.active_touch_points.borrow_mut();
1335                if let Some(index) = active_touch_points
1336                    .iter()
1337                    .position(|t| t.Identifier() == event.touch_id.0)
1338                {
1339                    active_touch_points.swap_remove(index);
1340                    self.remove_pointer_id_for_touch(event.touch_id.0);
1341                } else {
1342                    warn!(
1343                        "Received {:?} for a non-active touch point {}",
1344                        event.event_type, event.touch_id.0
1345                    );
1346                }
1347            },
1348        }
1349    }
1350
1351    /// The entry point for all key processing for web content
1352    fn handle_keyboard_event(
1353        &self,
1354        keyboard_event: EmbedderKeyboardEvent,
1355        can_gc: CanGc,
1356    ) -> InputEventResult {
1357        let document = self.window.Document();
1358        let focused = document.get_focused_element();
1359        let body = document.GetBody();
1360
1361        let target = match (&focused, &body) {
1362            (Some(focused), _) => focused.upcast(),
1363            (&None, Some(body)) => body.upcast(),
1364            (&None, &None) => self.window.upcast(),
1365        };
1366
1367        let keyevent = KeyboardEvent::new_with_platform_keyboard_event(
1368            &self.window,
1369            keyboard_event.event.state.event_type().into(),
1370            &keyboard_event.event,
1371            can_gc,
1372        );
1373
1374        let event = keyevent.upcast::<Event>();
1375        event.fire(target, can_gc);
1376
1377        let mut flags = event.flags();
1378        if flags.contains(EventFlags::Canceled) {
1379            return flags.into();
1380        }
1381
1382        // https://w3c.github.io/uievents/#keys-cancelable-keys
1383        // it MUST prevent the respective beforeinput and input
1384        // (and keypress if supported) events from being generated
1385        // TODO: keypress should be deprecated and superceded by beforeinput
1386
1387        let is_character_value_key = matches!(
1388            keyboard_event.event.key,
1389            Key::Character(_) | Key::Named(NamedKey::Enter)
1390        );
1391        if keyboard_event.event.state == KeyState::Down &&
1392            is_character_value_key &&
1393            !keyboard_event.event.is_composing
1394        {
1395            // https://w3c.github.io/uievents/#keypress-event-order
1396            let keypress_event = KeyboardEvent::new_with_platform_keyboard_event(
1397                &self.window,
1398                atom!("keypress"),
1399                &keyboard_event.event,
1400                can_gc,
1401            );
1402            let event = keypress_event.upcast::<Event>();
1403            event.fire(target, can_gc);
1404            flags = event.flags();
1405        }
1406
1407        flags.into()
1408    }
1409
1410    fn handle_ime_event(&self, event: ImeEvent, can_gc: CanGc) -> InputEventResult {
1411        let document = self.window.Document();
1412        let composition_event = match event {
1413            ImeEvent::Dismissed => {
1414                document.request_focus(
1415                    document.GetBody().as_ref().map(|e| e.upcast()),
1416                    FocusInitiator::Keyboard,
1417                    can_gc,
1418                );
1419                return Default::default();
1420            },
1421            ImeEvent::Composition(composition_event) => composition_event,
1422        };
1423
1424        // spec: https://w3c.github.io/uievents/#compositionstart
1425        // spec: https://w3c.github.io/uievents/#compositionupdate
1426        // spec: https://w3c.github.io/uievents/#compositionend
1427        // > Event.target : focused element processing the composition
1428        let focused = document.get_focused_element();
1429        let target = if let Some(elem) = &focused {
1430            elem.upcast()
1431        } else {
1432            // Event is only dispatched if there is a focused element.
1433            return Default::default();
1434        };
1435
1436        let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1437        let event = CompositionEvent::new(
1438            &self.window,
1439            composition_event.state.event_type().into(),
1440            true,
1441            cancelable,
1442            Some(&self.window),
1443            0,
1444            DOMString::from(composition_event.data),
1445            can_gc,
1446        );
1447
1448        let event = event.upcast::<Event>();
1449        event.fire(target, can_gc);
1450        event.flags().into()
1451    }
1452
1453    fn handle_wheel_event(
1454        &self,
1455        event: EmbedderWheelEvent,
1456        input_event: &ConstellationInputEvent,
1457        can_gc: CanGc,
1458    ) -> InputEventResult {
1459        // Ignore all incoming events without a hit test.
1460        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1461            return Default::default();
1462        };
1463
1464        let Some(el) = hit_test_result
1465            .node
1466            .inclusive_ancestors(ShadowIncluding::Yes)
1467            .find_map(DomRoot::downcast::<Element>)
1468        else {
1469            return Default::default();
1470        };
1471
1472        let node = el.upcast::<Node>();
1473        debug!(
1474            "wheel: on {:?} at {:?}",
1475            node.debug_str(),
1476            hit_test_result.point_in_frame
1477        );
1478
1479        // https://w3c.github.io/uievents/#event-wheelevents
1480        let dom_event = WheelEvent::new(
1481            &self.window,
1482            "wheel".into(),
1483            EventBubbles::Bubbles,
1484            EventCancelable::Cancelable,
1485            Some(&self.window),
1486            0i32,
1487            hit_test_result.point_in_frame.to_i32(),
1488            hit_test_result.point_in_frame.to_i32(),
1489            hit_test_result
1490                .point_relative_to_initial_containing_block
1491                .to_i32(),
1492            input_event.active_keyboard_modifiers,
1493            0i16,
1494            input_event.pressed_mouse_buttons,
1495            None,
1496            None,
1497            // winit defines positive wheel delta values as revealing more content left/up.
1498            // https://docs.rs/winit-gtk/latest/winit/event/enum.MouseScrollDelta.html
1499            // This is the opposite of wheel delta in uievents
1500            // https://w3c.github.io/uievents/#dom-wheeleventinit-deltaz
1501            Finite::wrap(-event.delta.x),
1502            Finite::wrap(-event.delta.y),
1503            Finite::wrap(-event.delta.z),
1504            event.delta.mode as u32,
1505            can_gc,
1506        );
1507
1508        let dom_event = dom_event.upcast::<Event>();
1509        dom_event.set_trusted(true);
1510        dom_event.fire(node.upcast(), can_gc);
1511
1512        dom_event.flags().into()
1513    }
1514
1515    #[cfg(feature = "gamepad")]
1516    fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1517        match gamepad_event {
1518            EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1519                self.handle_gamepad_connect(
1520                    index.0,
1521                    name,
1522                    bounds.axis_bounds,
1523                    bounds.button_bounds,
1524                    supported_haptic_effects,
1525                );
1526            },
1527            EmbedderGamepadEvent::Disconnected(index) => {
1528                self.handle_gamepad_disconnect(index.0);
1529            },
1530            EmbedderGamepadEvent::Updated(index, update_type) => {
1531                self.receive_new_gamepad_button_or_axis(index.0, update_type);
1532            },
1533        };
1534    }
1535
1536    /// <https://www.w3.org/TR/gamepad/#dfn-gamepadconnected>
1537    #[cfg(feature = "gamepad")]
1538    fn handle_gamepad_connect(
1539        &self,
1540        // As the spec actually defines how to set the gamepad index, the GilRs index
1541        // is currently unused, though in practice it will almost always be the same.
1542        // More infra is currently needed to track gamepads across windows.
1543        _index: usize,
1544        name: String,
1545        axis_bounds: (f64, f64),
1546        button_bounds: (f64, f64),
1547        supported_haptic_effects: GamepadSupportedHapticEffects,
1548    ) {
1549        // TODO: 2. If document is not null and is not allowed to use the "gamepad" permission,
1550        //          then abort these steps.
1551        let trusted_window = Trusted::new(&*self.window);
1552        self.window
1553            .upcast::<GlobalScope>()
1554            .task_manager()
1555            .gamepad_task_source()
1556            .queue(task!(gamepad_connected: move || {
1557                let window = trusted_window.root();
1558
1559                let navigator = window.Navigator();
1560                let selected_index = navigator.select_gamepad_index();
1561                let gamepad = Gamepad::new(
1562                    &window,
1563                    selected_index,
1564                    name,
1565                    "standard".into(),
1566                    axis_bounds,
1567                    button_bounds,
1568                    supported_haptic_effects,
1569                    false,
1570                    CanGc::note(),
1571                );
1572                navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::note());
1573            }));
1574    }
1575
1576    /// <https://www.w3.org/TR/gamepad/#dfn-gamepaddisconnected>
1577    #[cfg(feature = "gamepad")]
1578    fn handle_gamepad_disconnect(&self, index: usize) {
1579        let trusted_window = Trusted::new(&*self.window);
1580        self.window
1581            .upcast::<GlobalScope>()
1582            .task_manager()
1583            .gamepad_task_source()
1584            .queue(task!(gamepad_disconnected: move || {
1585                let window = trusted_window.root();
1586                let navigator = window.Navigator();
1587                if let Some(gamepad) = navigator.get_gamepad(index) {
1588                    if window.Document().is_fully_active() {
1589                        gamepad.update_connected(false, gamepad.exposed(), CanGc::note());
1590                        navigator.remove_gamepad(index);
1591                    }
1592                }
1593            }));
1594    }
1595
1596    /// <https://www.w3.org/TR/gamepad/#receiving-inputs>
1597    #[cfg(feature = "gamepad")]
1598    fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1599        let trusted_window = Trusted::new(&*self.window);
1600
1601        // <https://w3c.github.io/gamepad/#dfn-update-gamepad-state>
1602        self.window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1603                task!(update_gamepad_state: move || {
1604                    let window = trusted_window.root();
1605                    let navigator = window.Navigator();
1606                    if let Some(gamepad) = navigator.get_gamepad(index) {
1607                        let current_time = window.Performance().Now();
1608                        gamepad.update_timestamp(*current_time);
1609                        match update_type {
1610                            GamepadUpdateType::Axis(index, value) => {
1611                                gamepad.map_and_normalize_axes(index, value);
1612                            },
1613                            GamepadUpdateType::Button(index, value) => {
1614                                gamepad.map_and_normalize_buttons(index, value);
1615                            }
1616                        };
1617                        if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1618                            navigator.set_has_gamepad_gesture(true);
1619                            navigator.GetGamepads()
1620                                .iter()
1621                                .filter_map(|g| g.as_ref())
1622                                .for_each(|gamepad| {
1623                                    gamepad.set_exposed(true);
1624                                    gamepad.update_timestamp(*current_time);
1625                                    let new_gamepad = Trusted::new(&**gamepad);
1626                                    if window.Document().is_fully_active() {
1627                                        window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1628                                            task!(update_gamepad_connect: move || {
1629                                                let gamepad = new_gamepad.root();
1630                                                gamepad.notify_event(GamepadEventType::Connected, CanGc::note());
1631                                            })
1632                                        );
1633                                    }
1634                                });
1635                        }
1636                    }
1637                })
1638            );
1639    }
1640
1641    /// <https://www.w3.org/TR/clipboard-apis/#clipboard-actions>
1642    pub(crate) fn handle_editing_action(
1643        &self,
1644        element: Option<DomRoot<Element>>,
1645        action: EditingActionEvent,
1646        can_gc: CanGc,
1647    ) -> InputEventResult {
1648        let clipboard_event_type = match action {
1649            EditingActionEvent::Copy => ClipboardEventType::Copy,
1650            EditingActionEvent::Cut => ClipboardEventType::Cut,
1651            EditingActionEvent::Paste => ClipboardEventType::Paste,
1652        };
1653
1654        // The script_triggered flag is set if the action runs because of a script, e.g. document.execCommand()
1655        let script_triggered = false;
1656
1657        // The script_may_access_clipboard flag is set
1658        // if action is paste and the script thread is allowed to read from clipboard or
1659        // if action is copy or cut and the script thread is allowed to modify the clipboard
1660        let script_may_access_clipboard = false;
1661
1662        // Step 1 If the script-triggered flag is set and the script-may-access-clipboard flag is unset
1663        if script_triggered && !script_may_access_clipboard {
1664            return InputEventResult::empty();
1665        }
1666
1667        // Step 2 Fire a clipboard event
1668        let clipboard_event =
1669            self.fire_clipboard_event(element.clone(), clipboard_event_type, can_gc);
1670
1671        // Step 3 If a script doesn't call preventDefault()
1672        // the event will be handled inside target's VirtualMethods::handle_event
1673        let event = clipboard_event.upcast::<Event>();
1674        if !event.IsTrusted() {
1675            return event.flags().into();
1676        }
1677
1678        // Step 4 If the event was canceled, then
1679        if event.DefaultPrevented() {
1680            let event_type = event.Type();
1681            match_domstring_ascii!(event_type,
1682
1683                "copy" => {
1684                    // Step 4.1 Call the write content to the clipboard algorithm,
1685                    // passing on the DataTransferItemList items, a clear-was-called flag and a types-to-clear list.
1686                    if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1687                        let drag_data_store =
1688                            clipboard_data.data_store().expect("This shouldn't fail");
1689                        self.write_content_to_the_clipboard(&drag_data_store);
1690                    }
1691                },
1692                "cut" => {
1693                    // Step 4.1 Call the write content to the clipboard algorithm,
1694                    // passing on the DataTransferItemList items, a clear-was-called flag and a types-to-clear list.
1695                    if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1696                        let drag_data_store =
1697                            clipboard_data.data_store().expect("This shouldn't fail");
1698                        self.write_content_to_the_clipboard(&drag_data_store);
1699                    }
1700
1701                    // Step 4.2 Fire a clipboard event named clipboardchange
1702                    self.fire_clipboard_event(element, ClipboardEventType::Change, can_gc);
1703                },
1704                // Step 4.1 Return false.
1705                // Note: This function deviates from the specification a bit by returning
1706                // the `InputEventResult` below.
1707                "paste" => (),
1708                _ => (),
1709            )
1710        }
1711
1712        // Step 5: Return true from the action.
1713        // In this case we are returning the `InputEventResult` instead of true or false.
1714        event.flags().into()
1715    }
1716
1717    /// <https://www.w3.org/TR/clipboard-apis/#fire-a-clipboard-event>
1718    pub(crate) fn fire_clipboard_event(
1719        &self,
1720        target: Option<DomRoot<Element>>,
1721        clipboard_event_type: ClipboardEventType,
1722        can_gc: CanGc,
1723    ) -> DomRoot<ClipboardEvent> {
1724        let clipboard_event = ClipboardEvent::new(
1725            &self.window,
1726            None,
1727            clipboard_event_type.as_str().into(),
1728            EventBubbles::Bubbles,
1729            EventCancelable::Cancelable,
1730            None,
1731            can_gc,
1732        );
1733
1734        // Step 1 Let clear_was_called be false
1735        // Step 2 Let types_to_clear an empty list
1736        let mut drag_data_store = DragDataStore::new();
1737
1738        // Step 4 let clipboard-entry be the sequence number of clipboard content, null if the OS doesn't support it.
1739
1740        // Step 5 let trusted be true if the event is generated by the user agent, false otherwise
1741        let trusted = true;
1742
1743        // Step 6 if the context is editable:
1744        let document = self.window.Document();
1745        let target = target.or(document.get_focused_element());
1746        let target = target
1747            .map(|target| DomRoot::from_ref(target.upcast()))
1748            .or_else(|| {
1749                document
1750                    .GetBody()
1751                    .map(|body| DomRoot::from_ref(body.upcast()))
1752            })
1753            .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast()));
1754
1755        // Step 6.2 else TODO require Selection see https://github.com/w3c/clipboard-apis/issues/70
1756        // Step 7
1757        match clipboard_event_type {
1758            ClipboardEventType::Copy | ClipboardEventType::Cut => {
1759                // Step 7.2.1
1760                drag_data_store.set_mode(Mode::ReadWrite);
1761            },
1762            ClipboardEventType::Paste => {
1763                let (callback, receiver) =
1764                    GenericCallback::new_blocking().expect("Could not create callback");
1765                self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
1766                    self.window.webview_id(),
1767                    callback,
1768                ));
1769                let text_contents = receiver
1770                    .recv()
1771                    .map(Result::unwrap_or_default)
1772                    .unwrap_or_default();
1773
1774                // Step 7.1.1
1775                drag_data_store.set_mode(Mode::ReadOnly);
1776                // Step 7.1.2 If trusted or the implementation gives script-generated events access to the clipboard
1777                if trusted {
1778                    // Step 7.1.2.1 For each clipboard-part on the OS clipboard:
1779
1780                    // Step 7.1.2.1.1 If clipboard-part contains plain text, then
1781                    let data = DOMString::from(text_contents);
1782                    let type_ = DOMString::from("text/plain");
1783                    let _ = drag_data_store.add(Kind::Text { data, type_ });
1784
1785                    // Step 7.1.2.1.2 TODO If clipboard-part represents file references, then for each file reference
1786                    // Step 7.1.2.1.3 TODO If clipboard-part contains HTML- or XHTML-formatted text then
1787
1788                    // Step 7.1.3 Update clipboard-event-data’s files to match clipboard-event-data’s items
1789                    // Step 7.1.4 Update clipboard-event-data’s types to match clipboard-event-data’s items
1790                }
1791            },
1792            ClipboardEventType::Change => (),
1793        }
1794
1795        // Step 3
1796        let clipboard_event_data = DataTransfer::new(
1797            &self.window,
1798            Rc::new(RefCell::new(Some(drag_data_store))),
1799            can_gc,
1800        );
1801
1802        // Step 8
1803        clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
1804
1805        // Step 9
1806        let event = clipboard_event.upcast::<Event>();
1807        event.set_trusted(trusted);
1808
1809        // Step 10 Set event’s composed to true.
1810        event.set_composed(true);
1811
1812        // Step 11
1813        event.dispatch(&target, false, can_gc);
1814
1815        DomRoot::from(clipboard_event)
1816    }
1817
1818    /// <https://www.w3.org/TR/clipboard-apis/#write-content-to-the-clipboard>
1819    fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
1820        // Step 1
1821        if drag_data_store.list_len() > 0 {
1822            // Step 1.1 Clear the clipboard.
1823            self.window
1824                .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1825            // Step 1.2
1826            for item in drag_data_store.iter_item_list() {
1827                match item {
1828                    Kind::Text { data, .. } => {
1829                        // Step 1.2.1.1 Ensure encoding is correct per OS and locale conventions
1830                        // Step 1.2.1.2 Normalize line endings according to platform conventions
1831                        // Step 1.2.1.3
1832                        self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
1833                            self.window.webview_id(),
1834                            data.to_string(),
1835                        ));
1836                    },
1837                    Kind::File { .. } => {
1838                        // Step 1.2.2 If data is of a type listed in the mandatory data types list, then
1839                        // Step 1.2.2.1 Place part on clipboard with the appropriate OS clipboard format description
1840                        // Step 1.2.3 Else this is left to the implementation
1841                    },
1842                }
1843            }
1844        } else {
1845            // Step 2.1
1846            if drag_data_store.clear_was_called {
1847                // Step 2.1.1 If types-to-clear list is empty, clear the clipboard
1848                self.window
1849                    .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1850                // Step 2.1.2 Else remove the types in the list from the clipboard
1851                // As of now this can't be done with Arboard, and it's possible that will be removed from the spec
1852            }
1853        }
1854    }
1855
1856    /// Handle a scroll event triggered by user interactions from the embedder.
1857    /// <https://drafts.csswg.org/cssom-view/#scrolling-events>
1858    #[expect(unsafe_code)]
1859    pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
1860        // If it is a viewport scroll.
1861        let document = self.window.Document();
1862        if scrolled_node.is_root() {
1863            document.handle_viewport_scroll_event();
1864        } else {
1865            // Otherwise, check whether it is for a relevant element within the document.
1866            let Some(node_id) = node_id_from_scroll_id(scrolled_node.0 as usize) else {
1867                return;
1868            };
1869            let node = unsafe {
1870                node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
1871            };
1872            let Some(element) = node
1873                .inclusive_ancestors(ShadowIncluding::Yes)
1874                .find_map(DomRoot::downcast::<Element>)
1875            else {
1876                return;
1877            };
1878
1879            element.handle_scroll_event();
1880        }
1881    }
1882
1883    /// <https://w3c.github.io/uievents/#keydown>
1884    ///
1885    /// > If the key is the Enter or (Space) key and the current focus is on a state-changing element,
1886    /// > the default action MUST be to dispatch a click event, and a DOMActivate event if that event
1887    /// > type is supported by the user agent.
1888    pub(crate) fn maybe_dispatch_simulated_click(
1889        &self,
1890        node: &Node,
1891        event: &KeyboardEvent,
1892        can_gc: CanGc,
1893    ) -> bool {
1894        if event.key() != Key::Named(NamedKey::Enter) && event.original_code() != Some(Code::Space)
1895        {
1896            return false;
1897        }
1898
1899        // Check whether this node is a state-changing element. Note that the specification doesn't
1900        // seem to have a good definition of what "state-changing" means, so we merely check to
1901        // see if the element is activatable here.
1902        if node
1903            .downcast::<Element>()
1904            .and_then(Element::as_maybe_activatable)
1905            .is_none()
1906        {
1907            return false;
1908        }
1909
1910        node.fire_synthetic_pointer_event_not_trusted(atom!("click"), can_gc);
1911        true
1912    }
1913
1914    pub(crate) fn run_default_keyboard_event_handler(
1915        &self,
1916        node: &Node,
1917        event: &KeyboardEvent,
1918        can_gc: CanGc,
1919    ) {
1920        if event.upcast::<Event>().type_() != atom!("keydown") {
1921            return;
1922        }
1923
1924        if self.maybe_dispatch_simulated_click(node, event, can_gc) {
1925            return;
1926        }
1927
1928        if self.maybe_handle_accesskey(event, can_gc) {
1929            return;
1930        }
1931
1932        let mut is_space = false;
1933        let scroll = match event.key() {
1934            Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
1935            Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
1936            Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
1937            Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
1938            Key::Named(NamedKey::End) => KeyboardScroll::End,
1939            Key::Named(NamedKey::Home) => KeyboardScroll::Home,
1940            Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
1941            Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
1942            Key::Character(string) if &string == " " => {
1943                is_space = true;
1944                if event.modifiers().contains(Modifiers::SHIFT) {
1945                    KeyboardScroll::PageUp
1946                } else {
1947                    KeyboardScroll::PageDown
1948                }
1949            },
1950            Key::Named(NamedKey::Tab) => {
1951                // From <https://w3c.github.io/uievents/#keydown>:
1952                //
1953                // > If the key is the Tab key, the default action MUST be to shift the document focus
1954                // > from the currently focused element (if any) to the new focused element, as
1955                // > described in Focus Event Types
1956                self.sequential_focus_navigation_via_keyboard_event(event, can_gc);
1957                return;
1958            },
1959            _ => return,
1960        };
1961
1962        if !event.modifiers().is_empty() && !is_space {
1963            return;
1964        }
1965
1966        self.do_keyboard_scroll(scroll);
1967    }
1968
1969    pub(crate) fn set_sequential_focus_navigation_starting_point(&self, node: &Node) {
1970        self.sequential_focus_navigation_starting_point
1971            .set(Some(node));
1972    }
1973
1974    pub(crate) fn sequential_focus_navigation_starting_point(&self) -> Option<DomRoot<Node>> {
1975        self.sequential_focus_navigation_starting_point
1976            .get()
1977            .filter(|node| node.is_connected())
1978    }
1979
1980    fn sequential_focus_navigation_via_keyboard_event(&self, event: &KeyboardEvent, can_gc: CanGc) {
1981        let direction = if event.modifiers().contains(Modifiers::SHIFT) {
1982            SequentialFocusDirection::Backward
1983        } else {
1984            SequentialFocusDirection::Forward
1985        };
1986
1987        self.sequential_focus_navigation(direction, can_gc);
1988    }
1989
1990    /// <<https://html.spec.whatwg.org/multipage/#sequential-focus-navigation:currently-focused-area-of-a-top-level-traversable>
1991    fn sequential_focus_navigation(&self, direction: SequentialFocusDirection, can_gc: CanGc) {
1992        // > When the user requests that focus move from the currently focused area of a top-level
1993        // > traversable to the next or previous focusable area (e.g., as the default action of
1994        // > pressing the tab key), or when the user requests that focus sequentially move to a
1995        // > top-level traversable in the first place (e.g., from the browser's location bar), the
1996        // > user agent must use the following algorithm:
1997
1998        // > 1. Let starting point be the currently focused area of a top-level traversable, if the
1999        // > user requested to move focus sequentially from there, or else the top-level traversable
2000        // > itself, if the user instead requested to move focus from outside the top-level
2001        // > traversable.
2002        //
2003        // TODO: We do not yet implement support for doing sequential focus navigation between traversibles
2004        // according to the specification, so the implementation is currently adapted to work with a single
2005        // traversible.
2006        //
2007        // Note: Here `None` represents the current traversible.
2008        let mut starting_point = self
2009            .window
2010            .Document()
2011            .get_focused_element()
2012            .map(DomRoot::upcast::<Node>);
2013
2014        // > 2. If there is a sequential focus navigation starting point defined and it is inside
2015        // > starting point, then let starting point be the sequential focus navigation starting point
2016        // > instead.
2017        if let Some(sequential_focus_navigation_starting_point) =
2018            self.sequential_focus_navigation_starting_point()
2019        {
2020            if starting_point.as_ref().is_none_or(|starting_point| {
2021                starting_point.is_ancestor_of(&sequential_focus_navigation_starting_point)
2022            }) {
2023                starting_point = Some(sequential_focus_navigation_starting_point);
2024            }
2025        }
2026
2027        // > 3. Let direction be "forward" if the user requested the next control, and "backward" if
2028        // > the user requested the previous control.
2029        //
2030        // Note: This is handled by the `direction` argument to this method.
2031
2032        // > 4. Loop: Let selection mechanism be "sequential" if starting point is a navigable or if
2033        // > starting point is in its Document's sequential focus navigation order.
2034        // > Otherwise, starting point is not in its Document's sequential focus navigation order;
2035        // > let selection mechanism be "DOM".
2036        // TODO: Implement this.
2037
2038        // > 5. Let candidate be the result of running the sequential navigation search algorithm
2039        // > with starting point, direction, and selection mechanism.
2040        let candidate = starting_point
2041            .map(|starting_point| {
2042                self.find_element_for_tab_focus_following_element(direction, starting_point)
2043            })
2044            .unwrap_or_else(|| self.find_first_tab_focusable_element(direction));
2045
2046        // > 6. If candidate is not null, then run the focusing steps for candidate and return.
2047        if let Some(candidate) = candidate {
2048            self.focus_and_scroll_to_element_for_key_event(&candidate, can_gc);
2049            return;
2050        }
2051
2052        // > 7. Otherwise, unset the sequential focus navigation starting point.
2053        self.sequential_focus_navigation_starting_point.clear();
2054
2055        // > 8. If starting point is a top-level traversable, or a focusable area in the top-level
2056        // > traversable, the user agent should transfer focus to its own controls appropriately (if
2057        // > any), honouring direction, and then return.
2058        // TODO: Implement this.
2059
2060        // > 9. Otherwise, starting point is a focusable area in a child navigable. Set starting
2061        // > point to that child navigable's parent and return to the step labeled loop.
2062        // TODO: Implement this.
2063    }
2064
2065    fn find_element_for_tab_focus_following_element(
2066        &self,
2067        direction: SequentialFocusDirection,
2068        starting_point: DomRoot<Node>,
2069    ) -> Option<DomRoot<Element>> {
2070        let root_node = self.window.Document().GetDocumentElement()?;
2071        let focused_element_tab_index = starting_point
2072            .downcast::<Element>()
2073            .and_then(Element::explicitly_set_tab_index)
2074            .unwrap_or_default();
2075        let mut winning_node_and_tab_index: Option<(DomRoot<Element>, i32)> = None;
2076        let mut saw_focused_element = false;
2077
2078        for node in root_node
2079            .upcast::<Node>()
2080            .traverse_preorder(ShadowIncluding::Yes)
2081        {
2082            if node == starting_point {
2083                saw_focused_element = true;
2084                continue;
2085            }
2086
2087            let Some(candidate_element) = DomRoot::downcast::<Element>(node) else {
2088                continue;
2089            };
2090            if !candidate_element.is_sequentially_focusable() {
2091                continue;
2092            }
2093
2094            let candidate_element_tab_index = candidate_element
2095                .explicitly_set_tab_index()
2096                .unwrap_or_default();
2097            let ordering =
2098                compare_tab_indices(focused_element_tab_index, candidate_element_tab_index);
2099            match direction {
2100                SequentialFocusDirection::Forward => {
2101                    // If moving forward the first element with equal tab index after the current
2102                    // element is the winner.
2103                    if saw_focused_element && ordering == Ordering::Equal {
2104                        return Some(candidate_element);
2105                    }
2106                    // If the candidate element does not have a lesser tab index, then discard it.
2107                    if ordering != Ordering::Less {
2108                        continue;
2109                    }
2110                    let Some((_, winning_tab_index)) = winning_node_and_tab_index else {
2111                        // If this candidate has a tab index which is one greater than the current
2112                        // tab index, then we know it is the winner, because we give precedence to
2113                        // elements earlier in the DOM.
2114                        if candidate_element_tab_index == focused_element_tab_index + 1 {
2115                            return Some(candidate_element);
2116                        }
2117
2118                        winning_node_and_tab_index =
2119                            Some((candidate_element, candidate_element_tab_index));
2120                        continue;
2121                    };
2122                    // If the candidate element has a lesser tab index than than the current winner,
2123                    // then it becomes the winner.
2124                    if compare_tab_indices(candidate_element_tab_index, winning_tab_index) ==
2125                        Ordering::Less
2126                    {
2127                        winning_node_and_tab_index =
2128                            Some((candidate_element, candidate_element_tab_index))
2129                    }
2130                },
2131                SequentialFocusDirection::Backward => {
2132                    // If moving backward the last element with an equal tab index that precedes
2133                    // the focused element in the DOM is the winner.
2134                    if !saw_focused_element && ordering == Ordering::Equal {
2135                        winning_node_and_tab_index =
2136                            Some((candidate_element, candidate_element_tab_index));
2137                        continue;
2138                    }
2139                    // If the candidate does not have a greater tab index, then discard it.
2140                    if ordering != Ordering::Greater {
2141                        continue;
2142                    }
2143                    let Some((_, winning_tab_index)) = winning_node_and_tab_index else {
2144                        winning_node_and_tab_index =
2145                            Some((candidate_element, candidate_element_tab_index));
2146                        continue;
2147                    };
2148                    // If the candidate element's tab index is not less than the current winner,
2149                    // then it becomes the new winner. This means that when the tab indices are
2150                    // equal, we give preference to the last one in DOM order.
2151                    if compare_tab_indices(candidate_element_tab_index, winning_tab_index) !=
2152                        Ordering::Less
2153                    {
2154                        winning_node_and_tab_index =
2155                            Some((candidate_element, candidate_element_tab_index))
2156                    }
2157                },
2158            }
2159        }
2160
2161        Some(winning_node_and_tab_index?.0)
2162    }
2163
2164    fn find_first_tab_focusable_element(
2165        &self,
2166        direction: SequentialFocusDirection,
2167    ) -> Option<DomRoot<Element>> {
2168        let root_node = self.window.Document().GetDocumentElement()?;
2169        let mut winning_node_and_tab_index: Option<(DomRoot<Element>, i32)> = None;
2170        for node in root_node
2171            .upcast::<Node>()
2172            .traverse_preorder(ShadowIncluding::Yes)
2173        {
2174            let Some(candidate_element) = DomRoot::downcast::<Element>(node) else {
2175                continue;
2176            };
2177            if !candidate_element.is_sequentially_focusable() {
2178                continue;
2179            }
2180
2181            let candidate_element_tab_index = candidate_element
2182                .explicitly_set_tab_index()
2183                .unwrap_or_default();
2184            match direction {
2185                SequentialFocusDirection::Forward => {
2186                    // We can immediately return the first time we find an element with the lowest
2187                    // possible tab index (1). We are guaranteed not to find any lower tab index
2188                    // and all other equal tab indices are later in the DOM.
2189                    if candidate_element_tab_index == 1 {
2190                        return Some(candidate_element);
2191                    }
2192
2193                    // Only promote a candidate to the current winner if it has a lesser tab
2194                    // index than the current winner or there is currently no winer.
2195                    if winning_node_and_tab_index
2196                        .as_ref()
2197                        .is_none_or(|(_, winning_tab_index)| {
2198                            compare_tab_indices(candidate_element_tab_index, *winning_tab_index) ==
2199                                Ordering::Less
2200                        })
2201                    {
2202                        winning_node_and_tab_index =
2203                            Some((candidate_element, candidate_element_tab_index));
2204                    }
2205                },
2206                SequentialFocusDirection::Backward => {
2207                    // Only promote a candidate to winner if it has tab index equal to or
2208                    // greater than the winner's tab index. This gives precedence to elements
2209                    // later in the DOM.
2210                    if winning_node_and_tab_index
2211                        .as_ref()
2212                        .is_none_or(|(_, winning_tab_index)| {
2213                            compare_tab_indices(candidate_element_tab_index, *winning_tab_index) !=
2214                                Ordering::Less
2215                        })
2216                    {
2217                        winning_node_and_tab_index =
2218                            Some((candidate_element, candidate_element_tab_index));
2219                    }
2220                },
2221            }
2222        }
2223
2224        Some(winning_node_and_tab_index?.0)
2225    }
2226
2227    pub(crate) fn do_keyboard_scroll(&self, scroll: KeyboardScroll) {
2228        let scroll_axis = match scroll {
2229            KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
2230            _ => ScrollingBoxAxis::Y,
2231        };
2232
2233        let document = self.window.Document();
2234        let mut scrolling_box = document
2235            .get_focused_element()
2236            .or(self.most_recently_clicked_element.get())
2237            .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
2238            .unwrap_or_else(|| {
2239                document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2240            });
2241
2242        while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2243            // Always fall back to trying to scroll the entire document.
2244            if scrolling_box.is_viewport() {
2245                break;
2246            }
2247            let parent = scrolling_box.parent().unwrap_or_else(|| {
2248                document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
2249            });
2250            scrolling_box = parent;
2251        }
2252
2253        let calculate_current_scroll_offset_and_delta = || {
2254            const LINE_HEIGHT: f32 = 76.0;
2255            const LINE_WIDTH: f32 = 76.0;
2256
2257            let current_scroll_offset = scrolling_box.scroll_position();
2258            (
2259                current_scroll_offset,
2260                match scroll {
2261                    KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
2262                    KeyboardScroll::End => Vector2D::new(
2263                        0.0,
2264                        -current_scroll_offset.y + scrolling_box.content_size().height -
2265                            scrolling_box.size().height,
2266                    ),
2267                    KeyboardScroll::PageDown => {
2268                        Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
2269                    },
2270                    KeyboardScroll::PageUp => {
2271                        Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
2272                    },
2273                    KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
2274                    KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
2275                    KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
2276                    KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
2277                },
2278            )
2279        };
2280
2281        // If trying to scroll the viewport of this `Window` and this is the root `Document`
2282        // of the `WebView`, then send the srolling operation to the renderer, so that it
2283        // can properly pan any pinch zoom viewport.
2284        let parent_pipeline = self.window.parent_info();
2285        if scrolling_box.is_viewport() && parent_pipeline.is_none() {
2286            let (_, delta) = calculate_current_scroll_offset_and_delta();
2287            self.window
2288                .paint_api()
2289                .scroll_viewport_by_delta(self.window.webview_id(), delta);
2290        }
2291
2292        // If this is the viewport and we cannot scroll, try to ask a parent viewport to scroll,
2293        // if we are inside an `<iframe>`.
2294        if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
2295            assert!(scrolling_box.is_viewport());
2296
2297            let window_proxy = document.window().window_proxy();
2298            if let Some(iframe) = window_proxy.frame_element() {
2299                // When the `<iframe>` is local (in this ScriptThread), we can
2300                // synchronously chain up the keyboard scrolling event.
2301                let cx = GlobalScope::get_cx();
2302                let iframe_window = iframe.owner_window();
2303                let _ac = JSAutoRealm::new(*cx, iframe_window.reflector().get_jsobject().get());
2304                iframe_window
2305                    .Document()
2306                    .event_handler()
2307                    .do_keyboard_scroll(scroll);
2308            } else if let Some(parent_pipeline) = parent_pipeline {
2309                // Otherwise, if we have a parent (presumably from a different origin)
2310                // asynchronously ask the Constellation to forward the event to the parent
2311                // pipeline, if we have one.
2312                document.window().send_to_constellation(
2313                    ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
2314                );
2315            };
2316            return;
2317        }
2318
2319        let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
2320        scrolling_box.scroll_to(delta + current_scroll_offset, ScrollBehavior::Auto);
2321    }
2322
2323    /// Get or create a pointer ID for the given touch identifier.
2324    /// Returns the pointer ID to use for this touch.
2325    fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
2326        let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
2327
2328        if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
2329            return pointer_id;
2330        }
2331
2332        let pointer_id = self.next_touch_pointer_id.get();
2333        active_pointer_ids.insert(touch_id, pointer_id);
2334        self.next_touch_pointer_id.set(pointer_id + 1);
2335        pointer_id
2336    }
2337
2338    /// Remove the pointer ID mapping for the given touch identifier.
2339    fn remove_pointer_id_for_touch(&self, touch_id: i32) {
2340        self.active_pointer_ids.borrow_mut().remove(&touch_id);
2341    }
2342
2343    /// Check if this is the primary pointer (for touch events).
2344    /// The first touch to make contact is the primary pointer.
2345    fn is_primary_pointer(&self, pointer_id: i32) -> bool {
2346        // For touch, the primary pointer is the one with the smallest pointer ID
2347        // that is still active.
2348        self.active_pointer_ids
2349            .borrow()
2350            .values()
2351            .min()
2352            .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
2353    }
2354
2355    /// Fire pointerenter events hierarchically from topmost ancestor to target element.
2356    /// Fire pointerleave events hierarchically from target element to topmost ancestor.
2357    /// Used for touch devices that don't support hover.
2358    #[allow(clippy::too_many_arguments)]
2359    fn fire_pointer_event_for_touch(
2360        &self,
2361        target_element: &Element,
2362        touch: &Touch,
2363        pointer_id: i32,
2364        event_name: &str,
2365        is_primary: bool,
2366        input_event: &ConstellationInputEvent,
2367        hit_test_result: &HitTestResult,
2368        can_gc: CanGc,
2369    ) {
2370        // Collect ancestors from target to root
2371        let mut targets: Vec<DomRoot<Node>> = vec![];
2372        let mut current: Option<DomRoot<Node>> = Some(DomRoot::from_ref(target_element.upcast()));
2373        while let Some(node) = current {
2374            targets.push(DomRoot::from_ref(&*node));
2375            current = node.parent_in_flat_tree();
2376        }
2377
2378        // Reverse to dispatch from topmost ancestor to target
2379        if event_name == "pointerenter" {
2380            targets.reverse();
2381        }
2382
2383        for target in targets {
2384            let pointer_event = touch.to_pointer_event(
2385                &self.window,
2386                event_name,
2387                pointer_id,
2388                is_primary,
2389                input_event.active_keyboard_modifiers,
2390                false,
2391                Some(hit_test_result.point_in_node),
2392                can_gc,
2393            );
2394            pointer_event
2395                .upcast::<Event>()
2396                .fire(target.upcast(), can_gc);
2397        }
2398    }
2399
2400    pub(crate) fn has_assigned_access_key(&self, element: &HTMLElement) -> bool {
2401        self.access_key_handlers
2402            .borrow()
2403            .values()
2404            .any(|value| &**value == element)
2405    }
2406
2407    pub(crate) fn unassign_access_key(&self, element: &HTMLElement) {
2408        self.access_key_handlers
2409            .borrow_mut()
2410            .retain(|_, value| &**value != element)
2411    }
2412
2413    pub(crate) fn assign_access_key(&self, element: &HTMLElement, code: Code) {
2414        let mut access_key_handlers = self.access_key_handlers.borrow_mut();
2415        // If an element is already assigned this access key, ignore the request.
2416        access_key_handlers
2417            .entry(code.into())
2418            .or_insert(Dom::from_ref(element));
2419    }
2420
2421    fn maybe_handle_accesskey(&self, event: &KeyboardEvent, can_gc: CanGc) -> bool {
2422        #[cfg(target_os = "macos")]
2423        let access_key_modifiers = Modifiers::CONTROL | Modifiers::ALT;
2424        #[cfg(not(target_os = "macos"))]
2425        let access_key_modifiers = Modifiers::SHIFT | Modifiers::ALT;
2426
2427        if event.modifiers() != access_key_modifiers {
2428            return false;
2429        }
2430
2431        let Ok(code) = Code::from_str(&event.Code().str()) else {
2432            return false;
2433        };
2434
2435        let Some(html_element) = self
2436            .access_key_handlers
2437            .borrow()
2438            .get(&code.into())
2439            .map(|html_element| html_element.as_rooted())
2440        else {
2441            return false;
2442        };
2443
2444        // From <https://html.spec.whatwg.org/multipage/#the-accesskey-attribute>:
2445        // > When the user presses the key combination corresponding to the assigned access key for
2446        // > an element, if the element defines a command, the command's Hidden State facet is false
2447        // > (visible), the command's Disabled State facet is also false (enabled), the element is in
2448        // > a document that has a non-null browsing context, and neither the element nor any of its
2449        // > ancestors has a hidden attribute specified, then the user agent must trigger the Action
2450        // > of the command.
2451        let Ok(command) = InteractiveElementCommand::try_from(&*html_element) else {
2452            return false;
2453        };
2454
2455        if command.disabled() || command.hidden() {
2456            return false;
2457        }
2458
2459        let node = html_element.upcast::<Node>();
2460        if !node.is_connected() {
2461            return false;
2462        }
2463
2464        for node in node.inclusive_ancestors(ShadowIncluding::Yes) {
2465            if node
2466                .downcast::<HTMLElement>()
2467                .is_some_and(|html_element| html_element.Hidden())
2468            {
2469                return false;
2470            }
2471        }
2472
2473        // This behavior is unspecified, but all browsers do this. When activating the element it is
2474        // focused and scrolled into view.
2475        self.focus_and_scroll_to_element_for_key_event(html_element.upcast(), can_gc);
2476        command.perform_action(can_gc);
2477        true
2478    }
2479
2480    fn focus_and_scroll_to_element_for_key_event(&self, element: &Element, can_gc: CanGc) {
2481        element
2482            .owner_document()
2483            .request_focus(Some(element), FocusInitiator::Keyboard, can_gc);
2484        let scroll_axis = ScrollAxisState {
2485            position: ScrollLogicalPosition::Center,
2486            requirement: ScrollRequirement::IfNotVisible,
2487        };
2488        element.scroll_into_view_with_options(
2489            ScrollBehavior::Auto,
2490            scroll_axis,
2491            scroll_axis,
2492            None,
2493            None,
2494        );
2495    }
2496}
2497
2498/// <https://html.spec.whatwg.org/multipage/#sequential-focus-direction>
2499///
2500/// > A sequential focus direction is one of two possible values: "forward", or "backward". They are
2501/// > used in the below algorithms to describe the direction in which sequential focus travels at the
2502/// > user's request.
2503#[derive(Clone, Copy, PartialEq)]
2504enum SequentialFocusDirection {
2505    Forward,
2506    Backward,
2507}
2508
2509fn compare_tab_indices(a: i32, b: i32) -> Ordering {
2510    if a == b {
2511        Ordering::Equal
2512    } else if a == 0 {
2513        Ordering::Greater
2514    } else if b == 0 {
2515        Ordering::Less
2516    } else {
2517        a.cmp(&b)
2518    }
2519}
2520
2521pub(crate) fn character_to_code(character: char) -> Option<Code> {
2522    Some(match character.to_ascii_lowercase() {
2523        '`' => Code::Backquote,
2524        '\\' => Code::Backslash,
2525        '[' | '{' => Code::BracketLeft,
2526        ']' | '}' => Code::BracketRight,
2527        ',' | '<' => Code::Comma,
2528        '0' => Code::Digit0,
2529        '1' => Code::Digit1,
2530        '2' => Code::Digit2,
2531        '3' => Code::Digit3,
2532        '4' => Code::Digit4,
2533        '5' => Code::Digit5,
2534        '6' => Code::Digit6,
2535        '7' => Code::Digit7,
2536        '8' => Code::Digit8,
2537        '9' => Code::Digit9,
2538        '=' => Code::Equal,
2539        'a' => Code::KeyA,
2540        'b' => Code::KeyB,
2541        'c' => Code::KeyC,
2542        'd' => Code::KeyD,
2543        'e' => Code::KeyE,
2544        'f' => Code::KeyF,
2545        'g' => Code::KeyG,
2546        'h' => Code::KeyH,
2547        'i' => Code::KeyI,
2548        'j' => Code::KeyJ,
2549        'k' => Code::KeyK,
2550        'l' => Code::KeyL,
2551        'm' => Code::KeyM,
2552        'n' => Code::KeyN,
2553        'o' => Code::KeyO,
2554        'p' => Code::KeyP,
2555        'q' => Code::KeyQ,
2556        'r' => Code::KeyR,
2557        's' => Code::KeyS,
2558        't' => Code::KeyT,
2559        'u' => Code::KeyU,
2560        'v' => Code::KeyV,
2561        'w' => Code::KeyW,
2562        'x' => Code::KeyX,
2563        'y' => Code::KeyY,
2564        'z' => Code::KeyZ,
2565        '-' => Code::Minus,
2566        '.' => Code::Period,
2567        '\'' | '"' => Code::Quote,
2568        ';' => Code::Semicolon,
2569        '/' => Code::Slash,
2570        ' ' => Code::Space,
2571        _ => return None,
2572    })
2573}