Implement scroll event listener

Signed-off-by: PotatoCP <kenzieradityatirtarahardja.18@gmail.com>
Co-authored-by: Asun0204 <asun0204@163.com>
This commit is contained in:
PotatoCP 2025-04-25 14:55:39 +08:00
parent 7f0cebd442
commit 2d3fa482d7
5 changed files with 88 additions and 20 deletions

View file

@ -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()
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 pipeline_details = self.pipelines.get_mut(pipeline_id)?;
if previous_pipeline_id.replace(pipeline_id) != Some(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<f32, DevicePixel, DevicePixel> {
Scale::new(self.viewport_zoom.get())
}

View file

@ -98,6 +98,7 @@ mod from_compositor {
InputEvent::MouseMove(..) => target_variant!("MouseMove"),
InputEvent::Touch(..) => target_variant!("Touch"),
InputEvent::Wheel(..) => target_variant!("Wheel"),
InputEvent::Scroll(..) => target_variant!("Scroll"),
}
}
}

View file

@ -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;
@ -2337,6 +2338,40 @@ impl Document {
}
}
#[allow(unsafe_code)]
pub(crate) fn handle_scroll_event(&self, event: ScrollEvent, can_gc: CanGc) {
// <https://drafts.csswg.org/cssom-view/#scrolling-events>
// 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::<Document>)
.next()
else {
return;
};
DomRoot::upcast::<EventTarget>(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::<Element>)
.next()
else {
return;
};
DomRoot::upcast::<EventTarget>(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,

View file

@ -1143,6 +1143,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);

View file

@ -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),