mirror of
https://github.com/servo/servo.git
synced 2025-08-06 14:10:11 +01:00
layout: Simplify PositioningContext
by having it hold a single Vec
(#36795)
`PositioningContext` held two vectors, one inside an `Option`, to differentiate between the version used for a containing block for all descendants (including `position: absolute` and `position: fixed`) or only for `position: absolute` descendants. This distinction was really hard to reason about and required a lot of bookkeeping about what kind of `PositioningContext` a layout box's parent expected. In addition, it led to a lot of mistakes. This change simplifies things so that `PositioningContext` only holds a single vector. When it comes time to lay out hoisted absolutely positioned fragments, the code then: - lays out all of them (in the case of a `PositioningContext` for all descendants), or - only lays out the `position: absolute` descendants and preserves the `position: fixed` descendants (in the case the `PositioningContext` is only for `position: absolute`.), or - lays out none of them if the `PositioningContext` was created for box that did not establish a containing block for absolutes. It's possible that this way of dealing with hoisted absolutes is a bit less efficient, but, the number of these descendants is typically quite small, so it should not be significant. In addition, this decreases the size in memory of all `PositioningContexts` which are created in more situations as time goes on. Testing: There is a new WPT test with this change. Fixes: #36696. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
e25e63b587
commit
9bc16482a3
12 changed files with 203 additions and 323 deletions
|
@ -42,16 +42,6 @@ pub(crate) struct AbsolutelyPositionedBox {
|
|||
pub context: IndependentFormattingContext,
|
||||
}
|
||||
|
||||
#[derive(Clone, MallocSizeOf)]
|
||||
pub(crate) struct PositioningContext {
|
||||
for_nearest_positioned_ancestor: Option<Vec<HoistedAbsolutelyPositionedBox>>,
|
||||
|
||||
// For nearest `containing block for all descendants` as defined by the CSS transforms
|
||||
// spec.
|
||||
// https://www.w3.org/TR/css-transforms-1/#containing-block-for-all-descendants
|
||||
for_nearest_containing_block_for_all_descendants: Vec<HoistedAbsolutelyPositionedBox>,
|
||||
}
|
||||
|
||||
#[derive(Clone, MallocSizeOf)]
|
||||
pub(crate) struct HoistedAbsolutelyPositionedBox {
|
||||
absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
|
||||
|
@ -104,55 +94,26 @@ impl AbsolutelyPositionedBox {
|
|||
}
|
||||
}
|
||||
|
||||
impl IndependentFormattingContext {
|
||||
#[inline]
|
||||
pub(crate) fn new_positioning_context(&self) -> Option<PositioningContext> {
|
||||
self.base.new_positioning_context()
|
||||
}
|
||||
}
|
||||
|
||||
impl LayoutBoxBase {
|
||||
#[inline]
|
||||
pub(crate) fn new_positioning_context(&self) -> Option<PositioningContext> {
|
||||
PositioningContext::new_for_style(&self.style, &self.base_fragment_info.flags)
|
||||
}
|
||||
#[derive(Clone, Default, MallocSizeOf)]
|
||||
pub(crate) struct PositioningContext {
|
||||
absolutes: Vec<HoistedAbsolutelyPositionedBox>,
|
||||
}
|
||||
|
||||
impl PositioningContext {
|
||||
pub(crate) fn new_for_containing_block_for_all_descendants() -> Self {
|
||||
Self {
|
||||
for_nearest_positioned_ancestor: None,
|
||||
for_nearest_containing_block_for_all_descendants: Vec::new(),
|
||||
}
|
||||
#[inline]
|
||||
pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> {
|
||||
Self::new_for_style_and_fragment_flags(
|
||||
&layout_box_base.style,
|
||||
&layout_box_base.base_fragment_info.flags,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a [PositioningContext] to use for laying out a subtree. The idea is that
|
||||
/// when subtree layout is finished, the newly hoisted boxes can be processed
|
||||
/// (normally adjusting their static insets) and then appended to the parent
|
||||
/// [PositioningContext].
|
||||
pub(crate) fn new_for_subtree(collects_for_nearest_positioned_ancestor: bool) -> Self {
|
||||
Self {
|
||||
for_nearest_positioned_ancestor: if collects_for_nearest_positioned_ancestor {
|
||||
Some(Vec::new())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
for_nearest_containing_block_for_all_descendants: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn collects_for_nearest_positioned_ancestor(&self) -> bool {
|
||||
self.for_nearest_positioned_ancestor.is_some()
|
||||
}
|
||||
|
||||
fn new_for_style(style: &ComputedValues, flags: &FragmentFlags) -> Option<Self> {
|
||||
if style.establishes_containing_block_for_all_descendants(*flags) {
|
||||
Some(Self::new_for_containing_block_for_all_descendants())
|
||||
} else if style.establishes_containing_block_for_absolute_descendants(*flags) {
|
||||
Some(Self {
|
||||
for_nearest_positioned_ancestor: Some(Vec::new()),
|
||||
for_nearest_containing_block_for_all_descendants: Vec::new(),
|
||||
})
|
||||
fn new_for_style_and_fragment_flags(
|
||||
style: &ComputedValues,
|
||||
flags: &FragmentFlags,
|
||||
) -> Option<Self> {
|
||||
if style.establishes_containing_block_for_absolute_descendants(*flags) {
|
||||
Some(Self::default())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -195,20 +156,9 @@ impl PositioningContext {
|
|||
offset: &PhysicalVec<Au>,
|
||||
index: PositioningContextLength,
|
||||
) {
|
||||
if let Some(hoisted_boxes) = self.for_nearest_positioned_ancestor.as_mut() {
|
||||
hoisted_boxes
|
||||
.iter_mut()
|
||||
.skip(index.for_nearest_positioned_ancestor)
|
||||
.for_each(|hoisted_fragment| {
|
||||
hoisted_fragment
|
||||
.fragment
|
||||
.borrow_mut()
|
||||
.adjust_offsets(offset)
|
||||
})
|
||||
}
|
||||
self.for_nearest_containing_block_for_all_descendants
|
||||
self.absolutes
|
||||
.iter_mut()
|
||||
.skip(index.for_nearest_containing_block_for_all_descendants)
|
||||
.skip(index.0)
|
||||
.for_each(|hoisted_fragment| {
|
||||
hoisted_fragment
|
||||
.fragment
|
||||
|
@ -227,19 +177,23 @@ impl PositioningContext {
|
|||
base: &LayoutBoxBase,
|
||||
fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
|
||||
) -> BoxFragment {
|
||||
// Try to create a context, but if one isn't necessary, simply create the fragment
|
||||
// using the given closure and the current `PositioningContext`.
|
||||
let mut new_context = match base.new_positioning_context() {
|
||||
Some(new_context) => new_context,
|
||||
None => return fragment_layout_fn(self),
|
||||
};
|
||||
// If a new `PositioningContext` isn't necessary, simply create the fragment using
|
||||
// the given closure and the current `PositioningContext`.
|
||||
let establishes_containing_block_for_absolutes = base
|
||||
.style
|
||||
.establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
|
||||
if !establishes_containing_block_for_absolutes {
|
||||
return fragment_layout_fn(self);
|
||||
}
|
||||
|
||||
let mut new_context = PositioningContext::default();
|
||||
let mut new_fragment = fragment_layout_fn(&mut new_context);
|
||||
new_context.layout_collected_children(layout_context, &mut new_fragment);
|
||||
|
||||
// If the new context has any hoisted boxes for the nearest containing block for
|
||||
// pass them up the tree.
|
||||
// Lay out all of the absolutely positioned children for this fragment, and, if it
|
||||
// isn't a containing block for fixed elements, then pass those up to the parent.
|
||||
new_context.layout_collected_children(layout_context, &mut new_fragment);
|
||||
self.append(new_context);
|
||||
|
||||
if base.style.clone_position() == Position::Relative {
|
||||
new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block)
|
||||
.to_physical_vector(containing_block.style.writing_mode)
|
||||
|
@ -248,13 +202,61 @@ impl PositioningContext {
|
|||
new_fragment
|
||||
}
|
||||
|
||||
fn take_boxes_for_fragment(
|
||||
&mut self,
|
||||
new_fragment: &BoxFragment,
|
||||
boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
|
||||
boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
|
||||
) {
|
||||
debug_assert!(
|
||||
new_fragment
|
||||
.style
|
||||
.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
|
||||
);
|
||||
|
||||
if new_fragment
|
||||
.style
|
||||
.establishes_containing_block_for_all_descendants(new_fragment.base.flags)
|
||||
{
|
||||
boxes_to_layout_out.append(&mut self.absolutes);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: This could potentially use `extract_if` when that is stabilized.
|
||||
let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
|
||||
.absolutes
|
||||
.drain(..)
|
||||
.partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
|
||||
boxes_to_layout_out.append(&mut boxes_to_layout);
|
||||
boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
|
||||
}
|
||||
|
||||
// Lay out the hoisted boxes collected into this `PositioningContext` and add them
|
||||
// to the given `BoxFragment`.
|
||||
pub fn layout_collected_children(
|
||||
pub(crate) fn layout_collected_children(
|
||||
&mut self,
|
||||
layout_context: &LayoutContext,
|
||||
new_fragment: &mut BoxFragment,
|
||||
) {
|
||||
if self.absolutes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and
|
||||
// then these are processed later. In that case and if this fragment doesn't establish a
|
||||
// containing block for absolutes at all, we just do nothing. All hoisted fragments will
|
||||
// later be passed up to a parent PositioningContext.
|
||||
//
|
||||
// Handling this case here, when the PositioningContext is completely ineffectual other than
|
||||
// as a temporary container for hoisted boxes, means that callers can execute less conditional
|
||||
// code.
|
||||
if !new_fragment
|
||||
.style
|
||||
.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let padding_rect = PhysicalRect::new(
|
||||
// Ignore the content rect’s position in its own containing block:
|
||||
PhysicalPoint::origin(),
|
||||
|
@ -268,83 +270,58 @@ impl PositioningContext {
|
|||
style: &new_fragment.style,
|
||||
};
|
||||
|
||||
let take_hoisted_boxes_pending_layout =
|
||||
|context: &mut Self| match context.for_nearest_positioned_ancestor.as_mut() {
|
||||
Some(fragments) => mem::take(fragments),
|
||||
None => mem::take(&mut context.for_nearest_containing_block_for_all_descendants),
|
||||
};
|
||||
let mut fixed_position_boxes_to_hoist = Vec::new();
|
||||
let mut boxes_to_layout = Vec::new();
|
||||
self.take_boxes_for_fragment(
|
||||
new_fragment,
|
||||
&mut boxes_to_layout,
|
||||
&mut fixed_position_boxes_to_hoist,
|
||||
);
|
||||
|
||||
// Loop because it’s possible that we discover (the static position of)
|
||||
// more absolutely-positioned boxes while doing layout for others.
|
||||
let mut hoisted_boxes = take_hoisted_boxes_pending_layout(self);
|
||||
let mut laid_out_child_fragments = Vec::new();
|
||||
while !hoisted_boxes.is_empty() {
|
||||
// Laying out a `position: absolute` child (which only establishes a containing block for
|
||||
// `position: absolute` descendants) can result in more `position: fixed` descendants
|
||||
// collecting in `self.absolutes`. We need to loop here in order to keep either laying them
|
||||
// out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more
|
||||
// when `self.absolutes` is empty.
|
||||
while !boxes_to_layout.is_empty() {
|
||||
HoistedAbsolutelyPositionedBox::layout_many(
|
||||
layout_context,
|
||||
&mut hoisted_boxes,
|
||||
&mut laid_out_child_fragments,
|
||||
&mut self.for_nearest_containing_block_for_all_descendants,
|
||||
std::mem::take(&mut boxes_to_layout),
|
||||
&mut new_fragment.children,
|
||||
&mut self.absolutes,
|
||||
&containing_block,
|
||||
new_fragment.padding,
|
||||
);
|
||||
hoisted_boxes = take_hoisted_boxes_pending_layout(self);
|
||||
|
||||
self.take_boxes_for_fragment(
|
||||
new_fragment,
|
||||
&mut boxes_to_layout,
|
||||
&mut fixed_position_boxes_to_hoist,
|
||||
);
|
||||
}
|
||||
|
||||
new_fragment.children.extend(laid_out_child_fragments);
|
||||
// We replace here instead of simply preserving these in `take_boxes_for_fragment`
|
||||
// so that we don't have to continually re-iterate over them when laying out in the
|
||||
// loop above.
|
||||
self.absolutes = fixed_position_boxes_to_hoist;
|
||||
}
|
||||
|
||||
pub(crate) fn push(&mut self, box_: HoistedAbsolutelyPositionedBox) {
|
||||
if let Some(nearest) = &mut self.for_nearest_positioned_ancestor {
|
||||
let position = box_
|
||||
.absolutely_positioned_box
|
||||
.borrow()
|
||||
.context
|
||||
.style()
|
||||
.clone_position();
|
||||
match position {
|
||||
Position::Fixed => {}, // fall through
|
||||
Position::Absolute => return nearest.push(box_),
|
||||
Position::Static | Position::Relative | Position::Sticky => unreachable!(),
|
||||
}
|
||||
}
|
||||
self.for_nearest_containing_block_for_all_descendants
|
||||
.push(box_)
|
||||
pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
|
||||
debug_assert!(matches!(
|
||||
hoisted_box.position(),
|
||||
Position::Absolute | Position::Fixed
|
||||
));
|
||||
self.absolutes.push(hoisted_box);
|
||||
}
|
||||
|
||||
pub(crate) fn is_empty(&self) -> bool {
|
||||
self.for_nearest_containing_block_for_all_descendants
|
||||
.is_empty() &&
|
||||
self.for_nearest_positioned_ancestor
|
||||
.as_ref()
|
||||
.is_none_or(|vector| vector.is_empty())
|
||||
}
|
||||
|
||||
pub(crate) fn append(&mut self, other: Self) {
|
||||
if other.is_empty() {
|
||||
pub(crate) fn append(&mut self, mut other: Self) {
|
||||
if other.absolutes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
vec_append_owned(
|
||||
&mut self.for_nearest_containing_block_for_all_descendants,
|
||||
other.for_nearest_containing_block_for_all_descendants,
|
||||
);
|
||||
|
||||
match (
|
||||
self.for_nearest_positioned_ancestor.as_mut(),
|
||||
other.for_nearest_positioned_ancestor,
|
||||
) {
|
||||
(Some(us), Some(them)) => vec_append_owned(us, them),
|
||||
(None, Some(them)) => {
|
||||
// This is the case where we have laid out the absolute children in a containing
|
||||
// block for absolutes and we then are passing up the fixed-position descendants
|
||||
// to the containing block for all descendants.
|
||||
vec_append_owned(
|
||||
&mut self.for_nearest_containing_block_for_all_descendants,
|
||||
them,
|
||||
);
|
||||
},
|
||||
(None, None) => {},
|
||||
_ => unreachable!(),
|
||||
if self.absolutes.is_empty() {
|
||||
self.absolutes = other.absolutes;
|
||||
} else {
|
||||
self.absolutes.append(&mut other.absolutes)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,19 +331,16 @@ impl PositioningContext {
|
|||
initial_containing_block: &DefiniteContainingBlock,
|
||||
fragments: &mut Vec<Fragment>,
|
||||
) {
|
||||
debug_assert!(self.for_nearest_positioned_ancestor.is_none());
|
||||
|
||||
// Loop because it’s possible that we discover (the static position of)
|
||||
// more absolutely-positioned boxes while doing layout for others.
|
||||
while !self
|
||||
.for_nearest_containing_block_for_all_descendants
|
||||
.is_empty()
|
||||
{
|
||||
// Laying out a `position: absolute` child (which only establishes a containing block for
|
||||
// `position: absolute` descendants) can result in more `position: fixed` descendants
|
||||
// collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We
|
||||
// know there aren't any more when `self.absolutes` is empty.
|
||||
while !self.absolutes.is_empty() {
|
||||
HoistedAbsolutelyPositionedBox::layout_many(
|
||||
layout_context,
|
||||
&mut mem::take(&mut self.for_nearest_containing_block_for_all_descendants),
|
||||
mem::take(&mut self.absolutes),
|
||||
fragments,
|
||||
&mut self.for_nearest_containing_block_for_all_descendants,
|
||||
&mut self.absolutes,
|
||||
initial_containing_block,
|
||||
Default::default(),
|
||||
)
|
||||
|
@ -375,58 +349,46 @@ impl PositioningContext {
|
|||
|
||||
/// Get the length of this [PositioningContext].
|
||||
pub(crate) fn len(&self) -> PositioningContextLength {
|
||||
PositioningContextLength {
|
||||
for_nearest_positioned_ancestor: self
|
||||
.for_nearest_positioned_ancestor
|
||||
.as_ref()
|
||||
.map_or(0, |vec| vec.len()),
|
||||
for_nearest_containing_block_for_all_descendants: self
|
||||
.for_nearest_containing_block_for_all_descendants
|
||||
.len(),
|
||||
}
|
||||
PositioningContextLength(self.absolutes.len())
|
||||
}
|
||||
|
||||
/// Truncate this [PositioningContext] to the given [PositioningContextLength]. This
|
||||
/// is useful for "unhoisting" boxes in this context and returning it to the state at
|
||||
/// the time that [`PositioningContext::len()`] was called.
|
||||
pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
|
||||
if let Some(vec) = self.for_nearest_positioned_ancestor.as_mut() {
|
||||
vec.truncate(length.for_nearest_positioned_ancestor);
|
||||
}
|
||||
self.for_nearest_containing_block_for_all_descendants
|
||||
.truncate(length.for_nearest_containing_block_for_all_descendants);
|
||||
self.absolutes.truncate(length.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A data structure which stores the size of a positioning context.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub(crate) struct PositioningContextLength {
|
||||
/// The number of boxes that will be hoisted the the nearest positioned ancestor for
|
||||
/// layout.
|
||||
for_nearest_positioned_ancestor: usize,
|
||||
/// The number of boxes that will be hoisted the the nearest ancestor which
|
||||
/// establishes a containing block for all descendants for layout.
|
||||
for_nearest_containing_block_for_all_descendants: usize,
|
||||
}
|
||||
pub(crate) struct PositioningContextLength(usize);
|
||||
|
||||
impl Zero for PositioningContextLength {
|
||||
fn zero() -> Self {
|
||||
PositioningContextLength {
|
||||
for_nearest_positioned_ancestor: 0,
|
||||
for_nearest_containing_block_for_all_descendants: 0,
|
||||
}
|
||||
Self(0)
|
||||
}
|
||||
|
||||
fn is_zero(&self) -> bool {
|
||||
self.for_nearest_positioned_ancestor == 0 &&
|
||||
self.for_nearest_containing_block_for_all_descendants == 0
|
||||
self.0.is_zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl HoistedAbsolutelyPositionedBox {
|
||||
fn position(&self) -> Position {
|
||||
let position = self
|
||||
.absolutely_positioned_box
|
||||
.borrow()
|
||||
.context
|
||||
.style()
|
||||
.clone_position();
|
||||
assert!(position == Position::Fixed || position == Position::Absolute);
|
||||
position
|
||||
}
|
||||
|
||||
pub(crate) fn layout_many(
|
||||
layout_context: &LayoutContext,
|
||||
boxes: &mut [Self],
|
||||
mut boxes: Vec<Self>,
|
||||
fragments: &mut Vec<Fragment>,
|
||||
for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
|
||||
containing_block: &DefiniteContainingBlock,
|
||||
|
@ -473,7 +435,7 @@ impl HoistedAbsolutelyPositionedBox {
|
|||
pub(crate) fn layout(
|
||||
&mut self,
|
||||
layout_context: &LayoutContext,
|
||||
for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
|
||||
hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
|
||||
containing_block: &DefiniteContainingBlock,
|
||||
containing_block_padding: PhysicalSides<Au>,
|
||||
) -> Fragment {
|
||||
|
@ -596,7 +558,7 @@ impl HoistedAbsolutelyPositionedBox {
|
|||
.sizes
|
||||
}));
|
||||
|
||||
let mut positioning_context = context.new_positioning_context().unwrap();
|
||||
let mut positioning_context = PositioningContext::default();
|
||||
let mut new_fragment = {
|
||||
let content_size: LogicalVec2<Au>;
|
||||
let fragments;
|
||||
|
@ -709,6 +671,10 @@ impl HoistedAbsolutelyPositionedBox {
|
|||
)
|
||||
.with_specific_layout_info(specific_layout_info)
|
||||
};
|
||||
|
||||
// This is an absolutely positioned element, which means it also establishes a
|
||||
// containing block for absolutes. We lay out any absolutely positioned children
|
||||
// here and pass the rest to `hoisted_absolutes_from_children.`
|
||||
positioning_context.layout_collected_children(layout_context, &mut new_fragment);
|
||||
|
||||
// Any hoisted boxes that remain in this positioning context are going to be hoisted
|
||||
|
@ -721,8 +687,7 @@ impl HoistedAbsolutelyPositionedBox {
|
|||
PositioningContextLength::zero(),
|
||||
);
|
||||
|
||||
for_nearest_containing_block_for_all_descendants
|
||||
.extend(positioning_context.for_nearest_containing_block_for_all_descendants);
|
||||
hoisted_absolutes_from_children.extend(positioning_context.absolutes);
|
||||
|
||||
let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
|
||||
context.base.set_fragment(fragment.clone());
|
||||
|
@ -1024,14 +989,6 @@ impl AbsoluteAxisSolver<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn vec_append_owned<T>(a: &mut Vec<T>, mut b: Vec<T>) {
|
||||
if a.is_empty() {
|
||||
*a = b
|
||||
} else {
|
||||
a.append(&mut b)
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://drafts.csswg.org/css2/visuren.html#relative-positioning>
|
||||
pub(crate) fn relative_adjustement(
|
||||
style: &ComputedValues,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue