diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index b0e91ccb02e..11da7251568 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -12,8 +12,8 @@ use compositing_traits::{SendableFrameTree, WebViewTrait}; use constellation_traits::{EmbedderToConstellationMessage, ScrollState, WindowSizeType}; use embedder_traits::{ AnimationState, CompositorHitTestResult, InputEvent, MouseButton, MouseButtonAction, - MouseButtonEvent, MouseMoveEvent, ShutdownState, TouchEvent, TouchEventResult, TouchEventType, - TouchId, ViewportDetails, + MouseButtonEvent, MouseMoveEvent, ScrollEvent as EmbedderScrollEvent, ShutdownState, + TouchEvent, TouchEventResult, TouchEventType, TouchId, ViewportDetails, }; use euclid::{Box2D, Point2D, Scale, Size2D, Vector2D}; use fnv::FnvHashSet; @@ -51,9 +51,9 @@ enum ScrollZoomEvent { Scroll(ScrollEvent), } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Debug)] pub(crate) struct ScrollResult { - pub pipeline_id: PipelineId, + pub hit_test_result: CompositorHitTestResult, pub external_scroll_id: ExternalScrollId, pub offset: LayoutVector2D, } @@ -813,8 +813,14 @@ impl WebViewRenderer { combined_event.scroll_location, ) }); - if let Some(scroll_result) = scroll_result { - self.send_scroll_positions_to_layout_for_pipeline(scroll_result.pipeline_id); + if let Some(scroll_result) = scroll_result.clone() { + self.send_scroll_positions_to_layout_for_pipeline( + scroll_result.hit_test_result.pipeline_id, + ); + self.dispatch_scroll_event( + scroll_result.external_scroll_id, + scroll_result.hit_test_result, + ); } let pinch_zoom_result = match self @@ -829,8 +835,8 @@ impl WebViewRenderer { /// Perform a hit test at the given [`DevicePoint`] and apply the [`ScrollLocation`] /// scrolling to the applicable scroll node under that point. If a scroll was - /// performed, returns the [`PipelineId`] of the node scrolled, the id, and the final - /// scroll delta. + /// performed, returns the hit test result contains [`PipelineId`] of the node + /// scrolled, the id, and the final scroll delta. fn scroll_node_at_device_point( &mut self, cursor: DevicePoint, @@ -864,20 +870,17 @@ impl WebViewRenderer { // This is needed to propagate the scroll events from a pipeline representing an iframe to // its ancestor pipelines. let mut previous_pipeline_id = None; - for CompositorHitTestResult { - pipeline_id, - scroll_tree_node, - .. - } in hit_test_results.iter() - { - let pipeline_details = self.pipelines.get_mut(pipeline_id)?; - if previous_pipeline_id.replace(pipeline_id) != Some(pipeline_id) { + for hit_test_result in hit_test_results.iter() { + let pipeline_details = self.pipelines.get_mut(&hit_test_result.pipeline_id)?; + if previous_pipeline_id.replace(&hit_test_result.pipeline_id) != + Some(&hit_test_result.pipeline_id) + { let scroll_result = pipeline_details .scroll_tree - .scroll_node_or_ancestor(scroll_tree_node, scroll_location); + .scroll_node_or_ancestor(&hit_test_result.scroll_tree_node, scroll_location); if let Some((external_scroll_id, offset)) = scroll_result { return Some(ScrollResult { - pipeline_id: *pipeline_id, + hit_test_result: hit_test_result.clone(), external_scroll_id, offset, }); @@ -887,6 +890,22 @@ impl WebViewRenderer { None } + fn dispatch_scroll_event( + &self, + external_id: ExternalScrollId, + hit_test_result: CompositorHitTestResult, + ) { + let event = InputEvent::Scroll(EmbedderScrollEvent { external_id }); + let msg = EmbedderToConstellationMessage::ForwardInputEvent( + self.id, + event, + Some(hit_test_result), + ); + if let Err(e) = self.global.borrow().constellation_sender.send(msg) { + warn!("Sending scroll event to constellation failed ({:?}).", e); + } + } + pub(crate) fn pinch_zoom_level(&self) -> Scale { Scale::new(self.viewport_zoom.get()) } diff --git a/components/constellation/tracing.rs b/components/constellation/tracing.rs index 99551bd58fc..640341d1eef 100644 --- a/components/constellation/tracing.rs +++ b/components/constellation/tracing.rs @@ -99,6 +99,7 @@ mod from_compositor { InputEvent::MouseMove(..) => target_variant!("MouseMove"), InputEvent::Touch(..) => target_variant!("Touch"), InputEvent::Wheel(..) => target_variant!("Wheel"), + InputEvent::Scroll(..) => target_variant!("Scroll"), } } } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 68efa07537c..4a037767285 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -31,7 +31,8 @@ use dom_struct::dom_struct; use embedder_traits::{ AllowOrDeny, AnimationState, CompositorHitTestResult, ContextMenuResult, EditingActionEvent, EmbedderMsg, FocusSequenceNumber, ImeEvent, InputEvent, LoadStatus, MouseButton, - MouseButtonAction, MouseButtonEvent, TouchEvent, TouchEventType, TouchId, WheelEvent, + MouseButtonAction, MouseButtonEvent, ScrollEvent, TouchEvent, TouchEventType, TouchId, + UntrustedNodeAddress, WheelEvent, }; use encoding_rs::{Encoding, UTF_8}; use euclid::default::{Point2D, Rect, Size2D}; @@ -54,7 +55,7 @@ use profile_traits::ipc as profile_ipc; use profile_traits::time::TimerMetadataFrameType; use regex::bytes::Regex; use script_bindings::interfaces::DocumentHelpers; -use script_layout_interface::{PendingRestyle, TrustedNodeAddress}; +use script_layout_interface::{PendingRestyle, TrustedNodeAddress, node_id_from_scroll_id}; use script_traits::{ConstellationInputEvent, DocumentActivity, ProgressiveWebMetricType}; use servo_arc::Arc; use servo_config::pref; @@ -2340,6 +2341,40 @@ impl Document { } } + #[allow(unsafe_code)] + pub(crate) fn handle_scroll_event(&self, event: ScrollEvent, can_gc: CanGc) { + // + // If target is a Document, fire an event named scroll that bubbles at target. + if event.external_id.is_root() { + let Some(document) = self + .node + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::) + .next() + else { + return; + }; + DomRoot::upcast::(document) + .fire_bubbling_event(Atom::from("scroll"), can_gc); + } else { + // Otherwise, fire an event named scroll at target. + let Some(node_id) = node_id_from_scroll_id(event.external_id.0 as usize) else { + return; + }; + let node = unsafe { + node::from_untrusted_node_address(UntrustedNodeAddress::from_id(node_id)) + }; + let Some(element) = node + .inclusive_ancestors(ShadowIncluding::No) + .filter_map(DomRoot::downcast::) + .next() + else { + return; + }; + DomRoot::upcast::(element).fire_event(Atom::from("scroll"), can_gc); + } + } + /// The entry point for all key processing for web content pub(crate) fn dispatch_key_event( &self, diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 87c66b5655a..9260e73b077 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -1152,6 +1152,9 @@ impl ScriptThread { InputEvent::EditingAction(editing_action_event) => { document.handle_editing_action(editing_action_event, can_gc); }, + InputEvent::Scroll(scroll_event) => { + document.handle_scroll_event(scroll_event, can_gc); + }, } } ScriptThread::set_user_interacting(false); diff --git a/components/shared/embedder/input_events.rs b/components/shared/embedder/input_events.rs index ff48bd7b7d8..71e82d3c5b6 100644 --- a/components/shared/embedder/input_events.rs +++ b/components/shared/embedder/input_events.rs @@ -6,6 +6,7 @@ use keyboard_types::{CompositionEvent, KeyboardEvent}; use log::error; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; +use webrender_api::ExternalScrollId; use webrender_api::units::DevicePoint; use crate::WebDriverMessageId; @@ -21,6 +22,7 @@ pub enum InputEvent { MouseMove(MouseMoveEvent), Touch(TouchEvent), Wheel(WheelEvent), + Scroll(ScrollEvent), } /// An editing action that should be performed on a `WebView`. @@ -42,6 +44,7 @@ impl InputEvent { InputEvent::MouseMove(event) => Some(event.point), InputEvent::Touch(event) => Some(event.point), InputEvent::Wheel(event) => Some(event.point), + InputEvent::Scroll(..) => None, } } @@ -55,6 +58,7 @@ impl InputEvent { InputEvent::MouseMove(event) => event.webdriver_id, InputEvent::Touch(..) => None, InputEvent::Wheel(..) => None, + InputEvent::Scroll(..) => None, } } @@ -72,6 +76,7 @@ impl InputEvent { }, InputEvent::Touch(..) => {}, InputEvent::Wheel(..) => {}, + InputEvent::Scroll(..) => {}, }; self @@ -277,6 +282,11 @@ pub struct WheelEvent { pub point: DevicePoint, } +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +pub struct ScrollEvent { + pub external_id: ExternalScrollId, +} + #[derive(Clone, Debug, Deserialize, Serialize)] pub enum ImeEvent { Composition(CompositionEvent),