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

@ -30,7 +30,6 @@ use embedder_traits::{
TouchEventType, UntrustedNodeAddress, ViewportDetails, WheelDelta, WheelEvent, WheelMode,
};
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D};
use fnv::FnvHashMap;
use ipc_channel::ipc::{self, IpcSharedMemory};
use libc::c_void;
use log::{debug, info, trace, warn};
@ -48,10 +47,10 @@ use webrender_api::units::{
};
use webrender_api::{
self, BuiltDisplayList, DirtyRect, DisplayListPayload, DocumentId, Epoch as WebRenderEpoch,
ExternalScrollId, FontInstanceFlags, FontInstanceKey, FontInstanceOptions, FontKey,
HitTestFlags, PipelineId as WebRenderPipelineId, PropertyBinding, ReferenceFrameKind,
RenderReasons, SampledScrollOffset, ScrollLocation, SpaceAndClipInfo, SpatialId,
SpatialTreeItemKey, TransformStyle,
FontInstanceFlags, FontInstanceKey, FontInstanceOptions, FontKey, HitTestFlags,
PipelineId as WebRenderPipelineId, PropertyBinding, ReferenceFrameKind, RenderReasons,
SampledScrollOffset, ScrollLocation, SpaceAndClipInfo, SpatialId, SpatialTreeItemKey,
TransformStyle,
};
use crate::InitialCompositorState;
@ -260,26 +259,9 @@ impl PipelineDetails {
}
fn install_new_scroll_tree(&mut self, new_scroll_tree: ScrollTree) {
let old_scroll_offsets: FnvHashMap<ExternalScrollId, LayoutVector2D> = self
.scroll_tree
.nodes
.drain(..)
.filter_map(|node| match (node.external_id(), node.offset()) {
(Some(external_id), Some(offset)) => Some((external_id, offset)),
_ => None,
})
.collect();
let old_scroll_offsets = self.scroll_tree.scroll_offsets();
self.scroll_tree = new_scroll_tree;
for node in self.scroll_tree.nodes.iter_mut() {
match node.external_id() {
Some(external_id) => match old_scroll_offsets.get(&external_id) {
Some(new_offset) => node.set_offset(*new_offset),
None => continue,
},
_ => continue,
};
}
self.scroll_tree.set_all_scroll_offsets(&old_scroll_offsets);
}
}
@ -728,7 +710,7 @@ impl IOCompositor {
self.global.borrow_mut().send_transaction(txn);
},
CompositorMsg::SendScrollNode(webview_id, pipeline_id, point, external_scroll_id) => {
CompositorMsg::SendScrollNode(webview_id, pipeline_id, offset, external_scroll_id) => {
let Some(webview_renderer) = self.webview_renderers.get_mut(webview_id) else {
return;
};
@ -739,15 +721,17 @@ impl IOCompositor {
return;
};
let offset = LayoutVector2D::new(point.x, point.y);
let Some(offset) = pipeline_details
.scroll_tree
.set_scroll_offsets_for_node_with_external_scroll_id(
.set_scroll_offset_for_node_with_external_scroll_id(
external_scroll_id,
-offset,
offset,
ScrollType::Script,
)
else {
// The renderer should be fully up-to-date with script at this point and script
// should never try to scroll to an invalid location.
warn!("Could not scroll node with id: {external_scroll_id:?}");
return;
};

View file

@ -13,7 +13,7 @@ use compositing_traits::viewport_description::{
DEFAULT_ZOOM, MAX_ZOOM, MIN_ZOOM, ViewportDescription,
};
use compositing_traits::{SendableFrameTree, WebViewTrait};
use constellation_traits::{EmbedderToConstellationMessage, ScrollState, WindowSizeType};
use constellation_traits::{EmbedderToConstellationMessage, WindowSizeType};
use embedder_traits::{
AnimationState, CompositorHitTestResult, InputEvent, MouseButton, MouseButtonAction,
MouseButtonEvent, MouseMoveEvent, ShutdownState, TouchEvent, TouchEventResult, TouchEventType,
@ -204,18 +204,18 @@ impl WebViewRenderer {
return;
};
let mut scroll_states = Vec::new();
details.scroll_tree.nodes.iter().for_each(|node| {
if let (Some(scroll_id), Some(scroll_offset)) = (node.external_id(), node.offset()) {
scroll_states.push(ScrollState {
scroll_id,
scroll_offset,
});
}
});
let scroll_offsets = details.scroll_tree.scroll_offsets();
// This might be true if we have not received a display list from the layout
// associated with this pipeline yet. In that case, the layout is not ready to
// receive scroll offsets anyway, so just save time and prevent other issues by
// not sending them.
if scroll_offsets.is_empty() {
return;
}
let _ = self.global.borrow().constellation_sender.send(
EmbedderToConstellationMessage::SetScrollStates(pipeline_id, scroll_states),
EmbedderToConstellationMessage::SetScrollStates(pipeline_id, scroll_offsets),
);
}