aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout/positioned.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout/positioned.rs')
-rw-r--r--components/layout/positioned.rs339
1 files changed, 148 insertions, 191 deletions
diff --git a/components/layout/positioned.rs b/components/layout/positioned.rs
index 6bfe2af87ef..ff361396fa3 100644
--- a/components/layout/positioned.rs
+++ b/components/layout/positioned.rs
@@ -43,16 +43,6 @@ pub(crate) struct AbsolutelyPositionedBox {
}
#[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(),
- }
- }
-
- /// 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()
+ #[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,
+ )
}
- 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);
- }
- new_fragment.children.extend(laid_out_child_fragments);
- }
-
- 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.take_boxes_for_fragment(
+ new_fragment,
+ &mut boxes_to_layout,
+ &mut fixed_position_boxes_to_hoist,
+ );
}
- self.for_nearest_containing_block_for_all_descendants
- .push(box_)
+
+ // 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 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 push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
+ debug_assert!(matches!(
+ hoisted_box.position(),
+ Position::Absolute | Position::Fixed
+ ));
+ self.absolutes.push(hoisted_box);
}
- 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,