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