diff options
Diffstat (limited to 'components/layout/positioned.rs')
-rw-r--r-- | components/layout/positioned.rs | 339 |
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, |