aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/display_list
diff options
context:
space:
mode:
authorMartin Robinson <mrobinson@igalia.com>2023-11-29 10:03:24 +0100
committerGitHub <noreply@github.com>2023-11-29 09:03:24 +0000
commita05598402e8b21948e1ee9567dca76a491bd266e (patch)
treea74aca7e7862100030ce94f34d448087329eaa1a /components/layout_2020/display_list
parentbab2b58216f8845b3ecc8e0eaeba3b7175034f64 (diff)
downloadservo-a05598402e8b21948e1ee9567dca76a491bd266e.tar.gz
servo-a05598402e8b21948e1ee9567dca76a491bd266e.zip
Add initial support for sticky positioning for non-legacy layout (#30686)
* Add initial support for sticky positioning for non-legacy layout Many tests still fail for a variety of reasons. One of the primary ones is that CSSOM currently does not return correct values for elements positioned by sticky nodes. This requires changes to WebRender to work properly. * Fix an assertion failure in the legacy layout sticky code
Diffstat (limited to 'components/layout_2020/display_list')
-rw-r--r--components/layout_2020/display_list/stacking_context.rs224
1 files changed, 187 insertions, 37 deletions
diff --git a/components/layout_2020/display_list/stacking_context.rs b/components/layout_2020/display_list/stacking_context.rs
index 22d7a5679e6..0432fa288d7 100644
--- a/components/layout_2020/display_list/stacking_context.rs
+++ b/components/layout_2020/display_list/stacking_context.rs
@@ -6,6 +6,7 @@ use std::cell::RefCell;
use std::mem;
use euclid::default::Rect;
+use euclid::SideOffsets2D;
use gfx_traits::print_tree::PrintTree;
use log::warn;
use script_traits::compositor::{ScrollTreeNodeId, ScrollableNodeInfo};
@@ -23,6 +24,8 @@ use style::values::specified::box_::DisplayOutside;
use webrender_api as wr;
use webrender_api::units::{LayoutPoint, LayoutRect, LayoutTransform, LayoutVector2D};
use webrender_api::ScrollSensitivity;
+use wr::units::{LayoutPixel, LayoutSize};
+use wr::StickyOffsetBounds;
use super::DisplayList;
use crate::cell::ArcRefCell;
@@ -31,7 +34,7 @@ use crate::display_list::DisplayListBuilder;
use crate::fragment_tree::{
AnonymousFragment, BoxFragment, ContainingBlockManager, Fragment, FragmentTree,
};
-use crate::geom::PhysicalRect;
+use crate::geom::{PhysicalRect, PhysicalSides};
use crate::style_ext::ComputedValuesExt;
#[derive(Clone)]
@@ -40,6 +43,11 @@ pub(crate) struct ContainingBlock {
/// of this containing block.
scroll_node_id: ScrollTreeNodeId,
+ /// The size of the parent scroll frame of this containing block, used for resolving
+ /// sticky margins. If this is None, then this is a direct descendant of a reference
+ /// 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,
@@ -50,14 +58,16 @@ pub(crate) struct ContainingBlock {
impl ContainingBlock {
pub(crate) fn new(
- rect: &PhysicalRect<Length>,
+ rect: PhysicalRect<Length>,
scroll_node_id: ScrollTreeNodeId,
+ scroll_frame_size: Option<LayoutSize>,
clip_chain_id: wr::ClipChainId,
) -> Self {
ContainingBlock {
scroll_node_id,
+ scroll_frame_size,
clip_chain_id,
- rect: *rect,
+ rect,
}
}
@@ -90,13 +100,15 @@ impl DisplayList {
.define_clip_chain(None, [wr::ClipId::root(self.wr.pipeline_id)]);
let cb_for_non_fixed_descendants = ContainingBlock::new(
- &fragment_tree.initial_containing_block,
+ fragment_tree.initial_containing_block,
self.compositor_info.root_scroll_node_id,
+ Some(self.compositor_info.viewport_size),
root_clip_chain_id,
);
let cb_for_fixed_descendants = ContainingBlock::new(
- &fragment_tree.initial_containing_block,
+ fragment_tree.initial_containing_block,
self.compositor_info.root_reference_frame_id,
+ None,
root_clip_chain_id,
);
@@ -114,7 +126,7 @@ impl DisplayList {
let mut root_stacking_context = StackingContext::create_root(&self.wr, debug);
for fragment in &fragment_tree.root_fragments {
- fragment.borrow().build_stacking_context_tree(
+ fragment.borrow_mut().build_stacking_context_tree(
fragment,
self,
&containing_block_info,
@@ -197,6 +209,29 @@ impl DisplayList {
);
(new_scroll_node_id, new_clip_chain_id)
}
+
+ fn define_sticky_frame(
+ &mut self,
+ parent_scroll_node_id: &ScrollTreeNodeId,
+ frame_rect: LayoutRect,
+ margins: SideOffsets2D<Option<f32>, LayoutPixel>,
+ vertical_offset_bounds: StickyOffsetBounds,
+ horizontal_offset_bounds: StickyOffsetBounds,
+ ) -> ScrollTreeNodeId {
+ 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(),
+ );
+ self.compositor_info.scroll_tree.add_scroll_tree_node(
+ Some(parent_scroll_node_id),
+ new_spatial_id,
+ None,
+ )
+ }
}
/// A piece of content that directly belongs to a section of a stacking context.
@@ -769,7 +804,7 @@ pub(crate) enum StackingContextBuildMode {
impl Fragment {
pub(crate) fn build_stacking_context_tree(
- &self,
+ &mut self,
fragment_ref: &ArcRefCell<Fragment>,
display_list: &mut DisplayList,
containing_block_info: &ContainingBlockInfo,
@@ -810,7 +845,7 @@ impl Fragment {
None => unreachable!("Found hoisted box with missing fragment."),
};
- fragment_ref.borrow().build_stacking_context_tree(
+ fragment_ref.borrow_mut().build_stacking_context_tree(
fragment_ref,
display_list,
containing_block_info,
@@ -882,7 +917,7 @@ impl BoxFragment {
}
fn build_stacking_context_tree(
- &self,
+ &mut self,
fragment: &ArcRefCell<Fragment>,
display_list: &mut DisplayList,
containing_block: &ContainingBlock,
@@ -899,7 +934,7 @@ impl BoxFragment {
}
fn build_stacking_context_tree_maybe_creating_reference_frame(
- &self,
+ &mut self,
fragment: &ArcRefCell<Fragment>,
display_list: &mut DisplayList,
containing_block: &ContainingBlock,
@@ -941,10 +976,11 @@ impl BoxFragment {
.style
.establishes_containing_block_for_all_descendants());
let adjusted_containing_block = ContainingBlock::new(
- &containing_block
+ containing_block
.rect
.translate(-reference_frame_data.origin.to_vector()),
new_spatial_id,
+ None,
containing_block.clip_chain_id,
);
let new_containing_block_info =
@@ -962,7 +998,7 @@ impl BoxFragment {
}
fn build_stacking_context_tree_maybe_creating_stacking_context(
- &self,
+ &mut self,
fragment: &ArcRefCell<Fragment>,
display_list: &mut DisplayList,
containing_block: &ContainingBlock,
@@ -1025,7 +1061,7 @@ impl BoxFragment {
}
fn build_stacking_context_tree_for_children(
- &self,
+ &mut self,
fragment: &ArcRefCell<Fragment>,
display_list: &mut DisplayList,
containing_block: &ContainingBlock,
@@ -1034,6 +1070,19 @@ impl BoxFragment {
) {
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_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,
+ &new_scroll_node_id,
+ &containing_block.rect,
+ &new_scroll_frame_size,
+ ) {
+ new_scroll_node_id = scroll_node_id;
+ }
+
if let Some(clip_chain_id) = self.build_clip_frame_if_necessary(
display_list,
&new_scroll_node_id,
@@ -1067,14 +1116,17 @@ 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((scroll_node_id, clip_chain_id)) = self.build_scroll_frame_if_necessary(
- display_list,
- &new_scroll_node_id,
- &new_clip_chain_id,
- &containing_block.rect,
- ) {
+ if let Some((scroll_node_id, clip_chain_id, scroll_frame_size)) = self
+ .build_scroll_frame_if_necessary(
+ display_list,
+ &new_scroll_node_id,
+ &new_clip_chain_id,
+ &containing_block.rect,
+ )
+ {
new_scroll_node_id = scroll_node_id;
new_clip_chain_id = clip_chain_id;
+ new_scroll_frame_size = Some(scroll_frame_size);
}
let padding_rect = self
@@ -1086,10 +1138,18 @@ impl BoxFragment {
.to_physical(self.style.writing_mode, &containing_block.rect)
.translate(containing_block.rect.origin.to_vector());
- let for_absolute_descendants =
- ContainingBlock::new(&padding_rect, new_scroll_node_id, new_clip_chain_id);
- let for_non_absolute_descendants =
- ContainingBlock::new(&content_rect, new_scroll_node_id, new_clip_chain_id);
+ let for_absolute_descendants = ContainingBlock::new(
+ padding_rect,
+ new_scroll_node_id,
+ new_scroll_frame_size,
+ new_clip_chain_id,
+ );
+ let for_non_absolute_descendants = ContainingBlock::new(
+ content_rect,
+ new_scroll_node_id,
+ new_scroll_frame_size,
+ new_clip_chain_id,
+ );
// Create a new `ContainingBlockInfo` for descendants depending on
// whether or not this fragment establishes a containing block for
@@ -1115,7 +1175,7 @@ impl BoxFragment {
};
for child in &self.children {
- child.borrow().build_stacking_context_tree(
+ child.borrow_mut().build_stacking_context_tree(
child,
display_list,
&new_containing_block_info,
@@ -1174,7 +1234,7 @@ impl BoxFragment {
parent_scroll_node_id: &ScrollTreeNodeId,
parent_clip_id: &wr::ClipChainId,
containing_block_rect: &PhysicalRect<Length>,
- ) -> Option<(ScrollTreeNodeId, wr::ClipChainId)> {
+ ) -> Option<(ScrollTreeNodeId, wr::ClipChainId, LayoutSize)> {
let overflow_x = self.style.get_box().overflow_x;
let overflow_y = self.style.get_box().overflow_y;
if overflow_x == ComputedOverflow::Visible && overflow_y == ComputedOverflow::Visible {
@@ -1200,17 +1260,107 @@ impl BoxFragment {
.translate(containing_block_rect.origin.to_vector())
.to_webrender();
- Some(
- display_list.define_scroll_frame(
- parent_scroll_node_id,
- parent_clip_id,
- external_id,
- self.scrollable_overflow(&containing_block_rect)
- .to_webrender(),
- padding_rect,
- sensitivity,
- ),
- )
+ let (scroll_tree_node_id, clip_chain_id) = display_list.define_scroll_frame(
+ parent_scroll_node_id,
+ parent_clip_id,
+ external_id,
+ self.scrollable_overflow(&containing_block_rect)
+ .to_webrender(),
+ padding_rect,
+ sensitivity,
+ );
+
+ Some((scroll_tree_node_id, clip_chain_id, padding_rect.size))
+ }
+
+ fn build_sticky_frame_if_necessary(
+ &mut self,
+ display_list: &mut DisplayList,
+ parent_scroll_node_id: &ScrollTreeNodeId,
+ containing_block_rect: &PhysicalRect<Length>,
+ scroll_frame_size: &Option<LayoutSize>,
+ ) -> Option<ScrollTreeNodeId> {
+ if self.style.get_box().position != ComputedPosition::Sticky {
+ return None;
+ }
+
+ let scroll_frame_size_for_resolve = match scroll_frame_size {
+ Some(size) => size,
+ None => {
+ // This is a direct descendant of a reference frame.
+ &display_list.compositor_info.viewport_size
+ },
+ };
+
+ // Percentages sticky positions offsets are resovled against the size of the
+ // nearest scroll frame instead of the containing block like for other types
+ // of positioning.
+ let position = self.style.get_position();
+ let offsets = PhysicalSides::new(
+ position
+ .top
+ .map(|v| v.resolve(Length::new(scroll_frame_size_for_resolve.height))),
+ position
+ .right
+ .map(|v| v.resolve(Length::new(scroll_frame_size_for_resolve.width))),
+ position
+ .bottom
+ .map(|v| v.resolve(Length::new(scroll_frame_size_for_resolve.height))),
+ position
+ .left
+ .map(|v| v.resolve(Length::new(scroll_frame_size_for_resolve.width))),
+ );
+ self.resolved_sticky_insets = Some(offsets);
+
+ if scroll_frame_size.is_none() {
+ return None;
+ }
+
+ if offsets.top.is_auto() &&
+ offsets.right.is_auto() &&
+ offsets.bottom.is_auto() &&
+ offsets.left.is_auto()
+ {
+ return None;
+ }
+
+ let frame_rect = self
+ .border_rect()
+ .to_physical(self.style.writing_mode, &containing_block_rect)
+ .translate(containing_block_rect.origin.to_vector())
+ .to_webrender();
+
+ // Position:sticky elements are always restricted based on the size and position of their
+ // containing block.
+ let containing_block_rect = containing_block_rect.to_webrender();
+
+ // This is the minimum negative offset and then the maximum positive offset. We just
+ // specify every edge, but if the corresponding margin is None, that offset has no effect.
+ let vertical_offset_bounds = wr::StickyOffsetBounds::new(
+ containing_block_rect.min_y() - frame_rect.min_y(),
+ containing_block_rect.max_y() - frame_rect.max_y(),
+ );
+ let horizontal_offset_bounds = wr::StickyOffsetBounds::new(
+ containing_block_rect.min_x() - frame_rect.min_x(),
+ containing_block_rect.max_x() - frame_rect.max_x(),
+ );
+
+ let margins = SideOffsets2D::new(
+ offsets.top.non_auto().map(|v| v.px()),
+ offsets.right.non_auto().map(|v| v.px()),
+ offsets.bottom.non_auto().map(|v| v.px()),
+ offsets.left.non_auto().map(|v| v.px()),
+ );
+
+ let sticky_node_id = display_list.define_sticky_frame(
+ parent_scroll_node_id,
+ frame_rect,
+ margins,
+ vertical_offset_bounds,
+ horizontal_offset_bounds,
+ );
+
+ Some(sticky_node_id)
}
/// Optionally returns the data for building a reference frame, without yet building it.
@@ -1363,7 +1513,7 @@ impl AnonymousFragment {
containing_block_info.new_for_non_absolute_descendants(&new_containing_block);
for child in &self.children {
- child.borrow().build_stacking_context_tree(
+ child.borrow_mut().build_stacking_context_tree(
child,
display_list,
&new_containing_block_info,