script: Batch scroll event firing (#38222)

The user interaction triggered `scroll` event was initially implemented
in https://github.com/servo/servo/pull/36687.

In triggering the `scroll` event, the event should be batched and
triggered within update the rendering. This serves as a preparatory PR
for the JS API triggered `scroll` event.

Part of: #31665

---------

Signed-off-by: Jo Steven Novaryo <jo.steven.novaryo@huawei.com>
This commit is contained in:
Jo Steven Novaryo 2025-07-25 01:48:12 +08:00 committed by GitHub
parent eeac336518
commit 1fb782bc38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 107 additions and 11 deletions

View file

@ -572,6 +572,8 @@ pub(crate) struct Document {
/// Cached frozen array of [`Self::adopted_stylesheets`]
#[ignore_malloc_size_of = "mozjs"]
adopted_stylesheets_frozen_types: CachedFrozenArray,
/// <https://drafts.csswg.org/cssom-view/#document-pending-scroll-event-targets>
pending_scroll_event_targets: DomRefCell<Vec<Dom<EventTarget>>>,
}
#[allow(non_snake_case)]
@ -2402,10 +2404,102 @@ impl Document {
}
}
/// <https://drafts.csswg.org/cssom-view/#document-run-the-scroll-steps>
pub(crate) fn run_the_scroll_steps(&self, can_gc: CanGc) {
// Step 1.
// > Run the steps to dispatch pending scrollsnapchanging events for doc.
// TODO(#7673): Implement scroll snapping
// Step 2
// > For each item target in docs pending scroll event targets, in the order they
// > were added to the list, run these substeps:
for target in self.pending_scroll_event_targets.borrow().iter() {
// Step 2.1
// > If target is a Document, fire an event named scroll that bubbles at target.
if target.downcast::<Document>().is_some() {
target.fire_bubbling_event(Atom::from("scroll"), can_gc);
}
// Step 2.2
// > Otherwise, fire an event named scroll at target.
if target.downcast::<Element>().is_some() {
target.fire_event(Atom::from("scroll"), can_gc);
}
}
// Step 3.
// > Empty docs pending scroll event targets.
self.pending_scroll_event_targets.borrow_mut().clear();
// Step 4.
// > Run the steps to dispatch pending scrollsnapchange events for doc.
// TODO(#7673): Implement scroll snapping
}
/// Whenever a viewport gets scrolled (whether in response to user interaction or by an
/// API), the user agent must run these steps:
/// <https://drafts.csswg.org/cssom-view/#scrolling-events>
pub(crate) fn handle_viewport_scroll_event(&self) {
// Step 2.
// > If doc is a snap container, run the steps to update scrollsnapchanging targets
// > for doc with docs eventual snap target in the block axis as newBlockTarget and
// > docs eventual snap target in the inline axis as newInlineTarget.
// TODO(#7673): Implement scroll snapping
// Step 3.
// > If doc is already in docs pending scroll event targets, abort these steps.
let target = self.upcast::<EventTarget>();
if self
.pending_scroll_event_targets
.borrow()
.iter()
.any(|other_target| *other_target == target)
{
return;
}
// Step 4.
// > Append doc to docs pending scroll event targets.
self.pending_scroll_event_targets
.borrow_mut()
.push(Dom::from_ref(target));
}
/// Whenever an element gets scrolled (whether in response to user interaction or by an
/// API), the user agent must run these steps:
/// <https://drafts.csswg.org/cssom-view/#scrolling-events>
pub(crate) fn handle_element_scroll_event(&self, element: &Element) {
// Step 2.
// > If the element is a snap container, run the steps to update scrollsnapchanging
// > targets for the element with the elements eventual snap target in the block
// > axis as newBlockTarget and the elements eventual snap target in the inline axis
// > as newInlineTarget.
// TODO(#7673): Implement scroll snapping
// Step 3.
// > If the element is already in docs pending scroll event targets, abort these steps.
let target = element.upcast::<EventTarget>();
if self
.pending_scroll_event_targets
.borrow()
.iter()
.any(|other_target| *other_target == target)
{
return;
}
// Step 4.
// > Append the element to docs pending scroll event targets.
self.pending_scroll_event_targets
.borrow_mut()
.push(Dom::from_ref(target));
}
/// Handle scroll event triggered by user interactions from embedder side.
/// <https://drafts.csswg.org/cssom-view/#scrolling-events>
#[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.
pub(crate) fn handle_embedder_scroll_event(&self, event: ScrollEvent) {
// If it is a viewport scroll.
if event.external_id.is_root() {
let Some(document) = self
.node
@ -2415,10 +2509,10 @@ impl Document {
else {
return;
};
DomRoot::upcast::<EventTarget>(document)
.fire_bubbling_event(Atom::from("scroll"), can_gc);
document.handle_viewport_scroll_event();
} else {
// Otherwise, fire an event named scroll at target.
// Otherwise, check whether it is for a relevant element within the document.
let Some(node_id) = node_id_from_scroll_id(event.external_id.0 as usize) else {
return;
};
@ -2432,7 +2526,8 @@ impl Document {
else {
return;
};
DomRoot::upcast::<EventTarget>(element).fire_event(Atom::from("scroll"), can_gc);
self.handle_element_scroll_event(&element);
}
}
@ -4266,6 +4361,7 @@ impl Document {
highlighted_dom_node: Default::default(),
adopted_stylesheets: Default::default(),
adopted_stylesheets_frozen_types: CachedFrozenArray::new(),
pending_scroll_event_targets: Default::default(),
}
}

View file

@ -1148,7 +1148,7 @@ impl ScriptThread {
document.handle_editing_action(editing_action_event, can_gc);
},
InputEvent::Scroll(scroll_event) => {
document.handle_scroll_event(scroll_event, can_gc);
document.handle_embedder_scroll_event(scroll_event);
},
}
}
@ -1254,9 +1254,6 @@ impl ScriptThread {
// https://html.spec.whatwg.org/multipage/#flush-autofocus-candidates.
self.process_pending_input_events(*pipeline_id, can_gc);
// TODO(#31665): Implement the "run the scroll steps" from
// https://drafts.csswg.org/cssom-view/#document-run-the-scroll-steps.
// > 8. For each doc of docs, run the resize steps for doc. [CSSOMVIEW]
if document.window().run_the_resize_steps(can_gc) {
// Evaluate media queries and report changes.
@ -1269,6 +1266,9 @@ impl ScriptThread {
document.react_to_environment_changes()
}
// > 9. For each doc of docs, run the scroll steps for doc.
document.run_the_scroll_steps(can_gc);
// > 11. For each doc of docs, update animations and send events for doc, passing
// > in relative high resolution time given frameTimestamp and doc's relevant
// > global object as the timestamp [WEBANIMATIONS]