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