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),
);
}

View file

@ -116,9 +116,8 @@ use constellation_traits::{
EmbedderToConstellationMessage, IFrameLoadInfo, IFrameLoadInfoWithData, IFrameSandboxState,
IFrameSizeMsg, Job, LoadData, LoadOrigin, LogEntry, MessagePortMsg, NavigationHistoryBehavior,
PaintMetricEvent, PortMessageTask, PortTransferInfo, SWManagerMsg, SWManagerSenders,
ScriptToConstellationChan, ScriptToConstellationMessage, ScrollState,
ServiceWorkerManagerFactory, ServiceWorkerMsg, StructuredSerializedData, TraversalDirection,
WindowSizeType,
ScriptToConstellationChan, ScriptToConstellationMessage, ServiceWorkerManagerFactory,
ServiceWorkerMsg, StructuredSerializedData, TraversalDirection, WindowSizeType,
};
use crossbeam_channel::{Receiver, Select, Sender, unbounded};
use devtools_traits::{
@ -167,7 +166,8 @@ use webgpu_traits::{WebGPU, WebGPURequest};
#[cfg(feature = "webgpu")]
use webrender::RenderApi;
use webrender::RenderApiSender;
use webrender_api::{DocumentId, ImageKey};
use webrender_api::units::LayoutVector2D;
use webrender_api::{DocumentId, ExternalScrollId, ImageKey};
use crate::browsingcontext::{
AllBrowsingContextsIterator, BrowsingContext, FullyActiveBrowsingContextsIterator,
@ -6051,7 +6051,11 @@ where
feature = "tracing",
tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace")
)]
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(pipeline) = self.pipelines.get(&pipeline_id) else {
warn!("Discarding scroll offset update for unknown pipeline");
return;

View file

@ -15,16 +15,16 @@ use app_units::Au;
use base::Epoch;
use base::id::{PipelineId, WebViewId};
use compositing_traits::CrossProcessCompositorApi;
use constellation_traits::ScrollState;
use compositing_traits::display_list::ScrollType;
use embedder_traits::{Theme, UntrustedNodeAddress, ViewportDetails};
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect};
use euclid::{Point2D, Scale, Size2D, Vector2D};
use euclid::{Point2D, Scale, Size2D};
use fnv::FnvHashMap;
use fonts::{FontContext, FontContextWebFontMethods};
use fonts_traits::StylesheetWebFontLoadFinishedCallback;
use fxhash::FxHashMap;
use ipc_channel::ipc::IpcSender;
use log::{debug, error};
use log::{debug, error, warn};
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
use net_traits::image_cache::{ImageCache, UsePlaceholder};
use parking_lot::{Mutex, RwLock};
@ -74,7 +74,7 @@ use style::{Zero, driver};
use style_traits::{CSSPixel, SpeculativePainter};
use stylo_atoms::Atom;
use url::Url;
use webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel, LayoutPoint, LayoutSize};
use webrender_api::units::{DevicePixel, DevicePoint, LayoutSize, LayoutVector2D};
use webrender_api::{ExternalScrollId, HitTestFlags};
use crate::context::{CachedImageOrError, LayoutContext};
@ -149,6 +149,12 @@ pub struct LayoutThread {
/// layout trees remain the same.
need_new_display_list: Cell<bool>,
/// Whether or not the existing stacking context tree is dirty and needs to be
/// rebuilt. This happens after a relayout or overflow update. The reason that we
/// don't simply clear the stacking context tree when it becomes dirty is that we need
/// to preserve scroll offsets from the old tree to the new one.
need_new_stacking_context_tree: Cell<bool>,
/// The box tree.
box_tree: RefCell<Option<Arc<BoxTree>>>,
@ -161,9 +167,6 @@ pub struct LayoutThread {
/// A counter for epoch messages
epoch: Cell<Epoch>,
/// Scroll offsets of nodes that scroll.
scroll_offsets: RefCell<HashMap<ExternalScrollId, Vector2D<f32, LayoutPixel>>>,
// A cache that maps image resources specified in CSS (e.g as the `url()` value
// for `background-image` or `content` properties) to either the final resolved
// image data, or an error if the image cache failed to load/decode the image.
@ -485,11 +488,20 @@ impl Layout for LayoutThread {
) {
}
fn set_scroll_offsets(&mut self, scroll_states: &[ScrollState]) {
*self.scroll_offsets.borrow_mut() = scroll_states
.iter()
.map(|scroll_state| (scroll_state.scroll_id, scroll_state.scroll_offset))
.collect();
fn set_scroll_offsets_from_renderer(
&mut self,
scroll_states: &HashMap<ExternalScrollId, LayoutVector2D>,
) {
let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
let Some(stacking_context_tree) = stacking_context_tree.as_mut() else {
warn!("Received scroll offsets before finishing layout.");
return;
};
stacking_context_tree
.compositor_info
.scroll_tree
.set_all_scroll_offsets(scroll_states);
}
}
@ -533,13 +545,13 @@ impl LayoutThread {
have_added_user_agent_stylesheets: false,
have_ever_generated_display_list: Cell::new(false),
need_new_display_list: Cell::new(false),
need_new_stacking_context_tree: Cell::new(false),
box_tree: Default::default(),
fragment_tree: Default::default(),
stacking_context_tree: Default::default(),
// Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR
epoch: Cell::new(Epoch(1)),
compositor_api: config.compositor_api,
scroll_offsets: Default::default(),
stylist: Stylist::new(device, QuirksMode::NoQuirks),
resolved_images_cache: Default::default(),
debug: opts::get().debug.clone(),
@ -674,8 +686,9 @@ impl LayoutThread {
let built_display_list =
self.build_display_list(&reflow_request, damage, &mut layout_context);
if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal {
self.update_scroll_node_state(&scroll_state);
if let ReflowGoal::UpdateScrollNode(external_scroll_id, offset) = reflow_request.reflow_goal
{
self.set_scroll_offset_from_script(external_scroll_id, offset);
}
let pending_images = std::mem::take(&mut *layout_context.pending_images.lock());
@ -828,12 +841,10 @@ impl LayoutThread {
*self.fragment_tree.borrow_mut() = Some(fragment_tree);
// The FragmentTree has been updated, so any existing StackingContext tree that layout
// had is now out of date and should be rebuilt.
*self.stacking_context_tree.borrow_mut() = None;
// Force display list generation as layout has changed.
// Changes to layout require us to generate a new stacking context tree and display
// list the next time one is requested.
self.need_new_display_list.set(true);
self.need_new_stacking_context_tree.set(true);
if self.debug.dump_style_tree {
println!(
@ -866,6 +877,11 @@ impl LayoutThread {
fragment_tree.print();
}
}
// Changes to overflow require us to generate a new stacking context tree and
// display list the next time one is requested.
self.need_new_display_list.set(true);
self.need_new_stacking_context_tree.set(true);
}
fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, damage: RestyleDamage) {
@ -878,7 +894,7 @@ impl LayoutThread {
return;
};
if !damage.contains(RestyleDamage::REBUILD_STACKING_CONTEXT) &&
self.stacking_context_tree.borrow().is_some()
!self.need_new_stacking_context_tree.get()
{
return;
}
@ -889,19 +905,39 @@ impl LayoutThread {
viewport_size.height.to_f32_px(),
);
let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
let old_scroll_offsets = stacking_context_tree
.as_ref()
.map(|tree| tree.compositor_info.scroll_tree.scroll_offsets());
// Build the StackingContextTree. This turns the `FragmentTree` into a
// tree of fragments in CSS painting order and also creates all
// applicable spatial and clip nodes.
*self.stacking_context_tree.borrow_mut() = Some(StackingContextTree::new(
let mut new_stacking_context_tree = StackingContextTree::new(
fragment_tree,
viewport_size,
self.id.into(),
!self.have_ever_generated_display_list.get(),
&self.debug,
));
);
// When a new StackingContextTree is built, it contains a freshly built
// ScrollTree. We want to preserve any existing scroll offsets in that tree,
// adjusted by any new scroll constraints.
if let Some(old_scroll_offsets) = old_scroll_offsets {
new_stacking_context_tree
.compositor_info
.scroll_tree
.set_all_scroll_offsets(&old_scroll_offsets);
}
*stacking_context_tree = Some(new_stacking_context_tree);
// Force display list generation as layout has changed.
self.need_new_display_list.set(true);
// The stacking context tree is up-to-date again.
self.need_new_stacking_context_tree.set(false);
}
/// Build the display list for the current layout and send it to the renderer. If no display
@ -959,18 +995,33 @@ impl LayoutThread {
true
}
fn update_scroll_node_state(&self, state: &ScrollState) {
self.scroll_offsets
.borrow_mut()
.insert(state.scroll_id, state.scroll_offset);
let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y);
fn set_scroll_offset_from_script(
&self,
external_scroll_id: ExternalScrollId,
offset: LayoutVector2D,
) {
let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
let Some(stacking_context_tree) = stacking_context_tree.as_mut() else {
return;
};
if let Some(offset) = stacking_context_tree
.compositor_info
.scroll_tree
.set_scroll_offset_for_node_with_external_scroll_id(
external_scroll_id,
offset,
ScrollType::Script,
)
{
self.compositor_api.send_scroll_node(
self.webview_id,
self.id.into(),
LayoutPoint::from_untyped(point),
state.scroll_id,
offset,
external_scroll_id,
);
}
}
/// Returns profiling information which is passed to the time profiler.
fn profiler_metadata(&self) -> Option<TimerMetadata> {

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);
}
}

View file

@ -4,6 +4,8 @@
//! Defines data structures which are consumed by the Compositor.
use std::collections::HashMap;
use base::id::ScrollTreeNodeId;
use bitflags::bitflags;
use embedder_traits::Cursor;
@ -338,7 +340,7 @@ impl ScrollTree {
/// Given an [`ExternalScrollId`] and an offset, update the scroll offset of the scroll node
/// with the given id.
pub fn set_scroll_offsets_for_node_with_external_scroll_id(
pub fn set_scroll_offset_for_node_with_external_scroll_id(
&mut self,
external_scroll_id: ExternalScrollId,
offset: LayoutVector2D,
@ -353,6 +355,29 @@ impl ScrollTree {
_ => None,
})
}
/// Given a set of all scroll offsets coming from the Servo renderer, update all of the offsets
/// for nodes that actually exist in this tree.
pub fn set_all_scroll_offsets(&mut self, offsets: &HashMap<ExternalScrollId, LayoutVector2D>) {
for node in self.nodes.iter_mut() {
if let SpatialTreeNodeInfo::Scroll(ref mut scroll_info) = node.info {
if let Some(offset) = offsets.get(&scroll_info.external_id) {
scroll_info.offset = *offset;
}
}
}
}
/// Collect all of the scroll offsets of the scrolling nodes of this tree into a
/// [`HashMap`] which can be applied to another tree.
pub fn scroll_offsets(&self) -> HashMap<ExternalScrollId, LayoutVector2D> {
HashMap::from_iter(self.nodes.iter().filter_map(|node| match node.info {
SpatialTreeNodeInfo::Scroll(ref scroll_info) => {
Some((scroll_info.external_id, scroll_info.offset))
},
_ => None,
}))
}
}
/// A data structure which stores compositor-side information about

View file

@ -35,7 +35,7 @@ use ipc_channel::ipc::{self, IpcSharedMemory};
use profile_traits::mem::{OpaqueSender, ReportsChan};
use serde::{Deserialize, Serialize};
use servo_geometry::{DeviceIndependentIntRect, DeviceIndependentIntSize};
use webrender_api::units::{DevicePoint, LayoutPoint, TexelRect};
use webrender_api::units::{DevicePoint, LayoutVector2D, TexelRect};
use webrender_api::{
BuiltDisplayList, BuiltDisplayListDescriptor, ExternalImage, ExternalImageData,
ExternalImageHandler, ExternalImageId, ExternalImageSource, ExternalScrollId,
@ -124,7 +124,7 @@ pub enum CompositorMsg {
SendScrollNode(
WebViewId,
WebRenderPipelineId,
LayoutPoint,
LayoutVector2D,
ExternalScrollId,
),
/// Inform WebRender of a new display list for the given pipeline.
@ -232,7 +232,7 @@ impl CrossProcessCompositorApi {
&self,
webview_id: WebViewId,
pipeline_id: WebRenderPipelineId,
point: LayoutPoint,
point: LayoutVector2D,
scroll_id: ExternalScrollId,
) {
if let Err(e) = self.0.send(CompositorMsg::SendScrollNode(

View file

@ -22,7 +22,6 @@ use embedder_traits::{
CompositorHitTestResult, Cursor, InputEvent, JavaScriptEvaluationId, MediaSessionActionType,
Theme, ViewportDetails, WebDriverCommandMsg,
};
use euclid::Vector2D;
pub use from_script_message::*;
use ipc_channel::ipc::IpcSender;
use malloc_size_of_derive::MallocSizeOf;
@ -32,7 +31,7 @@ use servo_url::{ImmutableOrigin, ServoUrl};
pub use structured_data::*;
use strum_macros::IntoStaticStr;
use webrender_api::ExternalScrollId;
use webrender_api::units::LayoutPixel;
use webrender_api::units::LayoutVector2D;
/// Messages to the Constellation from the embedding layer, whether from `ServoRenderer` or
/// from `libservo` itself.
@ -90,7 +89,7 @@ pub enum EmbedderToConstellationMessage {
SetWebViewThrottled(WebViewId, bool),
/// The Servo renderer scrolled and is updating the scroll states of the nodes in the
/// given pipeline via the constellation.
SetScrollStates(PipelineId, Vec<ScrollState>),
SetScrollStates(PipelineId, HashMap<ExternalScrollId, LayoutVector2D>),
/// Notify the constellation that a particular paint metric event has happened for the given pipeline.
PaintMetric(PipelineId, PaintMetricEvent),
/// Evaluate a JavaScript string in the context of a `WebView`. When execution is complete or an
@ -136,15 +135,6 @@ pub enum WindowSizeType {
Resize,
}
/// The scroll state of a stacking context.
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
pub struct ScrollState {
/// The ID of the scroll root.
pub scroll_id: ExternalScrollId,
/// The scrolling offset of this stacking context.
pub scroll_offset: Vector2D<f32, LayoutPixel>,
}
/// The direction of a history traversal
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum TraversalDirection {

View file

@ -9,6 +9,7 @@
#![deny(missing_docs)]
#![deny(unsafe_code)]
use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;
@ -20,8 +21,8 @@ use bluetooth_traits::BluetoothRequest;
use canvas_traits::webgl::WebGLPipeline;
use compositing_traits::CrossProcessCompositorApi;
use constellation_traits::{
LoadData, NavigationHistoryBehavior, ScriptToConstellationChan, ScrollState,
StructuredSerializedData, WindowSizeType,
LoadData, NavigationHistoryBehavior, ScriptToConstellationChan, StructuredSerializedData,
WindowSizeType,
};
use crossbeam_channel::{RecvTimeoutError, Sender};
use devtools_traits::ScriptToDevtoolsControlMsg;
@ -47,8 +48,8 @@ use style_traits::{CSSPixel, SpeculativePainter};
use stylo_atoms::Atom;
#[cfg(feature = "webgpu")]
use webgpu_traits::WebGPUMsg;
use webrender_api::ImageKey;
use webrender_api::units::DevicePixel;
use webrender_api::units::{DevicePixel, LayoutVector2D};
use webrender_api::{ExternalScrollId, ImageKey};
/// The initial data required to create a new layout attached to an existing script thread.
#[derive(Debug, Deserialize, Serialize)]
@ -246,7 +247,7 @@ pub enum ScriptThreadMessage {
SetWebGPUPort(IpcReceiver<WebGPUMsg>),
/// The compositor scrolled and is updating the scroll states of the nodes in the given
/// pipeline via the Constellation.
SetScrollStates(PipelineId, Vec<ScrollState>),
SetScrollStates(PipelineId, HashMap<ExternalScrollId, LayoutVector2D>),
/// Evaluate the given JavaScript and return a result via a corresponding message
/// to the Constellation.
EvaluateJavaScript(PipelineId, JavaScriptEvaluationId, String),

View file

@ -11,6 +11,7 @@
pub mod wrapper_traits;
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicIsize, AtomicU64, Ordering};
@ -19,7 +20,7 @@ use atomic_refcell::AtomicRefCell;
use base::Epoch;
use base::id::{BrowsingContextId, PipelineId, WebViewId};
use compositing_traits::CrossProcessCompositorApi;
use constellation_traits::{LoadData, ScrollState};
use constellation_traits::LoadData;
use embedder_traits::{Theme, UntrustedNodeAddress, ViewportDetails};
use euclid::default::{Point2D, Rect};
use fnv::FnvHashMap;
@ -48,8 +49,8 @@ use style::properties::PropertyId;
use style::properties::style_structs::Font;
use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
use style::stylesheets::Stylesheet;
use webrender_api::ImageKey;
use webrender_api::units::DeviceIntSize;
use webrender_api::units::{DeviceIntSize, LayoutVector2D};
use webrender_api::{ExternalScrollId, ImageKey};
pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
fn as_any(&self) -> &dyn Any;
@ -245,7 +246,10 @@ pub trait Layout {
);
/// Set the scroll states of this layout after a compositor scroll.
fn set_scroll_offsets(&mut self, scroll_states: &[ScrollState]);
fn set_scroll_offsets_from_renderer(
&mut self,
scroll_states: &HashMap<ExternalScrollId, LayoutVector2D>,
);
fn query_content_box(&self, node: TrustedNodeAddress) -> Option<Rect<Au>>;
fn query_content_boxes(&self, node: TrustedNodeAddress) -> Vec<Rect<Au>>;
@ -333,7 +337,7 @@ pub enum ReflowGoal {
/// Tells layout about a single new scrolling offset from the script. The rest will
/// remain untouched and layout won't forward this back to script.
UpdateScrollNode(ScrollState),
UpdateScrollNode(ExternalScrollId, LayoutVector2D),
}
impl ReflowGoal {
@ -341,7 +345,7 @@ impl ReflowGoal {
/// be present or false if it only needs stacking-relative positions.
pub fn needs_display_list(&self) -> bool {
match *self {
ReflowGoal::UpdateTheRendering | ReflowGoal::UpdateScrollNode(_) => true,
ReflowGoal::UpdateTheRendering | ReflowGoal::UpdateScrollNode(..) => true,
ReflowGoal::LayoutQuery(ref querymsg) => match *querymsg {
QueryMsg::ElementInnerOuterTextQuery |
QueryMsg::InnerWindowDimensionsQuery |
@ -363,7 +367,7 @@ impl ReflowGoal {
/// false if a layout_thread display list is sufficient.
pub fn needs_display(&self) -> bool {
match *self {
ReflowGoal::UpdateTheRendering | ReflowGoal::UpdateScrollNode(_) => true,
ReflowGoal::UpdateTheRendering | ReflowGoal::UpdateScrollNode(..) => true,
ReflowGoal::LayoutQuery(ref querymsg) => match *querymsg {
QueryMsg::NodesFromPointQuery |
QueryMsg::TextIndexQuery |