mirror of
https://github.com/servo/servo.git
synced 2025-06-21 15:49:04 +01:00
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:
parent
6cac782fb1
commit
f451dccd0b
11 changed files with 182 additions and 122 deletions
|
@ -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,17 +995,32 @@ 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);
|
||||
self.compositor_api.send_scroll_node(
|
||||
self.webview_id,
|
||||
self.id.into(),
|
||||
LayoutPoint::from_untyped(point),
|
||||
state.scroll_id,
|
||||
);
|
||||
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(),
|
||||
offset,
|
||||
external_scroll_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns profiling information which is passed to the time profiler.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue