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