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::f64::consts::PI;
8use std::mem;
9use std::rc::Rc;
10use std::time::{Duration, Instant};
11
12use base::generic_channel::GenericCallback;
13use constellation_traits::{KeyboardScroll, ScriptToConstellationMessage};
14use embedder_traits::{
15    Cursor, EditingActionEvent, EmbedderMsg, ImeEvent, InputEvent, InputEventAndId,
16    InputEventResult, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction,
17    MouseButtonEvent, MouseLeftViewportEvent, TouchEvent as EmbedderTouchEvent, TouchEventType,
18    TouchId, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent,
19};
20#[cfg(feature = "gamepad")]
21use embedder_traits::{
22    GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType,
23};
24use euclid::{Point2D, Vector2D};
25use js::jsapi::JSAutoRealm;
26use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
27use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id};
28use rustc_hash::FxHashMap;
29use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
30use script_bindings::codegen::GenericBindings::EventBinding::EventMethods;
31use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods;
32use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods;
33use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods;
34use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods;
35use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods};
36use script_bindings::inheritance::Castable;
37use script_bindings::match_domstring_ascii;
38use script_bindings::num::Finite;
39use script_bindings::reflector::DomObject;
40use script_bindings::root::{Dom, DomRoot, DomSlice};
41use script_bindings::script_runtime::CanGc;
42use script_bindings::str::DOMString;
43use script_traits::ConstellationInputEvent;
44use servo_config::pref;
45use style_traits::CSSPixel;
46use webrender_api::ExternalScrollId;
47
48use crate::dom::bindings::cell::DomRefCell;
49use crate::dom::bindings::refcounted::Trusted;
50use crate::dom::bindings::root::MutNullableDom;
51use crate::dom::clipboardevent::ClipboardEventType;
52use crate::dom::document::{FireMouseEventType, FocusInitiator};
53use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
54#[cfg(feature = "gamepad")]
55use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
56#[cfg(feature = "gamepad")]
57use crate::dom::gamepad::gamepadevent::GamepadEventType;
58use crate::dom::inputevent::HitTestResult;
59use crate::dom::node::{self, Node, NodeTraits, ShadowIncluding};
60use crate::dom::pointerevent::{PointerEvent, PointerId};
61use crate::dom::scrolling_box::ScrollingBoxAxis;
62use crate::dom::types::{
63    ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
64    HTMLAnchorElement, KeyboardEvent, MouseEvent, Touch, TouchEvent, TouchList, WheelEvent, Window,
65};
66use crate::drag_data_store::{DragDataStore, Kind, Mode};
67use crate::realms::enter_realm;
68
69/// A data structure used for tracking the current click count. This can be
70/// reset to 0 if a mouse button event happens at a sufficient distance or time
71/// from the previous one.
72///
73/// From <https://w3c.github.io/uievents/#current-click-count>:
74/// > Implementations MUST maintain the current click count when generating mouse
75/// > events. This MUST be a non-negative integer indicating the number of consecutive
76/// > clicks of a pointing device button within a specific time. The delay after which
77/// > the count resets is specific to the environment configuration.
78#[derive(Default, JSTraceable, MallocSizeOf)]
79struct ClickCountingInfo {
80    time: Option<Instant>,
81    #[no_trace]
82    point: Option<Point2D<f32, CSSPixel>>,
83    #[no_trace]
84    button: Option<MouseButton>,
85    count: usize,
86}
87
88impl ClickCountingInfo {
89    fn reset_click_count_if_necessary(
90        &mut self,
91        button: MouseButton,
92        point_in_frame: Point2D<f32, CSSPixel>,
93    ) {
94        let (Some(previous_button), Some(previous_point), Some(previous_time)) =
95            (self.button, self.point, self.time)
96        else {
97            assert_eq!(self.count, 0);
98            return;
99        };
100
101        let double_click_timeout =
102            Duration::from_millis(pref!(dom_document_dblclick_timeout) as u64);
103        let double_click_distance_threshold = pref!(dom_document_dblclick_dist) as u64;
104
105        // Calculate distance between this click and the previous click.
106        let line = point_in_frame - previous_point;
107        let distance = (line.dot(line) as f64).sqrt();
108        if previous_button != button ||
109            Instant::now().duration_since(previous_time) > double_click_timeout ||
110            distance > double_click_distance_threshold as f64
111        {
112            self.count = 0;
113            self.time = None;
114            self.point = None;
115        }
116    }
117
118    fn increment_click_count(
119        &mut self,
120        button: MouseButton,
121        point: Point2D<f32, CSSPixel>,
122    ) -> usize {
123        self.time = Some(Instant::now());
124        self.point = Some(point);
125        self.button = Some(button);
126        self.count += 1;
127        self.count
128    }
129}
130
131/// The [`DocumentEventHandler`] is a structure responsible for handling input events for
132/// the [`crate::Document`] and storing data related to event handling. It exists to
133/// decrease the size of the [`crate::Document`] structure.
134#[derive(JSTraceable, MallocSizeOf)]
135#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
136pub(crate) struct DocumentEventHandler {
137    /// The [`Window`] element for this [`DocumentEventHandler`].
138    window: Dom<Window>,
139    /// Pending input events, to be handled at the next rendering opportunity.
140    #[no_trace]
141    #[ignore_malloc_size_of = "InputEvent contains data from outside crates"]
142    pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
143    /// The index of the last mouse move event in the pending input events queue.
144    mouse_move_event_index: DomRefCell<Option<usize>>,
145    /// <https://w3c.github.io/uievents/#event-type-dblclick>
146    click_counting_info: DomRefCell<ClickCountingInfo>,
147    #[no_trace]
148    last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
149    /// The number of currently down buttons, used to decide which kind
150    /// of pointer event to dispatch on MouseDown/MouseUp.
151    down_button_count: Cell<u32>,
152    /// The element that is currently hovered by the cursor.
153    current_hover_target: MutNullableDom<Element>,
154    /// The element that was most recently clicked.
155    most_recently_clicked_element: MutNullableDom<Element>,
156    /// The most recent mouse movement point, used for processing `mouseleave` events.
157    #[no_trace]
158    most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
159    /// The currently set [`Cursor`] or `None` if the `Document` isn't being hovered
160    /// by the cursor.
161    #[no_trace]
162    current_cursor: Cell<Option<Cursor>>,
163    /// <http://w3c.github.io/touch-events/#dfn-active-touch-point>
164    active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
165    /// The active keyboard modifiers for the WebView. This is updated when receiving any input event.
166    #[no_trace]
167    active_keyboard_modifiers: Cell<Modifiers>,
168    /// Map from touch identifier to pointer ID for active touch points
169    active_pointer_ids: DomRefCell<FxHashMap<i32, i32>>,
170    /// Counter for generating unique pointer IDs for touch inputs
171    next_touch_pointer_id: Cell<i32>,
172}
173
174impl DocumentEventHandler {
175    pub(crate) fn new(window: &Window) -> Self {
176        Self {
177            window: Dom::from_ref(window),
178            pending_input_events: Default::default(),
179            mouse_move_event_index: Default::default(),
180            click_counting_info: Default::default(),
181            last_mouse_button_down_point: Default::default(),
182            down_button_count: Cell::new(0),
183            current_hover_target: Default::default(),
184            most_recently_clicked_element: Default::default(),
185            most_recent_mousemove_point: Default::default(),
186            current_cursor: Default::default(),
187            active_touch_points: Default::default(),
188            active_keyboard_modifiers: Default::default(),
189            active_pointer_ids: Default::default(),
190            next_touch_pointer_id: Cell::new(1),
191        }
192    }
193
194    /// Note a pending input event, to be processed at the next `update_the_rendering` task.
195    pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
196        let mut pending_input_events = self.pending_input_events.borrow_mut();
197        if matches!(event.event.event, InputEvent::MouseMove(..)) {
198            // First try to replace any existing mouse move event.
199            if let Some(mouse_move_event) = self
200                .mouse_move_event_index
201                .borrow()
202                .and_then(|index| pending_input_events.get_mut(index))
203            {
204                *mouse_move_event = event;
205                return;
206            }
207
208            *self.mouse_move_event_index.borrow_mut() = Some(pending_input_events.len());
209        }
210
211        pending_input_events.push(event);
212    }
213
214    /// Whether or not this [`Document`] has any pending input events to be processed during
215    /// "update the rendering."
216    pub(crate) fn has_pending_input_events(&self) -> bool {
217        !self.pending_input_events.borrow().is_empty()
218    }
219
220    pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
221        #[cfg(target_os = "macos")]
222        {
223            self.active_keyboard_modifiers
224                .get()
225                .contains(Modifiers::META)
226        }
227        #[cfg(not(target_os = "macos"))]
228        {
229            self.active_keyboard_modifiers
230                .get()
231                .contains(Modifiers::CONTROL)
232        }
233    }
234
235    pub(crate) fn handle_pending_input_events(&self, can_gc: CanGc) {
236        let _realm = enter_realm(&*self.window);
237
238        // Reset the mouse event index.
239        *self.mouse_move_event_index.borrow_mut() = None;
240        let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
241
242        for event in pending_input_events {
243            self.active_keyboard_modifiers
244                .set(event.active_keyboard_modifiers);
245
246            // TODO: For some of these we still aren't properly calculating whether or not
247            // the event was handled or if `preventDefault()` was called on it. Each of
248            // these cases needs to be examined and some of them either fire more than one
249            // event or fire events later. We have to make a good decision about what to
250            // return to the embedder when that happens.
251            let result = match event.event.event.clone() {
252                InputEvent::MouseButton(mouse_button_event) => {
253                    self.handle_native_mouse_button_event(mouse_button_event, &event, can_gc);
254                    InputEventResult::default()
255                },
256                InputEvent::MouseMove(_) => {
257                    self.handle_native_mouse_move_event(&event, can_gc);
258                    InputEventResult::default()
259                },
260                InputEvent::MouseLeftViewport(mouse_leave_event) => {
261                    self.handle_mouse_left_viewport_event(&event, &mouse_leave_event, can_gc);
262                    InputEventResult::default()
263                },
264                InputEvent::Touch(touch_event) => {
265                    self.handle_touch_event(touch_event, &event, can_gc)
266                },
267                InputEvent::Wheel(wheel_event) => {
268                    self.handle_wheel_event(wheel_event, &event, can_gc)
269                },
270                InputEvent::Keyboard(keyboard_event) => {
271                    self.handle_keyboard_event(keyboard_event, can_gc)
272                },
273                InputEvent::Ime(ime_event) => self.handle_ime_event(ime_event, can_gc),
274                #[cfg(feature = "gamepad")]
275                InputEvent::Gamepad(gamepad_event) => {
276                    self.handle_gamepad_event(gamepad_event);
277                    InputEventResult::default()
278                },
279                InputEvent::EditingAction(editing_action_event) => {
280                    self.handle_editing_action(None, editing_action_event, can_gc)
281                },
282            };
283
284            self.notify_embedder_that_event_was_handled(event.event, result);
285        }
286    }
287
288    fn notify_embedder_that_event_was_handled(
289        &self,
290        event: InputEventAndId,
291        result: InputEventResult,
292    ) {
293        // Wait to to notify the embedder that the vent was handled until all pending DOM
294        // event processing is finished.
295        let id = event.id;
296        let trusted_window = Trusted::new(&*self.window);
297        self.window
298            .as_global_scope()
299            .task_manager()
300            .dom_manipulation_task_source()
301            .queue(task!(notify_webdriver_input_event_completed: move || {
302                let window = trusted_window.root();
303                window.send_to_embedder(
304                    EmbedderMsg::InputEventHandled(window.webview_id(), id, result));
305            }));
306    }
307
308    pub(crate) fn set_cursor(&self, cursor: Option<Cursor>) {
309        if cursor == self.current_cursor.get() {
310            return;
311        }
312        self.current_cursor.set(cursor);
313        self.window.send_to_embedder(EmbedderMsg::SetCursor(
314            self.window.webview_id(),
315            cursor.unwrap_or_default(),
316        ));
317    }
318
319    fn handle_mouse_left_viewport_event(
320        &self,
321        input_event: &ConstellationInputEvent,
322        mouse_leave_event: &MouseLeftViewportEvent,
323        can_gc: CanGc,
324    ) {
325        if let Some(current_hover_target) = self.current_hover_target.get() {
326            let current_hover_target = current_hover_target.upcast::<Node>();
327            for element in current_hover_target
328                .inclusive_ancestors(ShadowIncluding::Yes)
329                .filter_map(DomRoot::downcast::<Element>)
330            {
331                element.set_hover_state(false);
332                element.set_active_state(false);
333            }
334
335            if let Some(hit_test_result) = self
336                .most_recent_mousemove_point
337                .get()
338                .and_then(|point| self.window.hit_test_from_point_in_viewport(point))
339            {
340                MouseEvent::new_for_platform_motion_event(
341                    &self.window,
342                    FireMouseEventType::Out,
343                    &hit_test_result,
344                    input_event,
345                    can_gc,
346                )
347                .upcast::<Event>()
348                .fire(current_hover_target.upcast(), can_gc);
349                self.handle_mouse_enter_leave_event(
350                    DomRoot::from_ref(current_hover_target),
351                    None,
352                    FireMouseEventType::Leave,
353                    &hit_test_result,
354                    input_event,
355                    can_gc,
356                );
357            }
358        }
359
360        // We do not want to always inform the embedder that cursor has been set to the
361        // default cursor, in order to avoid a timing issue when moving between `<iframe>`
362        // elements. There is currently no way to control which `SetCursor` message will
363        // reach the embedder first. This is safer when leaving the `WebView` entirely.
364        if !mouse_leave_event.focus_moving_to_another_iframe {
365            // If focus is moving to another frame, it will decide what the new status
366            // text is, but if this mouse leave event is leaving the WebView entirely,
367            // then clear it.
368            self.window
369                .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
370            self.set_cursor(None);
371        } else {
372            self.current_cursor.set(None);
373        }
374
375        self.current_hover_target.set(None);
376        self.most_recent_mousemove_point.set(None);
377    }
378
379    fn handle_mouse_enter_leave_event(
380        &self,
381        event_target: DomRoot<Node>,
382        related_target: Option<DomRoot<Node>>,
383        event_type: FireMouseEventType,
384        hit_test_result: &HitTestResult,
385        input_event: &ConstellationInputEvent,
386        can_gc: CanGc,
387    ) {
388        assert!(matches!(
389            event_type,
390            FireMouseEventType::Enter | FireMouseEventType::Leave
391        ));
392
393        let common_ancestor = match related_target.as_ref() {
394            Some(related_target) => event_target
395                .common_ancestor(related_target, ShadowIncluding::Yes)
396                .unwrap_or_else(|| DomRoot::from_ref(&*event_target)),
397            None => DomRoot::from_ref(&*event_target),
398        };
399
400        // We need to create a target chain in case the event target shares
401        // its boundaries with its ancestors.
402        let mut targets = vec![];
403        let mut current = Some(event_target);
404        while let Some(node) = current {
405            if node == common_ancestor {
406                break;
407            }
408            current = node.GetParentNode();
409            targets.push(node);
410        }
411
412        // The order for dispatching mouseenter events starts from the topmost
413        // common ancestor of the event target and the related target.
414        if event_type == FireMouseEventType::Enter {
415            targets = targets.into_iter().rev().collect();
416        }
417
418        for target in targets {
419            MouseEvent::new_for_platform_motion_event(
420                &self.window,
421                event_type,
422                hit_test_result,
423                input_event,
424                can_gc,
425            )
426            .upcast::<Event>()
427            .fire(target.upcast(), can_gc);
428        }
429    }
430
431    /// <https://w3c.github.io/uievents/#handle-native-mouse-move>
432    fn handle_native_mouse_move_event(&self, input_event: &ConstellationInputEvent, can_gc: CanGc) {
433        // Ignore all incoming events without a hit test.
434        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
435            return;
436        };
437
438        let old_mouse_move_point = self
439            .most_recent_mousemove_point
440            .replace(Some(hit_test_result.point_in_frame));
441        if old_mouse_move_point == Some(hit_test_result.point_in_frame) {
442            return;
443        }
444
445        // Update the cursor when the mouse moves, if it has changed.
446        self.set_cursor(Some(hit_test_result.cursor));
447
448        let Some(new_target) = hit_test_result
449            .node
450            .inclusive_ancestors(ShadowIncluding::Yes)
451            .find_map(DomRoot::downcast::<Element>)
452        else {
453            return;
454        };
455
456        let target_has_changed = self
457            .current_hover_target
458            .get()
459            .is_none_or(|old_target| old_target != new_target);
460
461        // Here we know the target has changed, so we must update the state,
462        // dispatch mouseout to the previous one, mouseover to the new one.
463        if target_has_changed {
464            // Dispatch mouseout and mouseleave to previous target.
465            if let Some(old_target) = self.current_hover_target.get() {
466                let old_target_is_ancestor_of_new_target = old_target
467                    .upcast::<Node>()
468                    .is_ancestor_of(new_target.upcast::<Node>());
469
470                // If the old target is an ancestor of the new target, this can be skipped
471                // completely, since the node's hover state will be reset below.
472                if !old_target_is_ancestor_of_new_target {
473                    for element in old_target
474                        .upcast::<Node>()
475                        .inclusive_ancestors(ShadowIncluding::No)
476                        .filter_map(DomRoot::downcast::<Element>)
477                    {
478                        element.set_hover_state(false);
479                        element.set_active_state(false);
480                    }
481                }
482
483                MouseEvent::new_for_platform_motion_event(
484                    &self.window,
485                    FireMouseEventType::Out,
486                    &hit_test_result,
487                    input_event,
488                    can_gc,
489                )
490                .upcast::<Event>()
491                .fire(old_target.upcast(), can_gc);
492
493                if !old_target_is_ancestor_of_new_target {
494                    let event_target = DomRoot::from_ref(old_target.upcast::<Node>());
495                    let moving_into = Some(DomRoot::from_ref(new_target.upcast::<Node>()));
496                    self.handle_mouse_enter_leave_event(
497                        event_target,
498                        moving_into,
499                        FireMouseEventType::Leave,
500                        &hit_test_result,
501                        input_event,
502                        can_gc,
503                    );
504                }
505            }
506
507            // Dispatch mouseover and mouseenter to new target.
508            for element in new_target
509                .upcast::<Node>()
510                .inclusive_ancestors(ShadowIncluding::Yes)
511                .filter_map(DomRoot::downcast::<Element>)
512            {
513                element.set_hover_state(true);
514            }
515
516            MouseEvent::new_for_platform_motion_event(
517                &self.window,
518                FireMouseEventType::Over,
519                &hit_test_result,
520                input_event,
521                can_gc,
522            )
523            .upcast::<Event>()
524            .dispatch(new_target.upcast(), false, can_gc);
525
526            let moving_from = self
527                .current_hover_target
528                .get()
529                .map(|old_target| DomRoot::from_ref(old_target.upcast::<Node>()));
530            let event_target = DomRoot::from_ref(new_target.upcast::<Node>());
531            self.handle_mouse_enter_leave_event(
532                event_target,
533                moving_from,
534                FireMouseEventType::Enter,
535                &hit_test_result,
536                input_event,
537                can_gc,
538            );
539        }
540
541        // Send mousemove event to topmost target, unless it's an iframe, in which case
542        // `Paint` should have also sent an event to the inner document.
543        let mouse_event = MouseEvent::new_for_platform_motion_event(
544            &self.window,
545            FireMouseEventType::Move,
546            &hit_test_result,
547            input_event,
548            can_gc,
549        );
550
551        // Send pointermove event before mousemove.
552        let pointer_event = mouse_event.to_pointer_event("pointermove", can_gc);
553        pointer_event
554            .upcast::<Event>()
555            .fire(new_target.upcast(), can_gc);
556
557        // Send mousemove event to topmost target, unless it's an iframe, in which case
558        // `Paint` should have also sent an event to the inner document.
559        mouse_event
560            .upcast::<Event>()
561            .fire(new_target.upcast(), can_gc);
562
563        self.update_current_hover_target_and_status(Some(new_target));
564    }
565
566    fn update_current_hover_target_and_status(&self, new_hover_target: Option<DomRoot<Element>>) {
567        let current_hover_target = self.current_hover_target.get();
568        if current_hover_target == new_hover_target {
569            return;
570        }
571
572        let previous_hover_target = self.current_hover_target.get();
573        self.current_hover_target.set(new_hover_target.as_deref());
574
575        // If the new hover target is an anchor with a status value, inform the embedder
576        // of the new value.
577        if let Some(target) = self.current_hover_target.get() {
578            if let Some(anchor) = target
579                .upcast::<Node>()
580                .inclusive_ancestors(ShadowIncluding::Yes)
581                .find_map(DomRoot::downcast::<HTMLAnchorElement>)
582            {
583                let status = anchor
584                    .full_href_url_for_user_interface()
585                    .map(|url| url.to_string());
586                self.window
587                    .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), status));
588                return;
589            }
590        }
591
592        // No state was set above, which means that the new value of the status in the embedder
593        // should be `None`. Set that now. If `previous_hover_target` is `None` that means this
594        // is the first mouse move event we are seeing after getting the cursor. In that case,
595        // we also clear the status.
596        if previous_hover_target.is_none_or(|previous_hover_target| {
597            previous_hover_target
598                .upcast::<Node>()
599                .inclusive_ancestors(ShadowIncluding::Yes)
600                .any(|node| node.is::<HTMLAnchorElement>())
601        }) {
602            self.window
603                .send_to_embedder(EmbedderMsg::Status(self.window.webview_id(), None));
604        }
605    }
606
607    pub(crate) fn handle_refresh_cursor(&self) {
608        let Some(most_recent_mousemove_point) = self.most_recent_mousemove_point.get() else {
609            return;
610        };
611
612        let Some(hit_test_result) = self
613            .window
614            .hit_test_from_point_in_viewport(most_recent_mousemove_point)
615        else {
616            return;
617        };
618
619        self.set_cursor(Some(hit_test_result.cursor));
620    }
621
622    /// <https://w3c.github.io/uievents/#mouseevent-algorithms>
623    /// Handles native mouse down, mouse up, mouse click.
624    fn handle_native_mouse_button_event(
625        &self,
626        event: MouseButtonEvent,
627        input_event: &ConstellationInputEvent,
628        can_gc: CanGc,
629    ) {
630        // Ignore all incoming events without a hit test.
631        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
632            return;
633        };
634
635        debug!(
636            "{:?}: at {:?}",
637            event.action, hit_test_result.point_in_frame
638        );
639
640        let Some(element) = hit_test_result
641            .node
642            .inclusive_ancestors(ShadowIncluding::Yes)
643            .find_map(DomRoot::downcast::<Element>)
644        else {
645            return;
646        };
647
648        let node = element.upcast::<Node>();
649        debug!("{:?} on {:?}", event.action, node.debug_str());
650
651        // https://w3c.github.io/uievents/#hit-test
652        // Prevent mouse event if element is disabled.
653        // TODO: also inert.
654        if element.is_actually_disabled() {
655            return;
656        }
657
658        let mouse_event_type_string = match event.action {
659            embedder_traits::MouseButtonAction::Up => "mouseup",
660            embedder_traits::MouseButtonAction::Down => "mousedown",
661        };
662
663        // From <https://w3c.github.io/uievents/#event-type-mousedown>
664        // and <https://w3c.github.io/uievents/#event-type-mouseup>:
665        //
666        // UIEvent.detail: indicates the current click count incremented by one. For
667        // example, if no click happened before the mousedown, detail will contain
668        // the value 1
669        if event.action == MouseButtonAction::Down {
670            self.click_counting_info
671                .borrow_mut()
672                .reset_click_count_if_necessary(event.button, hit_test_result.point_in_frame);
673        }
674
675        let dom_event = DomRoot::upcast::<Event>(MouseEvent::for_platform_button_event(
676            mouse_event_type_string,
677            event,
678            input_event.pressed_mouse_buttons,
679            &self.window,
680            &hit_test_result,
681            input_event.active_keyboard_modifiers,
682            self.click_counting_info.borrow().count + 1,
683            can_gc,
684        ));
685
686        let activatable = element.as_maybe_activatable();
687        match event.action {
688            MouseButtonAction::Down => {
689                self.last_mouse_button_down_point
690                    .set(Some(hit_test_result.point_in_frame));
691
692                if let Some(a) = activatable {
693                    a.enter_formal_activation_state();
694                }
695
696                // Step 6. Dispatch pointerdown event.
697                let down_button_count = self.down_button_count.get();
698
699                let event_type = if down_button_count == 0 {
700                    "pointerdown"
701                } else {
702                    "pointermove"
703                };
704                let pointer_event = dom_event
705                    .downcast::<MouseEvent>()
706                    .unwrap()
707                    .to_pointer_event(event_type, can_gc);
708
709                pointer_event.upcast::<Event>().fire(node.upcast(), can_gc);
710
711                self.down_button_count.set(down_button_count + 1);
712
713                // For a node within a text input UA shadow DOM,
714                // delegate the focus target into its shadow host.
715                // TODO: This focus delegation should be done
716                // with shadow DOM delegateFocus attribute.
717                let target_el = element.find_focusable_shadow_host_if_necessary();
718
719                let document = self.window.Document();
720                document.begin_focus_transaction();
721
722                // Try to focus `el`. If it's not focusable, focus the document instead.
723                document.request_focus(None, FocusInitiator::Local, can_gc);
724                document.request_focus(target_el.as_deref(), FocusInitiator::Local, can_gc);
725
726                // Step 7. Let result = dispatch event at target
727                let result = dom_event.dispatch(node.upcast(), false, can_gc);
728
729                // Step 8. If result is true and target is a focusable area
730                // that is click focusable, then Run the focusing steps at target.
731                if result && document.has_focus_transaction() {
732                    document.commit_focus_transaction(FocusInitiator::Local, can_gc);
733                }
734
735                // Step 9. If mbutton is the secondary mouse button, then
736                // Maybe show context menu with native, target.
737                if let MouseButton::Right = event.button {
738                    self.maybe_show_context_menu(
739                        node.upcast(),
740                        &hit_test_result,
741                        input_event,
742                        can_gc,
743                    );
744                }
745            },
746            // https://w3c.github.io/uievents/#handle-native-mouse-up
747            MouseButtonAction::Up => {
748                if let Some(a) = activatable {
749                    a.exit_formal_activation_state();
750                }
751
752                // Step 6. Dispatch pointerup event.
753                let down_button_count = self.down_button_count.get();
754
755                if down_button_count > 0 {
756                    self.down_button_count.set(down_button_count - 1);
757                }
758
759                let event_type = if down_button_count == 0 {
760                    "pointerup"
761                } else {
762                    "pointermove"
763                };
764                let pointer_event = dom_event
765                    .downcast::<MouseEvent>()
766                    .unwrap()
767                    .to_pointer_event(event_type, can_gc);
768
769                pointer_event.upcast::<Event>().fire(node.upcast(), can_gc);
770
771                // Step 7. dispatch event at target.
772                dom_event.dispatch(node.upcast(), false, can_gc);
773
774                // Click counts should still work for other buttons even though they
775                // do not trigger "click" and "dblclick" events, so we increment
776                // even when those events are not fired.
777                self.click_counting_info
778                    .borrow_mut()
779                    .increment_click_count(event.button, hit_test_result.point_in_frame);
780
781                self.maybe_trigger_click_for_mouse_button_down_event(
782                    event,
783                    input_event,
784                    &hit_test_result,
785                    &element,
786                    can_gc,
787                );
788            },
789        }
790    }
791
792    /// <https://w3c.github.io/uievents/#handle-native-mouse-click>
793    /// <https://w3c.github.io/uievents/#event-type-dblclick>
794    fn maybe_trigger_click_for_mouse_button_down_event(
795        &self,
796        event: MouseButtonEvent,
797        input_event: &ConstellationInputEvent,
798        hit_test_result: &HitTestResult,
799        element: &Element,
800        can_gc: CanGc,
801    ) {
802        if event.button != MouseButton::Left {
803            return;
804        }
805
806        let Some(last_mouse_button_down_point) = self.last_mouse_button_down_point.take() else {
807            return;
808        };
809
810        let distance = last_mouse_button_down_point.distance_to(hit_test_result.point_in_frame);
811        let maximum_click_distance = 10.0 * self.window.device_pixel_ratio().get();
812        if distance > maximum_click_distance {
813            return;
814        }
815
816        // From <https://w3c.github.io/uievents/#event-type-click>
817        // > The click event type MUST be dispatched on the topmost event target indicated by the
818        // > pointer, when the user presses down and releases the primary pointer button.
819        // For nodes inside a text input UA shadow DOM, dispatch dblclick at the shadow host.
820        let delegated = element.find_focusable_shadow_host_if_necessary();
821        let element = delegated.as_deref().unwrap_or(element);
822        self.most_recently_clicked_element.set(Some(element));
823
824        let click_count = self.click_counting_info.borrow().count;
825        element.set_click_in_progress(true);
826        MouseEvent::for_platform_button_event(
827            "click",
828            event,
829            input_event.pressed_mouse_buttons,
830            &self.window,
831            hit_test_result,
832            input_event.active_keyboard_modifiers,
833            click_count,
834            can_gc,
835        )
836        .upcast::<Event>()
837        .dispatch(element.upcast(), false, can_gc);
838        element.set_click_in_progress(false);
839
840        // The firing of "dbclick" events is dependent on the platform, so we have
841        // some flexibility here. Some browsers on some platforms only fire a
842        // "dbclick" when the click count is 2 and others essentially fire one for
843        // every 2 clicks in a sequence. In all cases, browsers set the click count
844        // `detail` property to 2.
845        //
846        // We follow the latter approach here, considering that every sequence of
847        // even numbered clicks is a series of double clicks.
848        if click_count % 2 == 0 {
849            MouseEvent::for_platform_button_event(
850                "dblclick",
851                event,
852                input_event.pressed_mouse_buttons,
853                &self.window,
854                hit_test_result,
855                input_event.active_keyboard_modifiers,
856                2,
857                can_gc,
858            )
859            .upcast::<Event>()
860            .dispatch(element.upcast(), false, can_gc);
861        }
862    }
863
864    /// <https://www.w3.org/TR/uievents/#maybe-show-context-menu>
865    fn maybe_show_context_menu(
866        &self,
867        target: &EventTarget,
868        hit_test_result: &HitTestResult,
869        input_event: &ConstellationInputEvent,
870        can_gc: CanGc,
871    ) {
872        // <https://w3c.github.io/uievents/#contextmenu>
873        let menu_event = PointerEvent::new(
874            &self.window,                   // window
875            DOMString::from("contextmenu"), // type
876            EventBubbles::Bubbles,          // can_bubble
877            EventCancelable::Cancelable,    // cancelable
878            Some(&self.window),             // view
879            0,                              // detail
880            hit_test_result.point_in_frame.to_i32(),
881            hit_test_result.point_in_frame.to_i32(),
882            hit_test_result
883                .point_relative_to_initial_containing_block
884                .to_i32(),
885            input_event.active_keyboard_modifiers,
886            2i16, // button, right mouse button
887            input_event.pressed_mouse_buttons,
888            None,                     // related_target
889            None,                     // point_in_target
890            PointerId::Mouse as i32,  // pointer_id
891            1,                        // width
892            1,                        // height
893            0.5,                      // pressure
894            0.0,                      // tangential_pressure
895            0,                        // tilt_x
896            0,                        // tilt_y
897            0,                        // twist
898            PI / 2.0,                 // altitude_angle
899            0.0,                      // azimuth_angle
900            DOMString::from("mouse"), // pointer_type
901            true,                     // is_primary
902            vec![],                   // coalesced_events
903            vec![],                   // predicted_events
904            can_gc,
905        );
906
907        // Step 3. Let result = dispatch menuevent at target.
908        let result = menu_event.upcast::<Event>().fire(target, can_gc);
909
910        // Step 4. If result is true, then show the UA context menu
911        if result {
912            self.window
913                .Document()
914                .embedder_controls()
915                .show_context_menu(hit_test_result);
916        };
917    }
918
919    fn handle_touch_event(
920        &self,
921        event: EmbedderTouchEvent,
922        input_event: &ConstellationInputEvent,
923        can_gc: CanGc,
924    ) -> InputEventResult {
925        // Ignore all incoming events without a hit test.
926        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
927            self.update_active_touch_points_when_early_return(event);
928            return Default::default();
929        };
930
931        let TouchId(identifier) = event.touch_id;
932
933        let Some(element) = hit_test_result
934            .node
935            .inclusive_ancestors(ShadowIncluding::Yes)
936            .find_map(DomRoot::downcast::<Element>)
937        else {
938            self.update_active_touch_points_when_early_return(event);
939            return Default::default();
940        };
941
942        let current_target = DomRoot::upcast::<EventTarget>(element);
943        let window = &*self.window;
944
945        let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
946        let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
947        let page_x =
948            Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
949        let page_y =
950            Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
951
952        // This is used to construct pointerevent and touchdown event.
953        let pointer_touch = Touch::new(
954            window,
955            identifier,
956            &current_target,
957            client_x,
958            client_y, // TODO: Get real screen coordinates?
959            client_x,
960            client_y,
961            page_x,
962            page_y,
963            can_gc,
964        );
965
966        // Dispatch pointer event before updating active touch points and before touch event.
967        let pointer_event_type = match event.event_type {
968            TouchEventType::Down => "pointerdown",
969            TouchEventType::Move => "pointermove",
970            TouchEventType::Up => "pointerup",
971            TouchEventType::Cancel => "pointercancel",
972        };
973
974        // Get or create pointer ID for this touch
975        let pointer_id = self.get_or_create_pointer_id_for_touch(identifier);
976        let is_primary = self.is_primary_pointer(pointer_id);
977
978        let pointer_event = pointer_touch.to_pointer_event(
979            window,
980            pointer_event_type,
981            pointer_id,
982            is_primary,
983            input_event.active_keyboard_modifiers,
984            event.is_cancelable(),
985            Some(hit_test_result.point_in_node),
986            can_gc,
987        );
988        pointer_event
989            .upcast::<Event>()
990            .fire(&current_target, can_gc);
991
992        let (touch_dispatch_target, changed_touch) = match event.event_type {
993            TouchEventType::Down => {
994                // Add a new touch point
995                self.active_touch_points
996                    .borrow_mut()
997                    .push(Dom::from_ref(&*pointer_touch));
998                (current_target, pointer_touch)
999            },
1000            _ => {
1001                // From <https://w3c.github.io/touch-events/#dfn-touchend>:
1002                // > For move/up/cancel:
1003                // > The target of this event must be the same Element on which the touch
1004                // > point started when it was first placed on the surface, even if the touch point
1005                // > has since moved outside the interactive area of the target element.
1006                let mut active_touch_points = self.active_touch_points.borrow_mut();
1007                let Some(index) = active_touch_points
1008                    .iter()
1009                    .position(|point| point.Identifier() == identifier)
1010                else {
1011                    warn!("No active touch point for {:?}", event.event_type);
1012                    return Default::default();
1013                };
1014                // This is the original target that was selected during `touchstart` event handling.
1015                let original_target = active_touch_points[index].Target();
1016
1017                let touch_with_touchstart_target = Touch::new(
1018                    window,
1019                    identifier,
1020                    &original_target,
1021                    client_x,
1022                    client_y,
1023                    client_x,
1024                    client_y,
1025                    page_x,
1026                    page_y,
1027                    can_gc,
1028                );
1029
1030                // Update or remove the stored touch
1031                match event.event_type {
1032                    TouchEventType::Move => {
1033                        active_touch_points[index] = Dom::from_ref(&*touch_with_touchstart_target);
1034                    },
1035                    TouchEventType::Up | TouchEventType::Cancel => {
1036                        active_touch_points.swap_remove(index);
1037                        self.remove_pointer_id_for_touch(identifier);
1038                    },
1039                    TouchEventType::Down => unreachable!("Should have been handled above"),
1040                }
1041                (original_target, touch_with_touchstart_target)
1042            },
1043        };
1044
1045        rooted_vec!(let mut target_touches);
1046        target_touches.extend(
1047            self.active_touch_points
1048                .borrow()
1049                .iter()
1050                .filter(|touch| touch.Target() == touch_dispatch_target)
1051                .cloned(),
1052        );
1053
1054        let event_name = match event.event_type {
1055            TouchEventType::Down => "touchstart",
1056            TouchEventType::Move => "touchmove",
1057            TouchEventType::Up => "touchend",
1058            TouchEventType::Cancel => "touchcancel",
1059        };
1060
1061        let touch_event = TouchEvent::new(
1062            window,
1063            DOMString::from(event_name),
1064            EventBubbles::Bubbles,
1065            EventCancelable::from(event.is_cancelable()),
1066            EventComposed::Composed,
1067            Some(window),
1068            0i32,
1069            &TouchList::new(window, self.active_touch_points.borrow().r(), can_gc),
1070            &TouchList::new(window, from_ref(&&*changed_touch), can_gc),
1071            &TouchList::new(window, target_touches.r(), can_gc),
1072            // FIXME: modifier keys
1073            false,
1074            false,
1075            false,
1076            false,
1077            can_gc,
1078        );
1079        let event = touch_event.upcast::<Event>();
1080        event.fire(&touch_dispatch_target, can_gc);
1081        event.flags().into()
1082    }
1083
1084    /// Updates the active touch points when a hit test fails early.
1085    ///
1086    /// - For `Down`: No action needed; a failed down event won't create an active point.
1087    /// - For `Move`: No action needed; position information is unavailable, so we cannot update.
1088    /// - For `Up`/`Cancel`: Remove the corresponding touch point and its pointer ID mapping.
1089    ///
1090    /// When a touchup or touchcancel occurs at that touch point,
1091    /// a warning is triggered: Received touchup/touchcancel event for a non-active touch point.
1092    fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
1093        match event.event_type {
1094            TouchEventType::Down | TouchEventType::Move => {},
1095            TouchEventType::Up | TouchEventType::Cancel => {
1096                let mut active_touch_points = self.active_touch_points.borrow_mut();
1097                if let Some(index) = active_touch_points
1098                    .iter()
1099                    .position(|t| t.Identifier() == event.touch_id.0)
1100                {
1101                    active_touch_points.swap_remove(index);
1102                    self.remove_pointer_id_for_touch(event.touch_id.0);
1103                } else {
1104                    warn!(
1105                        "Received {:?} for a non-active touch point {}",
1106                        event.event_type, event.touch_id.0
1107                    );
1108                }
1109            },
1110        }
1111    }
1112
1113    /// The entry point for all key processing for web content
1114    fn handle_keyboard_event(
1115        &self,
1116        keyboard_event: EmbedderKeyboardEvent,
1117        can_gc: CanGc,
1118    ) -> InputEventResult {
1119        let document = self.window.Document();
1120        let focused = document.get_focused_element();
1121        let body = document.GetBody();
1122
1123        let target = match (&focused, &body) {
1124            (Some(focused), _) => focused.upcast(),
1125            (&None, Some(body)) => body.upcast(),
1126            (&None, &None) => self.window.upcast(),
1127        };
1128
1129        let keyevent = KeyboardEvent::new(
1130            &self.window,
1131            DOMString::from(keyboard_event.event.state.event_type()),
1132            true,
1133            true,
1134            Some(&self.window),
1135            0,
1136            keyboard_event.event.key.clone(),
1137            DOMString::from(keyboard_event.event.code.to_string()),
1138            keyboard_event.event.location as u32,
1139            keyboard_event.event.repeat,
1140            keyboard_event.event.is_composing,
1141            keyboard_event.event.modifiers,
1142            0,
1143            keyboard_event.event.key.legacy_keycode(),
1144            can_gc,
1145        );
1146
1147        let event = keyevent.upcast::<Event>();
1148        event.fire(target, can_gc);
1149
1150        let mut flags = event.flags();
1151        if flags.contains(EventFlags::Canceled) {
1152            return flags.into();
1153        }
1154
1155        // https://w3c.github.io/uievents/#keys-cancelable-keys
1156        // it MUST prevent the respective beforeinput and input
1157        // (and keypress if supported) events from being generated
1158        // TODO: keypress should be deprecated and superceded by beforeinput
1159
1160        let is_character_value_key = matches!(
1161            keyboard_event.event.key,
1162            Key::Character(_) | Key::Named(NamedKey::Enter)
1163        );
1164        if keyboard_event.event.state == KeyState::Down &&
1165            is_character_value_key &&
1166            !keyboard_event.event.is_composing
1167        {
1168            // https://w3c.github.io/uievents/#keypress-event-order
1169            let keypress_event = KeyboardEvent::new(
1170                &self.window,
1171                DOMString::from("keypress"),
1172                true,
1173                true,
1174                Some(&self.window),
1175                0,
1176                keyboard_event.event.key.clone(),
1177                DOMString::from(keyboard_event.event.code.to_string()),
1178                keyboard_event.event.location as u32,
1179                keyboard_event.event.repeat,
1180                keyboard_event.event.is_composing,
1181                keyboard_event.event.modifiers,
1182                keyboard_event.event.key.legacy_charcode(),
1183                0,
1184                can_gc,
1185            );
1186            let event = keypress_event.upcast::<Event>();
1187            event.fire(target, can_gc);
1188            flags = event.flags();
1189        }
1190
1191        if flags.contains(EventFlags::Canceled) {
1192            return flags.into();
1193        }
1194
1195        // This behavior is unspecced
1196        // We are supposed to dispatch synthetic click activation for Space and/or Return,
1197        // however *when* we do it is up to us.
1198        // Here, we're dispatching it after the key event so the script has a chance to cancel it
1199        // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27337
1200        if (keyboard_event.event.key == Key::Named(NamedKey::Enter) ||
1201            keyboard_event.event.code == Code::Space) &&
1202            keyboard_event.event.state == KeyState::Up
1203        {
1204            if let Some(elem) = target.downcast::<Element>() {
1205                elem.upcast::<Node>()
1206                    .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
1207            }
1208        }
1209
1210        flags.into()
1211    }
1212
1213    fn handle_ime_event(&self, event: ImeEvent, can_gc: CanGc) -> InputEventResult {
1214        let document = self.window.Document();
1215        let composition_event = match event {
1216            ImeEvent::Dismissed => {
1217                document.request_focus(
1218                    document.GetBody().as_ref().map(|e| e.upcast()),
1219                    FocusInitiator::Local,
1220                    can_gc,
1221                );
1222                return Default::default();
1223            },
1224            ImeEvent::Composition(composition_event) => composition_event,
1225        };
1226
1227        // spec: https://w3c.github.io/uievents/#compositionstart
1228        // spec: https://w3c.github.io/uievents/#compositionupdate
1229        // spec: https://w3c.github.io/uievents/#compositionend
1230        // > Event.target : focused element processing the composition
1231        let focused = document.get_focused_element();
1232        let target = if let Some(elem) = &focused {
1233            elem.upcast()
1234        } else {
1235            // Event is only dispatched if there is a focused element.
1236            return Default::default();
1237        };
1238
1239        let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1240        let event = CompositionEvent::new(
1241            &self.window,
1242            DOMString::from(composition_event.state.event_type()),
1243            true,
1244            cancelable,
1245            Some(&self.window),
1246            0,
1247            DOMString::from(composition_event.data),
1248            can_gc,
1249        );
1250
1251        let event = event.upcast::<Event>();
1252        event.fire(target, can_gc);
1253        event.flags().into()
1254    }
1255
1256    fn handle_wheel_event(
1257        &self,
1258        event: EmbedderWheelEvent,
1259        input_event: &ConstellationInputEvent,
1260        can_gc: CanGc,
1261    ) -> InputEventResult {
1262        // Ignore all incoming events without a hit test.
1263        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1264            return Default::default();
1265        };
1266
1267        let Some(el) = hit_test_result
1268            .node
1269            .inclusive_ancestors(ShadowIncluding::Yes)
1270            .find_map(DomRoot::downcast::<Element>)
1271        else {
1272            return Default::default();
1273        };
1274
1275        let node = el.upcast::<Node>();
1276        let wheel_event_type_string = "wheel".to_owned();
1277        debug!(
1278            "{}: on {:?} at {:?}",
1279            wheel_event_type_string,
1280            node.debug_str(),
1281            hit_test_result.point_in_frame
1282        );
1283
1284        // https://w3c.github.io/uievents/#event-wheelevents
1285        let dom_event = WheelEvent::new(
1286            &self.window,
1287            DOMString::from(wheel_event_type_string),
1288            EventBubbles::Bubbles,
1289            EventCancelable::Cancelable,
1290            Some(&self.window),
1291            0i32,
1292            hit_test_result.point_in_frame.to_i32(),
1293            hit_test_result.point_in_frame.to_i32(),
1294            hit_test_result
1295                .point_relative_to_initial_containing_block
1296                .to_i32(),
1297            input_event.active_keyboard_modifiers,
1298            0i16,
1299            input_event.pressed_mouse_buttons,
1300            None,
1301            None,
1302            // winit defines positive wheel delta values as revealing more content left/up.
1303            // https://docs.rs/winit-gtk/latest/winit/event/enum.MouseScrollDelta.html
1304            // This is the opposite of wheel delta in uievents
1305            // https://w3c.github.io/uievents/#dom-wheeleventinit-deltaz
1306            Finite::wrap(-event.delta.x),
1307            Finite::wrap(-event.delta.y),
1308            Finite::wrap(-event.delta.z),
1309            event.delta.mode as u32,
1310            can_gc,
1311        );
1312
1313        let dom_event = dom_event.upcast::<Event>();
1314        dom_event.set_trusted(true);
1315        dom_event.fire(node.upcast(), can_gc);
1316
1317        dom_event.flags().into()
1318    }
1319
1320    #[cfg(feature = "gamepad")]
1321    fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1322        match gamepad_event {
1323            EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1324                self.handle_gamepad_connect(
1325                    index.0,
1326                    name,
1327                    bounds.axis_bounds,
1328                    bounds.button_bounds,
1329                    supported_haptic_effects,
1330                );
1331            },
1332            EmbedderGamepadEvent::Disconnected(index) => {
1333                self.handle_gamepad_disconnect(index.0);
1334            },
1335            EmbedderGamepadEvent::Updated(index, update_type) => {
1336                self.receive_new_gamepad_button_or_axis(index.0, update_type);
1337            },
1338        };
1339    }
1340
1341    /// <https://www.w3.org/TR/gamepad/#dfn-gamepadconnected>
1342    #[cfg(feature = "gamepad")]
1343    fn handle_gamepad_connect(
1344        &self,
1345        // As the spec actually defines how to set the gamepad index, the GilRs index
1346        // is currently unused, though in practice it will almost always be the same.
1347        // More infra is currently needed to track gamepads across windows.
1348        _index: usize,
1349        name: String,
1350        axis_bounds: (f64, f64),
1351        button_bounds: (f64, f64),
1352        supported_haptic_effects: GamepadSupportedHapticEffects,
1353    ) {
1354        // TODO: 2. If document is not null and is not allowed to use the "gamepad" permission,
1355        //          then abort these steps.
1356        let trusted_window = Trusted::new(&*self.window);
1357        self.window
1358            .upcast::<GlobalScope>()
1359            .task_manager()
1360            .gamepad_task_source()
1361            .queue(task!(gamepad_connected: move || {
1362                let window = trusted_window.root();
1363
1364                let navigator = window.Navigator();
1365                let selected_index = navigator.select_gamepad_index();
1366                let gamepad = Gamepad::new(
1367                    &window,
1368                    selected_index,
1369                    name,
1370                    "standard".into(),
1371                    axis_bounds,
1372                    button_bounds,
1373                    supported_haptic_effects,
1374                    false,
1375                    CanGc::note(),
1376                );
1377                navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::note());
1378            }));
1379    }
1380
1381    /// <https://www.w3.org/TR/gamepad/#dfn-gamepaddisconnected>
1382    #[cfg(feature = "gamepad")]
1383    fn handle_gamepad_disconnect(&self, index: usize) {
1384        let trusted_window = Trusted::new(&*self.window);
1385        self.window
1386            .upcast::<GlobalScope>()
1387            .task_manager()
1388            .gamepad_task_source()
1389            .queue(task!(gamepad_disconnected: move || {
1390                let window = trusted_window.root();
1391                let navigator = window.Navigator();
1392                if let Some(gamepad) = navigator.get_gamepad(index) {
1393                    if window.Document().is_fully_active() {
1394                        gamepad.update_connected(false, gamepad.exposed(), CanGc::note());
1395                        navigator.remove_gamepad(index);
1396                    }
1397                }
1398            }));
1399    }
1400
1401    /// <https://www.w3.org/TR/gamepad/#receiving-inputs>
1402    #[cfg(feature = "gamepad")]
1403    fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1404        let trusted_window = Trusted::new(&*self.window);
1405
1406        // <https://w3c.github.io/gamepad/#dfn-update-gamepad-state>
1407        self.window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1408                task!(update_gamepad_state: move || {
1409                    let window = trusted_window.root();
1410                    let navigator = window.Navigator();
1411                    if let Some(gamepad) = navigator.get_gamepad(index) {
1412                        let current_time = window.Performance().Now();
1413                        gamepad.update_timestamp(*current_time);
1414                        match update_type {
1415                            GamepadUpdateType::Axis(index, value) => {
1416                                gamepad.map_and_normalize_axes(index, value);
1417                            },
1418                            GamepadUpdateType::Button(index, value) => {
1419                                gamepad.map_and_normalize_buttons(index, value);
1420                            }
1421                        };
1422                        if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1423                            navigator.set_has_gamepad_gesture(true);
1424                            navigator.GetGamepads()
1425                                .iter()
1426                                .filter_map(|g| g.as_ref())
1427                                .for_each(|gamepad| {
1428                                    gamepad.set_exposed(true);
1429                                    gamepad.update_timestamp(*current_time);
1430                                    let new_gamepad = Trusted::new(&**gamepad);
1431                                    if window.Document().is_fully_active() {
1432                                        window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1433                                            task!(update_gamepad_connect: move || {
1434                                                let gamepad = new_gamepad.root();
1435                                                gamepad.notify_event(GamepadEventType::Connected, CanGc::note());
1436                                            })
1437                                        );
1438                                    }
1439                                });
1440                        }
1441                    }
1442                })
1443            );
1444    }
1445
1446    /// <https://www.w3.org/TR/clipboard-apis/#clipboard-actions>
1447    pub(crate) fn handle_editing_action(
1448        &self,
1449        element: Option<DomRoot<Element>>,
1450        action: EditingActionEvent,
1451        can_gc: CanGc,
1452    ) -> InputEventResult {
1453        let clipboard_event_type = match action {
1454            EditingActionEvent::Copy => ClipboardEventType::Copy,
1455            EditingActionEvent::Cut => ClipboardEventType::Cut,
1456            EditingActionEvent::Paste => ClipboardEventType::Paste,
1457        };
1458
1459        // The script_triggered flag is set if the action runs because of a script, e.g. document.execCommand()
1460        let script_triggered = false;
1461
1462        // The script_may_access_clipboard flag is set
1463        // if action is paste and the script thread is allowed to read from clipboard or
1464        // if action is copy or cut and the script thread is allowed to modify the clipboard
1465        let script_may_access_clipboard = false;
1466
1467        // Step 1 If the script-triggered flag is set and the script-may-access-clipboard flag is unset
1468        if script_triggered && !script_may_access_clipboard {
1469            return InputEventResult::empty();
1470        }
1471
1472        // Step 2 Fire a clipboard event
1473        let clipboard_event =
1474            self.fire_clipboard_event(element.clone(), clipboard_event_type, can_gc);
1475
1476        // Step 3 If a script doesn't call preventDefault()
1477        // the event will be handled inside target's VirtualMethods::handle_event
1478        let event = clipboard_event.upcast::<Event>();
1479        if !event.IsTrusted() {
1480            return event.flags().into();
1481        }
1482
1483        // Step 4 If the event was canceled, then
1484        if event.DefaultPrevented() {
1485            let event_type = event.Type();
1486            match_domstring_ascii!(event_type,
1487
1488                "copy" => {
1489                    // Step 4.1 Call the write content to the clipboard algorithm,
1490                    // passing on the DataTransferItemList items, a clear-was-called flag and a types-to-clear list.
1491                    if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1492                        let drag_data_store =
1493                            clipboard_data.data_store().expect("This shouldn't fail");
1494                        self.write_content_to_the_clipboard(&drag_data_store);
1495                    }
1496                },
1497                "cut" => {
1498                    // Step 4.1 Call the write content to the clipboard algorithm,
1499                    // passing on the DataTransferItemList items, a clear-was-called flag and a types-to-clear list.
1500                    if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1501                        let drag_data_store =
1502                            clipboard_data.data_store().expect("This shouldn't fail");
1503                        self.write_content_to_the_clipboard(&drag_data_store);
1504                    }
1505
1506                    // Step 4.2 Fire a clipboard event named clipboardchange
1507                    self.fire_clipboard_event(element, ClipboardEventType::Change, can_gc);
1508                },
1509                // Step 4.1 Return false.
1510                // Note: This function deviates from the specification a bit by returning
1511                // the `InputEventResult` below.
1512                "paste" => (),
1513                _ => (),
1514            )
1515        }
1516
1517        // Step 5: Return true from the action.
1518        // In this case we are returning the `InputEventResult` instead of true or false.
1519        event.flags().into()
1520    }
1521
1522    /// <https://www.w3.org/TR/clipboard-apis/#fire-a-clipboard-event>
1523    pub(crate) fn fire_clipboard_event(
1524        &self,
1525        target: Option<DomRoot<Element>>,
1526        clipboard_event_type: ClipboardEventType,
1527        can_gc: CanGc,
1528    ) -> DomRoot<ClipboardEvent> {
1529        let clipboard_event = ClipboardEvent::new(
1530            &self.window,
1531            None,
1532            DOMString::from(clipboard_event_type.as_str()),
1533            EventBubbles::Bubbles,
1534            EventCancelable::Cancelable,
1535            None,
1536            can_gc,
1537        );
1538
1539        // Step 1 Let clear_was_called be false
1540        // Step 2 Let types_to_clear an empty list
1541        let mut drag_data_store = DragDataStore::new();
1542
1543        // Step 4 let clipboard-entry be the sequence number of clipboard content, null if the OS doesn't support it.
1544
1545        // Step 5 let trusted be true if the event is generated by the user agent, false otherwise
1546        let trusted = true;
1547
1548        // Step 6 if the context is editable:
1549        let document = self.window.Document();
1550        let target = target.or(document.get_focused_element());
1551        let target = target
1552            .map(|target| DomRoot::from_ref(target.upcast()))
1553            .or_else(|| {
1554                document
1555                    .GetBody()
1556                    .map(|body| DomRoot::from_ref(body.upcast()))
1557            })
1558            .unwrap_or_else(|| DomRoot::from_ref(self.window.upcast()));
1559
1560        // Step 6.2 else TODO require Selection see https://github.com/w3c/clipboard-apis/issues/70
1561        // Step 7
1562        match clipboard_event_type {
1563            ClipboardEventType::Copy | ClipboardEventType::Cut => {
1564                // Step 7.2.1
1565                drag_data_store.set_mode(Mode::ReadWrite);
1566            },
1567            ClipboardEventType::Paste => {
1568                let (callback, receiver) =
1569                    GenericCallback::new_blocking().expect("Could not create callback");
1570                self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
1571                    self.window.webview_id(),
1572                    callback,
1573                ));
1574                let text_contents = receiver
1575                    .recv()
1576                    .map(Result::unwrap_or_default)
1577                    .unwrap_or_default();
1578
1579                // Step 7.1.1
1580                drag_data_store.set_mode(Mode::ReadOnly);
1581                // Step 7.1.2 If trusted or the implementation gives script-generated events access to the clipboard
1582                if trusted {
1583                    // Step 7.1.2.1 For each clipboard-part on the OS clipboard:
1584
1585                    // Step 7.1.2.1.1 If clipboard-part contains plain text, then
1586                    let data = DOMString::from(text_contents.to_string());
1587                    let type_ = DOMString::from("text/plain");
1588                    let _ = drag_data_store.add(Kind::Text { data, type_ });
1589
1590                    // Step 7.1.2.1.2 TODO If clipboard-part represents file references, then for each file reference
1591                    // Step 7.1.2.1.3 TODO If clipboard-part contains HTML- or XHTML-formatted text then
1592
1593                    // Step 7.1.3 Update clipboard-event-data’s files to match clipboard-event-data’s items
1594                    // Step 7.1.4 Update clipboard-event-data’s types to match clipboard-event-data’s items
1595                }
1596            },
1597            ClipboardEventType::Change => (),
1598        }
1599
1600        // Step 3
1601        let clipboard_event_data = DataTransfer::new(
1602            &self.window,
1603            Rc::new(RefCell::new(Some(drag_data_store))),
1604            can_gc,
1605        );
1606
1607        // Step 8
1608        clipboard_event.set_clipboard_data(Some(&clipboard_event_data));
1609
1610        // Step 9
1611        let event = clipboard_event.upcast::<Event>();
1612        event.set_trusted(trusted);
1613
1614        // Step 10 Set event’s composed to true.
1615        event.set_composed(true);
1616
1617        // Step 11
1618        event.dispatch(&target, false, can_gc);
1619
1620        DomRoot::from(clipboard_event)
1621    }
1622
1623    /// <https://www.w3.org/TR/clipboard-apis/#write-content-to-the-clipboard>
1624    fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
1625        // Step 1
1626        if drag_data_store.list_len() > 0 {
1627            // Step 1.1 Clear the clipboard.
1628            self.window
1629                .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1630            // Step 1.2
1631            for item in drag_data_store.iter_item_list() {
1632                match item {
1633                    Kind::Text { data, .. } => {
1634                        // Step 1.2.1.1 Ensure encoding is correct per OS and locale conventions
1635                        // Step 1.2.1.2 Normalize line endings according to platform conventions
1636                        // Step 1.2.1.3
1637                        self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
1638                            self.window.webview_id(),
1639                            data.to_string(),
1640                        ));
1641                    },
1642                    Kind::File { .. } => {
1643                        // Step 1.2.2 If data is of a type listed in the mandatory data types list, then
1644                        // Step 1.2.2.1 Place part on clipboard with the appropriate OS clipboard format description
1645                        // Step 1.2.3 Else this is left to the implementation
1646                    },
1647                }
1648            }
1649        } else {
1650            // Step 2.1
1651            if drag_data_store.clear_was_called {
1652                // Step 2.1.1 If types-to-clear list is empty, clear the clipboard
1653                self.window
1654                    .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1655                // Step 2.1.2 Else remove the types in the list from the clipboard
1656                // As of now this can't be done with Arboard, and it's possible that will be removed from the spec
1657            }
1658        }
1659    }
1660
1661    /// Handle a scroll event triggered by user interactions from the embedder.
1662    /// <https://drafts.csswg.org/cssom-view/#scrolling-events>
1663    #[expect(unsafe_code)]
1664    pub(crate) fn handle_embedder_scroll_event(&self, scrolled_node: ExternalScrollId) {
1665        // If it is a viewport scroll.
1666        let document = self.window.Document();
1667        if scrolled_node.is_root() {
1668            document.handle_viewport_scroll_event();
1669        } else {
1670            // Otherwise, check whether it is for a relevant element within the document.
1671            let Some(node_id) = node_id_from_scroll_id(scrolled_node.0 as usize) else {
1672                return;
1673            };
1674            let node = unsafe {
1675                node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
1676            };
1677            let Some(element) = node
1678                .inclusive_ancestors(ShadowIncluding::Yes)
1679                .find_map(DomRoot::downcast::<Element>)
1680            else {
1681                return;
1682            };
1683
1684            document.handle_element_scroll_event(&element);
1685        }
1686    }
1687
1688    pub(crate) fn run_default_keyboard_event_handler(&self, event: &KeyboardEvent) {
1689        if event.upcast::<Event>().type_() != atom!("keydown") {
1690            return;
1691        }
1692        if !event.modifiers().is_empty() {
1693            return;
1694        }
1695        let scroll = match event.key() {
1696            Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
1697            Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
1698            Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
1699            Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
1700            Key::Named(NamedKey::End) => KeyboardScroll::End,
1701            Key::Named(NamedKey::Home) => KeyboardScroll::Home,
1702            Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
1703            Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
1704            _ => return,
1705        };
1706        self.do_keyboard_scroll(scroll);
1707    }
1708
1709    pub(crate) fn do_keyboard_scroll(&self, scroll: KeyboardScroll) {
1710        let scroll_axis = match scroll {
1711            KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
1712            _ => ScrollingBoxAxis::Y,
1713        };
1714
1715        let document = self.window.Document();
1716        let mut scrolling_box = document
1717            .get_focused_element()
1718            .or(self.most_recently_clicked_element.get())
1719            .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
1720            .unwrap_or_else(|| {
1721                document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
1722            });
1723
1724        while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
1725            // Always fall back to trying to scroll the entire document.
1726            if scrolling_box.is_viewport() {
1727                break;
1728            }
1729            let parent = scrolling_box.parent().unwrap_or_else(|| {
1730                document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
1731            });
1732            scrolling_box = parent;
1733        }
1734
1735        let calculate_current_scroll_offset_and_delta = || {
1736            const LINE_HEIGHT: f32 = 76.0;
1737            const LINE_WIDTH: f32 = 76.0;
1738
1739            let current_scroll_offset = scrolling_box.scroll_position();
1740            (
1741                current_scroll_offset,
1742                match scroll {
1743                    KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
1744                    KeyboardScroll::End => Vector2D::new(
1745                        0.0,
1746                        -current_scroll_offset.y + scrolling_box.content_size().height -
1747                            scrolling_box.size().height,
1748                    ),
1749                    KeyboardScroll::PageDown => {
1750                        Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
1751                    },
1752                    KeyboardScroll::PageUp => {
1753                        Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
1754                    },
1755                    KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
1756                    KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
1757                    KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
1758                    KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
1759                },
1760            )
1761        };
1762
1763        // If trying to scroll the viewport of this `Window` and this is the root `Document`
1764        // of the `WebView`, then send the srolling operation to the renderer, so that it
1765        // can properly pan any pinch zoom viewport.
1766        let parent_pipeline = self.window.parent_info();
1767        if scrolling_box.is_viewport() && parent_pipeline.is_none() {
1768            let (_, delta) = calculate_current_scroll_offset_and_delta();
1769            self.window
1770                .paint_api()
1771                .scroll_viewport_by_delta(self.window.webview_id(), delta);
1772        }
1773
1774        // If this is the viewport and we cannot scroll, try to ask a parent viewport to scroll,
1775        // if we are inside an `<iframe>`.
1776        if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
1777            assert!(scrolling_box.is_viewport());
1778
1779            let window_proxy = document.window().window_proxy();
1780            if let Some(iframe) = window_proxy.frame_element() {
1781                // When the `<iframe>` is local (in this ScriptThread), we can
1782                // synchronously chain up the keyboard scrolling event.
1783                let cx = GlobalScope::get_cx();
1784                let iframe_window = iframe.owner_window();
1785                let _ac = JSAutoRealm::new(*cx, iframe_window.reflector().get_jsobject().get());
1786                iframe_window
1787                    .Document()
1788                    .event_handler()
1789                    .do_keyboard_scroll(scroll);
1790            } else if let Some(parent_pipeline) = parent_pipeline {
1791                // Otherwise, if we have a parent (presumably from a different origin)
1792                // asynchronously ask the Constellation to forward the event to the parent
1793                // pipeline, if we have one.
1794                document.window().send_to_constellation(
1795                    ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
1796                );
1797            };
1798            return;
1799        }
1800
1801        let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
1802        scrolling_box.scroll_to(delta + current_scroll_offset, ScrollBehavior::Auto);
1803    }
1804
1805    /// Get or create a pointer ID for the given touch identifier.
1806    /// Returns the pointer ID to use for this touch.
1807    fn get_or_create_pointer_id_for_touch(&self, touch_id: i32) -> i32 {
1808        let mut active_pointer_ids = self.active_pointer_ids.borrow_mut();
1809
1810        if let Some(&pointer_id) = active_pointer_ids.get(&touch_id) {
1811            return pointer_id;
1812        }
1813
1814        let pointer_id = self.next_touch_pointer_id.get();
1815        active_pointer_ids.insert(touch_id, pointer_id);
1816        self.next_touch_pointer_id.set(pointer_id + 1);
1817        pointer_id
1818    }
1819
1820    /// Remove the pointer ID mapping for the given touch identifier.
1821    fn remove_pointer_id_for_touch(&self, touch_id: i32) {
1822        self.active_pointer_ids.borrow_mut().remove(&touch_id);
1823    }
1824
1825    /// Check if this is the primary pointer (for touch events).
1826    /// The first touch to make contact is the primary pointer.
1827    fn is_primary_pointer(&self, pointer_id: i32) -> bool {
1828        // For touch, the primary pointer is the one with the smallest pointer ID
1829        // that is still active.
1830        self.active_pointer_ids
1831            .borrow()
1832            .values()
1833            .min()
1834            .is_some_and(|primary_pointer| *primary_pointer == pointer_id)
1835    }
1836}