layout: Store scroll offsets in the ScrollTree (#37428)

There are currently five places that scroll offsets are stored:

 - DOM: A set of scroll offsets used for script.
 - Layout: An array of scroll offsets that is used for tracking
   layout-side scroll offsets.
 - Layout: The scroll offsets stored in the `ScrollTree`. These are
   currently unset and unused.
 - Compositor: The scroll offsets stored in the `ScrollTree` mirrored
   from layout.
 - WebRender: The scrolled offsets stored in the WebRender spatial tree.

This change is the first step in combining the first three into the
layout `ScrollTree`. It eliminates the extra array of scroll offsets
stored in layout in favor of the storing them in the `ScrollTree`. A
followup change will eliminate the ones stored in the DOM.

- In addition the `ScrollState` data structure is eliminated as these
are
now stored in a `HashMap` everywhere when passing them via IPC.
- The offsests stored in layout can now never scroll past the boundaries
of the scrolled content.

Testing: This should not change behavior and is thus covered by existing
WPT tests.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: stevennovaryo <steven.novaryo@gmail.com>
This commit is contained in:
Martin Robinson 2025-06-13 14:01:27 +02:00 committed by GitHub
parent 6cac782fb1
commit f451dccd0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 182 additions and 122 deletions

View file

@ -24,7 +24,7 @@ use canvas_traits::webgl::WebGLChan;
use compositing_traits::CrossProcessCompositorApi;
use constellation_traits::{
DocumentState, LoadData, LoadOrigin, NavigationHistoryBehavior, ScriptToConstellationChan,
ScriptToConstellationMessage, ScrollState, StructuredSerializedData, WindowSizeType,
ScriptToConstellationMessage, StructuredSerializedData, WindowSizeType,
};
use crossbeam_channel::{Sender, unbounded};
use cssparser::SourceLocation;
@ -2097,10 +2097,7 @@ impl Window {
// TODO(mrobinson, #18709): Add smooth scrolling support to WebRender so that we can
// properly process ScrollBehavior here.
self.reflow(
ReflowGoal::UpdateScrollNode(ScrollState {
scroll_id,
scroll_offset: Vector2D::new(-x, -y),
}),
ReflowGoal::UpdateScrollNode(scroll_id, Vector2D::new(-x, -y)),
can_gc,
);
}
@ -3234,7 +3231,7 @@ fn should_move_clip_rect(clip_rect: UntypedRect<Au>, new_viewport: UntypedRect<f
fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal) {
let goal_string = match *reflow_goal {
ReflowGoal::UpdateTheRendering => "\tFull",
ReflowGoal::UpdateScrollNode(_) => "\tUpdateScrollNode",
ReflowGoal::UpdateScrollNode(..) => "\tUpdateScrollNode",
ReflowGoal::LayoutQuery(ref query_msg) => match *query_msg {
QueryMsg::ContentBox => "\tContentBoxQuery",
QueryMsg::ContentBoxes => "\tContentBoxesQuery",

View file

@ -39,7 +39,7 @@ use chrono::{DateTime, Local};
use compositing_traits::CrossProcessCompositorApi;
use constellation_traits::{
JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, ScriptToConstellationChan,
ScriptToConstellationMessage, ScrollState, StructuredSerializedData, WindowSizeType,
ScriptToConstellationMessage, StructuredSerializedData, WindowSizeType,
};
use content_security_policy::{self as csp};
use crossbeam_channel::unbounded;
@ -99,7 +99,8 @@ use timers::{TimerEventRequest, TimerId, TimerScheduler};
use url::Position;
#[cfg(feature = "webgpu")]
use webgpu_traits::{WebGPUDevice, WebGPUMsg};
use webrender_api::units::DevicePixel;
use webrender_api::ExternalScrollId;
use webrender_api::units::{DevicePixel, LayoutVector2D};
use crate::document_collection::DocumentCollection;
use crate::document_loader::DocumentLoader;
@ -2010,7 +2011,11 @@ impl ScriptThread {
}
}
fn handle_set_scroll_states(&self, pipeline_id: PipelineId, scroll_states: Vec<ScrollState>) {
fn handle_set_scroll_states(
&self,
pipeline_id: PipelineId,
scroll_states: HashMap<ExternalScrollId, LayoutVector2D>,
) {
let Some(window) = self.documents.borrow().find_window(pipeline_id) else {
warn!("Received scroll states for closed pipeline {pipeline_id}");
return;
@ -2020,16 +2025,15 @@ impl ScriptThread {
ScriptThreadEventCategory::SetScrollState,
Some(pipeline_id),
|| {
window.layout_mut().set_scroll_offsets(&scroll_states);
window
.layout_mut()
.set_scroll_offsets_from_renderer(&scroll_states);
let mut scroll_offsets = HashMap::new();
for scroll_state in scroll_states.into_iter() {
let scroll_offset = scroll_state.scroll_offset;
if scroll_state.scroll_id.is_root() {
for (scroll_id, scroll_offset) in scroll_states.into_iter() {
if scroll_id.is_root() {
window.update_viewport_for_scroll(-scroll_offset.x, -scroll_offset.y);
} else if let Some(node_id) =
node_id_from_scroll_id(scroll_state.scroll_id.0 as usize)
{
} else if let Some(node_id) = node_id_from_scroll_id(scroll_id.0 as usize) {
scroll_offsets.insert(OpaqueNode(node_id), -scroll_offset);
}
}