/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/layout/positioned.rs
1 112 строк
45 KB
Oriol Brufau
layout: Adjust the static position of abspos inside float in inline layout (#46505)
16 июл 2026, 07:17
Не верифицирован
16 июл 2026, 07:17
955cbec
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::mem; use std::ops::Range; use std::sync::Arc; use app_units::Au; use malloc_size_of_derive::MallocSizeOf; use rayon::iter::IntoParallelRefMutIterator; use rayon::prelude::{IndexedParallelIterator, ParallelIterator}; use servo_arc::Arc as ServoArc; use style::Zero; use style::computed_values::position::T as Position; use style::logical_geometry::{Direction, WritingMode}; use style::properties::ComputedValues; use style::values::specified::align::AlignFlags; use crate::cell::ArcRefCell; use crate::context::LayoutContext; use crate::dom_traversal::{Contents, NodeAndStyleInfo}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::{ BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment, LayoutRootFragment, }; use crate::geom::{ AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, ToLogical, ToLogicalWithContainingBlock, }; use crate::layout_box_base::{IndependentFormattingContextLayoutResult, LayoutBoxBase}; use crate::sizing::{LazySize, Size, SizeConstraint, Sizes}; use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside}; use crate::{ ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock, PropagatedBoxTreeData, }; #[derive(Debug, MallocSizeOf)] pub(crate) struct AbsolutelyPositionedBox { pub context: IndependentFormattingContext, } #[derive(Clone, MallocSizeOf)] pub(crate) struct HoistedAbsolutelyPositionedBox { absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>, /// A reference to a Fragment which is shared between this `HoistedAbsolutelyPositionedBox` /// and its placeholder `AbsoluteOrFixedPositionedFragment` in the original tree position. /// This will be used later in order to paint this hoisted box in tree order. pub fragment: ArcRefCell<HoistedSharedFragment>, /// The adjusted "static-position rect" of this absolutely positioned box. This is /// defined by the layout mode from which the box originates. This is the /// [`HoistedSharedFragment::original_static_position_rect`] adjusted by the offests /// of ancestors between the tree position of the absolute and the /// [`PostioningContext`] that holds this [`HoistedAbsolutelyPositionedBox`]. /// /// If the value is `None`, the original static position rect has not been adjusted yet. /// /// See <https://drafts.csswg.org/css-position-3/#staticpos-rect> pub adjusted_static_position_rect: Option<PhysicalRect<Au>>, /// The resolved alignment values used for aligning this absolutely positioned element /// if the "static-position rect" ends up being the "inset-modified containing block". /// These values are dependent on the layout mode (currently only interesting for /// flexbox). pub resolved_alignment: LogicalVec2<AlignFlags>, /// This is the [`WritingMode`] of the original parent of the element that created this /// hoisted absolutely-positioned fragment. This helps to interpret the offset for /// static positioning. If the writing mode is right-to-left or bottom-to-top, the static /// offset needs to be adjusted by the absolutely positioned element's inline size. pub original_parent_writing_mode: WritingMode, } impl AbsolutelyPositionedBox { pub fn new(context: IndependentFormattingContext) -> Self { Self { context } } pub fn construct( context: &LayoutContext, node_info: &NodeAndStyleInfo, display_inside: DisplayInside, contents: Contents, ) -> Self { Self { context: IndependentFormattingContext::construct( context, node_info, display_inside, contents, // Text decorations are not propagated to any out-of-flow descendants. In addition, // absolutes don't affect the size of ancestors so it is fine to allow descendent // tables to resolve percentage columns. PropagatedBoxTreeData::default(), ), } } pub(crate) fn to_hoisted( absolutely_positioned_box: ArcRefCell<Self>, static_position_rect: PhysicalRect<Au>, resolved_alignment: LogicalVec2<AlignFlags>, original_parent_writing_mode: WritingMode, ) -> HoistedAbsolutelyPositionedBox { HoistedAbsolutelyPositionedBox { fragment: ArcRefCell::new(HoistedSharedFragment::new(static_position_rect)), adjusted_static_position_rect: None, resolved_alignment, original_parent_writing_mode, absolutely_positioned_box, } } } #[derive(Clone, Default, MallocSizeOf)] pub(crate) struct PositioningContext { absolutes: Vec<HoistedAbsolutelyPositionedBox>, } impl PositioningContext { #[inline] pub(crate) fn is_empty(&self) -> bool { self.absolutes.is_empty() } #[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_and_fragment_flags( style: &ComputedValues, flags: &FragmentFlags, ) -> Option<Self> { if style.establishes_containing_block_for_absolute_descendants(*flags) { Some(Self::default()) } else { None } } /// Absolute and fixed position fragments are hoisted up to their containing blocks /// from their tree position. When these fragments have static inset start positions, /// that position (relative to the ancestor containing block) needs to be included /// with the hoisted fragment so that it can be laid out properly at the containing /// block. /// /// This function is used to update the static position of hoisted boxes added after /// the given index at every level of the fragment tree as the hoisted fragments move /// up to their containing blocks. Once an ancestor fragment is laid out, this /// function can be used to aggregate its offset to any descendent boxes that are /// being hoisted. In this case, the appropriate index to use is the result of /// [`PositioningContext::len()`] cached before laying out the [`Fragment`]. pub(crate) fn adjust_static_position_of_hoisted_fragments( &mut self, parent_fragment: &Fragment, index: PositioningContextLength, ) { let Some(base) = parent_fragment.base() else { return; }; self.adjust_static_position_of_hoisted_fragments_with_offset( &base.rect().origin.to_vector(), index, ); } /// See documentation for [PositioningContext::adjust_static_position_of_hoisted_fragments]. pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset( &mut self, offset: &PhysicalVec<Au>, index: PositioningContextLength, ) { self.adjust_static_position_of_hoisted_fragments_in_range(offset, &(index..self.len())) } /// See documentation for [PositioningContext::adjust_static_position_of_hoisted_fragments]. pub(crate) fn adjust_static_position_of_hoisted_fragments_in_range( &mut self, offset: &PhysicalVec<Au>, range: &Range<PositioningContextLength>, ) { for hoisted_box in &mut self.absolutes[range.start.0..range.end.0] { hoisted_box.adjust_static_position_with_offset(offset); } } /// Given `fragment_layout_fn`, a closure which lays out a fragment in a provided /// `PositioningContext`, create a new positioning context if necessary for the fragment and /// lay out the fragment and all its children. Returns the newly created `BoxFragment`. pub(crate) fn layout_maybe_position_relative_fragment( &mut self, layout_context: &LayoutContext, containing_block: &ContainingBlock, base: &LayoutBoxBase, fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment, ) -> BoxFragment { // 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); // 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.base.translate_rect( relative_adjustement(&base.style, containing_block) .to_physical_vector(containing_block.style.writing_mode) .into(), ); } new_fragment } fn forget_unhoisted_boxes(&mut self, fragment: &BoxFragment) { let style = fragment.style(); debug_assert!( style.establishes_containing_block_for_absolute_descendants(fragment.base.flags) ); if style.establishes_containing_block_for_all_descendants(fragment.base.flags) { self.absolutes.clear(); } else { self.absolutes .retain(|hoisted_box| hoisted_box.position() == Position::Fixed); } } 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>, ) { let style = new_fragment.style(); debug_assert!( style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) ); if 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(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. let style = new_fragment.style().clone(); if !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(), new_fragment.base.rect().size, ) .outer_rect(new_fragment.padding); let containing_block = DefiniteContainingBlock { size: padding_rect.size.to_logical(style.writing_mode), style: &style, }; 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, ); // 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, std::mem::take(&mut boxes_to_layout), &mut new_fragment.children, &mut self.absolutes, &containing_block, new_fragment.padding, ); self.take_boxes_for_fragment( new_fragment, &mut boxes_to_layout, &mut fixed_position_boxes_to_hoist, ); } // 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, hoisted_box: HoistedAbsolutelyPositionedBox) { debug_assert!(hoisted_box.position().is_absolutely_positioned()); self.absolutes.push(hoisted_box); } pub(crate) fn append(&mut self, mut other: Self) { if other.absolutes.is_empty() { return; } if self.absolutes.is_empty() { self.absolutes = other.absolutes; } else { self.absolutes.append(&mut other.absolutes) } } pub(crate) fn layout_initial_containing_block_children( &mut self, layout_context: &LayoutContext, initial_containing_block: &DefiniteContainingBlock, fragments: &mut Vec<Fragment>, ) { // 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, mem::take(&mut self.absolutes), fragments, &mut self.absolutes, initial_containing_block, Default::default(), ) } } /// Get the length of this [PositioningContext]. pub(crate) fn len(&self) -> PositioningContextLength { 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) { 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(usize); impl Zero for PositioningContextLength { fn zero() -> Self { Self(0) } fn is_zero(&self) -> bool { self.0.is_zero() } } impl HoistedAbsolutelyPositionedBox { fn position(&self) -> Position { let position = self .absolutely_positioned_box .borrow() .context .style() .clone_position(); assert!(position.is_absolutely_positioned()); position } pub(crate) fn layout_many( layout_context: &LayoutContext, mut boxes: Vec<Self>, fragments: &mut Vec<Fragment>, for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>, containing_block: &DefiniteContainingBlock, containing_block_padding: PhysicalSides<Au>, ) { let job_sizes = boxes.iter().map(|hoisted_box| { hoisted_box .absolutely_positioned_box .borrow() .context .subtree_size() }); if layout_context.should_parallelize_layout(job_sizes) { let mut new_fragments = Vec::new(); let mut new_hoisted_boxes = Vec::new(); boxes .par_iter_mut() .map(|hoisted_box| { let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new(); let new_fragment = hoisted_box.layout( layout_context, &mut new_hoisted_boxes, containing_block, containing_block_padding, ); (new_fragment, new_hoisted_boxes) }) .unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes); fragments.extend(new_fragments); for_nearest_containing_block_for_all_descendants .extend(new_hoisted_boxes.into_iter().flatten()); } else { fragments.extend(boxes.iter_mut().map(|hoisted_box| { hoisted_box.layout( layout_context, for_nearest_containing_block_for_all_descendants, containing_block, containing_block_padding, ) })) } } pub(crate) fn layout( &mut self, layout_context: &LayoutContext, hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>, containing_block: &DefiniteContainingBlock, containing_block_padding: PhysicalSides<Au>, ) -> Fragment { // The static position rect was calculated assuming that the containing block would be // established by the content box of some ancestor, but the actual containing block is // established by the padding box. So we need to translate the rect by the padding of // that ancestor. let mut static_position_rect = self.static_position_rect().translate(PhysicalVec::new( containing_block_padding.left, containing_block_padding.top, )); static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero()); let fully_adjusted_static_position_rect = static_position_rect.to_logical(&containing_block.into()); let absolutely_positioned_box = self.absolutely_positioned_box.borrow(); let independent_formatting_context = &absolutely_positioned_box.context; let (box_fragment, mut positioning_context) = independent_formatting_context .layout_as_absolute( layout_context, &fully_adjusted_static_position_rect, containing_block, self.resolved_alignment, self.original_parent_writing_mode, ); // An absolutely-positioned box can be a layout root if it does not hoist any // fixed positioned boxes out of it. This condition ensures isolation from parent // layout meaning that laying out the absolutely positioned box again, will not // affect ancestor layout. let is_layout_root = positioning_context.is_empty(); // Any hoisted boxes that remain in this positioning context are going to be hoisted // up above this absolutely positioned box. These will necessarily be fixed position // elements, because absolutely positioned elements form containing blocks for all // other elements. If any of them have a static start position though, we need to // adjust it to account for the start corner of this absolute. positioning_context.adjust_static_position_of_hoisted_fragments_with_offset( &box_fragment.content_rect().origin.to_vector(), PositioningContextLength::zero(), ); hoisted_absolutes_from_children.extend(positioning_context.absolutes); let fragment = Fragment::Box(box_fragment); self.fragment.borrow_mut().fragment = Some(fragment.clone()); let fragment = match is_layout_root { false => fragment, true => Fragment::LayoutRoot(LayoutRootFragment { fragment: self.fragment.clone(), }), }; independent_formatting_context .base .set_fragment(fragment.clone()); *independent_formatting_context .layout_root_layout_inputs .borrow_mut() = is_layout_root.then(|| { Box::new(LayoutRootLayoutInputs { fully_adjusted_static_position_rect, resolved_alignment: self.resolved_alignment, containing_block_size: containing_block.size, containing_block_style: containing_block.style.clone(), original_parent_writing_mode: self.original_parent_writing_mode, }) }); fragment } fn static_position_rect(&self) -> PhysicalRect<Au> { self.adjusted_static_position_rect .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect) } fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) { self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset)); } } impl IndependentFormattingContext { pub(crate) fn layout_as_absolute( &self, layout_context: &LayoutContext, static_position_rect: &LogicalRect<Au>, containing_block: &DefiniteContainingBlock, resolved_alignment: LogicalVec2<AlignFlags>, original_parent_writing_mode: WritingMode, ) -> (Arc<BoxFragment>, PositioningContext) { let cbis = containing_block.size.inline; let cbbs = containing_block.size.block; let containing_block_writing_mode = containing_block.style.writing_mode; let style = self.style().clone(); let layout_style = self.layout_style(); let ContentBoxSizesAndPBM { content_box_sizes, pbm, .. } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into()); let is_table = layout_style.is_table(); let is_table_or_replaced = is_table || self.is_replaced(); let preferred_aspect_ratio = self.preferred_aspect_ratio(&pbm.padding_border_sums); let box_offset = style.box_offsets(containing_block.style.writing_mode); // When the "static-position rect" doesn't come into play, we do not do any alignment // in the inline axis. let inline_box_offsets = box_offset.inline_sides().percentages_relative_to(cbis); let inline_alignment = match inline_box_offsets.either_specified() { true => style.clone_justify_self().0, false => resolved_alignment.inline, }; let inline_axis_solver = AbsoluteAxisSolver { axis: Direction::Inline, containing_size: cbis, padding_border_sum: pbm.padding_border_sums.inline, computed_margin_start: pbm.margin.inline_start, computed_margin_end: pbm.margin.inline_end, computed_sizes: content_box_sizes.inline, avoid_negative_margin_start: true, box_offsets: inline_box_offsets, static_position_rect_axis: static_position_rect.get_axis(Direction::Inline), alignment: inline_alignment, flip_anchor: original_parent_writing_mode.is_bidi_ltr() != containing_block_writing_mode.is_bidi_ltr(), is_table_or_replaced, }; // When the "static-position rect" doesn't come into play, we re-resolve "align-self" // against this containing block. let block_box_offsets = box_offset.block_sides().percentages_relative_to(cbbs); let block_alignment = match block_box_offsets.either_specified() { true => style.clone_align_self().0, false => resolved_alignment.block, }; let block_axis_solver = AbsoluteAxisSolver { axis: Direction::Block, containing_size: cbbs, padding_border_sum: pbm.padding_border_sums.block, computed_margin_start: pbm.margin.block_start, computed_margin_end: pbm.margin.block_end, computed_sizes: content_box_sizes.block, avoid_negative_margin_start: false, box_offsets: block_box_offsets, static_position_rect_axis: static_position_rect.get_axis(Direction::Block), alignment: block_alignment, flip_anchor: false, is_table_or_replaced, }; // The block size can depend on layout results, so we only solve it tentatively, // we may have to resolve it properly later on. let block_automatic_size = block_axis_solver.automatic_size(); let block_stretch_size = Some(block_axis_solver.stretch_size()); let inline_stretch_size = inline_axis_solver.stretch_size(); let tentative_block_content_size = self.tentative_block_content_size(preferred_aspect_ratio, inline_stretch_size); let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size { SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve( Direction::Block, block_automatic_size, Au::zero, block_stretch_size, || block_content_size, is_table, )) } else { block_axis_solver.computed_sizes.resolve_extrinsic( block_automatic_size, Au::zero(), block_stretch_size, ) }; // The inline axis can be fully resolved, computing intrinsic sizes using the // extrinsic block size. let get_inline_content_size = || { let constraint_space = ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio); self.inline_content_sizes(layout_context, &constraint_space) .sizes }; let inline_size = inline_axis_solver.computed_sizes.resolve( Direction::Inline, inline_axis_solver.automatic_size(), Au::zero, Some(inline_stretch_size), get_inline_content_size, is_table, ); let containing_block_for_children = ContainingBlock { size: ContainingBlockSize { inline: inline_size, block: tentative_block_size, }, style: &style, }; // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows assert_eq!( containing_block_writing_mode.is_horizontal(), style.writing_mode.is_horizontal(), "Mixed horizontal and vertical writing modes are not supported yet" ); let mut positioning_context = PositioningContext::default(); let lazy_block_size = LazySize::new( &block_axis_solver.computed_sizes, Direction::Block, block_automatic_size, Au::zero, block_stretch_size, is_table, ); let containing_block = &containing_block.into(); let (layout, is_cached) = self.layout_and_is_cached( layout_context, &mut positioning_context, &containing_block_for_children, containing_block, preferred_aspect_ratio, &lazy_block_size, ); let IndependentFormattingContextLayoutResult { content_inline_size_for_table, content_block_size, fragments, specific_layout_info, .. } = layout; let content_size = LogicalVec2 { // Tables can become narrower than predicted due to collapsed columns. inline: content_inline_size_for_table.unwrap_or(inline_size), // Now we can properly solve the block size. block: lazy_block_size.resolve(|| content_block_size), }; let inline_margins = inline_axis_solver.solve_margins(content_size.inline); let block_margins = block_axis_solver.solve_margins(content_size.block); let margin = LogicalSides { inline_start: inline_margins.start, inline_end: inline_margins.end, block_start: block_margins.start, block_end: block_margins.end, }; let pb = pbm.padding + pbm.border; let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum(); let inline_origin = inline_axis_solver.origin_for_margin_box( margin_rect_size.inline, style.writing_mode, original_parent_writing_mode, containing_block_writing_mode, ); let block_origin = block_axis_solver.origin_for_margin_box( margin_rect_size.block, style.writing_mode, original_parent_writing_mode, containing_block_writing_mode, ); let content_rect = LogicalRect { start_corner: LogicalVec2 { inline: inline_origin + margin.inline_start + pb.inline_start, block: block_origin + margin.block_start + pb.block_start, }, size: content_size, } .as_physical(Some(containing_block)); if is_cached && let Some(old_fragment) = self.base.fragments().first() && let Some(old_box_fragment) = old_fragment .retrieve_box_fragment() .map(|fragment| fragment.clone()) && content_rect == old_box_fragment.content_rect() { // Drain the nested absolutes for which we are a containing block. // However, we are reusing the fragment, so no need to lay them out again. positioning_context.forget_unhoisted_boxes(&old_box_fragment); return (old_box_fragment, positioning_context); } let mut new_box_fragment = BoxFragment::new( self.base_fragment_info(), style, fragments, content_rect, pbm.padding.to_physical(containing_block_writing_mode), pbm.border.to_physical(containing_block_writing_mode), margin.to_physical(containing_block_writing_mode), 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_box_fragment); (new_box_fragment.into(), positioning_context) } } #[derive(Clone, Copy, Debug)] struct RectAxis { origin: Au, length: Au, } impl LogicalRect<Au> { fn get_axis(&self, axis: Direction) -> RectAxis { match axis { Direction::Block => RectAxis { origin: self.start_corner.block, length: self.size.block, }, Direction::Inline => RectAxis { origin: self.start_corner.inline, length: self.size.inline, }, } } } struct AbsoluteAxisSolver { axis: Direction, containing_size: Au, padding_border_sum: Au, computed_margin_start: AuOrAuto, computed_margin_end: AuOrAuto, computed_sizes: Sizes, avoid_negative_margin_start: bool, box_offsets: LogicalSides1D<AuOrAuto>, static_position_rect_axis: RectAxis, alignment: AlignFlags, flip_anchor: bool, is_table_or_replaced: bool, } impl AbsoluteAxisSolver { /// Returns the amount that we need to subtract from the containing block size in order to /// obtain the inset-modified containing block that we will use for sizing purposes. /// (Note that for alignment purposes, we may re-resolve auto insets to a different value.) /// <https://drafts.csswg.org/css-position/#resolving-insets> fn inset_sum(&self) -> Au { match ( self.box_offsets.start.non_auto(), self.box_offsets.end.non_auto(), ) { (None, None) => { if self.flip_anchor { self.containing_size - self.static_position_rect_axis.origin - self.static_position_rect_axis.length } else { self.static_position_rect_axis.origin } }, (Some(start), None) => start, (None, Some(end)) => end, (Some(start), Some(end)) => start + end, } } /// Returns the size of the inset-modified containing block. /// <https://drafts.csswg.org/css-position-3/#inset-modified-containing-block> #[inline] fn available_space(&self) -> Au { Au::zero().max(self.containing_size - self.inset_sum()) } #[inline] fn automatic_size(&self) -> Size<Au> { match self.alignment.value() { _ if self.box_offsets.either_auto() => Size::FitContent, AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch, AlignFlags::STRETCH => Size::Stretch, _ => Size::FitContent, } } #[inline] fn stretch_size(&self) -> Au { Au::zero().max( self.available_space() - self.padding_border_sum - self.computed_margin_start.auto_is(Au::zero) - self.computed_margin_end.auto_is(Au::zero), ) } fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> { if self.box_offsets.either_auto() { LogicalSides1D::new( self.computed_margin_start.auto_is(Au::zero), self.computed_margin_end.auto_is(Au::zero), ) } else { let free_space = self.available_space() - self.padding_border_sum - size; match (self.computed_margin_start, self.computed_margin_end) { (AuOrAuto::Auto, AuOrAuto::Auto) => { if self.avoid_negative_margin_start && free_space < Au::zero() { LogicalSides1D::new(Au::zero(), free_space) } else { let margin_start = free_space / 2; LogicalSides1D::new(margin_start, free_space - margin_start) } }, (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => { LogicalSides1D::new(free_space - end, end) }, (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => { LogicalSides1D::new(start, free_space - start) }, (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => { LogicalSides1D::new(start, end) }, } } } fn origin_for_margin_box( &self, size: Au, self_writing_mode: WritingMode, original_parent_writing_mode: WritingMode, containing_block_writing_mode: WritingMode, ) -> Au { let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match ( self.box_offsets.start.non_auto(), self.box_offsets.end.non_auto(), ) { (None, None) => ( self.static_position_rect_axis, original_parent_writing_mode, self.flip_anchor, None, ), (Some(start), Some(end)) => { let alignment_container = RectAxis { origin: start, length: self.available_space(), }; ( alignment_container, containing_block_writing_mode, false, Some(LogicalSides1D { start, end }), ) }, // If a single offset is auto, for alignment purposes it resolves to the amount // that makes the inset-modified containing block be exactly as big as the abspos. // Therefore the free space is zero and the alignment value is irrelevant. (Some(start), None) => return start, (None, Some(end)) => { return self.containing_size - size - end; }, }; assert_eq!( self_writing_mode.is_horizontal(), original_parent_writing_mode.is_horizontal(), "Mixed horizontal and vertical writing modes are not supported yet" ); assert_eq!( self_writing_mode.is_horizontal(), containing_block_writing_mode.is_horizontal(), "Mixed horizontal and vertical writing modes are not supported yet" ); let self_value_matches_container = || { self.axis == Direction::Block || self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr() }; // Here we resolve the alignment to either start, center, or end. // Note we need to handle both self-alignment values (when some inset isn't auto) // and distributed alignment values (when both insets are auto). // The latter are treated as their fallback alignment. let alignment = match self.alignment.value() { // https://drafts.csswg.org/css-align/#valdef-self-position-center // https://drafts.csswg.org/css-align/#valdef-align-content-space-around // https://drafts.csswg.org/css-align/#valdef-align-content-space-evenly AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => { AlignFlags::CENTER }, // https://drafts.csswg.org/css-align/#valdef-self-position-self-start AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START, AlignFlags::SELF_START => AlignFlags::END, // https://drafts.csswg.org/css-align/#valdef-self-position-self-end AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END, AlignFlags::SELF_END => AlignFlags::START, // https://drafts.csswg.org/css-align/#valdef-justify-content-left AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START, AlignFlags::LEFT => AlignFlags::END, // https://drafts.csswg.org/css-align/#valdef-justify-content-right AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END, AlignFlags::RIGHT => AlignFlags::START, // https://drafts.csswg.org/css-align/#valdef-self-position-end // https://drafts.csswg.org/css-align/#valdef-self-position-flex-end // https://drafts.csswg.org/css-align/#valdef-justify-self-last-baseline AlignFlags::END | AlignFlags::FLEX_END | AlignFlags::LAST_BASELINE => AlignFlags::END, // https://drafts.csswg.org/css-align/#valdef-self-position-start // https://drafts.csswg.org/css-align/#valdef-self-position-flex-start // https://drafts.csswg.org/css-align/#valdef-justify-self-first-baseline _ => AlignFlags::START, }; let alignment = match alignment { AlignFlags::START if flip_anchor => AlignFlags::END, AlignFlags::END if flip_anchor => AlignFlags::START, alignment => alignment, }; let free_space = alignment_container.length - size; let flags = self.alignment.flags(); let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() { AlignFlags::START } else { alignment }; let origin = match alignment { AlignFlags::START => alignment_container.origin, AlignFlags::CENTER => alignment_container.origin + free_space / 2, AlignFlags::END => alignment_container.origin + free_space, _ => unreachable!(), }; if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) || matches!( self.alignment, AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH ) { return origin; } let Some(offsets) = offsets else { return origin; }; // Handle default overflow alignment. // https://drafts.csswg.org/css-align/#auto-safety-position let min = Au::zero().min(offsets.start); let max = self.containing_size - Au::zero().min(offsets.end) - size; origin.clamp_between_extremums(min, Some(max)) } } /// <https://drafts.csswg.org/css2/visuren.html#relative-positioning> pub(crate) fn relative_adjustement( style: &ComputedValues, containing_block: &ContainingBlock, ) -> LogicalVec2<Au> { // It's not completely clear what to do with indefinite percentages // (https://github.com/w3c/csswg-drafts/issues/9353), so we match // other browsers and treat them as 'auto' offsets. let cbis = containing_block.size.inline; let cbbs = containing_block.size.block; let box_offsets = style .box_offsets(containing_block.style.writing_mode) .map_inline_and_block_axes( |value| value.map(|value| value.to_used_value(cbis)), |value| match cbbs { SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)), _ => match value.non_auto().and_then(|value| value.to_length()) { Some(value) => AuOrAuto::LengthPercentage(value.into()), None => AuOrAuto::Auto, }, }, ); fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au { match (start, end) { (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(), (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end, (AuOrAuto::LengthPercentage(start), _) => start, } } LogicalVec2 { inline: adjust(box_offsets.inline_start, box_offsets.inline_end), block: adjust(box_offsets.block_start, box_offsets.block_end), } } /// These are the recorded layout inputs that were used when laying out an /// absolutely-positioned element. They can be re-used when the absolutely-positioned /// element is a viable layout root (no escaping fixed position elements, currently). The /// information here is enough to re-run layout for an absolute. #[derive(MallocSizeOf)] pub(crate) struct LayoutRootLayoutInputs { /// The fully adjusted static position rectangle used to lay out the absolute. This is /// adjusted by the containing blocks of all of the boxes that come between an /// absolute's tree position and its layout containing block. fully_adjusted_static_position_rect: LogicalRect<Au>, /// The resolved alignment to use when laying out the absolute. This comes from the /// original box. resolved_alignment: LogicalVec2<AlignFlags>, /// This is the containing block size of the absolute's containing block. This is /// stored here because it's easier to access than the parent box. containing_block_size: LogicalVec2<Au>, /// This is the style of the containing block. This is stored here because it's easier /// to access than the parent box. #[conditional_malloc_size_of] containing_block_style: ServoArc<ComputedValues>, /// This is the writing mode of the absolute's tree parent. original_parent_writing_mode: WritingMode, } impl std::fmt::Debug for LayoutRootLayoutInputs { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("LayoutRootLayoutInputs") .field("containing_block_size", &self.containing_block_size) .finish() } } impl LayoutRootLayoutInputs { /// Re-run layout for the given inputs. This is used to run layout again at layout /// roots. pub(crate) fn layout( &self, layout_context: &LayoutContext, context: &IndependentFormattingContext, shared_fragment: &ArcRefCell<HoistedSharedFragment>, ) -> Result<(), ()> { let containing_block = DefiniteContainingBlock { size: self.containing_block_size, style: &self.containing_block_style, }; let (box_fragment, positioning_context) = context.layout_as_absolute( layout_context, &self.fully_adjusted_static_position_rect, &containing_block, self.resolved_alignment, self.original_parent_writing_mode, ); if !positioning_context.is_empty() { return Err(()); } shared_fragment.borrow_mut().fragment = Some(Fragment::Box(box_fragment)); Ok(()) } }