aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2025-05-20 15:42:39 +0200
committerGitHub <noreply@github.com>2025-05-20 13:42:39 +0000
commitd8294fa42378d2fa4645ddb7ada55a898d85c8ba (patch)
tree0ff685f95c9c055188d358c2cd590e9ecb33bb38
parent27c8a899ea64810dea224a49e292819488e8b5de (diff)
downloadservo-d8294fa42378d2fa4645ddb7ada55a898d85c8ba.tar.gz
servo-d8294fa42378d2fa4645ddb7ada55a898d85c8ba.zip
layout: Split stacking context and display list construction (#37047)
Previously, after a layout was finished (or skipped in the case of repaint-only layout), both the stacking context tree and display list were built. In the case of repaint-only layout, we should be able to skip the reconstruction of the stacking context tree and only do display list building. This change does that, also generally cleaning and up and clarifying the data structure used during this phase of layout. This opens up the possibility of a new kind of incremental layout that does both repaint and a rebuild of the stacking context tree. On the blaster.html test case[^1], this reduces tightly-measured layout time from ~45-50 milliseconds to ~25-30 milliseconds on my M3. [^1]: https://gist.github.com/mrobinson/44ec87d028c0198917a7715a06dd98a0 Testing: There are currently no performance tests for layout. :( This should not modify the results of WPT tests. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Oriol Brufau <obrufau@igalia.com>
-rw-r--r--Cargo.lock30
-rw-r--r--components/layout/display_list/background.rs4
-rw-r--r--components/layout/display_list/clip.rs276
-rw-r--r--components/layout/display_list/clip_path.rs259
-rw-r--r--components/layout/display_list/mod.rs392
-rw-r--r--components/layout/display_list/stacking_context.rs441
-rw-r--r--components/layout/fragment_tree/box_fragment.rs2
-rw-r--r--components/layout/fragment_tree/fragment_tree.rs11
-rw-r--r--components/layout/layout_impl.rs125
-rw-r--r--components/shared/base/id.rs5
-rw-r--r--components/shared/compositing/display_list.rs154
-rw-r--r--components/shared/compositing/lib.rs2
-rw-r--r--components/shared/compositing/tests/compositor.rs43
13 files changed, 961 insertions, 783 deletions
diff --git a/Cargo.lock b/Cargo.lock
index fbc631b9f51..a4db39275a7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1065,7 +1065,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -4261,7 +4261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c"
dependencies = [
"cfg-if",
- "windows-targets 0.48.5",
+ "windows-targets 0.52.6",
]
[[package]]
@@ -6517,7 +6517,7 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.28.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"bitflags 2.9.1",
"cssparser",
@@ -6812,7 +6812,7 @@ dependencies = [
[[package]]
name = "servo_arc"
version = "0.4.1"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"serde",
"stable_deref_trait",
@@ -7273,7 +7273,7 @@ dependencies = [
[[package]]
name = "stylo"
version = "0.3.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"app_units",
"arrayvec",
@@ -7331,7 +7331,7 @@ dependencies = [
[[package]]
name = "stylo_atoms"
version = "0.3.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"string_cache",
"string_cache_codegen",
@@ -7340,12 +7340,12 @@ dependencies = [
[[package]]
name = "stylo_config"
version = "0.3.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
[[package]]
name = "stylo_derive"
version = "0.3.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"darling",
"proc-macro2",
@@ -7357,7 +7357,7 @@ dependencies = [
[[package]]
name = "stylo_dom"
version = "0.3.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"bitflags 2.9.1",
"stylo_malloc_size_of",
@@ -7366,7 +7366,7 @@ dependencies = [
[[package]]
name = "stylo_malloc_size_of"
version = "0.3.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"app_units",
"cssparser",
@@ -7383,12 +7383,12 @@ dependencies = [
[[package]]
name = "stylo_static_prefs"
version = "0.3.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
[[package]]
name = "stylo_traits"
version = "0.3.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"app_units",
"bitflags 2.9.1",
@@ -7771,7 +7771,7 @@ dependencies = [
[[package]]
name = "to_shmem"
version = "0.2.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"cssparser",
"servo_arc",
@@ -7784,7 +7784,7 @@ dependencies = [
[[package]]
name = "to_shmem_derive"
version = "0.1.0"
-source = "git+https://github.com/servo/stylo?branch=2025-05-01#fb74b958cc7bb93a9335766130d6711ad3e071ce"
+source = "git+https://github.com/servo/stylo?branch=2025-05-01#88b34ae4293ca7286584cee37bca1f58ab79a8cb"
dependencies = [
"darling",
"proc-macro2",
@@ -8909,7 +8909,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
diff --git a/components/layout/display_list/background.rs b/components/layout/display_list/background.rs
index f49ddfbe6ce..563bce28450 100644
--- a/components/layout/display_list/background.rs
+++ b/components/layout/display_list/background.rs
@@ -66,7 +66,7 @@ impl<'a> BackgroundPainter<'a> {
if &BackgroundAttachment::Fixed ==
get_cyclic(&background.background_attachment.0, layer_index)
{
- let viewport_size = builder.display_list.compositor_info.viewport_size;
+ let viewport_size = builder.compositor_info.viewport_size;
return units::LayoutRect::from_origin_and_size(Point2D::origin(), viewport_size);
}
@@ -121,7 +121,7 @@ impl<'a> BackgroundPainter<'a> {
if &BackgroundAttachment::Fixed ==
get_cyclic(&style.get_background().background_attachment.0, layer_index)
{
- common.spatial_id = builder.current_reference_frame_scroll_node_id.spatial_id;
+ common.spatial_id = builder.spatial_id(builder.current_reference_frame_scroll_node_id);
}
common
}
diff --git a/components/layout/display_list/clip.rs b/components/layout/display_list/clip.rs
new file mode 100644
index 00000000000..d5bd0f52b69
--- /dev/null
+++ b/components/layout/display_list/clip.rs
@@ -0,0 +1,276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use app_units::Au;
+use base::id::ScrollTreeNodeId;
+use style::values::computed::basic_shape::{BasicShape, ClipPath};
+use style::values::computed::length_percentage::NonNegativeLengthPercentage;
+use style::values::computed::position::Position;
+use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox};
+use style::values::generics::position::GenericPositionOrAuto;
+use webrender_api::BorderRadius;
+use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize};
+
+use super::{BuilderForBoxFragment, compute_margin_box_radius, normalize_radii};
+
+/// An identifier for a clip used during StackingContextTree construction. This is a simple index in
+/// a [`ClipStore`]s vector of clips.
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub(crate) struct ClipId(pub usize);
+
+impl ClipId {
+ /// Equivalent to [`ClipChainId::INVALID`]. This means "no clip."
+ pub(crate) const INVALID: ClipId = ClipId(usize::MAX);
+}
+
+/// All the information needed to create a clip on a WebRender display list. These are created at
+/// two times: during `StackingContextTree` creation and during WebRender display list construction.
+/// Only the former are stored in a [`ClipStore`].
+#[derive(Clone)]
+pub(crate) struct Clip {
+ pub id: ClipId,
+ pub radii: BorderRadius,
+ pub rect: LayoutRect,
+ pub parent_scroll_node_id: ScrollTreeNodeId,
+ pub parent_clip_id: ClipId,
+}
+
+/// A simple vector of [`Clip`] that is built during `StackingContextTree` construction.
+/// These are later turned into WebRender clips and clip chains during WebRender display
+/// list construction.
+#[derive(Clone, Default)]
+pub(crate) struct StackingContextTreeClipStore(pub Vec<Clip>);
+
+impl StackingContextTreeClipStore {
+ pub(crate) fn add(
+ &mut self,
+ radii: webrender_api::BorderRadius,
+ rect: LayoutRect,
+ parent_scroll_node_id: ScrollTreeNodeId,
+ parent_clip_id: ClipId,
+ ) -> ClipId {
+ let id = ClipId(self.0.len());
+ self.0.push(Clip {
+ id,
+ radii,
+ rect,
+ parent_scroll_node_id,
+ parent_clip_id,
+ });
+ id
+ }
+
+ pub(super) fn add_for_clip_path(
+ &mut self,
+ clip_path: ClipPath,
+ parent_scroll_node_id: &ScrollTreeNodeId,
+ parent_clip_chain_id: &ClipId,
+ fragment_builder: BuilderForBoxFragment,
+ ) -> Option<ClipId> {
+ let geometry_box = match clip_path {
+ ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
+ ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
+ ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
+ ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
+ _ => return None,
+ };
+ let layout_rect = match geometry_box {
+ ShapeBox::BorderBox => fragment_builder.border_rect,
+ ShapeBox::ContentBox => *fragment_builder.content_rect(),
+ ShapeBox::PaddingBox => *fragment_builder.padding_rect(),
+ ShapeBox::MarginBox => *fragment_builder.margin_rect(),
+ };
+ if let ClipPath::Shape(shape, _) = clip_path {
+ match *shape {
+ BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => self
+ .add_for_basic_shape(
+ *shape,
+ layout_rect,
+ parent_scroll_node_id,
+ parent_clip_chain_id,
+ ),
+ BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None,
+ }
+ } else {
+ Some(self.add(
+ match geometry_box {
+ ShapeBox::MarginBox => compute_margin_box_radius(
+ fragment_builder.border_radius,
+ layout_rect.size(),
+ fragment_builder.fragment,
+ ),
+ _ => fragment_builder.border_radius,
+ },
+ layout_rect,
+ *parent_scroll_node_id,
+ *parent_clip_chain_id,
+ ))
+ }
+ }
+
+ #[cfg_attr(
+ feature = "tracing",
+ tracing::instrument(
+ name = "StackingContextClipStore::add_for_basic_shape",
+ skip_all,
+ fields(servo_profiling = true),
+ level = "trace",
+ )
+ )]
+ fn add_for_basic_shape(
+ &mut self,
+ shape: BasicShape,
+ layout_box: LayoutRect,
+ parent_scroll_node_id: &ScrollTreeNodeId,
+ parent_clip_chain_id: &ClipId,
+ ) -> Option<ClipId> {
+ match shape {
+ BasicShape::Rect(rect) => {
+ let box_height = Au::from_f32_px(layout_box.height());
+ let box_width = Au::from_f32_px(layout_box.width());
+ let insets = LayoutSideOffsets::new(
+ rect.rect.0.to_used_value(box_height).to_f32_px(),
+ rect.rect.1.to_used_value(box_width).to_f32_px(),
+ rect.rect.2.to_used_value(box_height).to_f32_px(),
+ rect.rect.3.to_used_value(box_width).to_f32_px(),
+ );
+
+ // `inner_rect()` will cause an assertion failure if the insets are larger than the
+ // rectangle dimension.
+ let shape_rect = if insets.left + insets.right >= layout_box.width() ||
+ insets.top + insets.bottom > layout_box.height()
+ {
+ LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero())
+ } else {
+ layout_box.to_rect().inner_rect(insets).to_box2d()
+ };
+
+ let corner = |corner: &style::values::computed::BorderCornerRadius| {
+ LayoutSize::new(
+ corner.0.width.0.to_used_value(box_width).to_f32_px(),
+ corner.0.height.0.to_used_value(box_height).to_f32_px(),
+ )
+ };
+ let mut radii = webrender_api::BorderRadius {
+ top_left: corner(&rect.round.top_left),
+ top_right: corner(&rect.round.top_right),
+ bottom_left: corner(&rect.round.bottom_left),
+ bottom_right: corner(&rect.round.bottom_right),
+ };
+ normalize_radii(&layout_box, &mut radii);
+ Some(self.add(
+ radii,
+ shape_rect,
+ *parent_scroll_node_id,
+ *parent_clip_chain_id,
+ ))
+ },
+ BasicShape::Circle(circle) => {
+ let center = match circle.position {
+ GenericPositionOrAuto::Position(position) => position,
+ GenericPositionOrAuto::Auto => Position::center(),
+ };
+ let anchor_x = center
+ .horizontal
+ .to_used_value(Au::from_f32_px(layout_box.width()));
+ let anchor_y = center
+ .vertical
+ .to_used_value(Au::from_f32_px(layout_box.height()));
+ let center = layout_box
+ .min
+ .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
+
+ let horizontal = compute_shape_radius(
+ center.x,
+ &circle.radius,
+ layout_box.min.x,
+ layout_box.max.x,
+ );
+ let vertical = compute_shape_radius(
+ center.y,
+ &circle.radius,
+ layout_box.min.y,
+ layout_box.max.y,
+ );
+
+ // If the value is `Length` then both values should be the same at this point.
+ let radius = match circle.radius {
+ GenericShapeRadius::FarthestSide => horizontal.max(vertical),
+ GenericShapeRadius::ClosestSide => horizontal.min(vertical),
+ GenericShapeRadius::Length(_) => horizontal,
+ };
+ let radius = LayoutSize::new(radius, radius);
+ let mut radii = webrender_api::BorderRadius {
+ top_left: radius,
+ top_right: radius,
+ bottom_left: radius,
+ bottom_right: radius,
+ };
+ let start = center.add_size(&-radius);
+ let rect = LayoutRect::from_origin_and_size(start, radius * 2.);
+ normalize_radii(&layout_box, &mut radii);
+ Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id))
+ },
+ BasicShape::Ellipse(ellipse) => {
+ let center = match ellipse.position {
+ GenericPositionOrAuto::Position(position) => position,
+ GenericPositionOrAuto::Auto => Position::center(),
+ };
+ let anchor_x = center
+ .horizontal
+ .to_used_value(Au::from_f32_px(layout_box.width()));
+ let anchor_y = center
+ .vertical
+ .to_used_value(Au::from_f32_px(layout_box.height()));
+ let center = layout_box
+ .min
+ .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
+
+ let width = compute_shape_radius(
+ center.x,
+ &ellipse.semiaxis_x,
+ layout_box.min.x,
+ layout_box.max.x,
+ );
+ let height = compute_shape_radius(
+ center.y,
+ &ellipse.semiaxis_y,
+ layout_box.min.y,
+ layout_box.max.y,
+ );
+
+ let mut radii = webrender_api::BorderRadius {
+ top_left: LayoutSize::new(width, height),
+ top_right: LayoutSize::new(width, height),
+ bottom_left: LayoutSize::new(width, height),
+ bottom_right: LayoutSize::new(width, height),
+ };
+ let size = LayoutSize::new(width, height);
+ let start = center.add_size(&-size);
+ let rect = LayoutRect::from_origin_and_size(start, size * 2.);
+ normalize_radii(&rect, &mut radii);
+ Some(self.add(radii, rect, *parent_scroll_node_id, *parent_clip_chain_id))
+ },
+ _ => None,
+ }
+ }
+}
+
+fn compute_shape_radius(
+ center: f32,
+ radius: &GenericShapeRadius<NonNegativeLengthPercentage>,
+ min_edge: f32,
+ max_edge: f32,
+) -> f32 {
+ let distance_from_min_edge = (min_edge - center).abs();
+ let distance_from_max_edge = (max_edge - center).abs();
+ match radius {
+ GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge),
+ GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge),
+ GenericShapeRadius::Length(length) => length
+ .0
+ .to_used_value(Au::from_f32_px(max_edge - min_edge))
+ .to_f32_px(),
+ }
+}
diff --git a/components/layout/display_list/clip_path.rs b/components/layout/display_list/clip_path.rs
deleted file mode 100644
index 419d15c3572..00000000000
--- a/components/layout/display_list/clip_path.rs
+++ /dev/null
@@ -1,259 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
-
-use app_units::Au;
-use base::id::ScrollTreeNodeId;
-use style::values::computed::basic_shape::{BasicShape, ClipPath};
-use style::values::computed::length_percentage::NonNegativeLengthPercentage;
-use style::values::computed::position::Position;
-use style::values::generics::basic_shape::{GenericShapeRadius, ShapeBox, ShapeGeometryBox};
-use style::values::generics::position::GenericPositionOrAuto;
-use webrender_api::ClipChainId;
-use webrender_api::units::{LayoutRect, LayoutSideOffsets, LayoutSize};
-
-use super::{BuilderForBoxFragment, DisplayList, compute_margin_box_radius, normalize_radii};
-
-pub(super) fn build_clip_path_clip_chain_if_necessary(
- clip_path: ClipPath,
- display_list: &mut DisplayList,
- parent_scroll_node_id: &ScrollTreeNodeId,
- parent_clip_chain_id: &ClipChainId,
- fragment_builder: BuilderForBoxFragment,
-) -> Option<ClipChainId> {
- let geometry_box = match clip_path {
- ClipPath::Shape(_, ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
- ClipPath::Shape(_, ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
- ClipPath::Box(ShapeGeometryBox::ShapeBox(shape_box)) => shape_box,
- ClipPath::Box(ShapeGeometryBox::ElementDependent) => ShapeBox::BorderBox,
- _ => return None,
- };
- let layout_rect = match geometry_box {
- ShapeBox::BorderBox => fragment_builder.border_rect,
- ShapeBox::ContentBox => *fragment_builder.content_rect(),
- ShapeBox::PaddingBox => *fragment_builder.padding_rect(),
- ShapeBox::MarginBox => *fragment_builder.margin_rect(),
- };
- if let ClipPath::Shape(shape, _) = clip_path {
- match *shape {
- BasicShape::Circle(_) | BasicShape::Ellipse(_) | BasicShape::Rect(_) => {
- build_simple_shape(
- *shape,
- layout_rect,
- parent_scroll_node_id,
- parent_clip_chain_id,
- display_list,
- )
- },
- BasicShape::Polygon(_) | BasicShape::PathOrShape(_) => None,
- }
- } else {
- Some(create_rect_clip_chain(
- match geometry_box {
- ShapeBox::MarginBox => compute_margin_box_radius(
- fragment_builder.border_radius,
- layout_rect.size(),
- fragment_builder.fragment,
- ),
- _ => fragment_builder.border_radius,
- },
- layout_rect,
- parent_scroll_node_id,
- parent_clip_chain_id,
- display_list,
- ))
- }
-}
-
-#[cfg_attr(
- feature = "tracing",
- tracing::instrument(
- name = "display_list::build_simple_shape",
- skip_all,
- fields(servo_profiling = true),
- level = "trace",
- )
-)]
-fn build_simple_shape(
- shape: BasicShape,
- layout_box: LayoutRect,
- parent_scroll_node_id: &ScrollTreeNodeId,
- parent_clip_chain_id: &ClipChainId,
- display_list: &mut DisplayList,
-) -> Option<ClipChainId> {
- match shape {
- BasicShape::Rect(rect) => {
- let box_height = Au::from_f32_px(layout_box.height());
- let box_width = Au::from_f32_px(layout_box.width());
- let insets = LayoutSideOffsets::new(
- rect.rect.0.to_used_value(box_height).to_f32_px(),
- rect.rect.1.to_used_value(box_width).to_f32_px(),
- rect.rect.2.to_used_value(box_height).to_f32_px(),
- rect.rect.3.to_used_value(box_width).to_f32_px(),
- );
-
- // `inner_rect()` will cause an assertion failure if the insets are larger than the
- // rectangle dimension.
- let shape_rect = if insets.left + insets.right >= layout_box.width() ||
- insets.top + insets.bottom > layout_box.height()
- {
- LayoutRect::from_origin_and_size(layout_box.min, LayoutSize::zero())
- } else {
- layout_box.to_rect().inner_rect(insets).to_box2d()
- };
-
- let corner = |corner: &style::values::computed::BorderCornerRadius| {
- LayoutSize::new(
- corner.0.width.0.to_used_value(box_width).to_f32_px(),
- corner.0.height.0.to_used_value(box_height).to_f32_px(),
- )
- };
- let mut radii = webrender_api::BorderRadius {
- top_left: corner(&rect.round.top_left),
- top_right: corner(&rect.round.top_right),
- bottom_left: corner(&rect.round.bottom_left),
- bottom_right: corner(&rect.round.bottom_right),
- };
- normalize_radii(&layout_box, &mut radii);
- Some(create_rect_clip_chain(
- radii,
- shape_rect,
- parent_scroll_node_id,
- parent_clip_chain_id,
- display_list,
- ))
- },
- BasicShape::Circle(circle) => {
- let center = match circle.position {
- GenericPositionOrAuto::Position(position) => position,
- GenericPositionOrAuto::Auto => Position::center(),
- };
- let anchor_x = center
- .horizontal
- .to_used_value(Au::from_f32_px(layout_box.width()));
- let anchor_y = center
- .vertical
- .to_used_value(Au::from_f32_px(layout_box.height()));
- let center = layout_box
- .min
- .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
-
- let horizontal =
- compute_shape_radius(center.x, &circle.radius, layout_box.min.x, layout_box.max.x);
- let vertical =
- compute_shape_radius(center.y, &circle.radius, layout_box.min.y, layout_box.max.y);
-
- // If the value is `Length` then both values should be the same at this point.
- let radius = match circle.radius {
- GenericShapeRadius::FarthestSide => horizontal.max(vertical),
- GenericShapeRadius::ClosestSide => horizontal.min(vertical),
- GenericShapeRadius::Length(_) => horizontal,
- };
- let radius = LayoutSize::new(radius, radius);
- let mut radii = webrender_api::BorderRadius {
- top_left: radius,
- top_right: radius,
- bottom_left: radius,
- bottom_right: radius,
- };
- let start = center.add_size(&-radius);
- let rect = LayoutRect::from_origin_and_size(start, radius * 2.);
- normalize_radii(&layout_box, &mut radii);
- Some(create_rect_clip_chain(
- radii,
- rect,
- parent_scroll_node_id,
- parent_clip_chain_id,
- display_list,
- ))
- },
- BasicShape::Ellipse(ellipse) => {
- let center = match ellipse.position {
- GenericPositionOrAuto::Position(position) => position,
- GenericPositionOrAuto::Auto => Position::center(),
- };
- let anchor_x = center
- .horizontal
- .to_used_value(Au::from_f32_px(layout_box.width()));
- let anchor_y = center
- .vertical
- .to_used_value(Au::from_f32_px(layout_box.height()));
- let center = layout_box
- .min
- .add_size(&LayoutSize::new(anchor_x.to_f32_px(), anchor_y.to_f32_px()));
-
- let width = compute_shape_radius(
- center.x,
- &ellipse.semiaxis_x,
- layout_box.min.x,
- layout_box.max.x,
- );
- let height = compute_shape_radius(
- center.y,
- &ellipse.semiaxis_y,
- layout_box.min.y,
- layout_box.max.y,
- );
-
- let mut radii = webrender_api::BorderRadius {
- top_left: LayoutSize::new(width, height),
- top_right: LayoutSize::new(width, height),
- bottom_left: LayoutSize::new(width, height),
- bottom_right: LayoutSize::new(width, height),
- };
- let size = LayoutSize::new(width, height);
- let start = center.add_size(&-size);
- let rect = LayoutRect::from_origin_and_size(start, size * 2.);
- normalize_radii(&rect, &mut radii);
- Some(create_rect_clip_chain(
- radii,
- rect,
- parent_scroll_node_id,
- parent_clip_chain_id,
- display_list,
- ))
- },
- _ => None,
- }
-}
-
-fn compute_shape_radius(
- center: f32,
- radius: &GenericShapeRadius<NonNegativeLengthPercentage>,
- min_edge: f32,
- max_edge: f32,
-) -> f32 {
- let distance_from_min_edge = (min_edge - center).abs();
- let distance_from_max_edge = (max_edge - center).abs();
- match radius {
- GenericShapeRadius::FarthestSide => distance_from_min_edge.max(distance_from_max_edge),
- GenericShapeRadius::ClosestSide => distance_from_min_edge.min(distance_from_max_edge),
- GenericShapeRadius::Length(length) => length
- .0
- .to_used_value(Au::from_f32_px(max_edge - min_edge))
- .to_f32_px(),
- }
-}
-fn create_rect_clip_chain(
- radii: webrender_api::BorderRadius,
- rect: LayoutRect,
- parent_scroll_node_id: &ScrollTreeNodeId,
- parent_clip_chain_id: &ClipChainId,
- display_list: &mut DisplayList,
-) -> ClipChainId {
- let new_clip_id = if radii.is_zero() {
- display_list
- .wr
- .define_clip_rect(parent_scroll_node_id.spatial_id, rect)
- } else {
- display_list.wr.define_clip_rounded_rect(
- parent_scroll_node_id.spatial_id,
- webrender_api::ComplexClipRegion {
- rect,
- radii,
- mode: webrender_api::ClipMode::Clip,
- },
- )
- };
- display_list.define_clip_chain(*parent_clip_chain_id, [new_clip_id])
-}
diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs
index f017642908d..95689cf1186 100644
--- a/components/layout/display_list/mod.rs
+++ b/components/layout/display_list/mod.rs
@@ -8,13 +8,15 @@ use std::sync::Arc;
use app_units::{AU_PER_PX, Au};
use base::WebRenderEpochToU16;
use base::id::ScrollTreeNodeId;
-use compositing_traits::display_list::{AxesScrollSensitivity, CompositorDisplayListInfo};
+use clip::{Clip, ClipId};
+use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo};
use embedder_traits::Cursor;
use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit};
use fonts::GlyphStore;
use gradient::WebRenderGradient;
use range::Range as ServoRange;
use servo_arc::Arc as ServoArc;
+use servo_config::opts::DebugOptions;
use servo_geometry::MaxRect;
use style::Zero;
use style::color::{AbsoluteColor, ColorSpace};
@@ -35,8 +37,9 @@ use style::values::specified::ui::CursorKind;
use style_traits::CSSPixel;
use webrender_api::units::{DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
use webrender_api::{
- self as wr, BorderDetails, BoxShadowClipMode, ClipChainId, CommonItemProperties,
- ImageRendering, NinePatchBorder, NinePatchBorderSource, SpatialId, units,
+ self as wr, BorderDetails, BoxShadowClipMode, BuiltDisplayList, ClipChainId, ClipMode,
+ CommonItemProperties, ComplexClipRegion, ImageRendering, NinePatchBorder,
+ NinePatchBorderSource, PropertyBinding, SpatialId, SpatialTreeItemKey, units,
};
use wr::units::LayoutVector2D;
@@ -55,7 +58,7 @@ use crate::replaced::NaturalSizes;
use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
mod background;
-mod clip_path;
+mod clip;
mod conversions;
mod gradient;
mod stacking_context;
@@ -74,68 +77,6 @@ type ItemTag = (u64, u16);
type HitInfo = Option<ItemTag>;
const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
-/// Where the information that's used to build display lists is stored. This
-/// includes both a [wr::DisplayListBuilder] for building up WebRender-specific
-/// display list information and a [CompositorDisplayListInfo] used to store
-/// information used by the compositor, such as a compositor-side scroll tree.
-pub struct DisplayList {
- /// The [wr::DisplayListBuilder] used to collect display list items.
- pub wr: wr::DisplayListBuilder,
-
- /// The information about the WebRender display list that the compositor
- /// consumes. This curerntly contains the out-of-band hit testing information
- /// data structure that the compositor uses to map hit tests to information
- /// about the item hit.
- pub compositor_info: CompositorDisplayListInfo,
-
- /// A count of the number of SpatialTree nodes pushed to the WebRender display
- /// list. This is merely to ensure that the currently-unused SpatialTreeItemKey
- /// produced for every SpatialTree node is unique.
- pub spatial_tree_count: u64,
-}
-
-impl DisplayList {
- /// Create a new [DisplayList] given the dimensions of the layout and the WebRender
- /// pipeline id.
- pub fn new(
- viewport_size: units::LayoutSize,
- content_size: units::LayoutSize,
- pipeline_id: wr::PipelineId,
- epoch: wr::Epoch,
- viewport_scroll_sensitivity: AxesScrollSensitivity,
- first_reflow: bool,
- ) -> Self {
- Self {
- wr: wr::DisplayListBuilder::new(pipeline_id),
- compositor_info: CompositorDisplayListInfo::new(
- viewport_size,
- content_size,
- pipeline_id,
- epoch,
- viewport_scroll_sensitivity,
- first_reflow,
- ),
- spatial_tree_count: 0,
- }
- }
-
- pub fn define_clip_chain<I>(&mut self, parent: ClipChainId, clips: I) -> ClipChainId
- where
- I: IntoIterator<Item = wr::ClipId>,
- I::IntoIter: ExactSizeIterator + Clone,
- {
- // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be
- // used for primitives, but `None` is used for stacking contexts and clip chains. We convert
- // to the `Option<ClipChainId>` representation here. Just passing Some(ClipChainId::INVALID)
- // leads to a crash.
- let parent = match parent {
- ClipChainId::INVALID => None,
- parent => Some(parent),
- };
- self.wr.define_clip_chain(parent, clips)
- }
-}
-
pub(crate) struct DisplayListBuilder<'a> {
/// The current [ScrollTreeNodeId] for this [DisplayListBuilder]. This
/// allows only passing the builder instead passing the containing
@@ -148,18 +89,21 @@ pub(crate) struct DisplayListBuilder<'a> {
/// `background-attachment: fixed` need to not scroll while the rest of the fragment does.
current_reference_frame_scroll_node_id: ScrollTreeNodeId,
- /// The current [wr::ClipId] for this [DisplayListBuilder]. This allows
+ /// The current [`ClipId`] for this [DisplayListBuilder]. This allows
/// only passing the builder instead passing the containing
/// [stacking_context::StackingContextContent::Fragment] as an argument to display
/// list building functions.
- current_clip_chain_id: ClipChainId,
+ current_clip_id: ClipId,
/// A [LayoutContext] used to get information about the device pixel ratio
/// and get handles to WebRender images.
pub context: &'a LayoutContext<'a>,
- /// The [DisplayList] used to collect display list items and metadata.
- pub display_list: &'a mut DisplayList,
+ /// The [`wr::DisplayListBuilder`] for this Servo [`DisplayListBuilder`].
+ pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder,
+
+ /// The [`CompositorDisplayListInfo`] used to collect display list items and metadata.
+ pub compositor_info: &'a mut CompositorDisplayListInfo,
/// Data about the fragments that are highlighted by the inspector, if any.
///
@@ -171,6 +115,10 @@ pub(crate) struct DisplayListBuilder<'a> {
/// element inherits the `<body>`'s background to paint the page canvas background.
/// See <https://drafts.csswg.org/css-backgrounds/#body-background>.
paint_body_background: bool,
+
+ /// A mapping from [`ClipId`] To WebRender [`ClipChainId`] used when building this WebRender
+ /// display list.
+ clip_map: Vec<ClipChainId>,
}
struct InspectorHighlight {
@@ -207,45 +155,224 @@ impl InspectorHighlight {
}
}
-impl DisplayList {
- pub fn build(
- &mut self,
+impl DisplayListBuilder<'_> {
+ pub(crate) fn build(
context: &LayoutContext,
+ stacking_context_tree: &mut StackingContextTree,
fragment_tree: &FragmentTree,
- root_stacking_context: &StackingContext,
- ) {
+ debug: &DebugOptions,
+ ) -> BuiltDisplayList {
+ // Build the rest of the display list which inclues all of the WebRender primitives.
+ let compositor_info = &mut stacking_context_tree.compositor_info;
+ compositor_info.hit_test_info.clear();
+
+ let mut webrender_display_list_builder =
+ webrender_api::DisplayListBuilder::new(compositor_info.pipeline_id);
+ webrender_display_list_builder.begin();
+
+ // `dump_serialized_display_list` doesn't actually print anything. It sets up
+ // the display list for printing the serialized version when `finalize()` is called.
+ // We need to call this before adding any display items so that they are printed
+ // during `finalize()`.
+ if debug.dump_display_list {
+ webrender_display_list_builder.dump_serialized_display_list();
+ }
+
#[cfg(feature = "tracing")]
- let _span = tracing::trace_span!("display_list::build", servo_profiling = true).entered();
+ let _span =
+ tracing::trace_span!("DisplayListBuilder::build", servo_profiling = true).entered();
let mut builder = DisplayListBuilder {
- current_scroll_node_id: self.compositor_info.root_reference_frame_id,
- current_reference_frame_scroll_node_id: self.compositor_info.root_reference_frame_id,
- current_clip_chain_id: ClipChainId::INVALID,
+ current_scroll_node_id: compositor_info.root_reference_frame_id,
+ current_reference_frame_scroll_node_id: compositor_info.root_reference_frame_id,
+ current_clip_id: ClipId::INVALID,
context,
- display_list: self,
+ webrender_display_list_builder: &mut webrender_display_list_builder,
+ compositor_info,
inspector_highlight: context
.highlighted_dom_node
.map(InspectorHighlight::for_node),
paint_body_background: true,
+ clip_map: Default::default(),
};
- fragment_tree.build_display_list(&mut builder, root_stacking_context);
- if let Some(highlight) = builder
- .inspector_highlight
- .take()
- .and_then(|highlight| highlight.state)
- {
- builder.paint_dom_inspector_highlight(highlight);
+ builder.add_all_spatial_nodes();
+
+ for clip in stacking_context_tree.clip_store.0.iter() {
+ builder.add_clip_to_display_list(clip);
}
+
+ // Paint the canvas’ background (if any) before/under everything else
+ stacking_context_tree
+ .root_stacking_context
+ .build_canvas_background_display_list(&mut builder, fragment_tree);
+ stacking_context_tree
+ .root_stacking_context
+ .build_display_list(&mut builder);
+ builder.paint_dom_inspector_highlight();
+
+ webrender_display_list_builder.end().1
}
-}
-impl DisplayListBuilder<'_> {
fn wr(&mut self) -> &mut wr::DisplayListBuilder {
- &mut self.display_list.wr
+ self.webrender_display_list_builder
+ }
+
+ fn pipeline_id(&mut self) -> wr::PipelineId {
+ self.compositor_info.pipeline_id
}
fn mark_is_contentful(&mut self) {
- self.display_list.compositor_info.is_contentful = true;
+ self.compositor_info.is_contentful = true;
+ }
+
+ fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId {
+ self.compositor_info.scroll_tree.webrender_id(&id)
+ }
+
+ fn clip_chain_id(&self, id: ClipId) -> ClipChainId {
+ match id {
+ ClipId::INVALID => ClipChainId::INVALID,
+ _ => *self
+ .clip_map
+ .get(id.0)
+ .expect("Should never try to get clip before adding it to WebRender display list"),
+ }
+ }
+
+ pub(crate) fn add_all_spatial_nodes(&mut self) {
+ // A count of the number of SpatialTree nodes pushed to the WebRender display
+ // list. This is merely to ensure that the currently-unused SpatialTreeItemKey
+ // produced for every SpatialTree node is unique.
+ let mut spatial_tree_count = 0;
+ let mut scroll_tree = std::mem::take(&mut self.compositor_info.scroll_tree);
+ let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
+
+ mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
+ mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
+
+ let pipeline_id = self.pipeline_id();
+ let pipeline_tag = ((pipeline_id.0 as u64) << 32) | pipeline_id.1 as u64;
+
+ for node in scroll_tree.nodes.iter().skip(2) {
+ let parent_scroll_node_id = node
+ .parent
+ .expect("Should have already added root reference frame");
+ let parent_spatial_node_id = mapping
+ .get(parent_scroll_node_id.index)
+ .expect("Should add spatial nodes to display list in order");
+
+ // Produce a new SpatialTreeItemKey. This is currently unused by WebRender,
+ // but has to be unique to the entire scene.
+ spatial_tree_count += 1;
+ let spatial_tree_item_key = SpatialTreeItemKey::new(pipeline_tag, spatial_tree_count);
+
+ mapping.push(match &node.info {
+ SpatialTreeNodeInfo::ReferenceFrame(info) => {
+ let spatial_id = self.wr().push_reference_frame(
+ info.origin,
+ *parent_spatial_node_id,
+ info.transform_style,
+ PropertyBinding::Value(info.transform),
+ info.kind,
+ spatial_tree_item_key,
+ );
+ self.wr().pop_reference_frame();
+ spatial_id
+ },
+ SpatialTreeNodeInfo::Scroll(info) => {
+ self.wr().define_scroll_frame(
+ *parent_spatial_node_id,
+ info.external_id,
+ info.content_rect,
+ info.clip_rect,
+ LayoutVector2D::zero(), /* external_scroll_offset */
+ 0, /* scroll_offset_generation */
+ wr::HasScrollLinkedEffect::No,
+ spatial_tree_item_key,
+ )
+ },
+ SpatialTreeNodeInfo::Sticky(info) => {
+ self.wr().define_sticky_frame(
+ *parent_spatial_node_id,
+ info.frame_rect,
+ info.margins,
+ info.vertical_offset_bounds,
+ info.horizontal_offset_bounds,
+ LayoutVector2D::zero(), /* previously_applied_offset */
+ spatial_tree_item_key,
+ None, /* transform */
+ )
+ },
+ });
+ }
+
+ scroll_tree.update_mapping(mapping);
+ self.compositor_info.scroll_tree = scroll_tree;
+ }
+
+ /// Add the given [`Clip`] to the WebRender display list and create a mapping from
+ /// its [`ClipId`] to a WebRender [`ClipChainId`]. This happens:
+ /// - When WebRender display list construction starts: All clips created during the
+ /// `StackingContextTree` construction are added in one batch. These clips are used
+ /// for things such as `overflow: scroll` elements.
+ /// - When a clip is added during WebRender display list construction for individual
+ /// items. In that case, this is called by [`Self::maybe_create_clip`].
+ pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
+ assert_eq!(
+ clip.id.0,
+ self.clip_map.len(),
+ "Clips should be added in order"
+ );
+
+ let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
+ let new_clip_id = if clip.radii.is_zero() {
+ self.wr().define_clip_rect(spatial_id, clip.rect)
+ } else {
+ self.wr().define_clip_rounded_rect(
+ spatial_id,
+ ComplexClipRegion {
+ rect: clip.rect,
+ radii: clip.radii,
+ mode: ClipMode::Clip,
+ },
+ )
+ };
+
+ // WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be
+ // used for primitives, but `None` is used for stacking contexts and clip chains. We convert
+ // to the `Option<ClipChainId>` representation here. Just passing Some(ClipChainId::INVALID)
+ // leads to a crash.
+ let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
+ ClipChainId::INVALID => None,
+ parent => Some(parent),
+ };
+ let clip_chain_id = self
+ .wr()
+ .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
+ self.clip_map.push(clip_chain_id);
+ clip_chain_id
+ }
+
+ /// Add a new clip to the WebRender display list being built. This only happens during
+ /// WebRender display list building and these clips should be added after all clips
+ /// from the `StackingContextTree` have already been processed.
+ fn maybe_create_clip(
+ &mut self,
+ radii: wr::BorderRadius,
+ rect: units::LayoutRect,
+ force_clip_creation: bool,
+ ) -> Option<ClipChainId> {
+ if radii.is_zero() && !force_clip_creation {
+ return None;
+ }
+
+ Some(self.add_clip_to_display_list(&Clip {
+ id: ClipId(self.clip_map.len()),
+ radii,
+ rect,
+ parent_scroll_node_id: self.current_scroll_node_id,
+ parent_clip_id: self.current_clip_id,
+ }))
}
fn common_properties(
@@ -258,8 +385,8 @@ impl DisplayListBuilder<'_> {
// for fragments that paint their entire border rectangle.
wr::CommonItemProperties {
clip_rect,
- spatial_id: self.current_scroll_node_id.spatial_id,
- clip_chain_id: self.current_clip_chain_id,
+ spatial_id: self.spatial_id(self.current_scroll_node_id),
+ clip_chain_id: self.clip_chain_id(self.current_clip_id),
flags: style.get_webrender_primitive_flags(),
}
}
@@ -277,19 +404,24 @@ impl DisplayListBuilder<'_> {
return None;
}
- let hit_test_index = self.display_list.compositor_info.add_hit_test_info(
+ let hit_test_index = self.compositor_info.add_hit_test_info(
tag?.node.0 as u64,
Some(cursor(inherited_ui.cursor.keyword, auto_cursor)),
self.current_scroll_node_id,
);
- Some((
- hit_test_index as u64,
- self.display_list.compositor_info.epoch.as_u16(),
- ))
+ Some((hit_test_index as u64, self.compositor_info.epoch.as_u16()))
}
/// Draw highlights around the node that is currently hovered in the devtools.
- fn paint_dom_inspector_highlight(&mut self, highlight: HighlightTraversalState) {
+ fn paint_dom_inspector_highlight(&mut self) {
+ let Some(highlight) = self
+ .inspector_highlight
+ .take()
+ .and_then(|highlight| highlight.state)
+ else {
+ return;
+ };
+
const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
r: 0.23,
g: 0.7,
@@ -327,8 +459,7 @@ impl DisplayListBuilder<'_> {
flags: wr::PrimitiveFlags::default(),
};
- self.display_list
- .wr
+ self.wr()
.push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
// Highlight margin, border and padding
@@ -442,12 +573,14 @@ impl Fragment {
is_hit_test_for_scrollable_overflow: bool,
is_collapsed_table_borders: bool,
) {
+ let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
+ let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
if let Some(inspector_highlight) = &mut builder.inspector_highlight {
if self.tag() == Some(inspector_highlight.tag) {
inspector_highlight.register_fragment_of_highlighted_dom_node(
self,
- builder.current_scroll_node_id.spatial_id,
- builder.current_clip_chain_id,
+ spatial_id,
+ clip_chain_id,
containing_block,
);
}
@@ -574,8 +707,8 @@ impl Fragment {
None => return,
};
- let clip_chain_id = builder.current_clip_chain_id;
- let spatial_id = builder.current_scroll_node_id.spatial_id;
+ let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
+ let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
builder.wr().push_hit_test(
rect.to_webrender(),
clip_chain_id,
@@ -765,8 +898,9 @@ impl Fragment {
if text_decoration_style == ComputedTextDecorationStyle::MozNone {
return;
}
- builder.display_list.wr.push_line(
- &builder.common_properties(rect, parent_style),
+ let common_properties = builder.common_properties(rect, parent_style);
+ builder.wr().push_line(
+ &common_properties,
&rect,
wavy_line_thickness,
wr::LineOrientation::Horizontal,
@@ -878,12 +1012,8 @@ impl<'a> BuilderForBoxFragment<'a> {
return Some(clip);
}
- let maybe_clip = create_clip_chain(
- self.border_radius,
- self.border_rect,
- builder,
- force_clip_creation,
- );
+ let maybe_clip =
+ builder.maybe_create_clip(self.border_radius, self.border_rect, force_clip_creation);
*self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
maybe_clip
}
@@ -899,7 +1029,7 @@ impl<'a> BuilderForBoxFragment<'a> {
let radii = inner_radii(self.border_radius, self.fragment.border.to_webrender());
let maybe_clip =
- create_clip_chain(radii, *self.padding_rect(), builder, force_clip_creation);
+ builder.maybe_create_clip(radii, *self.padding_rect(), force_clip_creation);
*self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
maybe_clip
}
@@ -918,7 +1048,7 @@ impl<'a> BuilderForBoxFragment<'a> {
(self.fragment.border + self.fragment.padding).to_webrender(),
);
let maybe_clip =
- create_clip_chain(radii, *self.content_rect(), builder, force_clip_creation);
+ builder.maybe_create_clip(radii, *self.content_rect(), force_clip_creation);
*self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
maybe_clip
}
@@ -1553,38 +1683,6 @@ fn offset_radii(mut radii: wr::BorderRadius, offset: f32) -> wr::BorderRadius {
radii
}
-fn create_clip_chain(
- radii: wr::BorderRadius,
- rect: units::LayoutRect,
- builder: &mut DisplayListBuilder,
- force_clip_creation: bool,
-) -> Option<ClipChainId> {
- if radii.is_zero() && !force_clip_creation {
- return None;
- }
-
- let spatial_id = builder.current_scroll_node_id.spatial_id;
- let parent_clip_chain_id = builder.current_clip_chain_id;
- let new_clip_id = if radii.is_zero() {
- builder.wr().define_clip_rect(spatial_id, rect)
- } else {
- builder.wr().define_clip_rounded_rect(
- spatial_id,
- wr::ComplexClipRegion {
- rect,
- radii,
- mode: wr::ClipMode::Clip,
- },
- )
- };
-
- Some(
- builder
- .display_list
- .define_clip_chain(parent_clip_chain_id, [new_clip_id]),
- )
-}
-
/// Resolve the WebRender border-image outset area from the style values.
fn resolve_border_image_outset(
outset: BorderImageOutset,
diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs
index 27fa73a680c..bcd882e3fcc 100644
--- a/components/layout/display_list/stacking_context.rs
+++ b/components/layout/display_list/stacking_context.rs
@@ -9,18 +9,19 @@ use std::mem;
use app_units::Au;
use base::id::ScrollTreeNodeId;
use base::print_tree::PrintTree;
-use compositing_traits::display_list::{AxesScrollSensitivity, ScrollableNodeInfo};
+use compositing_traits::display_list::{
+ AxesScrollSensitivity, CompositorDisplayListInfo, ReferenceFrameNodeInfo, ScrollableNodeInfo,
+ SpatialTreeNodeInfo, StickyNodeInfo,
+};
use euclid::SideOffsets2D;
use euclid::default::{Point2D, Rect, Size2D};
use log::warn;
-use servo_arc::Arc as ServoArc;
use servo_config::opts::DebugOptions;
use style::Zero;
use style::computed_values::float::T as ComputedFloat;
use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
use style::computed_values::overflow_x::T as ComputedOverflow;
use style::computed_values::position::T as ComputedPosition;
-use style::properties::ComputedValues;
use style::values::computed::angle::Angle;
use style::values::computed::basic_shape::ClipPath;
use style::values::computed::{ClipRectOrAuto, Length};
@@ -29,11 +30,12 @@ use style::values::generics::transform::{self, GenericRotate, GenericScale, Gene
use style::values::specified::box_::DisplayOutside;
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D};
use webrender_api::{self as wr, BorderRadius};
+use wr::StickyOffsetBounds;
use wr::units::{LayoutPixel, LayoutSize};
-use wr::{ClipChainId, SpatialTreeItemKey, StickyOffsetBounds};
-use super::DisplayList;
-use super::clip_path::build_clip_path_clip_chain_if_necessary;
+use super::ClipId;
+use super::clip::StackingContextTreeClipStore;
+use crate::ArcRefCell;
use crate::display_list::conversions::{FilterToWebRender, ToWebRender};
use crate::display_list::{BuilderForBoxFragment, DisplayListBuilder, offset_radii};
use crate::fragment_tree::{
@@ -54,9 +56,8 @@ pub(crate) struct ContainingBlock {
/// frame and sticky positioning isn't taken into account.
scroll_frame_size: Option<LayoutSize>,
- /// The WebRender ClipId to use for this children of this containing
- /// block.
- clip_chain_id: wr::ClipChainId,
+ /// The [`ClipId`] to use for the children of this containing block.
+ clip_id: ClipId,
/// The physical rect of this containing block.
rect: PhysicalRect<Au>,
@@ -67,12 +68,12 @@ impl ContainingBlock {
rect: PhysicalRect<Au>,
scroll_node_id: ScrollTreeNodeId,
scroll_frame_size: Option<LayoutSize>,
- clip_chain_id: wr::ClipChainId,
+ clip_id: ClipId,
) -> Self {
ContainingBlock {
scroll_node_id,
scroll_frame_size,
- clip_chain_id,
+ clip_id,
rect,
}
}
@@ -95,40 +96,56 @@ pub(crate) enum StackingContextSection {
Outline,
}
-impl DisplayList {
- /// Produce a new SpatialTreeItemKey. This is currently unused by WebRender,
- /// but has to be unique to the entire scene.
- fn get_next_spatial_tree_item_key(&mut self) -> SpatialTreeItemKey {
- self.spatial_tree_count += 1;
- let pipeline_tag = ((self.wr.pipeline_id.0 as u64) << 32) | self.wr.pipeline_id.1 as u64;
- SpatialTreeItemKey::new(pipeline_tag, self.spatial_tree_count)
- }
+pub(crate) struct StackingContextTree {
+ /// The root stacking context of this [`StackingContextTree`].
+ pub root_stacking_context: StackingContext,
- #[cfg_attr(
- feature = "tracing",
- tracing::instrument(
- name = "display_list::build_stacking_context_tree",
- skip_all,
- fields(servo_profiling = true),
- level = "trace",
- )
- )]
- pub fn build_stacking_context_tree(
- &mut self,
+ /// The information about the WebRender display list that the compositor
+ /// consumes. This curerntly contains the out-of-band hit testing information
+ /// data structure that the compositor uses to map hit tests to information
+ /// about the item hit.
+ pub compositor_info: CompositorDisplayListInfo,
+
+ /// All of the clips collected for this [`StackingContextTree`]. These are added
+ /// for things like `overflow`. More clips may be created later during WebRender
+ /// display list construction, but they are never added here.
+ pub clip_store: StackingContextTreeClipStore,
+}
+
+impl StackingContextTree {
+ /// Create a new [DisplayList] given the dimensions of the layout and the WebRender
+ /// pipeline id.
+ pub fn new(
fragment_tree: &FragmentTree,
+ viewport_size: LayoutSize,
+ content_size: LayoutSize,
+ pipeline_id: wr::PipelineId,
+ viewport_scroll_sensitivity: AxesScrollSensitivity,
+ first_reflow: bool,
debug: &DebugOptions,
- ) -> StackingContext {
+ ) -> Self {
+ let compositor_info = CompositorDisplayListInfo::new(
+ viewport_size,
+ content_size,
+ pipeline_id,
+ // This epoch is set when the WebRender display list is built. For now use a dummy value.
+ wr::Epoch(0),
+ viewport_scroll_sensitivity,
+ first_reflow,
+ );
+
+ let root_scroll_node_id = compositor_info.root_scroll_node_id;
let cb_for_non_fixed_descendants = ContainingBlock::new(
fragment_tree.initial_containing_block,
- self.compositor_info.root_scroll_node_id,
- Some(self.compositor_info.viewport_size),
- ClipChainId::INVALID,
+ root_scroll_node_id,
+ Some(compositor_info.viewport_size),
+ ClipId::INVALID,
);
let cb_for_fixed_descendants = ContainingBlock::new(
fragment_tree.initial_containing_block,
- self.compositor_info.root_reference_frame_id,
+ compositor_info.root_reference_frame_id,
None,
- ClipChainId::INVALID,
+ ClipId::INVALID,
);
// We need to specify all three containing blocks here, because absolute
@@ -143,17 +160,31 @@ impl DisplayList {
for_absolute_and_fixed_descendants: &cb_for_fixed_descendants,
};
- let mut root_stacking_context = StackingContext::create_root(&self.wr, debug);
+ let mut stacking_context_tree = Self {
+ // This is just a temporary value that will be replaced once we have finished building the tree.
+ root_stacking_context: StackingContext::create_root(root_scroll_node_id, debug),
+ compositor_info,
+ clip_store: Default::default(),
+ };
+
+ let mut root_stacking_context = StackingContext::create_root(root_scroll_node_id, debug);
for fragment in &fragment_tree.root_fragments {
fragment.build_stacking_context_tree(
- self,
+ &mut stacking_context_tree,
&containing_block_info,
&mut root_stacking_context,
StackingContextBuildMode::SkipHoisted,
);
}
root_stacking_context.sort();
- root_stacking_context
+
+ if debug.dump_stacking_context_tree {
+ root_stacking_context.debug_print();
+ }
+
+ stacking_context_tree.root_stacking_context = root_stacking_context;
+
+ stacking_context_tree
}
fn push_reference_frame(
@@ -161,53 +192,20 @@ impl DisplayList {
origin: LayoutPoint,
parent_scroll_node_id: &ScrollTreeNodeId,
transform_style: wr::TransformStyle,
- transform: wr::PropertyBinding<LayoutTransform>,
+ transform: LayoutTransform,
kind: wr::ReferenceFrameKind,
) -> ScrollTreeNodeId {
- let spatial_tree_item_key = self.get_next_spatial_tree_item_key();
- let new_spatial_id = self.wr.push_reference_frame(
- origin,
- parent_scroll_node_id.spatial_id,
- transform_style,
- transform,
- kind,
- spatial_tree_item_key,
- );
self.compositor_info.scroll_tree.add_scroll_tree_node(
Some(parent_scroll_node_id),
- new_spatial_id,
- None,
+ SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
+ origin,
+ transform_style,
+ transform,
+ kind,
+ }),
)
}
- fn pop_reference_frame(&mut self) {
- self.wr.pop_reference_frame();
- }
-
- fn clip_overflow_frame(
- &mut self,
- parent_scroll_node_id: &ScrollTreeNodeId,
- parent_clip_id: &ClipChainId,
- clip_rect: LayoutRect,
- radii: wr::BorderRadius,
- ) -> ClipChainId {
- let new_clip_id = if radii.is_zero() {
- self.wr
- .define_clip_rect(parent_scroll_node_id.spatial_id, clip_rect)
- } else {
- self.wr.define_clip_rounded_rect(
- parent_scroll_node_id.spatial_id,
- webrender_api::ComplexClipRegion {
- rect: clip_rect,
- radii,
- mode: webrender_api::ClipMode::Clip,
- },
- )
- };
-
- self.define_clip_chain(*parent_clip_id, [new_clip_id])
- }
-
fn define_scroll_frame(
&mut self,
parent_scroll_node_id: &ScrollTreeNodeId,
@@ -216,25 +214,12 @@ impl DisplayList {
clip_rect: LayoutRect,
scroll_sensitivity: AxesScrollSensitivity,
) -> ScrollTreeNodeId {
- let spatial_tree_item_key = self.get_next_spatial_tree_item_key();
-
- let new_spatial_id = self.wr.define_scroll_frame(
- parent_scroll_node_id.spatial_id,
- external_id,
- content_rect,
- clip_rect,
- LayoutVector2D::zero(), /* external_scroll_offset */
- 0, /* scroll_offset_generation */
- wr::HasScrollLinkedEffect::No,
- spatial_tree_item_key,
- );
-
self.compositor_info.scroll_tree.add_scroll_tree_node(
Some(parent_scroll_node_id),
- new_spatial_id,
- Some(ScrollableNodeInfo {
+ SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
external_id,
- scrollable_size: content_rect.size() - clip_rect.size(),
+ content_rect,
+ clip_rect,
scroll_sensitivity,
offset: LayoutVector2D::zero(),
}),
@@ -249,21 +234,14 @@ impl DisplayList {
vertical_offset_bounds: StickyOffsetBounds,
horizontal_offset_bounds: StickyOffsetBounds,
) -> ScrollTreeNodeId {
- let spatial_tree_item_key = self.get_next_spatial_tree_item_key();
- let new_spatial_id = self.wr.define_sticky_frame(
- parent_scroll_node_id.spatial_id,
- frame_rect,
- margins,
- vertical_offset_bounds,
- horizontal_offset_bounds,
- LayoutVector2D::zero(), /* previously_applied_offset */
- spatial_tree_item_key,
- None, /* transform */
- );
self.compositor_info.scroll_tree.add_scroll_tree_node(
Some(parent_scroll_node_id),
- new_spatial_id,
- None,
+ SpatialTreeNodeInfo::Sticky(StickyNodeInfo {
+ frame_rect,
+ margins,
+ vertical_offset_bounds,
+ horizontal_offset_bounds,
+ }),
)
}
}
@@ -277,7 +255,7 @@ pub(crate) enum StackingContextContent {
Fragment {
scroll_node_id: ScrollTreeNodeId,
reference_frame_scroll_node_id: ScrollTreeNodeId,
- clip_chain_id: wr::ClipChainId,
+ clip_id: ClipId,
section: StackingContextSection,
containing_block: PhysicalRect<Au>,
fragment: Fragment,
@@ -308,7 +286,7 @@ impl StackingContextContent {
Self::Fragment {
scroll_node_id,
reference_frame_scroll_node_id,
- clip_chain_id,
+ clip_id,
section,
containing_block,
fragment,
@@ -317,7 +295,7 @@ impl StackingContextContent {
} => {
builder.current_scroll_node_id = *scroll_node_id;
builder.current_reference_frame_scroll_node_id = *reference_frame_scroll_node_id;
- builder.current_clip_chain_id = *clip_chain_id;
+ builder.current_clip_id = *clip_id;
fragment.build_display_list(
builder,
containing_block,
@@ -349,16 +327,14 @@ pub(crate) enum StackingContextType {
pub struct StackingContext {
/// The spatial id of this fragment. This is used to properly handle
/// things like preserve-3d.
- spatial_id: wr::SpatialId,
+ scroll_tree_node_id: ScrollTreeNodeId,
/// The clip chain id of this stacking context if it has one. Used for filter clipping.
- clip_chain_id: Option<wr::ClipChainId>,
-
- /// The style of the fragment that established this stacking context.
- initializing_fragment_style: Option<ServoArc<ComputedValues>>,
+ clip_id: Option<ClipId>,
- /// The [`FragmentFlags`] of the [`Fragment`] that established this stacking context.
- initializing_fragment_flags: FragmentFlags,
+ /// The [`BoxFragment`] that established this stacking context. We store the fragment here
+ /// rather than just the style, so that incremental layout can automatically update the style.
+ initializing_fragment: Option<ArcRefCell<BoxFragment>>,
/// The type of this stacking context. Used for collecting and sorting.
context_type: StackingContextType,
@@ -415,25 +391,23 @@ pub enum DebugPrintField {
impl StackingContext {
fn create_descendant(
&self,
- spatial_id: wr::SpatialId,
- clip_chain_id: wr::ClipChainId,
- initializing_fragment_style: ServoArc<ComputedValues>,
- initializing_fragment_flags: FragmentFlags,
+ spatial_id: ScrollTreeNodeId,
+ clip_id: ClipId,
+ initializing_fragment: ArcRefCell<BoxFragment>,
context_type: StackingContextType,
) -> Self {
// WebRender has two different ways of expressing "no clip." ClipChainId::INVALID should be
// used for primitives, but `None` is used for stacking contexts and clip chains. We convert
- // to the `Option<ClipChainId>` representation here. Just passing Some(ClipChainId::INVALID)
+ // to the `Option<ClipId>` representation here. Just passing Some(ClipChainId::INVALID)
// leads to a crash.
- let clip_chain_id: Option<ClipChainId> = match clip_chain_id {
- ClipChainId::INVALID => None,
- clip_chain_id => Some(clip_chain_id),
+ let clip_id = match clip_id {
+ ClipId::INVALID => None,
+ clip_id => Some(clip_id),
};
Self {
- spatial_id,
- clip_chain_id,
- initializing_fragment_style: Some(initializing_fragment_style),
- initializing_fragment_flags,
+ scroll_tree_node_id: spatial_id,
+ clip_id,
+ initializing_fragment: Some(initializing_fragment),
context_type,
contents: vec![],
real_stacking_contexts_and_positioned_stacking_containers: vec![],
@@ -443,12 +417,11 @@ impl StackingContext {
}
}
- pub(crate) fn create_root(wr: &wr::DisplayListBuilder, debug: &DebugOptions) -> Self {
+ fn create_root(root_scroll_node_id: ScrollTreeNodeId, debug: &DebugOptions) -> Self {
Self {
- spatial_id: wr::SpaceAndClipInfo::root_scroll(wr.pipeline_id).spatial_id,
- clip_chain_id: None,
- initializing_fragment_style: None,
- initializing_fragment_flags: FragmentFlags::empty(),
+ scroll_tree_node_id: root_scroll_node_id,
+ clip_id: None,
+ initializing_fragment: None,
context_type: StackingContextType::RealStackingContext,
contents: vec![],
real_stacking_contexts_and_positioned_stacking_containers: vec![],
@@ -476,11 +449,10 @@ impl StackingContext {
}
fn z_index(&self) -> i32 {
- self.initializing_fragment_style
- .as_ref()
- .map_or(0, |style| {
- style.effective_z_index(self.initializing_fragment_flags)
- })
+ self.initializing_fragment.as_ref().map_or(0, |fragment| {
+ let fragment = fragment.borrow();
+ fragment.style.effective_z_index(fragment.base.flags)
+ })
}
pub(crate) fn sort(&mut self) {
@@ -519,13 +491,14 @@ impl StackingContext {
&self,
builder: &mut DisplayListBuilder,
) -> bool {
- let style = match self.initializing_fragment_style.as_ref() {
- Some(style) => style,
+ let fragment = match self.initializing_fragment.as_ref() {
+ Some(fragment) => fragment.borrow(),
None => return false,
};
// WebRender only uses the stacking context to apply certain effects. If we don't
// actually need to create a stacking context, just avoid creating one.
+ let style = &fragment.style;
let effects = style.get_effects();
if effects.filter.0.is_empty() &&
effects.opacity == 1.0 &&
@@ -557,11 +530,13 @@ impl StackingContext {
// This will require additional tracking during layout
// before we start collecting stacking contexts so that
// information will be available when we reach this point.
+ let spatial_id = builder.spatial_id(self.scroll_tree_node_id);
+ let clip_chain_id = self.clip_id.map(|clip_id| builder.clip_chain_id(clip_id));
builder.wr().push_stacking_context(
LayoutPoint::zero(), // origin
- self.spatial_id,
+ spatial_id,
style.get_webrender_primitive_flags(),
- self.clip_chain_id,
+ clip_chain_id,
style.get_used_transform_style().to_webrender(),
effects.mix_blend_mode.to_webrender(),
&filters,
@@ -635,10 +610,7 @@ impl StackingContext {
if background_color.alpha > 0.0 {
let common = builder.common_properties(painting_area, &source_style);
let color = super::rgba(background_color);
- builder
- .display_list
- .wr
- .push_rect(&common, painting_area, color)
+ builder.wr().push_rect(&common, painting_area, color)
}
let mut fragment_builder = BuilderForBoxFragment::new(
@@ -741,7 +713,7 @@ impl StackingContext {
}
if pushed_context {
- builder.display_list.wr.pop_stacking_context();
+ builder.wr().pop_stacking_context();
}
}
@@ -824,7 +796,7 @@ pub(crate) enum StackingContextBuildMode {
impl Fragment {
pub(crate) fn build_stacking_context_tree(
&self,
- display_list: &mut DisplayList,
+ stacking_context_tree: &mut StackingContextTree,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
mode: StackingContextBuildMode,
@@ -852,7 +824,7 @@ impl Fragment {
fragment.build_stacking_context_tree(
fragment_clone,
- display_list,
+ stacking_context_tree,
containing_block,
containing_block_info,
stacking_context,
@@ -866,7 +838,7 @@ impl Fragment {
};
fragment_ref.build_stacking_context_tree(
- display_list,
+ stacking_context_tree,
containing_block_info,
stacking_context,
StackingContextBuildMode::IncludeHoisted,
@@ -875,7 +847,7 @@ impl Fragment {
Fragment::Positioning(fragment) => {
let fragment = fragment.borrow();
fragment.build_stacking_context_tree(
- display_list,
+ stacking_context_tree,
containing_block,
containing_block_info,
stacking_context,
@@ -890,7 +862,7 @@ impl Fragment {
reference_frame_scroll_node_id: containing_block_info
.for_absolute_and_fixed_descendants
.scroll_node_id,
- clip_chain_id: containing_block.clip_chain_id,
+ clip_id: containing_block.clip_id,
containing_block: containing_block.rect,
fragment: fragment_clone,
is_hit_test_for_scrollable_overflow: false,
@@ -912,7 +884,7 @@ struct ScrollFrameData {
}
struct OverflowFrameData {
- clip_chain_id: wr::ClipChainId,
+ clip_id: ClipId,
scroll_frame_data: Option<ScrollFrameData>,
}
@@ -953,14 +925,14 @@ impl BoxFragment {
fn build_stacking_context_tree(
&self,
fragment: Fragment,
- display_list: &mut DisplayList,
+ stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
parent_stacking_context: &mut StackingContext,
) {
self.build_stacking_context_tree_maybe_creating_reference_frame(
fragment,
- display_list,
+ stacking_context_tree,
containing_block,
containing_block_info,
parent_stacking_context,
@@ -970,7 +942,7 @@ impl BoxFragment {
fn build_stacking_context_tree_maybe_creating_reference_frame(
&self,
fragment: Fragment,
- display_list: &mut DisplayList,
+ stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
parent_stacking_context: &mut StackingContext,
@@ -981,7 +953,7 @@ impl BoxFragment {
None => {
return self.build_stacking_context_tree_maybe_creating_stacking_context(
fragment,
- display_list,
+ stacking_context_tree,
containing_block,
containing_block_info,
parent_stacking_context,
@@ -989,11 +961,11 @@ impl BoxFragment {
},
};
- let new_spatial_id = display_list.push_reference_frame(
+ let new_spatial_id = stacking_context_tree.push_reference_frame(
reference_frame_data.origin.to_webrender(),
&containing_block.scroll_node_id,
self.style.get_box().transform_style.to_webrender(),
- wr::PropertyBinding::Value(reference_frame_data.transform),
+ reference_frame_data.transform,
reference_frame_data.kind,
);
@@ -1016,26 +988,24 @@ impl BoxFragment {
.translate(-reference_frame_data.origin.to_vector()),
new_spatial_id,
None,
- containing_block.clip_chain_id,
+ containing_block.clip_id,
);
let new_containing_block_info =
containing_block_info.new_for_non_absolute_descendants(&adjusted_containing_block);
self.build_stacking_context_tree_maybe_creating_stacking_context(
fragment,
- display_list,
+ stacking_context_tree,
&adjusted_containing_block,
&new_containing_block_info,
parent_stacking_context,
);
-
- display_list.pop_reference_frame();
}
fn build_stacking_context_tree_maybe_creating_stacking_context(
&self,
fragment: Fragment,
- display_list: &mut DisplayList,
+ stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
parent_stacking_context: &mut StackingContext,
@@ -1045,7 +1015,7 @@ impl BoxFragment {
None => {
self.build_stacking_context_tree_for_children(
fragment,
- display_list,
+ stacking_context_tree,
containing_block,
containing_block_info,
parent_stacking_context,
@@ -1068,30 +1038,37 @@ impl BoxFragment {
// `clip-path` needs to be applied before filters and creates a stacking context, so it can be
// applied directly to the stacking context itself.
// before
- let stacking_context_clip_chain_id = build_clip_path_clip_chain_if_necessary(
- self.style.clone_clip_path(),
- display_list,
- &containing_block.scroll_node_id,
- &containing_block.clip_chain_id,
- BuilderForBoxFragment::new(
- self,
- &containing_block.rect,
- false, /* is_hit_test_for_scrollable_overflow */
- false, /* is_collapsed_table_borders */
- ),
- )
- .unwrap_or(containing_block.clip_chain_id);
+ let stacking_context_clip_id = stacking_context_tree
+ .clip_store
+ .add_for_clip_path(
+ self.style.clone_clip_path(),
+ &containing_block.scroll_node_id,
+ &containing_block.clip_id,
+ BuilderForBoxFragment::new(
+ self,
+ &containing_block.rect,
+ false, /* is_hit_test_for_scrollable_overflow */
+ false, /* is_collapsed_table_borders */
+ ),
+ )
+ .unwrap_or(containing_block.clip_id);
+
+ let box_fragment = match fragment {
+ Fragment::Box(ref box_fragment) | Fragment::Float(ref box_fragment) => {
+ box_fragment.clone()
+ },
+ _ => unreachable!("Should never try to make stacking context for non-BoxFragment"),
+ };
let mut child_stacking_context = parent_stacking_context.create_descendant(
- containing_block.scroll_node_id.spatial_id,
- stacking_context_clip_chain_id,
- self.style.clone(),
- self.base.flags,
+ containing_block.scroll_node_id,
+ stacking_context_clip_id,
+ box_fragment,
context_type,
);
self.build_stacking_context_tree_for_children(
fragment,
- display_list,
+ stacking_context_tree,
containing_block,
containing_block_info,
&mut child_stacking_context,
@@ -1116,19 +1093,19 @@ impl BoxFragment {
fn build_stacking_context_tree_for_children(
&self,
fragment: Fragment,
- display_list: &mut DisplayList,
+ stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
) {
let mut new_scroll_node_id = containing_block.scroll_node_id;
- let mut new_clip_chain_id = containing_block.clip_chain_id;
+ let mut new_clip_id = containing_block.clip_id;
let mut new_scroll_frame_size = containing_block_info
.for_non_absolute_descendants
.scroll_frame_size;
if let Some(scroll_node_id) = self.build_sticky_frame_if_necessary(
- display_list,
+ stacking_context_tree,
&new_scroll_node_id,
&containing_block.rect,
&new_scroll_frame_size,
@@ -1136,20 +1113,19 @@ impl BoxFragment {
new_scroll_node_id = scroll_node_id;
}
- if let Some(clip_chain_id) = self.build_clip_frame_if_necessary(
- display_list,
+ if let Some(clip_id) = self.build_clip_frame_if_necessary(
+ stacking_context_tree,
&new_scroll_node_id,
- &new_clip_chain_id,
+ new_clip_id,
&containing_block.rect,
) {
- new_clip_chain_id = clip_chain_id;
+ new_clip_id = clip_id;
}
- if let Some(clip_chain_id) = build_clip_path_clip_chain_if_necessary(
+ if let Some(clip_id) = stacking_context_tree.clip_store.add_for_clip_path(
self.style.clone_clip_path(),
- display_list,
&new_scroll_node_id,
- &new_clip_chain_id,
+ &new_clip_id,
BuilderForBoxFragment::new(
self,
&containing_block.rect,
@@ -1157,7 +1133,7 @@ impl BoxFragment {
false, /* is_collapsed_table_borders */
),
) {
- new_clip_chain_id = clip_chain_id;
+ new_clip_id = clip_id;
}
let establishes_containing_block_for_all_descendants = self
@@ -1182,7 +1158,7 @@ impl BoxFragment {
.push(StackingContextContent::Fragment {
scroll_node_id: new_scroll_node_id,
reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments,
- clip_chain_id: new_clip_chain_id,
+ clip_id: new_clip_id,
section,
containing_block: containing_block.rect,
fragment: fragment.clone(),
@@ -1200,12 +1176,12 @@ impl BoxFragment {
// We want to build the scroll frame after the background and border, because
// they shouldn't scroll with the rest of the box content.
if let Some(overflow_frame_data) = self.build_overflow_frame_if_necessary(
- display_list,
+ stacking_context_tree,
&new_scroll_node_id,
- &new_clip_chain_id,
+ new_clip_id,
&containing_block.rect,
) {
- new_clip_chain_id = overflow_frame_data.clip_chain_id;
+ new_clip_id = overflow_frame_data.clip_id;
if let Some(scroll_frame_data) = overflow_frame_data.scroll_frame_data {
new_scroll_node_id = scroll_frame_data.scroll_tree_node_id;
new_scroll_frame_size = Some(scroll_frame_data.scroll_frame_rect.size());
@@ -1216,7 +1192,7 @@ impl BoxFragment {
scroll_node_id: new_scroll_node_id,
reference_frame_scroll_node_id:
reference_frame_scroll_node_id_for_fragments,
- clip_chain_id: new_clip_chain_id,
+ clip_id: new_clip_id,
section,
containing_block: containing_block.rect,
fragment: fragment.clone(),
@@ -1237,13 +1213,13 @@ impl BoxFragment {
padding_rect,
new_scroll_node_id,
new_scroll_frame_size,
- new_clip_chain_id,
+ new_clip_id,
);
let for_non_absolute_descendants = ContainingBlock::new(
content_rect,
new_scroll_node_id,
new_scroll_frame_size,
- new_clip_chain_id,
+ new_clip_id,
);
// Create a new `ContainingBlockInfo` for descendants depending on
@@ -1265,7 +1241,7 @@ impl BoxFragment {
for child in &self.children {
child.build_stacking_context_tree(
- display_list,
+ stacking_context_tree,
&new_containing_block_info,
stacking_context,
StackingContextBuildMode::SkipHoisted,
@@ -1281,7 +1257,7 @@ impl BoxFragment {
.push(StackingContextContent::Fragment {
scroll_node_id: new_scroll_node_id,
reference_frame_scroll_node_id: reference_frame_scroll_node_id_for_fragments,
- clip_chain_id: new_clip_chain_id,
+ clip_id: new_clip_id,
section,
containing_block: containing_block.rect,
fragment: fragment.clone(),
@@ -1293,11 +1269,11 @@ impl BoxFragment {
fn build_clip_frame_if_necessary(
&self,
- display_list: &mut DisplayList,
+ stacking_context_tree: &mut StackingContextTree,
parent_scroll_node_id: &ScrollTreeNodeId,
- parent_clip_chain_id: &wr::ClipChainId,
+ parent_clip_id: ClipId,
containing_block_rect: &PhysicalRect<Au>,
- ) -> Option<wr::ClipChainId> {
+ ) -> Option<ClipId> {
let position = self.style.get_box().position;
// https://drafts.csswg.org/css2/#clipping
// The clip property applies only to absolutely positioned elements
@@ -1316,18 +1292,19 @@ impl BoxFragment {
.for_border_rect(border_rect)
.translate(containing_block_rect.origin.to_vector())
.to_webrender();
-
- let clip_id = display_list
- .wr
- .define_clip_rect(parent_scroll_node_id.spatial_id, clip_rect);
- Some(display_list.define_clip_chain(*parent_clip_chain_id, [clip_id]))
+ Some(stacking_context_tree.clip_store.add(
+ BorderRadius::zero(),
+ clip_rect,
+ *parent_scroll_node_id,
+ parent_clip_id,
+ ))
}
fn build_overflow_frame_if_necessary(
&self,
- display_list: &mut DisplayList,
+ stacking_context_tree: &mut StackingContextTree,
parent_scroll_node_id: &ScrollTreeNodeId,
- parent_clip_chain_id: &wr::ClipChainId,
+ parent_clip_id: ClipId,
containing_block_rect: &PhysicalRect<Au>,
) -> Option<OverflowFrameData> {
let overflow = self.style.effective_overflow(self.base.flags);
@@ -1367,15 +1344,15 @@ impl BoxFragment {
radii = BorderRadius::zero();
}
- let clip_chain_id = display_list.clip_overflow_frame(
- parent_scroll_node_id,
- parent_clip_chain_id,
- overflow_clip_rect,
+ let clip_id = stacking_context_tree.clip_store.add(
radii,
+ overflow_clip_rect,
+ *parent_scroll_node_id,
+ parent_clip_id,
);
return Some(OverflowFrameData {
- clip_chain_id,
+ clip_id,
scroll_frame_data: None,
});
}
@@ -1404,17 +1381,17 @@ impl BoxFragment {
.translate(containing_block_rect.origin.to_vector())
.to_webrender();
- let clip_chain_id = display_list.clip_overflow_frame(
- parent_scroll_node_id,
- parent_clip_chain_id,
- scroll_frame_rect,
+ let clip_id = stacking_context_tree.clip_store.add(
BuilderForBoxFragment::new(self, containing_block_rect, false, false).border_radius,
+ scroll_frame_rect,
+ *parent_scroll_node_id,
+ parent_clip_id,
);
let tag = self.base.tag?;
let external_id = wr::ExternalScrollId(
tag.to_display_list_fragment_id(),
- display_list.wr.pipeline_id,
+ stacking_context_tree.compositor_info.pipeline_id,
);
let sensitivity = AxesScrollSensitivity {
@@ -1424,7 +1401,7 @@ impl BoxFragment {
let content_rect = self.reachable_scrollable_overflow_region().to_webrender();
- let scroll_tree_node_id = display_list.define_scroll_frame(
+ let scroll_tree_node_id = stacking_context_tree.define_scroll_frame(
parent_scroll_node_id,
external_id,
content_rect,
@@ -1433,7 +1410,7 @@ impl BoxFragment {
);
Some(OverflowFrameData {
- clip_chain_id,
+ clip_id,
scroll_frame_data: Some(ScrollFrameData {
scroll_tree_node_id,
scroll_frame_rect,
@@ -1443,7 +1420,7 @@ impl BoxFragment {
fn build_sticky_frame_if_necessary(
&self,
- display_list: &mut DisplayList,
+ stacking_context_tree: &mut StackingContextTree,
parent_scroll_node_id: &ScrollTreeNodeId,
containing_block_rect: &PhysicalRect<Au>,
scroll_frame_size: &Option<LayoutSize>,
@@ -1456,7 +1433,7 @@ impl BoxFragment {
Some(size) => size,
None => {
// This is a direct descendant of a reference frame.
- &display_list.compositor_info.viewport_size
+ &stacking_context_tree.compositor_info.viewport_size
},
};
@@ -1513,7 +1490,7 @@ impl BoxFragment {
offsets.left.non_auto().map(|v| v.to_f32_px()),
);
- let sticky_node_id = display_list.define_sticky_frame(
+ let sticky_node_id = stacking_context_tree.define_sticky_frame(
parent_scroll_node_id,
frame_rect,
margins,
@@ -1665,7 +1642,7 @@ impl BoxFragment {
impl PositioningFragment {
fn build_stacking_context_tree(
&self,
- display_list: &mut DisplayList,
+ stacking_context_tree: &mut StackingContextTree,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
@@ -1679,7 +1656,7 @@ impl PositioningFragment {
for child in &self.children {
child.build_stacking_context_tree(
- display_list,
+ stacking_context_tree,
&new_containing_block_info,
stacking_context,
StackingContextBuildMode::SkipHoisted,
diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs
index 9b96b1c4fb4..b7c3a2a3524 100644
--- a/components/layout/fragment_tree/box_fragment.rs
+++ b/components/layout/fragment_tree/box_fragment.rs
@@ -92,7 +92,7 @@ pub(crate) struct BoxFragment {
pub scrollable_overflow_from_children: PhysicalRect<Au>,
/// The resolved box insets if this box is `position: sticky`. These are calculated
- /// during stacking context tree construction because they rely on the size of the
+ /// during `StackingContextTree` construction because they rely on the size of the
/// scroll container.
pub(crate) resolved_sticky_insets: AtomicRefCell<Option<PhysicalSides<AuOrAuto>>>,
diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs
index 979bd0090fc..ba03a72ac21 100644
--- a/components/layout/fragment_tree/fragment_tree.rs
+++ b/components/layout/fragment_tree/fragment_tree.rs
@@ -14,7 +14,6 @@ use webrender_api::units;
use super::{BoxFragment, ContainingBlockManager, Fragment};
use crate::ArcRefCell;
use crate::context::LayoutContext;
-use crate::display_list::StackingContext;
use crate::geom::PhysicalRect;
#[derive(MallocSizeOf)]
@@ -91,16 +90,6 @@ impl FragmentTree {
fragment_tree
}
- pub(crate) fn build_display_list(
- &self,
- builder: &mut crate::display_list::DisplayListBuilder,
- root_stacking_context: &StackingContext,
- ) {
- // Paint the canvas’ background (if any) before/under everything else
- root_stacking_context.build_canvas_background_display_list(builder, self);
- root_stacking_context.build_display_list(builder);
- }
-
pub fn print(&self) {
let mut print_tree = PrintTree::new("Fragment Tree".to_string());
for fragment in &self.root_fragments {
diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs
index fcf658036b2..b490b4a0506 100644
--- a/components/layout/layout_impl.rs
+++ b/components/layout/layout_impl.rs
@@ -77,7 +77,7 @@ use webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel, LayoutPoint, L
use webrender_api::{ExternalScrollId, HitTestFlags};
use crate::context::LayoutContext;
-use crate::display_list::{DisplayList, WebRenderImageInfo};
+use crate::display_list::{DisplayListBuilder, StackingContextTree, WebRenderImageInfo};
use crate::query::{
get_the_text_steps, process_client_rect_request, process_content_box_request,
process_content_boxes_request, process_node_scroll_area_request, process_offset_parent_query,
@@ -144,6 +144,9 @@ pub struct LayoutThread {
/// The fragment tree.
fragment_tree: RefCell<Option<Arc<FragmentTree>>>,
+ /// The [`StackingContextTree`] cached from previous layouts.
+ stacking_context_tree: RefCell<Option<StackingContextTree>>,
+
/// A counter for epoch messages
epoch: Cell<Epoch>,
@@ -521,6 +524,7 @@ impl LayoutThread {
first_reflow: Cell::new(true),
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)),
viewport_size: Size2D::new(
@@ -649,15 +653,17 @@ impl LayoutThread {
highlighted_dom_node: reflow_request.highlighted_dom_node,
};
- self.restyle_and_build_trees(
+ let did_reflow = self.restyle_and_build_trees(
&reflow_request,
root_element,
rayon_pool,
&mut layout_context,
);
+
+ self.build_stacking_context_tree(&reflow_request, did_reflow);
self.build_display_list(&reflow_request, &mut layout_context);
- self.first_reflow.set(false);
+ self.first_reflow.set(false);
if let ReflowGoal::UpdateScrollNode(scroll_state) = reflow_request.reflow_goal {
self.update_scroll_node_state(&scroll_state);
}
@@ -666,6 +672,7 @@ impl LayoutThread {
let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
let node_to_image_animation_map =
std::mem::take(&mut *layout_context.node_image_animation_map.write());
+
Some(ReflowResult {
pending_images,
iframe_sizes,
@@ -742,7 +749,7 @@ impl LayoutThread {
root_element: ServoLayoutElement<'_>,
rayon_pool: Option<&ThreadPool>,
layout_context: &mut LayoutContext<'_>,
- ) {
+ ) -> bool {
let dirty_root = unsafe {
ServoLayoutNode::new(&reflow_request.dirty_root.unwrap())
.as_element()
@@ -758,7 +765,7 @@ impl LayoutThread {
if !token.should_traverse() {
layout_context.style_context.stylist.rule_tree().maybe_gc();
- return;
+ return false;
}
let dirty_root: ServoLayoutNode =
@@ -768,7 +775,7 @@ impl LayoutThread {
let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node);
if damage == RestyleDamage::REPAINT {
layout_context.style_context.stylist.rule_tree().maybe_gc();
- return;
+ return false;
}
let mut box_tree = self.box_tree.borrow_mut();
@@ -803,8 +810,15 @@ impl LayoutThread {
run_layout()
});
+ if self.debug.dump_flow_tree {
+ fragment_tree.print();
+ }
*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;
+
if self.debug.dump_style_tree {
println!(
"{:?}",
@@ -822,75 +836,80 @@ impl LayoutThread {
// GC the rule tree if some heuristics are met.
layout_context.style_context.stylist.rule_tree().maybe_gc();
+ true
}
- fn build_display_list(
- &self,
- reflow_request: &ReflowRequest,
- layout_context: &mut LayoutContext<'_>,
- ) {
+ fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, did_reflow: bool) {
+ if !reflow_request.reflow_goal.needs_display_list() &&
+ !reflow_request.reflow_goal.needs_display()
+ {
+ return;
+ }
let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
return;
};
- if !reflow_request.reflow_goal.needs_display_list() {
+ if !did_reflow && self.stacking_context_tree.borrow().is_some() {
return;
}
- let mut epoch = self.epoch.get();
- epoch.next();
- self.epoch.set(epoch);
-
let viewport_size = LayoutSize::from_untyped(Size2D::new(
self.viewport_size.width.to_f32_px(),
self.viewport_size.height.to_f32_px(),
));
- let mut display_list = DisplayList::new(
+
+ // 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(
+ fragment_tree,
viewport_size,
fragment_tree.scrollable_overflow(),
self.id.into(),
- epoch.into(),
fragment_tree.viewport_scroll_sensitivity,
self.first_reflow.get(),
- );
- display_list.wr.begin();
-
- // `dump_serialized_display_list` doesn't actually print anything. It sets up
- // the display list for printing the serialized version when `finalize()` is called.
- // We need to call this before adding any display items so that they are printed
- // during `finalize()`.
- if self.debug.dump_display_list {
- display_list.wr.dump_serialized_display_list();
- }
+ &self.debug,
+ ));
+ }
- // Build the root stacking context. This turns the `FragmentTree` into a
- // tree of fragments in CSS painting order and also creates all
- // applicable spatial and clip nodes.
- let root_stacking_context =
- display_list.build_stacking_context_tree(fragment_tree, &self.debug);
+ fn build_display_list(
+ &self,
+ reflow_request: &ReflowRequest,
+ layout_context: &mut LayoutContext<'_>,
+ ) {
+ if !reflow_request.reflow_goal.needs_display() {
+ return;
+ }
+ let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
+ return;
+ };
- // Build the rest of the display list which inclues all of the WebRender primitives.
- display_list.build(layout_context, fragment_tree, &root_stacking_context);
+ let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
+ let Some(stacking_context_tree) = stacking_context_tree.as_mut() else {
+ return;
+ };
- if self.debug.dump_flow_tree {
- fragment_tree.print();
- }
- if self.debug.dump_stacking_context_tree {
- root_stacking_context.debug_print();
- }
+ let mut epoch = self.epoch.get();
+ epoch.next();
+ self.epoch.set(epoch);
+ stacking_context_tree.compositor_info.epoch = epoch.into();
- if reflow_request.reflow_goal.needs_display() {
- self.compositor_api.send_display_list(
- self.webview_id,
- display_list.compositor_info,
- display_list.wr.end().1,
- );
+ let built_display_list = DisplayListBuilder::build(
+ layout_context,
+ stacking_context_tree,
+ fragment_tree,
+ &self.debug,
+ );
+ self.compositor_api.send_display_list(
+ self.webview_id,
+ &stacking_context_tree.compositor_info,
+ built_display_list,
+ );
- let (keys, instance_keys) = self
- .font_context
- .collect_unused_webrender_resources(false /* all */);
- self.compositor_api
- .remove_unused_font_resources(keys, instance_keys)
- }
+ let (keys, instance_keys) = self
+ .font_context
+ .collect_unused_webrender_resources(false /* all */);
+ self.compositor_api
+ .remove_unused_font_resources(keys, instance_keys)
}
fn update_scroll_node_state(&self, state: &ScrollState) {
diff --git a/components/shared/base/id.rs b/components/shared/base/id.rs
index cc4ad947494..b6ad1b3de9b 100644
--- a/components/shared/base/id.rs
+++ b/components/shared/base/id.rs
@@ -17,7 +17,7 @@ use malloc_size_of::MallocSizeOfOps;
use malloc_size_of_derive::MallocSizeOf;
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
-use webrender_api::{ExternalScrollId, PipelineId as WebRenderPipelineId, SpatialId};
+use webrender_api::{ExternalScrollId, PipelineId as WebRenderPipelineId};
/// Asserts the size of a type at compile time.
macro_rules! size_of_test {
@@ -397,7 +397,4 @@ pub const TEST_WEBVIEW_ID: WebViewId = WebViewId(TEST_BROWSING_CONTEXT_ID);
pub struct ScrollTreeNodeId {
/// The index of this scroll tree node in the tree's array of nodes.
pub index: usize,
-
- /// The WebRender spatial id of this scroll tree node.
- pub spatial_id: SpatialId,
}
diff --git a/components/shared/compositing/display_list.rs b/components/shared/compositing/display_list.rs
index 6aa822cb145..4fb0d5e94db 100644
--- a/components/shared/compositing/display_list.rs
+++ b/components/shared/compositing/display_list.rs
@@ -6,11 +6,17 @@
use base::id::ScrollTreeNodeId;
use embedder_traits::Cursor;
+use euclid::SideOffsets2D;
use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize};
use style::values::specified::Overflow;
-use webrender_api::units::{LayoutSize, LayoutVector2D};
-use webrender_api::{Epoch, ExternalScrollId, PipelineId, ScrollLocation, SpatialId};
+use webrender_api::units::{
+ LayoutPixel, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D,
+};
+use webrender_api::{
+ Epoch, ExternalScrollId, PipelineId, ReferenceFrameKind, ScrollLocation, SpatialId,
+ StickyOffsetBounds, TransformStyle,
+};
/// The scroll sensitivity of a scroll node in a particular axis ie whether it can be scrolled due to
/// input events and script events or only script events.
@@ -56,6 +62,29 @@ pub struct HitTestInfo {
pub scroll_tree_node: ScrollTreeNodeId,
}
+#[derive(Debug, Deserialize, Serialize)]
+pub enum SpatialTreeNodeInfo {
+ ReferenceFrame(ReferenceFrameNodeInfo),
+ Scroll(ScrollableNodeInfo),
+ Sticky(StickyNodeInfo),
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct StickyNodeInfo {
+ pub frame_rect: LayoutRect,
+ pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
+ pub vertical_offset_bounds: StickyOffsetBounds,
+ pub horizontal_offset_bounds: StickyOffsetBounds,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct ReferenceFrameNodeInfo {
+ pub origin: LayoutPoint,
+ pub transform_style: TransformStyle,
+ pub transform: LayoutTransform,
+ pub kind: ReferenceFrameKind,
+}
+
/// Data stored for nodes in the [ScrollTree] that actually scroll,
/// as opposed to reference frames and sticky nodes which do not.
#[derive(Debug, Deserialize, Serialize)]
@@ -64,8 +93,11 @@ pub struct ScrollableNodeInfo {
/// it between successive re-layouts.
pub external_id: ExternalScrollId,
- /// Amount that this `ScrollableNode` can scroll in both directions.
- pub scrollable_size: LayoutSize,
+ /// The content rectangle for this scroll node;
+ pub content_rect: LayoutRect,
+
+ /// The clip rectange for this scroll node.
+ pub clip_rect: LayoutRect,
/// Whether this `ScrollableNode` is sensitive to input events.
pub scroll_sensitivity: AxesScrollSensitivity,
@@ -74,6 +106,12 @@ pub struct ScrollableNodeInfo {
pub offset: LayoutVector2D,
}
+impl ScrollableNodeInfo {
+ fn scrollable_size(&self) -> LayoutSize {
+ self.content_rect.size() - self.clip_rect.size()
+ }
+}
+
#[derive(Debug, Deserialize, Serialize)]
/// A node in a tree of scroll nodes. This may either be a scrollable
/// node which responds to scroll events or a non-scrollable one.
@@ -82,35 +120,51 @@ pub struct ScrollTreeNode {
/// None then this is the root node.
pub parent: Option<ScrollTreeNodeId>,
- /// Scrolling data which will not be None if this is a scrolling node.
- pub scroll_info: Option<ScrollableNodeInfo>,
+ /// The WebRender id, which is filled in when this tree is serialiezd
+ /// into a WebRender display list.
+ pub webrender_id: Option<SpatialId>,
+
+ /// Specific information about this node, depending on whether it is a scroll node
+ /// or a reference frame.
+ pub info: SpatialTreeNodeInfo,
}
impl ScrollTreeNode {
+ /// Get the WebRender [`SpatialId`] for the given [`ScrollNodeId`]. This will
+ /// panic if [`ScrollTree::build_display_list`] has not been called yet.
+ pub fn webrender_id(&self) -> SpatialId {
+ self.webrender_id
+ .expect("Should have called ScrollTree::build_display_list before querying SpatialId")
+ }
+
/// Get the external id of this node.
pub fn external_id(&self) -> Option<ExternalScrollId> {
- self.scroll_info.as_ref().map(|info| info.external_id)
+ match self.info {
+ SpatialTreeNodeInfo::Scroll(ref info) => Some(info.external_id),
+ _ => None,
+ }
}
/// Get the offset id of this node if it applies.
pub fn offset(&self) -> Option<LayoutVector2D> {
- self.scroll_info.as_ref().map(|info| info.offset)
+ match self.info {
+ SpatialTreeNodeInfo::Scroll(ref info) => Some(info.offset),
+ _ => None,
+ }
}
/// Set the offset for this node, returns false if this was a
/// non-scrolling node for which you cannot set the offset.
pub fn set_offset(&mut self, new_offset: LayoutVector2D) -> bool {
- match self.scroll_info {
- Some(ref mut info) => {
- let scrollable_width = info.scrollable_size.width;
- let scrollable_height = info.scrollable_size.height;
-
- if scrollable_width > 0. {
- info.offset.x = (new_offset.x).min(0.0).max(-scrollable_width);
+ match self.info {
+ SpatialTreeNodeInfo::Scroll(ref mut info) => {
+ let scrollable_size = info.scrollable_size();
+ if scrollable_size.width > 0. {
+ info.offset.x = (new_offset.x).min(0.0).max(-scrollable_size.width);
}
- if scrollable_height > 0. {
- info.offset.y = (new_offset.y).min(0.0).max(-scrollable_height);
+ if scrollable_size.height > 0. {
+ info.offset.y = (new_offset.y).min(0.0).max(-scrollable_size.height);
}
true
},
@@ -125,9 +179,9 @@ impl ScrollTreeNode {
&mut self,
scroll_location: ScrollLocation,
) -> Option<(ExternalScrollId, LayoutVector2D)> {
- let info = match self.scroll_info {
- Some(ref mut data) => data,
- None => return None,
+ let info = match self.info {
+ SpatialTreeNodeInfo::Scroll(ref mut info) => info,
+ _ => return None,
};
if info.scroll_sensitivity.x != ScrollSensitivity::ScriptAndInputEvents &&
@@ -148,7 +202,7 @@ impl ScrollTreeNode {
return Some((info.external_id, info.offset));
},
ScrollLocation::End => {
- let end_pos = -info.scrollable_size.height;
+ let end_pos = -info.scrollable_size().height;
if info.offset.y.round() <= end_pos {
// Nothing to do on this layer.
return None;
@@ -159,20 +213,23 @@ impl ScrollTreeNode {
},
};
- let scrollable_width = info.scrollable_size.width;
- let scrollable_height = info.scrollable_size.height;
+ let scrollable_size = info.scrollable_size();
let original_layer_scroll_offset = info.offset;
- if scrollable_width > 0. &&
+ if scrollable_size.width > 0. &&
info.scroll_sensitivity.x == ScrollSensitivity::ScriptAndInputEvents
{
- info.offset.x = (info.offset.x + delta.x).min(0.0).max(-scrollable_width);
+ info.offset.x = (info.offset.x + delta.x)
+ .min(0.0)
+ .max(-scrollable_size.width);
}
- if scrollable_height > 0. &&
+ if scrollable_size.height > 0. &&
info.scroll_sensitivity.y == ScrollSensitivity::ScriptAndInputEvents
{
- info.offset.y = (info.offset.y + delta.y).min(0.0).max(-scrollable_height);
+ info.offset.y = (info.offset.y + delta.y)
+ .min(0.0)
+ .max(-scrollable_size.height);
}
if info.offset != original_layer_scroll_offset {
@@ -199,16 +256,23 @@ impl ScrollTree {
pub fn add_scroll_tree_node(
&mut self,
parent: Option<&ScrollTreeNodeId>,
- spatial_id: SpatialId,
- scroll_info: Option<ScrollableNodeInfo>,
+ info: SpatialTreeNodeInfo,
) -> ScrollTreeNodeId {
self.nodes.push(ScrollTreeNode {
parent: parent.cloned(),
- scroll_info,
+ webrender_id: None,
+ info,
});
ScrollTreeNodeId {
index: self.nodes.len() - 1,
- spatial_id,
+ }
+ }
+
+ /// Once WebRender display list construction is complete for this [`ScrollTree`], update
+ /// the mapping of nodes to WebRender [`SpatialId`]s.
+ pub fn update_mapping(&mut self, mapping: Vec<SpatialId>) {
+ for (spatial_id, node) in mapping.into_iter().zip(self.nodes.iter_mut()) {
+ node.webrender_id = Some(spatial_id);
}
}
@@ -218,10 +282,16 @@ impl ScrollTree {
}
/// Get an immutable reference to the node with the given index.
- pub fn get_node(&mut self, id: &ScrollTreeNodeId) -> &ScrollTreeNode {
+ pub fn get_node(&self, id: &ScrollTreeNodeId) -> &ScrollTreeNode {
&self.nodes[id.index]
}
+ /// Get the WebRender [`SpatialId`] for the given [`ScrollNodeId`]. This will
+ /// panic if [`ScrollTree::build_display_list`] has not been called yet.
+ pub fn webrender_id(&self, id: &ScrollTreeNodeId) -> SpatialId {
+ self.get_node(id).webrender_id()
+ }
+
/// Scroll the given scroll node on this scroll tree. If the node cannot be scrolled,
/// because it isn't a scrollable node or it's already scrolled to the maximum scroll
/// extent, try to scroll an ancestor of this node. Returns the node scrolled and the
@@ -251,8 +321,10 @@ impl ScrollTree {
offset: LayoutVector2D,
) -> bool {
for node in self.nodes.iter_mut() {
- match node.scroll_info {
- Some(ref mut scroll_info) if scroll_info.external_id == external_scroll_id => {
+ match node.info {
+ SpatialTreeNodeInfo::Scroll(ref mut scroll_info)
+ if scroll_info.external_id == external_scroll_id =>
+ {
scroll_info.offset = offset;
return true;
},
@@ -320,15 +392,19 @@ impl CompositorDisplayListInfo {
let mut scroll_tree = ScrollTree::default();
let root_reference_frame_id = scroll_tree.add_scroll_tree_node(
None,
- SpatialId::root_reference_frame(pipeline_id),
- None,
+ SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
+ origin: Default::default(),
+ transform_style: TransformStyle::Flat,
+ transform: LayoutTransform::identity(),
+ kind: ReferenceFrameKind::default(),
+ }),
);
let root_scroll_node_id = scroll_tree.add_scroll_tree_node(
Some(&root_reference_frame_id),
- SpatialId::root_scroll_node(pipeline_id),
- Some(ScrollableNodeInfo {
+ SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
external_id: ExternalScrollId(0, pipeline_id),
- scrollable_size: content_size - viewport_size,
+ content_rect: LayoutRect::from_origin_and_size(LayoutPoint::zero(), content_size),
+ clip_rect: LayoutRect::from_origin_and_size(LayoutPoint::zero(), viewport_size),
scroll_sensitivity: viewport_scroll_sensitivity,
offset: LayoutVector2D::zero(),
}),
diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs
index a6701ca2b52..061dfe023df 100644
--- a/components/shared/compositing/lib.rs
+++ b/components/shared/compositing/lib.rs
@@ -236,7 +236,7 @@ impl CrossProcessCompositorApi {
pub fn send_display_list(
&self,
webview_id: WebViewId,
- display_list_info: CompositorDisplayListInfo,
+ display_list_info: &CompositorDisplayListInfo,
list: BuiltDisplayList,
) {
let (display_list_data, display_list_descriptor) = list.into_data();
diff --git a/components/shared/compositing/tests/compositor.rs b/components/shared/compositing/tests/compositor.rs
index 4d2ecfd99c9..e04f1770964 100644
--- a/components/shared/compositing/tests/compositor.rs
+++ b/components/shared/compositing/tests/compositor.rs
@@ -4,11 +4,12 @@
use base::id::ScrollTreeNodeId;
use compositing_traits::display_list::{
- AxesScrollSensitivity, ScrollSensitivity, ScrollTree, ScrollableNodeInfo,
+ AxesScrollSensitivity, ScrollSensitivity, ScrollTree, ScrollableNodeInfo, SpatialTreeNodeInfo,
+ StickyNodeInfo,
};
-use euclid::Size2D;
+use euclid::{SideOffsets2D, Size2D};
use webrender_api::units::LayoutVector2D;
-use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation, SpatialId};
+use webrender_api::{ExternalScrollId, PipelineId, ScrollLocation, StickyOffsetBounds};
fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId {
let pipeline_id = PipelineId(0, 0);
@@ -16,7 +17,6 @@ fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId {
let parent = if num_nodes > 0 {
Some(ScrollTreeNodeId {
index: num_nodes - 1,
- spatial_id: SpatialId::new(num_nodes - 1, pipeline_id),
})
} else {
None
@@ -24,10 +24,10 @@ fn add_mock_scroll_node(tree: &mut ScrollTree) -> ScrollTreeNodeId {
tree.add_scroll_tree_node(
parent.as_ref(),
- SpatialId::new(num_nodes, pipeline_id),
- Some(ScrollableNodeInfo {
+ SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
external_id: ExternalScrollId(num_nodes as u64, pipeline_id),
- scrollable_size: Size2D::new(100.0, 100.0),
+ content_rect: Size2D::new(200.0, 200.0).into(),
+ clip_rect: Size2D::new(100.0, 100.0).into(),
scroll_sensitivity: AxesScrollSensitivity {
x: ScrollSensitivity::ScriptAndInputEvents,
y: ScrollSensitivity::ScriptAndInputEvents,
@@ -78,8 +78,15 @@ fn test_scroll_tree_simple_scroll_chaining() {
let pipeline_id = PipelineId(0, 0);
let parent_id = add_mock_scroll_node(&mut scroll_tree);
- let unscrollable_child_id =
- scroll_tree.add_scroll_tree_node(Some(&parent_id), SpatialId::new(1, pipeline_id), None);
+ let unscrollable_child_id = scroll_tree.add_scroll_tree_node(
+ Some(&parent_id),
+ SpatialTreeNodeInfo::Sticky(StickyNodeInfo {
+ frame_rect: Size2D::new(100.0, 100.0).into(),
+ margins: SideOffsets2D::default(),
+ vertical_offset_bounds: StickyOffsetBounds::new(0.0, 0.0),
+ horizontal_offset_bounds: StickyOffsetBounds::new(0.0, 0.0),
+ }),
+ );
let (scrolled_id, offset) = scroll_tree
.scroll_node_or_ancestor(
@@ -157,16 +164,14 @@ fn test_scroll_tree_chain_through_overflow_hidden() {
let pipeline_id = PipelineId(0, 0);
let parent_id = add_mock_scroll_node(&mut scroll_tree);
let overflow_hidden_id = add_mock_scroll_node(&mut scroll_tree);
- scroll_tree
- .get_node_mut(&overflow_hidden_id)
- .scroll_info
- .as_mut()
- .map(|info| {
- info.scroll_sensitivity = AxesScrollSensitivity {
- x: ScrollSensitivity::Script,
- y: ScrollSensitivity::Script,
- };
- });
+ let node = scroll_tree.get_node_mut(&overflow_hidden_id);
+
+ if let SpatialTreeNodeInfo::Scroll(ref mut scroll_node_info) = node.info {
+ scroll_node_info.scroll_sensitivity = AxesScrollSensitivity {
+ x: ScrollSensitivity::Script,
+ y: ScrollSensitivity::Script,
+ };
+ }
let (scrolled_id, offset) = scroll_tree
.scroll_node_or_ancestor(