/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::array::from_ref; use std::cell::{Cell, RefCell}; use std::f64::consts::PI; use std::mem; use std::rc::Rc; use std::time::{Duration, Instant}; use base::generic_channel; use constellation_traits::{KeyboardScroll, ScriptToConstellationMessage}; use embedder_traits::{ Cursor, EditingActionEvent, EmbedderMsg, GamepadEvent as EmbedderGamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType, ImeEvent, InputEvent, KeyboardEvent as EmbedderKeyboardEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseLeftViewportEvent, ScrollEvent, TouchEvent as EmbedderTouchEvent, TouchEventType, TouchId, UntrustedNodeAddress, WheelEvent as EmbedderWheelEvent, }; use euclid::{Point2D, Vector2D}; use ipc_channel::ipc; use js::jsapi::JSAutoRealm; use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey}; use layout_api::{ScrollContainerQueryFlags, node_id_from_scroll_id}; use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods; use script_bindings::codegen::GenericBindings::EventBinding::EventMethods; use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods; use script_bindings::codegen::GenericBindings::NodeBinding::NodeMethods; use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods; use script_bindings::codegen::GenericBindings::TouchBinding::TouchMethods; use script_bindings::codegen::GenericBindings::WindowBinding::{ScrollBehavior, WindowMethods}; use script_bindings::inheritance::Castable; use script_bindings::num::Finite; use script_bindings::reflector::DomObject; use script_bindings::root::{Dom, DomRoot, DomSlice}; use script_bindings::script_runtime::CanGc; use script_bindings::str::DOMString; use script_traits::ConstellationInputEvent; use servo_config::pref; use style_traits::CSSPixel; use xml5ever::{local_name, ns}; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::root::MutNullableDom; use crate::dom::clipboardevent::ClipboardEventType; use crate::dom::document::{FireMouseEventType, FocusInitiator, TouchEventResult}; use crate::dom::event::{EventBubbles, EventCancelable, EventComposed, EventDefault}; use crate::dom::gamepad::gamepad::{Gamepad, contains_user_gesture}; use crate::dom::gamepad::gamepadevent::GamepadEventType; use crate::dom::inputevent::HitTestResult; use crate::dom::node::{self, Node, NodeTraits, ShadowIncluding}; use crate::dom::pointerevent::PointerId; use crate::dom::scrolling_box::ScrollingBoxAxis; use crate::dom::types::{ ClipboardEvent, CompositionEvent, DataTransfer, Element, Event, EventTarget, GlobalScope, HTMLAnchorElement, KeyboardEvent, MouseEvent, PointerEvent, Touch, TouchEvent, TouchList, WheelEvent, Window, }; use crate::drag_data_store::{DragDataStore, Kind, Mode}; use crate::realms::enter_realm; /// The [`DocumentEventHandler`] is a structure responsible for handling input events for /// the [`crate::Document`] and storing data related to event handling. It exists to /// decrease the size of the [`crate::Document`] structure. #[derive(JSTraceable, MallocSizeOf)] #[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] pub(crate) struct DocumentEventHandler { /// The [`Window`] element for this [`DocumentEventHandler`]. window: Dom, /// Pending input events, to be handled at the next rendering opportunity. #[no_trace] #[ignore_malloc_size_of = "CompositorEvent contains data from outside crates"] pending_input_events: DomRefCell>, /// The index of the last mouse move event in the pending compositor events queue. mouse_move_event_index: DomRefCell>, /// #[ignore_malloc_size_of = "Defined in std"] #[no_trace] last_click_info: DomRefCell)>>, /// The element that is currently hovered by the cursor. current_hover_target: MutNullableDom, /// The element that was most recently clicked. most_recently_clicked_element: MutNullableDom, /// The most recent mouse movement point, used for processing `mouseleave` events. #[no_trace] most_recent_mousemove_point: Cell>>, /// The currently set [`Cursor`] or `None` if the `Document` isn't being hovered /// by the cursor. #[no_trace] current_cursor: Cell>, /// active_touch_points: DomRefCell>>, /// The active keyboard modifiers for the WebView. This is updated when receiving any input event. #[no_trace] active_keyboard_modifiers: Cell, } impl DocumentEventHandler { pub(crate) fn new(window: &Window) -> Self { Self { window: Dom::from_ref(window), pending_input_events: Default::default(), mouse_move_event_index: Default::default(), last_click_info: Default::default(), current_hover_target: Default::default(), most_recently_clicked_element: Default::default(), most_recent_mousemove_point: Default::default(), current_cursor: Default::default(), active_touch_points: Default::default(), active_keyboard_modifiers: Default::default(), } } /// Note a pending compositor event, to be processed at the next `update_the_rendering` task. pub(crate) fn note_pending_input_event(&self, event: ConstellationInputEvent) { let mut pending_compositor_events = self.pending_input_events.borrow_mut(); if matches!(event.event, InputEvent::MouseMove(..)) { // First try to replace any existing mouse move event. if let Some(mouse_move_event) = self .mouse_move_event_index .borrow() .and_then(|index| pending_compositor_events.get_mut(index)) { *mouse_move_event = event; return; } *self.mouse_move_event_index.borrow_mut() = Some(pending_compositor_events.len()); } pending_compositor_events.push(event); } /// Whether or not this [`Document`] has any pending input events to be processed during /// "update the rendering." pub(crate) fn has_pending_input_events(&self) -> bool { !self.pending_input_events.borrow().is_empty() } pub(crate) fn alternate_action_keyboard_modifier_active(&self) -> bool { #[cfg(target_os = "macos")] { self.active_keyboard_modifiers .get() .contains(Modifiers::META) } #[cfg(not(target_os = "macos"))] { self.active_keyboard_modifiers .get() .contains(Modifiers::CONTROL) } } pub(crate) fn handle_pending_input_events(&self, can_gc: CanGc) { let _realm = enter_realm(&*self.window); // Reset the mouse event index. *self.mouse_move_event_index.borrow_mut() = None; let pending_input_events = mem::take(&mut *self.pending_input_events.borrow_mut()); for event in pending_input_events { self.active_keyboard_modifiers .set(event.active_keyboard_modifiers); match event.event.clone() { InputEvent::MouseButton(mouse_button_event) => { self.handle_native_mouse_button_event(mouse_button_event, &event, can_gc); }, InputEvent::MouseMove(_) => { self.handle_native_mouse_move_event(&event, can_gc); }, InputEvent::MouseLeftViewport(mouse_leave_event) => { self.handle_mouse_left_viewport_event(&event, &mouse_leave_event, can_gc); }, InputEvent::Touch(touch_event) => { self.handle_touch_event(touch_event, &event, can_gc); }, InputEvent::Wheel(wheel_event) => { self.handle_wheel_event(wheel_event, &event, can_gc); }, InputEvent::Keyboard(keyboard_event) => { self.handle_keyboard_event(keyboard_event, can_gc); }, InputEvent::Ime(ime_event) => { self.handle_ime_event(ime_event, can_gc); }, InputEvent::Gamepad(gamepad_event) => { self.handle_gamepad_event(gamepad_event); }, InputEvent::EditingAction(editing_action_event) => { self.handle_editing_action(editing_action_event, can_gc); }, InputEvent::Scroll(scroll_event) => { self.handle_embedder_scroll_event(scroll_event); }, } self.notify_webdriver_input_event_completed(event.event); } } fn notify_webdriver_input_event_completed(&self, event: InputEvent) { let Some(id) = event.webdriver_message_id() else { return; }; // Webdriver should be notified once all current dom events have been processed. let trusted_window = Trusted::new(&*self.window); self.window .as_global_scope() .task_manager() .dom_manipulation_task_source() .queue(task!(notify_webdriver_input_event_completed: move || { let window = trusted_window.root(); window.send_to_constellation(ScriptToConstellationMessage::WebDriverInputComplete(id)); })); } pub(crate) fn set_cursor(&self, cursor: Option) { if cursor == self.current_cursor.get() { return; } self.current_cursor.set(cursor); self.window.send_to_embedder(EmbedderMsg::SetCursor( self.window.webview_id(), cursor.unwrap_or_default(), )); } fn handle_mouse_left_viewport_event( &self, input_event: &ConstellationInputEvent, mouse_leave_event: &MouseLeftViewportEvent, can_gc: CanGc, ) { if let Some(current_hover_target) = self.current_hover_target.get() { let current_hover_target = current_hover_target.upcast::(); for element in current_hover_target .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::) { element.set_hover_state(false); element.set_active_state(false); } if let Some(hit_test_result) = self .most_recent_mousemove_point .get() .and_then(|point| self.window.hit_test_from_point_in_viewport(point)) { MouseEvent::new_simple( &self.window, FireMouseEventType::Out, EventBubbles::Bubbles, EventCancelable::Cancelable, &hit_test_result, input_event, can_gc, ) .upcast::() .fire(current_hover_target.upcast(), can_gc); self.handle_mouse_enter_leave_event( DomRoot::from_ref(current_hover_target), None, FireMouseEventType::Leave, &hit_test_result, input_event, can_gc, ); } } // We do not want to always inform the embedder that cursor has been set to the // default cursor, in order to avoid a timing issue when moving between `