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