script/dom/
document_event_handler.rs

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