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::match_domstring_ascii;
35use script_bindings::num::Finite;
36use script_bindings::reflector::DomObject;
37use script_bindings::root::{Dom, DomRoot, DomSlice};
38use script_bindings::script_runtime::CanGc;
39use script_bindings::str::DOMString;
40use script_traits::ConstellationInputEvent;
41use servo_config::pref;
42use style_traits::CSSPixel;
43use xml5ever::{local_name, ns};
44
45use crate::dom::bindings::cell::DomRefCell;
46use crate::dom::bindings::refcounted::Trusted;
47use crate::dom::bindings::root::MutNullableDom;
48use crate::dom::clipboardevent::ClipboardEventType;
49use crate::dom::document::{FireMouseEventType, FocusInitiator};
50use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventFlags};
51use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture};
52use crate::dom::gamepad::gamepadevent::GamepadEventType;
53use crate::dom::inputevent::HitTestResult;
54use crate::dom::node::{self, Node, NodeTraits, ShadowIncluding};
55use crate::dom::pointerevent::PointerId;
56use crate::dom::scrolling_box::ScrollingBoxAxis;
57use crate::dom::types::{
58    ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope,
59    HTMLAnchorElement, KeyboardEvent, MouseEvent, PointerEvent, Touch, TouchEvent, TouchList,
60    WheelEvent, Window,
61};
62use crate::drag_data_store::{DragDataStore, Kind, Mode};
63use crate::realms::enter_realm;
64
65/// The [`DocumentEventHandler`] is a structure responsible for handling input events for
66/// the [`crate::Document`] and storing data related to event handling. It exists to
67/// decrease the size of the [`crate::Document`] structure.
68#[derive(JSTraceable, MallocSizeOf)]
69#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
70pub(crate) struct DocumentEventHandler {
71    /// The [`Window`] element for this [`DocumentEventHandler`].
72    window: Dom<Window>,
73    /// Pending input events, to be handled at the next rendering opportunity.
74    #[no_trace]
75    #[ignore_malloc_size_of = "CompositorEvent contains data from outside crates"]
76    pending_input_events: DomRefCell<Vec<ConstellationInputEvent>>,
77    /// The index of the last mouse move event in the pending compositor events queue.
78    mouse_move_event_index: DomRefCell<Option<usize>>,
79    /// <https://w3c.github.io/uievents/#event-type-dblclick>
80    #[ignore_malloc_size_of = "Defined in std"]
81    #[no_trace]
82    last_click_info: DomRefCell<Option<(Instant, Point2D<f32, CSSPixel>)>>,
83    #[no_trace]
84    last_mouse_button_down_point: Cell<Option<Point2D<f32, CSSPixel>>>,
85    /// The element that is currently hovered by the cursor.
86    current_hover_target: MutNullableDom<Element>,
87    /// The element that was most recently clicked.
88    most_recently_clicked_element: MutNullableDom<Element>,
89    /// The most recent mouse movement point, used for processing `mouseleave` events.
90    #[no_trace]
91    most_recent_mousemove_point: Cell<Option<Point2D<f32, CSSPixel>>>,
92    /// The currently set [`Cursor`] or `None` if the `Document` isn't being hovered
93    /// by the cursor.
94    #[no_trace]
95    current_cursor: Cell<Option<Cursor>>,
96    /// <http://w3c.github.io/touch-events/#dfn-active-touch-point>
97    active_touch_points: DomRefCell<Vec<Dom<Touch>>>,
98    /// The active keyboard modifiers for the WebView. This is updated when receiving any input event.
99    #[no_trace]
100    active_keyboard_modifiers: Cell<Modifiers>,
101}
102
103impl DocumentEventHandler {
104    pub(crate) fn new(window: &Window) -> Self {
105        Self {
106            window: Dom::from_ref(window),
107            pending_input_events: Default::default(),
108            mouse_move_event_index: Default::default(),
109            last_click_info: Default::default(),
110            last_mouse_button_down_point: Default::default(),
111            current_hover_target: Default::default(),
112            most_recently_clicked_element: Default::default(),
113            most_recent_mousemove_point: Default::default(),
114            current_cursor: Default::default(),
115            active_touch_points: Default::default(),
116            active_keyboard_modifiers: Default::default(),
117        }
118    }
119
120    /// Note a pending compositor event, to be processed at the next `update_the_rendering` task.
121    pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) {
122        let mut pending_compositor_events = self.pending_input_events.borrow_mut();
123        if matches!(event.event.event, InputEvent::MouseMove(..)) {
124            // First try to replace any existing mouse move event.
125            if let Some(mouse_move_event) = self
126                .mouse_move_event_index
127                .borrow()
128                .and_then(|index| pending_compositor_events.get_mut(index))
129            {
130                *mouse_move_event = event;
131                return;
132            }
133
134            *self.mouse_move_event_index.borrow_mut() = Some(pending_compositor_events.len());
135        }
136
137        pending_compositor_events.push(event);
138    }
139
140    /// Whether or not this [`Document`] has any pending input events to be processed during
141    /// "update the rendering."
142    pub(crate) fn has_pending_input_events(&self) -> bool {
143        !self.pending_input_events.borrow().is_empty()
144    }
145
146    pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool {
147        #[cfg(target_os = "macos")]
148        {
149            self.active_keyboard_modifiers
150                .get()
151                .contains(Modifiers::META)
152        }
153        #[cfg(not(target_os = "macos"))]
154        {
155            self.active_keyboard_modifiers
156                .get()
157                .contains(Modifiers::CONTROL)
158        }
159    }
160
161    pub(crate) fn handle_pending_input_events(&self, can_gc: CanGc) {
162        let _realm = enter_realm(&*self.window);
163
164        // Reset the mouse event index.
165        *self.mouse_move_event_index.borrow_mut() = None;
166        let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut());
167
168        for event in pending_input_events {
169            self.active_keyboard_modifiers
170                .set(event.active_keyboard_modifiers);
171
172            // TODO: For some of these we still aren't properly calculating whether or not
173            // the event was handled or if `preventDefault()` was called on it. Each of
174            // these cases needs to be examined and some of them either fire more than one
175            // event or fire events later. We have to make a good decision about what to
176            // return to the embedder when that happens.
177            let result = match event.event.event.clone() {
178                InputEvent::MouseButton(mouse_button_event) => {
179                    self.handle_native_mouse_button_event(mouse_button_event, &event, can_gc);
180                    InputEventResult::default()
181                },
182                InputEvent::MouseMove(_) => {
183                    self.handle_native_mouse_move_event(&event, can_gc);
184                    InputEventResult::default()
185                },
186                InputEvent::MouseLeftViewport(mouse_leave_event) => {
187                    self.handle_mouse_left_viewport_event(&event, &mouse_leave_event, can_gc);
188                    InputEventResult::default()
189                },
190                InputEvent::Touch(touch_event) => {
191                    self.handle_touch_event(touch_event, &event, can_gc)
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    ) -> InputEventResult {
842        // Ignore all incoming events without a hit test.
843        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
844            self.update_active_touch_points_when_early_return(event);
845            return Default::default();
846        };
847
848        let TouchId(identifier) = event.id;
849        let event_name = match event.event_type {
850            TouchEventType::Down => "touchstart",
851            TouchEventType::Move => "touchmove",
852            TouchEventType::Up => "touchend",
853            TouchEventType::Cancel => "touchcancel",
854        };
855
856        let Some(el) = hit_test_result
857            .node
858            .inclusive_ancestors(ShadowIncluding::No)
859            .filter_map(DomRoot::downcast::<Element>)
860            .next()
861        else {
862            self.update_active_touch_points_when_early_return(event);
863            return Default::default();
864        };
865
866        let target = DomRoot::upcast::<EventTarget>(el);
867        let window = &*self.window;
868
869        let client_x = Finite::wrap(hit_test_result.point_in_frame.x as f64);
870        let client_y = Finite::wrap(hit_test_result.point_in_frame.y as f64);
871        let page_x =
872            Finite::wrap(hit_test_result.point_in_frame.x as f64 + window.PageXOffset() as f64);
873        let page_y =
874            Finite::wrap(hit_test_result.point_in_frame.y as f64 + window.PageYOffset() as f64);
875
876        let touch = Touch::new(
877            window, identifier, &target, client_x,
878            client_y, // TODO: Get real screen coordinates?
879            client_x, client_y, page_x, page_y, can_gc,
880        );
881
882        match event.event_type {
883            TouchEventType::Down => {
884                // Add a new touch point
885                self.active_touch_points
886                    .borrow_mut()
887                    .push(Dom::from_ref(&*touch));
888            },
889            TouchEventType::Move => {
890                // Replace an existing touch point
891                let mut active_touch_points = self.active_touch_points.borrow_mut();
892                match active_touch_points
893                    .iter_mut()
894                    .find(|t| t.Identifier() == identifier)
895                {
896                    Some(t) => *t = Dom::from_ref(&*touch),
897                    None => warn!("Got a touchmove event for a non-active touch point"),
898                }
899            },
900            TouchEventType::Up | TouchEventType::Cancel => {
901                // Remove an existing touch point
902                let mut active_touch_points = self.active_touch_points.borrow_mut();
903                match active_touch_points
904                    .iter()
905                    .position(|t| t.Identifier() == identifier)
906                {
907                    Some(i) => {
908                        active_touch_points.swap_remove(i);
909                    },
910                    None => warn!("Got a touchend event for a non-active touch point"),
911                }
912            },
913        }
914
915        rooted_vec!(let mut target_touches);
916        let touches = {
917            let touches = self.active_touch_points.borrow();
918            target_touches.extend(touches.iter().filter(|t| t.Target() == target).cloned());
919            TouchList::new(window, touches.r(), can_gc)
920        };
921
922        let touch_event = TouchEvent::new(
923            window,
924            DOMString::from(event_name),
925            EventBubbles::Bubbles,
926            EventCancelable::from(event.is_cancelable()),
927            EventComposed::Composed,
928            Some(window),
929            0i32,
930            &touches,
931            &TouchList::new(window, from_ref(&&*touch), can_gc),
932            &TouchList::new(window, target_touches.r(), can_gc),
933            // FIXME: modifier keys
934            false,
935            false,
936            false,
937            false,
938            can_gc,
939        );
940
941        let event = touch_event.upcast::<Event>();
942        event.fire(&target, can_gc);
943        event.flags().into()
944    }
945
946    // If hittest fails, we still need to update the active point information.
947    fn update_active_touch_points_when_early_return(&self, event: EmbedderTouchEvent) {
948        match event.event_type {
949            TouchEventType::Down => {
950                // If the touchdown fails, we don't need to do anything.
951                // When a touchmove or touchdown occurs at that touch point,
952                // a warning is triggered: Got a touchmove/touchend event for a non-active touch point
953            },
954            TouchEventType::Move => {
955                // The failure of touchmove does not affect the number of active points.
956                // Since there is no position information when it fails, we do not need to update.
957            },
958            TouchEventType::Up | TouchEventType::Cancel => {
959                // Remove an existing touch point
960                let mut active_touch_points = self.active_touch_points.borrow_mut();
961                match active_touch_points
962                    .iter()
963                    .position(|t| t.Identifier() == event.id.0)
964                {
965                    Some(i) => {
966                        active_touch_points.swap_remove(i);
967                    },
968                    None => warn!("Got a touchend event for a non-active touch point"),
969                }
970            },
971        }
972    }
973
974    /// The entry point for all key processing for web content
975    fn handle_keyboard_event(
976        &self,
977        keyboard_event: EmbedderKeyboardEvent,
978        can_gc: CanGc,
979    ) -> InputEventResult {
980        let document = self.window.Document();
981        let focused = document.get_focused_element();
982        let body = document.GetBody();
983
984        let target = match (&focused, &body) {
985            (Some(focused), _) => focused.upcast(),
986            (&None, Some(body)) => body.upcast(),
987            (&None, &None) => self.window.upcast(),
988        };
989
990        let keyevent = KeyboardEvent::new(
991            &self.window,
992            DOMString::from(keyboard_event.event.state.event_type()),
993            true,
994            true,
995            Some(&self.window),
996            0,
997            keyboard_event.event.key.clone(),
998            DOMString::from(keyboard_event.event.code.to_string()),
999            keyboard_event.event.location as u32,
1000            keyboard_event.event.repeat,
1001            keyboard_event.event.is_composing,
1002            keyboard_event.event.modifiers,
1003            0,
1004            keyboard_event.event.key.legacy_keycode(),
1005            can_gc,
1006        );
1007
1008        let event = keyevent.upcast::<Event>();
1009        event.fire(target, can_gc);
1010
1011        let mut flags = event.flags();
1012        if flags.contains(EventFlags::Canceled) {
1013            return flags.into();
1014        }
1015
1016        // https://w3c.github.io/uievents/#keys-cancelable-keys
1017        // it MUST prevent the respective beforeinput and input
1018        // (and keypress if supported) events from being generated
1019        // TODO: keypress should be deprecated and superceded by beforeinput
1020
1021        let is_character_value_key = matches!(
1022            keyboard_event.event.key,
1023            Key::Character(_) | Key::Named(NamedKey::Enter)
1024        );
1025        if keyboard_event.event.state == KeyState::Down &&
1026            is_character_value_key &&
1027            !keyboard_event.event.is_composing
1028        {
1029            // https://w3c.github.io/uievents/#keypress-event-order
1030            let keypress_event = KeyboardEvent::new(
1031                &self.window,
1032                DOMString::from("keypress"),
1033                true,
1034                true,
1035                Some(&self.window),
1036                0,
1037                keyboard_event.event.key.clone(),
1038                DOMString::from(keyboard_event.event.code.to_string()),
1039                keyboard_event.event.location as u32,
1040                keyboard_event.event.repeat,
1041                keyboard_event.event.is_composing,
1042                keyboard_event.event.modifiers,
1043                keyboard_event.event.key.legacy_charcode(),
1044                0,
1045                can_gc,
1046            );
1047            let event = keypress_event.upcast::<Event>();
1048            event.fire(target, can_gc);
1049            flags = event.flags();
1050        }
1051
1052        if flags.contains(EventFlags::Canceled) {
1053            return flags.into();
1054        }
1055
1056        // This behavior is unspecced
1057        // We are supposed to dispatch synthetic click activation for Space and/or Return,
1058        // however *when* we do it is up to us.
1059        // Here, we're dispatching it after the key event so the script has a chance to cancel it
1060        // https://www.w3.org/Bugs/Public/show_bug.cgi?id=27337
1061        if (keyboard_event.event.key == Key::Named(NamedKey::Enter) ||
1062            keyboard_event.event.code == Code::Space) &&
1063            keyboard_event.event.state == KeyState::Up
1064        {
1065            if let Some(elem) = target.downcast::<Element>() {
1066                elem.upcast::<Node>()
1067                    .fire_synthetic_pointer_event_not_trusted(DOMString::from("click"), can_gc);
1068            }
1069        }
1070
1071        flags.into()
1072    }
1073
1074    fn handle_ime_event(&self, event: ImeEvent, can_gc: CanGc) -> InputEventResult {
1075        let document = self.window.Document();
1076        let composition_event = match event {
1077            ImeEvent::Dismissed => {
1078                document.request_focus(
1079                    document.GetBody().as_ref().map(|e| e.upcast()),
1080                    FocusInitiator::Local,
1081                    can_gc,
1082                );
1083                return Default::default();
1084            },
1085            ImeEvent::Composition(composition_event) => composition_event,
1086        };
1087
1088        // spec: https://w3c.github.io/uievents/#compositionstart
1089        // spec: https://w3c.github.io/uievents/#compositionupdate
1090        // spec: https://w3c.github.io/uievents/#compositionend
1091        // > Event.target : focused element processing the composition
1092        let focused = document.get_focused_element();
1093        let target = if let Some(elem) = &focused {
1094            elem.upcast()
1095        } else {
1096            // Event is only dispatched if there is a focused element.
1097            return Default::default();
1098        };
1099
1100        let cancelable = composition_event.state == keyboard_types::CompositionState::Start;
1101        let event = CompositionEvent::new(
1102            &self.window,
1103            DOMString::from(composition_event.state.event_type()),
1104            true,
1105            cancelable,
1106            Some(&self.window),
1107            0,
1108            DOMString::from(composition_event.data),
1109            can_gc,
1110        );
1111
1112        let event = event.upcast::<Event>();
1113        event.fire(target, can_gc);
1114        event.flags().into()
1115    }
1116
1117    fn handle_wheel_event(
1118        &self,
1119        event: EmbedderWheelEvent,
1120        input_event: &ConstellationInputEvent,
1121        can_gc: CanGc,
1122    ) -> InputEventResult {
1123        // Ignore all incoming events without a hit test.
1124        let Some(hit_test_result) = self.window.hit_test_from_input_event(input_event) else {
1125            return Default::default();
1126        };
1127
1128        let Some(el) = hit_test_result
1129            .node
1130            .inclusive_ancestors(ShadowIncluding::No)
1131            .filter_map(DomRoot::downcast::<Element>)
1132            .next()
1133        else {
1134            return Default::default();
1135        };
1136
1137        let node = el.upcast::<Node>();
1138        let wheel_event_type_string = "wheel".to_owned();
1139        debug!(
1140            "{}: on {:?} at {:?}",
1141            wheel_event_type_string,
1142            node.debug_str(),
1143            hit_test_result.point_in_frame
1144        );
1145
1146        // https://w3c.github.io/uievents/#event-wheelevents
1147        let dom_event = WheelEvent::new(
1148            &self.window,
1149            DOMString::from(wheel_event_type_string),
1150            EventBubbles::Bubbles,
1151            EventCancelable::Cancelable,
1152            Some(&self.window),
1153            0i32,
1154            hit_test_result.point_in_frame.to_i32(),
1155            hit_test_result.point_in_frame.to_i32(),
1156            hit_test_result
1157                .point_relative_to_initial_containing_block
1158                .to_i32(),
1159            input_event.active_keyboard_modifiers,
1160            0i16,
1161            input_event.pressed_mouse_buttons,
1162            None,
1163            None,
1164            // winit defines positive wheel delta values as revealing more content left/up.
1165            // https://docs.rs/winit-gtk/latest/winit/event/enum.MouseScrollDelta.html
1166            // This is the opposite of wheel delta in uievents
1167            // https://w3c.github.io/uievents/#dom-wheeleventinit-deltaz
1168            Finite::wrap(-event.delta.x),
1169            Finite::wrap(-event.delta.y),
1170            Finite::wrap(-event.delta.z),
1171            event.delta.mode as u32,
1172            can_gc,
1173        );
1174
1175        let dom_event = dom_event.upcast::<Event>();
1176        dom_event.set_trusted(true);
1177        dom_event.fire(node.upcast(), can_gc);
1178
1179        dom_event.flags().into()
1180    }
1181
1182    fn handle_gamepad_event(&self, gamepad_event: EmbedderGamepadEvent) {
1183        match gamepad_event {
1184            EmbedderGamepadEvent::Connected(index, name, bounds, supported_haptic_effects) => {
1185                self.handle_gamepad_connect(
1186                    index.0,
1187                    name,
1188                    bounds.axis_bounds,
1189                    bounds.button_bounds,
1190                    supported_haptic_effects,
1191                );
1192            },
1193            EmbedderGamepadEvent::Disconnected(index) => {
1194                self.handle_gamepad_disconnect(index.0);
1195            },
1196            EmbedderGamepadEvent::Updated(index, update_type) => {
1197                self.receive_new_gamepad_button_or_axis(index.0, update_type);
1198            },
1199        };
1200    }
1201
1202    /// <https://www.w3.org/TR/gamepad/#dfn-gamepadconnected>
1203    fn handle_gamepad_connect(
1204        &self,
1205        // As the spec actually defines how to set the gamepad index, the GilRs index
1206        // is currently unused, though in practice it will almost always be the same.
1207        // More infra is currently needed to track gamepads across windows.
1208        _index: usize,
1209        name: String,
1210        axis_bounds: (f64, f64),
1211        button_bounds: (f64, f64),
1212        supported_haptic_effects: GamepadSupportedHapticEffects,
1213    ) {
1214        // TODO: 2. If document is not null and is not allowed to use the "gamepad" permission,
1215        //          then abort these steps.
1216        let trusted_window = Trusted::new(&*self.window);
1217        self.window
1218            .upcast::<GlobalScope>()
1219            .task_manager()
1220            .gamepad_task_source()
1221            .queue(task!(gamepad_connected: move || {
1222                let window = trusted_window.root();
1223
1224                let navigator = window.Navigator();
1225                let selected_index = navigator.select_gamepad_index();
1226                let gamepad = Gamepad::new(
1227                    &window,
1228                    selected_index,
1229                    name,
1230                    "standard".into(),
1231                    axis_bounds,
1232                    button_bounds,
1233                    supported_haptic_effects,
1234                    false,
1235                    CanGc::note(),
1236                );
1237                navigator.set_gamepad(selected_index as usize, &gamepad, CanGc::note());
1238            }));
1239    }
1240
1241    /// <https://www.w3.org/TR/gamepad/#dfn-gamepaddisconnected>
1242    fn handle_gamepad_disconnect(&self, index: usize) {
1243        let trusted_window = Trusted::new(&*self.window);
1244        self.window
1245            .upcast::<GlobalScope>()
1246            .task_manager()
1247            .gamepad_task_source()
1248            .queue(task!(gamepad_disconnected: move || {
1249                let window = trusted_window.root();
1250                let navigator = window.Navigator();
1251                if let Some(gamepad) = navigator.get_gamepad(index) {
1252                    if window.Document().is_fully_active() {
1253                        gamepad.update_connected(false, gamepad.exposed(), CanGc::note());
1254                        navigator.remove_gamepad(index);
1255                    }
1256                }
1257            }));
1258    }
1259
1260    /// <https://www.w3.org/TR/gamepad/#receiving-inputs>
1261    fn receive_new_gamepad_button_or_axis(&self, index: usize, update_type: GamepadUpdateType) {
1262        let trusted_window = Trusted::new(&*self.window);
1263
1264        // <https://w3c.github.io/gamepad/#dfn-update-gamepad-state>
1265        self.window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1266                task!(update_gamepad_state: move || {
1267                    let window = trusted_window.root();
1268                    let navigator = window.Navigator();
1269                    if let Some(gamepad) = navigator.get_gamepad(index) {
1270                        let current_time = window.Performance().Now();
1271                        gamepad.update_timestamp(*current_time);
1272                        match update_type {
1273                            GamepadUpdateType::Axis(index, value) => {
1274                                gamepad.map_and_normalize_axes(index, value);
1275                            },
1276                            GamepadUpdateType::Button(index, value) => {
1277                                gamepad.map_and_normalize_buttons(index, value);
1278                            }
1279                        };
1280                        if !navigator.has_gamepad_gesture() && contains_user_gesture(update_type) {
1281                            navigator.set_has_gamepad_gesture(true);
1282                            navigator.GetGamepads()
1283                                .iter()
1284                                .filter_map(|g| g.as_ref())
1285                                .for_each(|gamepad| {
1286                                    gamepad.set_exposed(true);
1287                                    gamepad.update_timestamp(*current_time);
1288                                    let new_gamepad = Trusted::new(&**gamepad);
1289                                    if window.Document().is_fully_active() {
1290                                        window.upcast::<GlobalScope>().task_manager().gamepad_task_source().queue(
1291                                            task!(update_gamepad_connect: move || {
1292                                                let gamepad = new_gamepad.root();
1293                                                gamepad.notify_event(GamepadEventType::Connected, CanGc::note());
1294                                            })
1295                                        );
1296                                    }
1297                                });
1298                        }
1299                    }
1300                })
1301            );
1302    }
1303
1304    /// <https://www.w3.org/TR/clipboard-apis/#clipboard-actions>
1305    fn handle_editing_action(&self, action: EditingActionEvent, can_gc: CanGc) -> InputEventResult {
1306        let clipboard_event_type = match action {
1307            EditingActionEvent::Copy => ClipboardEventType::Copy,
1308            EditingActionEvent::Cut => ClipboardEventType::Cut,
1309            EditingActionEvent::Paste => ClipboardEventType::Paste,
1310        };
1311
1312        // The script_triggered flag is set if the action runs because of a script, e.g. document.execCommand()
1313        let script_triggered = false;
1314
1315        // The script_may_access_clipboard flag is set
1316        // if action is paste and the script thread is allowed to read from clipboard or
1317        // if action is copy or cut and the script thread is allowed to modify the clipboard
1318        let script_may_access_clipboard = false;
1319
1320        // Step 1 If the script-triggered flag is set and the script-may-access-clipboard flag is unset
1321        if script_triggered && !script_may_access_clipboard {
1322            return InputEventResult::empty();
1323        }
1324
1325        // Step 2 Fire a clipboard event
1326        let clipboard_event = ClipboardEvent::new(
1327            &self.window,
1328            None,
1329            DOMString::from(clipboard_event_type.as_str()),
1330            EventBubbles::Bubbles,
1331            EventCancelable::Cancelable,
1332            None,
1333            can_gc,
1334        );
1335        self.fire_clipboard_event(&clipboard_event, clipboard_event_type, can_gc);
1336
1337        // Step 3 If a script doesn't call preventDefault()
1338        // the event will be handled inside target's VirtualMethods::handle_event
1339        let event = clipboard_event.upcast::<Event>();
1340        if !event.IsTrusted() {
1341            return event.flags().into();
1342        }
1343
1344        // Step 4 If the event was canceled, then
1345        if event.DefaultPrevented() {
1346            let event_type = event.Type();
1347            match_domstring_ascii!(event_type,
1348
1349                "copy" => {
1350                    // Step 4.1 Call the write content to the clipboard algorithm,
1351                    // passing on the DataTransferItemList items, a clear-was-called flag and a types-to-clear list.
1352                    if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1353                        let drag_data_store =
1354                            clipboard_data.data_store().expect("This shouldn't fail");
1355                        self.write_content_to_the_clipboard(&drag_data_store);
1356                    }
1357                },
1358                "cut" => {
1359                    // Step 4.1 Call the write content to the clipboard algorithm,
1360                    // passing on the DataTransferItemList items, a clear-was-called flag and a types-to-clear list.
1361                    if let Some(clipboard_data) = clipboard_event.get_clipboard_data() {
1362                        let drag_data_store =
1363                            clipboard_data.data_store().expect("This shouldn't fail");
1364                        self.write_content_to_the_clipboard(&drag_data_store);
1365                    }
1366
1367                    // Step 4.2 Fire a clipboard event named clipboardchange
1368                    self.fire_clipboardchange_event(can_gc);
1369                },
1370                // Step 4.1 Return false.
1371                // Note: This function deviates from the specification a bit by returning
1372                // the `InputEventResult` below.
1373                "paste" => (),
1374                _ => (),
1375            )
1376        }
1377
1378        // Step 5: Return true from the action.
1379        // In this case we are returning the `InputEventResult` instead of true or false.
1380        event.flags().into()
1381    }
1382
1383    /// <https://www.w3.org/TR/clipboard-apis/#fire-a-clipboard-event>
1384    fn fire_clipboard_event(
1385        &self,
1386        event: &ClipboardEvent,
1387        action: ClipboardEventType,
1388        can_gc: CanGc,
1389    ) {
1390        // Step 1 Let clear_was_called be false
1391        // Step 2 Let types_to_clear an empty list
1392        let mut drag_data_store = DragDataStore::new();
1393
1394        // Step 4 let clipboard-entry be the sequence number of clipboard content, null if the OS doesn't support it.
1395
1396        // Step 5 let trusted be true if the event is generated by the user agent, false otherwise
1397        let trusted = true;
1398
1399        // Step 6 if the context is editable:
1400        let document = self.window.Document();
1401        let focused = document.get_focused_element();
1402        let body = document.GetBody();
1403
1404        let target = match (&focused, &body) {
1405            (Some(focused), _) => focused.upcast(),
1406            (&None, Some(body)) => body.upcast(),
1407            (&None, &None) => self.window.upcast(),
1408        };
1409        // Step 6.2 else TODO require Selection see https://github.com/w3c/clipboard-apis/issues/70
1410
1411        // Step 7
1412        match action {
1413            ClipboardEventType::Copy | ClipboardEventType::Cut => {
1414                // Step 7.2.1
1415                drag_data_store.set_mode(Mode::ReadWrite);
1416            },
1417            ClipboardEventType::Paste => {
1418                let (sender, receiver) = ipc::channel().unwrap();
1419                self.window.send_to_embedder(EmbedderMsg::GetClipboardText(
1420                    self.window.webview_id(),
1421                    sender,
1422                ));
1423                let text_contents = receiver
1424                    .recv()
1425                    .map(Result::unwrap_or_default)
1426                    .unwrap_or_default();
1427
1428                // Step 7.1.1
1429                drag_data_store.set_mode(Mode::ReadOnly);
1430                // Step 7.1.2 If trusted or the implementation gives script-generated events access to the clipboard
1431                if trusted {
1432                    // Step 7.1.2.1 For each clipboard-part on the OS clipboard:
1433
1434                    // Step 7.1.2.1.1 If clipboard-part contains plain text, then
1435                    let data = DOMString::from(text_contents.to_string());
1436                    let type_ = DOMString::from("text/plain");
1437                    let _ = drag_data_store.add(Kind::Text { data, type_ });
1438
1439                    // Step 7.1.2.1.2 TODO If clipboard-part represents file references, then for each file reference
1440                    // Step 7.1.2.1.3 TODO If clipboard-part contains HTML- or XHTML-formatted text then
1441
1442                    // Step 7.1.3 Update clipboard-event-data’s files to match clipboard-event-data’s items
1443                    // Step 7.1.4 Update clipboard-event-data’s types to match clipboard-event-data’s items
1444                }
1445            },
1446            ClipboardEventType::Change => (),
1447        }
1448
1449        // Step 3
1450        let clipboard_event_data = DataTransfer::new(
1451            &self.window,
1452            Rc::new(RefCell::new(Some(drag_data_store))),
1453            can_gc,
1454        );
1455
1456        // Step 8
1457        event.set_clipboard_data(Some(&clipboard_event_data));
1458        let event = event.upcast::<Event>();
1459        // Step 9
1460        event.set_trusted(trusted);
1461        // Step 10 Set event’s composed to true.
1462        event.set_composed(true);
1463        // Step 11
1464        event.dispatch(target, false, can_gc);
1465    }
1466
1467    pub(crate) fn fire_clipboardchange_event(&self, can_gc: CanGc) {
1468        let clipboardchange_event = ClipboardEvent::new(
1469            &self.window,
1470            None,
1471            DOMString::from("clipboardchange"),
1472            EventBubbles::Bubbles,
1473            EventCancelable::Cancelable,
1474            None,
1475            can_gc,
1476        );
1477        self.fire_clipboard_event(&clipboardchange_event, ClipboardEventType::Change, can_gc);
1478    }
1479
1480    /// <https://www.w3.org/TR/clipboard-apis/#write-content-to-the-clipboard>
1481    fn write_content_to_the_clipboard(&self, drag_data_store: &DragDataStore) {
1482        // Step 1
1483        if drag_data_store.list_len() > 0 {
1484            // Step 1.1 Clear the clipboard.
1485            self.window
1486                .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1487            // Step 1.2
1488            for item in drag_data_store.iter_item_list() {
1489                match item {
1490                    Kind::Text { data, .. } => {
1491                        // Step 1.2.1.1 Ensure encoding is correct per OS and locale conventions
1492                        // Step 1.2.1.2 Normalize line endings according to platform conventions
1493                        // Step 1.2.1.3
1494                        self.window.send_to_embedder(EmbedderMsg::SetClipboardText(
1495                            self.window.webview_id(),
1496                            data.to_string(),
1497                        ));
1498                    },
1499                    Kind::File { .. } => {
1500                        // Step 1.2.2 If data is of a type listed in the mandatory data types list, then
1501                        // Step 1.2.2.1 Place part on clipboard with the appropriate OS clipboard format description
1502                        // Step 1.2.3 Else this is left to the implementation
1503                    },
1504                }
1505            }
1506        } else {
1507            // Step 2.1
1508            if drag_data_store.clear_was_called {
1509                // Step 2.1.1 If types-to-clear list is empty, clear the clipboard
1510                self.window
1511                    .send_to_embedder(EmbedderMsg::ClearClipboard(self.window.webview_id()));
1512                // Step 2.1.2 Else remove the types in the list from the clipboard
1513                // As of now this can't be done with Arboard, and it's possible that will be removed from the spec
1514            }
1515        }
1516    }
1517
1518    /// Handle scroll event triggered by user interactions from embedder side.
1519    /// <https://drafts.csswg.org/cssom-view/#scrolling-events>
1520    #[allow(unsafe_code)]
1521    fn handle_embedder_scroll_event(&self, event: ScrollEvent) {
1522        // If it is a viewport scroll.
1523        let document = self.window.Document();
1524        if event.external_id.is_root() {
1525            document.handle_viewport_scroll_event();
1526        } else {
1527            // Otherwise, check whether it is for a relevant element within the document.
1528            let Some(node_id) = node_id_from_scroll_id(event.external_id.0 as usize) else {
1529                return;
1530            };
1531            let node = unsafe {
1532                node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id))
1533            };
1534            let Some(element) = node
1535                .inclusive_ancestors(ShadowIncluding::No)
1536                .filter_map(DomRoot::downcast::<Element>)
1537                .next()
1538            else {
1539                return;
1540            };
1541
1542            document.handle_element_scroll_event(&element);
1543        }
1544    }
1545
1546    pub(crate) fn run_default_keyboard_event_handler(&self, event: &KeyboardEvent) {
1547        if event.upcast::<Event>().type_() != atom!("keydown") {
1548            return;
1549        }
1550        if !event.modifiers().is_empty() {
1551            return;
1552        }
1553        let scroll = match event.key() {
1554            Key::Named(NamedKey::ArrowDown) => KeyboardScroll::Down,
1555            Key::Named(NamedKey::ArrowLeft) => KeyboardScroll::Left,
1556            Key::Named(NamedKey::ArrowRight) => KeyboardScroll::Right,
1557            Key::Named(NamedKey::ArrowUp) => KeyboardScroll::Up,
1558            Key::Named(NamedKey::End) => KeyboardScroll::End,
1559            Key::Named(NamedKey::Home) => KeyboardScroll::Home,
1560            Key::Named(NamedKey::PageDown) => KeyboardScroll::PageDown,
1561            Key::Named(NamedKey::PageUp) => KeyboardScroll::PageUp,
1562            _ => return,
1563        };
1564        self.do_keyboard_scroll(scroll);
1565    }
1566
1567    pub(crate) fn do_keyboard_scroll(&self, scroll: KeyboardScroll) {
1568        let scroll_axis = match scroll {
1569            KeyboardScroll::Left | KeyboardScroll::Right => ScrollingBoxAxis::X,
1570            _ => ScrollingBoxAxis::Y,
1571        };
1572
1573        let document = self.window.Document();
1574        let mut scrolling_box = document
1575            .get_focused_element()
1576            .or(self.most_recently_clicked_element.get())
1577            .and_then(|element| element.scrolling_box(ScrollContainerQueryFlags::Inclusive))
1578            .unwrap_or_else(|| {
1579                document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
1580            });
1581
1582        while !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
1583            // Always fall back to trying to scroll the entire document.
1584            if scrolling_box.is_viewport() {
1585                break;
1586            }
1587            let parent = scrolling_box.parent().unwrap_or_else(|| {
1588                document.viewport_scrolling_box(ScrollContainerQueryFlags::Inclusive)
1589            });
1590            scrolling_box = parent;
1591        }
1592
1593        let calculate_current_scroll_offset_and_delta = || {
1594            const LINE_HEIGHT: f32 = 76.0;
1595            const LINE_WIDTH: f32 = 76.0;
1596
1597            let current_scroll_offset = scrolling_box.scroll_position();
1598            (
1599                current_scroll_offset,
1600                match scroll {
1601                    KeyboardScroll::Home => Vector2D::new(0.0, -current_scroll_offset.y),
1602                    KeyboardScroll::End => Vector2D::new(
1603                        0.0,
1604                        -current_scroll_offset.y + scrolling_box.content_size().height -
1605                            scrolling_box.size().height,
1606                    ),
1607                    KeyboardScroll::PageDown => {
1608                        Vector2D::new(0.0, scrolling_box.size().height - 2.0 * LINE_HEIGHT)
1609                    },
1610                    KeyboardScroll::PageUp => {
1611                        Vector2D::new(0.0, 2.0 * LINE_HEIGHT - scrolling_box.size().height)
1612                    },
1613                    KeyboardScroll::Up => Vector2D::new(0.0, -LINE_HEIGHT),
1614                    KeyboardScroll::Down => Vector2D::new(0.0, LINE_HEIGHT),
1615                    KeyboardScroll::Left => Vector2D::new(-LINE_WIDTH, 0.0),
1616                    KeyboardScroll::Right => Vector2D::new(LINE_WIDTH, 0.0),
1617                },
1618            )
1619        };
1620
1621        // If trying to scroll the viewport of this `Window` and this is the root `Document`
1622        // of the `WebView`, then send the srolling operation to the renderer, so that it
1623        // can properly pan any pinch zoom viewport.
1624        let parent_pipeline = self.window.parent_info();
1625        if scrolling_box.is_viewport() && parent_pipeline.is_none() {
1626            let (_, delta) = calculate_current_scroll_offset_and_delta();
1627            self.window
1628                .compositor_api()
1629                .scroll_viewport_by_delta(self.window.webview_id(), delta);
1630        }
1631
1632        // If this is the viewport and we cannot scroll, try to ask a parent viewport to scroll,
1633        // if we are inside an `<iframe>`.
1634        if !scrolling_box.can_keyboard_scroll_in_axis(scroll_axis) {
1635            assert!(scrolling_box.is_viewport());
1636
1637            let window_proxy = document.window().window_proxy();
1638            if let Some(iframe) = window_proxy.frame_element() {
1639                // When the `<iframe>` is local (in this ScriptThread), we can
1640                // synchronously chain up the keyboard scrolling event.
1641                let cx = GlobalScope::get_cx();
1642                let iframe_window = iframe.owner_window();
1643                let _ac = JSAutoRealm::new(*cx, iframe_window.reflector().get_jsobject().get());
1644                iframe_window
1645                    .Document()
1646                    .event_handler()
1647                    .do_keyboard_scroll(scroll);
1648            } else if let Some(parent_pipeline) = parent_pipeline {
1649                // Otherwise, if we have a parent (presumably from a different origin)
1650                // asynchronously ask the Constellation to forward the event to the parent
1651                // pipeline, if we have one.
1652                document.window().send_to_constellation(
1653                    ScriptToConstellationMessage::ForwardKeyboardScroll(parent_pipeline, scroll),
1654                );
1655            };
1656            return;
1657        }
1658
1659        let (current_scroll_offset, delta) = calculate_current_scroll_offset_and_delta();
1660        scrolling_box.scroll_to(delta + current_scroll_offset, ScrollBehavior::Auto);
1661    }
1662}