Rework CB management during stacking context tree construction

Manage containing blocks and WebRender SpaceAndClip during stacking
context tree constuction using the ContainingBlockInfo data structure.
This will allow us to reuse this data structure whenever we traverse the
fragment tree. In addition, StackingContextBuilder is no longer
necessary at all. This change also fixes some bugs where fixed position
fragments were not placed in the correct spatial node. Unfortunately,
these fixes are difficult to test because of #29659.
This commit is contained in:
Martin Robinson 2023-04-21 15:19:14 +02:00
parent 0cffe557c2
commit 0c13fcb9f2
10 changed files with 466 additions and 322 deletions

View file

@ -5,9 +5,7 @@
use crate::cell::ArcRefCell;
use crate::display_list::conversions::ToWebRender;
use crate::display_list::DisplayListBuilder;
use crate::fragments::{
AbsoluteOrFixedPositionedFragment, AnonymousFragment, BoxFragment, Fragment,
};
use crate::fragments::{AnonymousFragment, BoxFragment, Fragment};
use crate::geom::PhysicalRect;
use crate::style_ext::ComputedValuesExt;
use euclid::default::Rect;
@ -44,56 +42,107 @@ impl ContainingBlock {
rect: *rect,
}
}
pub(crate) fn new_replacing_rect(&self, rect: &PhysicalRect<Length>) -> Self {
ContainingBlock {
space_and_clip: self.space_and_clip,
rect: *rect,
}
}
}
#[derive(Clone)]
pub(crate) struct ContainingBlockInfo {
/// The positioning rectangle established by the parent. This is sometimes
/// called the "containing block" in layout_2020.
pub rect: PhysicalRect<Length>,
pub(crate) struct ContainingBlockManager<'a, T> {
// The containing block for all non-absolute descendants. "...if the element's
// position is 'relative' or 'static', the containing block is formed by the
// content edge of the nearest block container ancestor box." This is also
// the case for 'position: sticky' elements.
// https://www.w3.org/TR/CSS2/visudet.html#containing-block-details
pub for_non_absolute_descendants: &'a T,
/// The nearest real containing block at this point in the construction of
/// the stacking context tree.
pub nearest_containing_block: Option<ContainingBlock>,
// The containing block for absolute descendants. "If the element has
// 'position: absolute', the containing block is
// established by the nearest ancestor with a 'position' of 'absolute',
// 'relative' or 'fixed', in the following way:
// 1. In the case that the ancestor is an inline element, the containing
// block is the bounding box around the padding boxes of the first and the
// last inline boxes generated for that element. In CSS 2.1, if the inline
// element is split across multiple lines, the containing block is
// undefined.
// 2. Otherwise, the containing block is formed by the padding edge of the
// ancestor."
// https://www.w3.org/TR/CSS2/visudet.html#containing-block-details
// If the ancestor forms a containing block for all descendants (see below),
// this value will be None and absolute descendants will use the containing
// block for fixed descendants.
pub for_absolute_descendants: Option<&'a T>,
/// The nearest containing block for all descendants at this point in the
/// stacking context tree. This containing blocks contains fixed position
/// elements.
pub containing_block_for_all_descendants: ContainingBlock,
// The containing block for fixed and absolute descendants.
// "For elements whose layout is governed by the CSS box model, any value
// other than none for the transform property also causes the element to
// establish a containing block for all descendants. Its padding box will be
// used to layout for all of its absolute-position descendants,
// fixed-position descendants, and descendant fixed background attachments."
// https://w3c.github.io/csswg-drafts/css-transforms-1/#containing-block-for-all-descendants
// See `ComputedValues::establishes_containing_block_for_all_descendants`
// for a list of conditions where an element forms a containing block for
// all descendants.
pub for_absolute_and_fixed_descendants: &'a T,
}
pub(crate) struct StackingContextBuilder<'a> {
/// The current SpatialId and ClipId information for this `DisplayListBuilder`.
pub current_space_and_clip: wr::SpaceAndClipInfo,
/// The id of the nearest ancestor reference frame for this `DisplayListBuilder`.
nearest_reference_frame: wr::SpatialId,
wr: &'a mut wr::DisplayListBuilder,
}
impl<'a> StackingContextBuilder<'a> {
pub fn new(wr: &'a mut wr::DisplayListBuilder) -> Self {
Self {
current_space_and_clip: wr::SpaceAndClipInfo::root_scroll(wr.pipeline_id),
nearest_reference_frame: wr::SpatialId::root_reference_frame(wr.pipeline_id),
wr,
impl<'a, T> ContainingBlockManager<'a, T> {
fn get_containing_block_for_fragment(&self, fragment: &Fragment) -> &T {
if let Fragment::Box(box_fragment) = fragment {
match box_fragment.style.clone_position() {
ComputedPosition::Fixed => self.for_absolute_and_fixed_descendants,
ComputedPosition::Absolute => self
.for_absolute_descendants
.unwrap_or(self.for_absolute_and_fixed_descendants),
_ => self.for_non_absolute_descendants,
}
} else {
self.for_non_absolute_descendants
}
}
fn clipping_and_scrolling_scope<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
let previous_space_and_clip = self.current_space_and_clip;
let previous_nearest_reference_frame = self.nearest_reference_frame;
pub(crate) fn new_for_non_absolute_descendants(
&self,
for_non_absolute_descendants: &'a T,
) -> Self {
return ContainingBlockManager {
for_non_absolute_descendants,
for_absolute_descendants: self.for_absolute_descendants,
for_absolute_and_fixed_descendants: self.for_absolute_and_fixed_descendants,
};
}
let result = f(self);
pub(crate) fn new_for_absolute_descendants(
&self,
for_non_absolute_descendants: &'a T,
for_absolute_descendants: &'a T,
) -> Self {
return ContainingBlockManager {
for_non_absolute_descendants,
for_absolute_descendants: Some(for_absolute_descendants),
for_absolute_and_fixed_descendants: self.for_absolute_and_fixed_descendants,
};
}
self.current_space_and_clip = previous_space_and_clip;
self.nearest_reference_frame = previous_nearest_reference_frame;
result
pub(crate) fn new_for_absolute_and_fixed_descendants(
&self,
for_non_absolute_descendants: &'a T,
for_absolute_and_fixed_descendants: &'a T,
) -> Self {
return ContainingBlockManager {
for_non_absolute_descendants,
for_absolute_descendants: None,
for_absolute_and_fixed_descendants,
};
}
}
pub(crate) type ContainingBlockInfo<'a> = ContainingBlockManager<'a, ContainingBlock>;
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) enum StackingContextSection {
BackgroundsAndBorders,
@ -448,11 +497,12 @@ impl Fragment {
pub(crate) fn build_stacking_context_tree(
&self,
fragment_ref: &ArcRefCell<Fragment>,
builder: &mut StackingContextBuilder,
wr: &mut wr::DisplayListBuilder,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
mode: StackingContextBuildMode,
) {
let containing_block = containing_block_info.get_containing_block_for_fragment(self);
match self {
Fragment::Box(fragment) => {
if mode == StackingContextBuildMode::SkipHoisted &&
@ -461,31 +511,41 @@ impl Fragment {
return;
}
// If this fragment has a transform applied that makes it take up no spae
// If this fragment has a transform applied that makes it take up no space
// then we don't need to create any stacking contexts for it.
let has_non_invertible_transform =
fragment.has_non_invertible_transform(&containing_block_info.rect.to_untyped());
fragment.has_non_invertible_transform(&containing_block.rect.to_untyped());
if has_non_invertible_transform {
return;
}
fragment.build_stacking_context_tree(
fragment_ref,
builder,
wr,
containing_block,
containing_block_info,
stacking_context,
);
},
Fragment::AbsoluteOrFixedPositioned(fragment) => {
fragment.build_stacking_context_tree(
builder,
let shared_fragment = fragment.borrow();
let fragment_ref = match shared_fragment.fragment.as_ref() {
Some(fragment_ref) => fragment_ref,
None => unreachable!("Found hoisted box with missing fragment."),
};
fragment_ref.borrow().build_stacking_context_tree(
fragment_ref,
wr,
containing_block_info,
stacking_context,
StackingContextBuildMode::IncludeHoisted,
);
},
Fragment::Anonymous(fragment) => {
fragment.build_stacking_context_tree(
builder,
wr,
containing_block,
containing_block_info,
stacking_context,
);
@ -493,8 +553,8 @@ impl Fragment {
Fragment::Text(_) | Fragment::Image(_) | Fragment::IFrame(_) => {
stacking_context.fragments.push(StackingContextFragment {
section: StackingContextSection::Content,
space_and_clip: builder.current_space_and_clip,
containing_block: containing_block_info.rect,
space_and_clip: containing_block.space_and_clip,
containing_block: containing_block.rect,
fragment: fragment_ref.clone(),
});
},
@ -542,105 +602,120 @@ impl BoxFragment {
StackingContextSection::BlockBackgroundsAndBorders
}
fn build_containing_block<'a>(
&'a self,
builder: &mut StackingContextBuilder,
padding_rect: &PhysicalRect<Length>,
containing_block_info: &mut ContainingBlockInfo,
) {
if !self
.style
.establishes_containing_block_for_absolute_descendants()
{
return;
}
let new_containing_block =
ContainingBlock::new(padding_rect, builder.current_space_and_clip);
if self
.style
.establishes_containing_block_for_all_descendants()
{
containing_block_info.nearest_containing_block = None;
containing_block_info.containing_block_for_all_descendants = new_containing_block;
} else {
containing_block_info.nearest_containing_block = Some(new_containing_block);
}
}
fn build_stacking_context_tree(
&self,
fragment: &ArcRefCell<Fragment>,
builder: &mut StackingContextBuilder,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
) {
builder.clipping_and_scrolling_scope(|builder| {
self.adjust_spatial_id_for_positioning(builder);
match self.get_stacking_context_type() {
Some(context_type) => {
self.build_stacking_context_tree_creating_stacking_context(
fragment,
builder,
containing_block_info,
stacking_context,
context_type,
);
},
None => {
self.build_stacking_context_tree_for_children(
fragment,
builder,
containing_block_info,
stacking_context,
);
},
}
});
}
fn build_stacking_context_tree_creating_stacking_context(
&self,
fragment: &ArcRefCell<Fragment>,
builder: &mut StackingContextBuilder,
wr: &mut wr::DisplayListBuilder,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
parent_stacking_context: &mut StackingContext,
) {
self.build_stacking_context_tree_maybe_creating_reference_frame(
fragment,
wr,
containing_block,
containing_block_info,
parent_stacking_context,
);
}
fn build_stacking_context_tree_maybe_creating_reference_frame(
&self,
fragment: &ArcRefCell<Fragment>,
wr: &mut wr::DisplayListBuilder,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
parent_stacking_context: &mut StackingContext,
context_type: StackingContextType,
) {
// If we are creating a stacking context, we may also need to create a reference
// frame first.
let reference_frame_data =
self.reference_frame_data_if_necessary(&containing_block_info.rect);
match self.reference_frame_data_if_necessary(&containing_block.rect) {
Some(reference_frame_data) => reference_frame_data,
None => {
return self.build_stacking_context_tree_maybe_creating_stacking_context(
fragment,
wr,
containing_block,
containing_block_info,
parent_stacking_context,
);
},
};
// WebRender reference frames establish a new coordinate system at their origin
// (the border box of the fragment). We need to ensure that any coordinates we
// give to WebRender in this reference frame are relative to the fragment border
// box. We do this by adjusting the containing block origin.
let mut new_containing_block_info = containing_block_info.clone();
let new_spatial_id = wr.push_reference_frame(
reference_frame_data.origin.to_webrender(),
containing_block.space_and_clip.spatial_id,
self.style.get_box().transform_style.to_webrender(),
wr::PropertyBinding::Value(reference_frame_data.transform),
reference_frame_data.kind,
);
if let Some(reference_frame_data) = &reference_frame_data {
new_containing_block_info.rect.origin -= reference_frame_data.origin.to_vector();
builder.current_space_and_clip.spatial_id = builder.wr.push_reference_frame(
reference_frame_data.origin.to_webrender(),
builder.current_space_and_clip.spatial_id,
self.style.get_box().transform_style.to_webrender(),
wr::PropertyBinding::Value(reference_frame_data.transform),
reference_frame_data.kind,
);
builder.nearest_reference_frame = builder.current_space_and_clip.spatial_id;
}
// WebRender reference frames establish a new coordinate system at their
// origin (the border box of the fragment). We need to ensure that any
// coordinates we give to WebRender in this reference frame are relative
// to the fragment border box. We do this by adjusting the containing
// block origin. Note that the `for_absolute_descendants` and
// `for_all_absolute_and_fixed_descendants` properties are now bogus,
// but all fragments that establish reference frames also establish
// containing blocks for absolute and fixed descendants, so those
// properties will be replaced before recursing into children.
assert!(self
.style
.establishes_containing_block_for_all_descendants());
let adjusted_containing_block = ContainingBlock::new(
&containing_block
.rect
.translate(-reference_frame_data.origin.to_vector()),
wr::SpaceAndClipInfo {
spatial_id: new_spatial_id,
clip_id: containing_block.space_and_clip.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,
wr,
&adjusted_containing_block,
&new_containing_block_info,
parent_stacking_context,
);
wr.pop_reference_frame();
}
fn build_stacking_context_tree_maybe_creating_stacking_context(
&self,
fragment: &ArcRefCell<Fragment>,
wr: &mut wr::DisplayListBuilder,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
parent_stacking_context: &mut StackingContext,
) {
let context_type = match self.get_stacking_context_type() {
Some(context_type) => context_type,
None => {
self.build_stacking_context_tree_for_children(
fragment,
wr,
containing_block,
containing_block_info,
parent_stacking_context,
);
return;
},
};
let mut child_stacking_context = StackingContext::new(
builder.current_space_and_clip.spatial_id,
containing_block.space_and_clip.spatial_id,
self.style.clone(),
context_type,
);
self.build_stacking_context_tree_for_children(
fragment,
builder,
&new_containing_block_info,
wr,
containing_block,
containing_block_info,
&mut child_stacking_context,
);
@ -659,58 +734,91 @@ impl BoxFragment {
parent_stacking_context
.stacking_contexts
.append(&mut stolen_children);
if reference_frame_data.is_some() {
builder.wr.pop_reference_frame();
}
}
fn build_stacking_context_tree_for_children<'a>(
&'a self,
fragment: &ArcRefCell<Fragment>,
builder: &mut StackingContextBuilder,
wr: &mut wr::DisplayListBuilder,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
) {
self.build_clip_frame_if_necessary(builder, containing_block_info);
let mut new_space_and_clip = containing_block.space_and_clip;
if let Some(new_clip_id) =
self.build_clip_frame_if_necessary(wr, new_space_and_clip, &containing_block.rect)
{
new_space_and_clip.clip_id = new_clip_id;
}
stacking_context.fragments.push(StackingContextFragment {
space_and_clip: builder.current_space_and_clip,
space_and_clip: new_space_and_clip,
section: self.get_stacking_context_section(),
containing_block: containing_block_info.rect,
containing_block: containing_block.rect,
fragment: fragment.clone(),
});
if self.style.get_outline().outline_width.px() > 0.0 {
stacking_context.fragments.push(StackingContextFragment {
space_and_clip: builder.current_space_and_clip,
space_and_clip: new_space_and_clip,
section: StackingContextSection::Outline,
containing_block: containing_block_info.rect,
containing_block: containing_block.rect,
fragment: fragment.clone(),
});
}
// We want to build the scroll frame after the background and border, because
// they shouldn't scroll with the rest of the box content.
self.build_scroll_frame_if_necessary(builder, containing_block_info);
if let Some(scroll_space_and_clip) =
self.build_scroll_frame_if_necessary(wr, new_space_and_clip, &containing_block.rect)
{
new_space_and_clip = scroll_space_and_clip;
}
let padding_rect = self
.padding_rect()
.to_physical(self.style.writing_mode, &containing_block_info.rect)
.translate(containing_block_info.rect.origin.to_vector());
let mut new_containing_block_info = containing_block_info.clone();
new_containing_block_info.rect = self
.to_physical(self.style.writing_mode, &containing_block.rect)
.translate(containing_block.rect.origin.to_vector());
let content_rect = self
.content_rect
.to_physical(self.style.writing_mode, &new_containing_block_info.rect)
.translate(new_containing_block_info.rect.origin.to_vector());
.to_physical(self.style.writing_mode, &containing_block.rect)
.translate(containing_block.rect.origin.to_vector());
// If we establish a containing block we use the padding rect as the offset. This is
// because for all but the initial containing block, the padding rect determines
// the size and position of the containing block.
self.build_containing_block(builder, &padding_rect, &mut new_containing_block_info);
let for_absolute_descendants = ContainingBlock {
rect: padding_rect,
space_and_clip: new_space_and_clip,
};
let for_non_absolute_descendants = ContainingBlock {
rect: content_rect,
space_and_clip: new_space_and_clip,
};
// Create a new `ContainingBlockInfo` for descendants depending on
// whether or not this fragment establishes a containing block for
// absolute and fixed descendants.
let new_containing_block_info = if self
.style
.establishes_containing_block_for_all_descendants()
{
containing_block_info.new_for_absolute_and_fixed_descendants(
&for_non_absolute_descendants,
&for_absolute_descendants,
)
} else if self
.style
.establishes_containing_block_for_absolute_descendants()
{
containing_block_info.new_for_absolute_descendants(
&for_non_absolute_descendants,
&for_absolute_descendants,
)
} else {
containing_block_info.new_for_non_absolute_descendants(&for_non_absolute_descendants)
};
for child in &self.children {
child.borrow().build_stacking_context_tree(
child,
builder,
wr,
&new_containing_block_info,
stacking_context,
StackingContextBuildMode::SkipHoisted,
@ -718,81 +826,74 @@ impl BoxFragment {
}
}
fn adjust_spatial_id_for_positioning(&self, builder: &mut StackingContextBuilder) {
if self.style.get_box().position != ComputedPosition::Fixed {
return;
}
// TODO(mrobinson): Eventually this should use the spatial id of the reference
// frame that is the parent of this one once we have full support for stacking
// contexts and transforms.
builder.current_space_and_clip.spatial_id = builder.nearest_reference_frame;
}
fn build_clip_frame_if_necessary(
&self,
builder: &mut StackingContextBuilder,
containing_block_info: &ContainingBlockInfo,
) {
wr: &mut wr::DisplayListBuilder,
current_space_and_clip: wr::SpaceAndClipInfo,
containing_block_rect: &PhysicalRect<Length>,
) -> Option<wr::ClipId> {
let position = self.style.get_box().position;
// https://drafts.csswg.org/css2/#clipping
// The clip property applies only to absolutely positioned elements
if position == ComputedPosition::Absolute || position == ComputedPosition::Fixed {
let clip = self.style.get_effects().clip;
if let ClipRectOrAuto::Rect(r) = clip {
let border_rect = self
.border_rect()
.to_physical(self.style.writing_mode, &containing_block_info.rect);
let clip_rect = r
.for_border_rect(border_rect)
.translate(containing_block_info.rect.origin.to_vector())
.to_webrender();
let parent = builder.current_space_and_clip;
builder.current_space_and_clip.clip_id =
builder.wr.define_clip_rect(&parent, clip_rect);
}
if position != ComputedPosition::Absolute && position != ComputedPosition::Fixed {
return None;
}
// Only rectangles are supported for now.
let clip_rect = match self.style.get_effects().clip {
ClipRectOrAuto::Rect(rect) => rect,
_ => return None,
};
let border_rect = self
.border_rect()
.to_physical(self.style.writing_mode, &containing_block_rect);
let clip_rect = clip_rect
.for_border_rect(border_rect)
.translate(containing_block_rect.origin.to_vector())
.to_webrender();
Some(wr.define_clip_rect(&current_space_and_clip, clip_rect))
}
fn build_scroll_frame_if_necessary<'a>(
&self,
builder: &mut StackingContextBuilder,
containing_block_info: &ContainingBlockInfo,
) {
wr: &mut wr::DisplayListBuilder,
current_space_and_clip: wr::SpaceAndClipInfo,
containing_block_rect: &PhysicalRect<Length>,
) -> Option<wr::SpaceAndClipInfo> {
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 {
return None;
}
let original_scroll_and_clip_info = builder.current_space_and_clip;
if overflow_x != ComputedOverflow::Visible || overflow_y != ComputedOverflow::Visible {
let external_id = wr::ExternalScrollId(
self.tag.to_display_list_fragment_id(),
builder.wr.pipeline_id,
);
let external_id =
wr::ExternalScrollId(self.tag.to_display_list_fragment_id(), wr.pipeline_id);
let sensitivity = if ComputedOverflow::Hidden == overflow_x &&
ComputedOverflow::Hidden == overflow_y
{
let sensitivity =
if ComputedOverflow::Hidden == overflow_x && ComputedOverflow::Hidden == overflow_y {
wr::ScrollSensitivity::Script
} else {
wr::ScrollSensitivity::ScriptAndInputEvents
};
let padding_rect = self
.padding_rect()
.to_physical(self.style.writing_mode, &containing_block_info.rect)
.translate(containing_block_info.rect.origin.to_vector())
.to_webrender();
builder.current_space_and_clip = builder.wr.define_scroll_frame(
&original_scroll_and_clip_info,
let padding_rect = self
.padding_rect()
.to_physical(self.style.writing_mode, &containing_block_rect)
.translate(containing_block_rect.origin.to_vector())
.to_webrender();
Some(
wr.define_scroll_frame(
&current_space_and_clip,
Some(external_id),
self.scrollable_overflow(&containing_block_info.rect)
self.scrollable_overflow(&containing_block_rect)
.to_webrender(),
padding_rect,
sensitivity,
LayoutVector2D::zero(),
);
}
),
)
}
/// Optionally returns the data for building a reference frame, without yet building it.
@ -925,19 +1026,23 @@ impl BoxFragment {
impl AnonymousFragment {
fn build_stacking_context_tree(
&self,
builder: &mut StackingContextBuilder,
wr: &mut wr::DisplayListBuilder,
containing_block: &ContainingBlock,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
) {
let mut new_containing_block_info = containing_block_info.clone();
new_containing_block_info.rect = self
let rect = self
.rect
.to_physical(self.mode, &containing_block_info.rect)
.translate(containing_block_info.rect.origin.to_vector());
.to_physical(self.mode, &containing_block.rect)
.translate(containing_block.rect.origin.to_vector());
let new_containing_block = containing_block.new_replacing_rect(&rect);
let new_containing_block_info =
containing_block_info.new_for_non_absolute_descendants(&new_containing_block);
for child in &self.children {
child.borrow().build_stacking_context_tree(
child,
builder,
wr,
&new_containing_block_info,
stacking_context,
StackingContextBuildMode::SkipHoisted,
@ -945,44 +1050,3 @@ impl AnonymousFragment {
}
}
}
impl AbsoluteOrFixedPositionedFragment {
fn build_stacking_context_tree(
&self,
builder: &mut StackingContextBuilder,
containing_block_info: &ContainingBlockInfo,
stacking_context: &mut StackingContext,
) {
let hoisted_fragment = self.hoisted_fragment.borrow();
let fragment_ref = match hoisted_fragment.fragment.as_ref() {
Some(fragment_ref) => fragment_ref,
None => unreachable!("Found hoisted box with missing fragment."),
};
let containing_block = match self.position {
ComputedPosition::Fixed => &containing_block_info.containing_block_for_all_descendants,
ComputedPosition::Absolute => containing_block_info
.nearest_containing_block
.as_ref()
.unwrap_or(&containing_block_info.containing_block_for_all_descendants),
ComputedPosition::Static | ComputedPosition::Relative => unreachable!(
"Found an AbsoluteOrFixedPositionedFragment for a \
non-absolutely or fixed position fragment."
),
};
builder.clipping_and_scrolling_scope(|builder| {
let mut new_containing_block_info = containing_block_info.clone();
new_containing_block_info.rect = containing_block.rect;
builder.current_space_and_clip = containing_block.space_and_clip;
fragment_ref.borrow().build_stacking_context_tree(
fragment_ref,
builder,
&new_containing_block_info,
stacking_context,
StackingContextBuildMode::IncludeHoisted,
);
});
}
}