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

View file

@ -8,9 +8,7 @@ use super::geom::{
use super::{FlexContainer, FlexLevelBox};
use crate::context::LayoutContext;
use crate::formatting_contexts::{IndependentFormattingContext, IndependentLayout};
use crate::fragments::{
AbsoluteOrFixedPositionedFragment, BoxFragment, CollapsedBlockMargins, Fragment,
};
use crate::fragments::{BoxFragment, CollapsedBlockMargins, Fragment};
use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::geom::LengthOrAuto;
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
@ -200,11 +198,6 @@ impl FlexContainer {
Fragment::Box(flex_item_fragments.next().unwrap())
},
Ok(absolutely_positioned) => {
let position = absolutely_positioned
.borrow()
.context
.style()
.clone_position();
let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
absolutely_positioned,
Vec2::zero(),
@ -213,10 +206,7 @@ impl FlexContainer {
);
let hoisted_fragment = hoisted_box.fragment.clone();
positioning_context.push(hoisted_box);
Fragment::AbsoluteOrFixedPositioned(AbsoluteOrFixedPositionedFragment {
hoisted_fragment,
position,
})
Fragment::AbsoluteOrFixedPositioned(hoisted_fragment)
},
})
.collect::<Vec<_>>();

View file

@ -8,8 +8,8 @@ use crate::flow::float::FloatBox;
use crate::flow::FlowLayout;
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragments::{
AbsoluteOrFixedPositionedFragment, AnonymousFragment, BoxFragment, CollapsedBlockMargins,
DebugId, FontMetrics, Fragment, Tag, TextFragment,
AnonymousFragment, BoxFragment, CollapsedBlockMargins, DebugId, FontMetrics, Fragment, Tag,
TextFragment,
};
use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::positioned::{
@ -338,14 +338,9 @@ impl InlineFormattingContext {
);
let hoisted_fragment = hoisted_box.fragment.clone();
ifc.push_hoisted_box_to_positioning_context(hoisted_box);
ifc.current_nesting_level.fragments_so_far.push(
Fragment::AbsoluteOrFixedPositioned(
AbsoluteOrFixedPositionedFragment {
hoisted_fragment,
position: style.clone_position(),
},
),
);
ifc.current_nesting_level
.fragments_so_far
.push(Fragment::AbsoluteOrFixedPositioned(hoisted_fragment));
},
InlineLevelBox::OutOfFlowFloatBox(_box_) => {
// TODO

View file

@ -12,8 +12,7 @@ use crate::formatting_contexts::{
IndependentFormattingContext, IndependentLayout, NonReplacedFormattingContext,
};
use crate::fragments::{
AbsoluteOrFixedPositionedFragment, AnonymousFragment, BoxFragment, CollapsedBlockMargins,
CollapsedMargin, Fragment, Tag,
AnonymousFragment, BoxFragment, CollapsedBlockMargins, CollapsedMargin, Fragment, Tag,
};
use crate::geom::flow_relative::{Rect, Sides, Vec2};
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext};
@ -215,10 +214,7 @@ fn layout_block_level_children(
placement_state.current_block_direction_position,
inline: Length::new(0.),
};
fragment
.hoisted_fragment
.borrow_mut()
.adjust_offsets(offset);
fragment.borrow_mut().adjust_offsets(offset);
},
Fragment::Anonymous(_) => {},
_ => unreachable!(),
@ -380,10 +376,7 @@ impl BlockLevelBox {
);
let hoisted_fragment = hoisted_box.fragment.clone();
positioning_context.push(hoisted_box);
Fragment::AbsoluteOrFixedPositioned(AbsoluteOrFixedPositionedFragment {
hoisted_fragment,
position: box_.borrow().context.style().clone_position(),
})
Fragment::AbsoluteOrFixedPositioned(hoisted_fragment)
},
BlockLevelBox::OutOfFlowFloatBox(_box_) => {
// FIXME: call layout_maybe_position_relative_fragment here

View file

@ -6,7 +6,6 @@ use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::display_list::stacking_context::{
ContainingBlock, ContainingBlockInfo, StackingContext, StackingContextBuildMode,
StackingContextBuilder,
};
use crate::dom_traversal::{iter_child_nodes, Contents, NodeAndStyleInfo, NodeExt};
use crate::element_data::LayoutBox;
@ -40,6 +39,7 @@ use style::properties::ComputedValues;
use style::selector_parser::PseudoElement;
use style::values::computed::Length;
use style_traits::CSSPixel;
use webrender_api::{ClipId, SpaceAndClipInfo, SpatialId};
#[derive(Serialize)]
pub struct BoxTree {
@ -386,30 +386,7 @@ impl BoxTree {
impl FragmentTree {
pub fn build_display_list(&self, builder: &mut crate::display_list::DisplayListBuilder) {
let mut stacking_context = StackingContext::create_root(&builder.wr);
{
let mut stacking_context_builder = StackingContextBuilder::new(&mut builder.wr);
let containing_block_info = ContainingBlockInfo {
rect: self.initial_containing_block,
nearest_containing_block: None,
containing_block_for_all_descendants: ContainingBlock::new(
&self.initial_containing_block,
stacking_context_builder.current_space_and_clip,
),
};
for fragment in &self.root_fragments {
fragment.borrow().build_stacking_context_tree(
fragment,
&mut stacking_context_builder,
&containing_block_info,
&mut stacking_context,
StackingContextBuildMode::SkipHoisted,
);
}
}
stacking_context.sort();
let stacking_context = self.build_stacking_context_tree(builder);
// Paint the canvas background (if any) before/under everything else
stacking_context.build_canvas_background_display_list(
@ -421,6 +398,47 @@ impl FragmentTree {
stacking_context.build_display_list(builder);
}
fn build_stacking_context_tree(
&self,
builder: &mut crate::display_list::DisplayListBuilder,
) -> StackingContext {
let mut stacking_context = StackingContext::create_root(&builder.wr);
let pipeline_id = builder.wr.pipeline_id;
let cb_for_non_fixed_descendants = ContainingBlock::new(
&self.initial_containing_block,
SpaceAndClipInfo::root_scroll(pipeline_id),
);
let cb_for_fixed_descendants = ContainingBlock::new(
&self.initial_containing_block,
SpaceAndClipInfo {
spatial_id: SpatialId::root_reference_frame(pipeline_id),
clip_id: ClipId::root(pipeline_id),
},
);
for fragment in &self.root_fragments {
fragment.borrow().build_stacking_context_tree(
fragment,
&mut builder.wr,
// We need to specify all three containing blocks here, because absolute
// descdendants of the root cannot share the containing block we specify
// for fixed descendants. In this case, they need to have the spatial
// id of the root scroll frame, whereas fixed descendants need the
// spatial id of the root reference frame so that they do not scroll with
// page content.
&ContainingBlockInfo {
for_non_absolute_descendants: &cb_for_non_fixed_descendants,
for_absolute_descendants: Some(&cb_for_non_fixed_descendants),
for_absolute_and_fixed_descendants: &cb_for_fixed_descendants,
},
&mut stacking_context,
StackingContextBuildMode::SkipHoisted,
);
}
stacking_context.sort();
stacking_context
}
pub fn print(&self) {
let mut print_tree = PrintTree::new("Fragment Tree".to_string());
for fragment in &self.root_fragments {

View file

@ -19,7 +19,6 @@ use serde::ser::{Serialize, Serializer};
use servo_arc::Arc as ServoArc;
use std::sync::Arc;
use style::computed_values::overflow_x::T as ComputedOverflow;
use style::computed_values::position::T as ComputedPosition;
use style::dom::OpaqueNode;
use style::logical_geometry::WritingMode;
use style::properties::ComputedValues;
@ -68,18 +67,19 @@ impl Tag {
pub(crate) enum Fragment {
Box(BoxFragment),
Anonymous(AnonymousFragment),
AbsoluteOrFixedPositioned(AbsoluteOrFixedPositionedFragment),
/// Absolute and fixed position fragments are hoisted up so that they
/// are children of the BoxFragment that establishes their containing
/// blocks, so that they can be laid out properly. When this happens
/// an `AbsoluteOrFixedPositioned` fragment is left at the original tree
/// position. This allows these hoisted fragments to be painted with
/// regard to their original tree order during stacking context tree /
/// display list construction.
AbsoluteOrFixedPositioned(ArcRefCell<HoistedSharedFragment>),
Text(TextFragment),
Image(ImageFragment),
IFrame(IFrameFragment),
}
#[derive(Serialize)]
pub(crate) struct AbsoluteOrFixedPositionedFragment {
pub position: ComputedPosition,
pub hoisted_fragment: ArcRefCell<HoistedSharedFragment>,
}
#[derive(Serialize)]
pub(crate) struct BoxFragment {
pub tag: Tag,
@ -214,7 +214,9 @@ impl Fragment {
pub fn print(&self, tree: &mut PrintTree) {
match self {
Fragment::Box(fragment) => fragment.print(tree),
Fragment::AbsoluteOrFixedPositioned(fragment) => fragment.print(tree),
Fragment::AbsoluteOrFixedPositioned(_) => {
tree.add_item("AbsoluteOrFixedPositioned".to_string());
},
Fragment::Anonymous(fragment) => fragment.print(tree),
Fragment::Text(fragment) => fragment.print(tree),
Fragment::Image(fragment) => fragment.print(tree),
@ -280,12 +282,6 @@ impl Fragment {
}
}
impl AbsoluteOrFixedPositionedFragment {
pub fn print(&self, tree: &mut PrintTree) {
tree.add_item(format!("AbsoluteOrFixedPositionedFragment"));
}
}
impl AnonymousFragment {
pub fn no_op(mode: WritingMode) -> Self {
Self {

View file

@ -257891,6 +257891,19 @@
{}
]
],
"transform-containing-block-and-scrolling-area-for-fixed.html": [
"2fd5f3873a211fc3a0970fabc9e1d23873bf2afe",
[
null,
[
[
"/css/css-transforms/transform-containing-block-and-scrolling-area-for-fixed-ref.html",
"=="
]
],
{}
]
],
"transform-containing-block-dynamic-1a.html": [
"7e6a10dda1c24e86f7635bd202efec48e6089fbd",
[
@ -400583,6 +400596,10 @@
"5122ad98c209f7a46fe9cad2a81497db2001cee2",
[]
],
"transform-containing-block-and-scrolling-area-for-fixed-ref.html": [
"2e88d4493fc23356a1f623983bd85292ca44ade5",
[]
],
"transform-descendant-ref.html": [
"ac60e7f52fd7477bfbe77bba38a568b1706c8ae9",
[]

View file

@ -0,0 +1,2 @@
[transform-containing-block-and-scrolling-area-for-fixed.html]
expected: FAIL

View file

@ -0,0 +1,26 @@
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
<style>
html, body { margin: 0; padding: 0 }
#transformed {
margin-left: 10px;
margin-top: 10px;
width: 200px;
height: 200px;
background: grey;
}
#fixed {
width: 50px;
height: 50px;
background: green;
}
</style>
<body>
<div id="transformed">
<div id="fixed"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS transforms: Transformed elements with overflow: hidden create scrolling areas for fixed descendants</title>
<link rel="author" title="Martin Robinson" href="mailto:mrobinson@igalia.com">
<link rel="help" href="https://drafts.csswg.org/css-transforms-1/#transform-rendering">
<link rel="help" href="https://drafts.csswg.org/css-transforms-1/#transform-property">
<link rel="match" href="transform-containing-block-and-scrolling-area-for-fixed-ref.html">
<meta name="assert" content="For elements whose layout is governed by the CSS box model, any value other than none for the transform results in the creation of both a stacking context and a containing block. The object acts as a containing block for fixed positioned descendants.">
<meta name="assert" content="The object acts as a containing block for fixed positioned descendants, but also creates scrolling areas for them."
<meta name="flags" content="dom">
<style>
html, body { margin: 0; padding: 0 }
#transformed {
transform: translateX(10px) translateY(10px);
width: 200px;
height: 200px;
background: grey;
overflow: hidden;
}
#fixed {
position: fixed;
width: 50px;
height: 50px;
top: 50px;
left: 50px;
background: green;
}
#spacer {
height: 10000px;
width: 10000px;
}
</style>
<body>
<div id="transformed">
<div id="fixed"></div>
<div id="spacer"></div>
</div>
<script>
document.getElementById('transformed').scrollTo(50, 50);
</script>
</body>