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.rs1058
1 files changed, 1058 insertions, 0 deletions
diff --git a/components/layout/positioned.rs b/components/layout/positioned.rs
new file mode 100644
index 00000000000..5f08e4e86c5
--- /dev/null
+++ b/components/layout/positioned.rs
@@ -0,0 +1,1058 @@
+/* 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 app_units::Au;
+use malloc_size_of_derive::MallocSizeOf;
+use rayon::iter::IntoParallelRefMutIterator;
+use rayon::prelude::{IndexedParallelIterator, ParallelIterator};
+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::NodeExt;
+use crate::dom_traversal::{Contents, NodeAndStyleInfo};
+use crate::formatting_contexts::{
+ IndependentFormattingContext, IndependentFormattingContextContents,
+};
+use crate::fragment_tree::{
+ BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment, SpecificLayoutInfo,
+};
+use crate::geom::{
+ AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2,
+ PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, Size, Sizes, ToLogical,
+ ToLogicalWithContainingBlock,
+};
+use crate::sizing::ContentSizes;
+use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
+use crate::{
+ ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock,
+ PropagatedBoxTreeData, SizeConstraint,
+};
+
+#[derive(Debug, MallocSizeOf)]
+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>,
+
+ /// 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>,
+}
+
+impl AbsolutelyPositionedBox {
+ pub fn new(context: IndependentFormattingContext) -> Self {
+ Self { context }
+ }
+
+ pub fn construct<'dom>(
+ context: &LayoutContext,
+ node_info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
+ 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_rectangle: PhysicalRect<Au>,
+ resolved_alignment: LogicalVec2<AlignFlags>,
+ original_parent_writing_mode: WritingMode,
+ ) -> HoistedAbsolutelyPositionedBox {
+ HoistedAbsolutelyPositionedBox {
+ fragment: ArcRefCell::new(HoistedSharedFragment::new(
+ static_position_rectangle,
+ resolved_alignment,
+ original_parent_writing_mode,
+ )),
+ absolutely_positioned_box,
+ }
+ }
+}
+
+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()
+ }
+
+ pub(crate) fn new_for_style(style: &ComputedValues) -> Option<Self> {
+ // NB: We never make PositioningContexts for replaced elements, which is why we always
+ // pass false here.
+ if style.establishes_containing_block_for_all_descendants(FragmentFlags::empty()) {
+ Some(Self::new_for_containing_block_for_all_descendants())
+ } else if style
+ .establishes_containing_block_for_absolute_descendants(FragmentFlags::empty())
+ {
+ Some(Self {
+ for_nearest_positioned_ancestor: Some(Vec::new()),
+ for_nearest_containing_block_for_all_descendants: Vec::new(),
+ })
+ } 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 start_offset = match &parent_fragment {
+ Fragment::Box(fragment) | Fragment::Float(fragment) => {
+ fragment.borrow().content_rect.origin
+ },
+ Fragment::AbsoluteOrFixedPositioned(_) => return,
+ Fragment::Positioning(fragment) => fragment.borrow().rect.origin,
+ _ => unreachable!(),
+ };
+ self.adjust_static_position_of_hoisted_fragments_with_offset(
+ &start_offset.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,
+ ) {
+ 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
+ .iter_mut()
+ .skip(index.for_nearest_containing_block_for_all_descendants)
+ .for_each(|hoisted_fragment| {
+ hoisted_fragment
+ .fragment
+ .borrow_mut()
+ .adjust_offsets(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,
+ style: &ComputedValues,
+ 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 Self::new_for_style(style) {
+ Some(new_context) => new_context,
+ None => return fragment_layout_fn(self),
+ };
+
+ 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.
+ self.append(new_context);
+
+ if style.clone_position() == Position::Relative {
+ new_fragment.content_rect.origin += relative_adjustement(style, containing_block)
+ .to_physical_vector(containing_block.style.writing_mode)
+ }
+
+ new_fragment
+ }
+
+ // Lay out the hoisted boxes collected into this `PositioningContext` and add them
+ // to the given `BoxFragment`.
+ pub fn layout_collected_children(
+ &mut self,
+ layout_context: &LayoutContext,
+ new_fragment: &mut BoxFragment,
+ ) {
+ let padding_rect = PhysicalRect::new(
+ // Ignore the content rect’s position in its own containing block:
+ PhysicalPoint::origin(),
+ new_fragment.content_rect.size,
+ )
+ .outer_rect(new_fragment.padding);
+ let containing_block = DefiniteContainingBlock {
+ size: padding_rect
+ .size
+ .to_logical(new_fragment.style.writing_mode),
+ 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),
+ };
+
+ // 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() {
+ HoistedAbsolutelyPositionedBox::layout_many(
+ layout_context,
+ &mut hoisted_boxes,
+ &mut laid_out_child_fragments,
+ &mut self.for_nearest_containing_block_for_all_descendants,
+ &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.for_nearest_containing_block_for_all_descendants
+ .push(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() {
+ 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!(),
+ }
+ }
+
+ pub(crate) fn layout_initial_containing_block_children(
+ &mut self,
+ layout_context: &LayoutContext,
+ 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()
+ {
+ HoistedAbsolutelyPositionedBox::layout_many(
+ layout_context,
+ &mut mem::take(&mut self.for_nearest_containing_block_for_all_descendants),
+ fragments,
+ &mut self.for_nearest_containing_block_for_all_descendants,
+ initial_containing_block,
+ Default::default(),
+ )
+ }
+ }
+
+ /// 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(),
+ }
+ }
+
+ /// 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);
+ }
+}
+
+/// 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,
+}
+
+impl Zero for PositioningContextLength {
+ fn zero() -> Self {
+ PositioningContextLength {
+ for_nearest_positioned_ancestor: 0,
+ for_nearest_containing_block_for_all_descendants: 0,
+ }
+ }
+
+ fn is_zero(&self) -> bool {
+ self.for_nearest_positioned_ancestor == 0 &&
+ self.for_nearest_containing_block_for_all_descendants == 0
+ }
+}
+
+impl HoistedAbsolutelyPositionedBox {
+ pub(crate) fn layout_many(
+ layout_context: &LayoutContext,
+ boxes: &mut [Self],
+ fragments: &mut Vec<Fragment>,
+ for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
+ containing_block: &DefiniteContainingBlock,
+ containing_block_padding: PhysicalSides<Au>,
+ ) {
+ if layout_context.use_rayon {
+ 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,
+ );
+
+ hoisted_box.fragment.borrow_mut().fragment = Some(new_fragment.clone());
+ (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(|box_| {
+ let new_fragment = box_.layout(
+ layout_context,
+ for_nearest_containing_block_for_all_descendants,
+ containing_block,
+ containing_block_padding,
+ );
+
+ box_.fragment.borrow_mut().fragment = Some(new_fragment.clone());
+ new_fragment
+ }))
+ }
+ }
+
+ pub(crate) fn layout(
+ &mut self,
+ layout_context: &LayoutContext,
+ for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
+ containing_block: &DefiniteContainingBlock,
+ containing_block_padding: PhysicalSides<Au>,
+ ) -> Fragment {
+ let cbis = containing_block.size.inline;
+ let cbbs = containing_block.size.block;
+ let containing_block_writing_mode = containing_block.style.writing_mode;
+ let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
+ let context = &absolutely_positioned_box.context;
+ let style = context.style().clone();
+ let layout_style = context.layout_style();
+ let ContentBoxSizesAndPBM {
+ content_box_sizes,
+ pbm,
+ ..
+ } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
+ let containing_block = &containing_block.into();
+ let is_table = layout_style.is_table();
+ let shared_fragment = self.fragment.borrow();
+
+ // 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 add the padding of that ancestor.
+ let mut static_position_rect = shared_fragment
+ .static_position_rect
+ .outer_rect(-containing_block_padding);
+ static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero());
+ let static_position_rect = static_position_rect.to_logical(containing_block);
+
+ 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();
+ let inline_alignment = match inline_box_offsets.either_specified() {
+ true => style.clone_justify_self().0.0,
+ false => shared_fragment.resolved_alignment.inline,
+ };
+
+ let mut 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: shared_fragment.original_parent_writing_mode.is_bidi_ltr() !=
+ containing_block_writing_mode.is_bidi_ltr(),
+ is_table,
+ };
+
+ // 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();
+ let block_alignment = match block_box_offsets.either_specified() {
+ true => style.clone_align_self().0.0,
+ false => shared_fragment.resolved_alignment.block,
+ };
+ let mut 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,
+ };
+
+ if let IndependentFormattingContextContents::Replaced(replaced) = &context.contents {
+ // https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
+ // https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
+ let inset_sums = LogicalVec2 {
+ inline: inline_axis_solver.inset_sum(),
+ block: block_axis_solver.inset_sum(),
+ };
+ let automatic_size = |alignment: AlignFlags, offsets: &LogicalSides1D<_>| {
+ if alignment.value() == AlignFlags::STRETCH && !offsets.either_auto() {
+ Size::Stretch
+ } else {
+ Size::FitContent
+ }
+ };
+ let used_size = replaced.used_size_as_if_inline_element_from_content_box_sizes(
+ containing_block,
+ &style,
+ context.preferred_aspect_ratio(&pbm.padding_border_sums),
+ LogicalVec2 {
+ inline: &inline_axis_solver.computed_sizes,
+ block: &block_axis_solver.computed_sizes,
+ },
+ LogicalVec2 {
+ inline: automatic_size(inline_alignment, &inline_axis_solver.box_offsets),
+ block: automatic_size(block_alignment, &block_axis_solver.box_offsets),
+ },
+ pbm.padding_border_sums + pbm.margin.auto_is(Au::zero).sum() + inset_sums,
+ );
+ inline_axis_solver.override_size(used_size.inline);
+ block_axis_solver.override_size(used_size.block);
+ }
+
+ // The block axis can depend on layout results, so we only solve it tentatively,
+ // we may have to resolve it properly later on.
+ let mut block_axis = block_axis_solver.solve_tentatively();
+
+ // The inline axis can be fully resolved, computing intrinsic sizes using the
+ // tentative block size.
+ let mut inline_axis = inline_axis_solver.solve(Some(|| {
+ let ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
+ let constraint_space = ConstraintSpace::new(block_axis.size, style.writing_mode, ratio);
+ context
+ .inline_content_sizes(layout_context, &constraint_space)
+ .sizes
+ }));
+
+ let mut positioning_context = PositioningContext::new_for_style(&style).unwrap();
+ let mut new_fragment = {
+ let content_size: LogicalVec2<Au>;
+ let fragments;
+ let mut specific_layout_info: Option<SpecificLayoutInfo> = None;
+ match &context.contents {
+ IndependentFormattingContextContents::Replaced(replaced) => {
+ // https://drafts.csswg.org/css2/visudet.html#abs-replaced-width
+ // https://drafts.csswg.org/css2/visudet.html#abs-replaced-height
+ content_size = LogicalVec2 {
+ inline: inline_axis.size.to_definite().unwrap(),
+ block: block_axis.size.to_definite().unwrap(),
+ };
+ fragments = replaced.make_fragments(
+ layout_context,
+ &style,
+ content_size.to_physical_size(containing_block_writing_mode),
+ );
+ },
+ IndependentFormattingContextContents::NonReplaced(non_replaced) => {
+ // https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width
+ // https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height
+ let inline_size = inline_axis.size.to_definite().unwrap();
+ let containing_block_for_children = ContainingBlock {
+ size: ContainingBlockSize {
+ inline: inline_size,
+ block: block_axis.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 independent_layout = non_replaced.layout(
+ layout_context,
+ &mut positioning_context,
+ &containing_block_for_children,
+ containing_block,
+ &context.base,
+ false, /* depends_on_block_constraints */
+ );
+
+ let inline_size = if let Some(inline_size) =
+ independent_layout.content_inline_size_for_table
+ {
+ // Tables can become narrower than predicted due to collapsed columns,
+ // so we need to solve again to update margins.
+ inline_axis_solver.override_size(inline_size);
+ inline_axis = inline_axis_solver.solve_tentatively();
+ inline_size
+ } else {
+ inline_size
+ };
+
+ // Now we can properly solve the block size.
+ block_axis = block_axis_solver
+ .solve(Some(|| independent_layout.content_block_size.into()));
+ let block_size = block_axis.size.to_definite().unwrap();
+
+ content_size = LogicalVec2 {
+ inline: inline_size,
+ block: block_size,
+ };
+ fragments = independent_layout.fragments;
+ specific_layout_info = independent_layout.specific_layout_info;
+ },
+ };
+
+ let margin = LogicalSides {
+ inline_start: inline_axis.margin_start,
+ inline_end: inline_axis.margin_end,
+ block_start: block_axis.margin_start,
+ block_end: block_axis.margin_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,
+ shared_fragment.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,
+ shared_fragment.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,
+ };
+ BoxFragment::new(
+ context.base_fragment_info(),
+ style,
+ fragments,
+ content_rect.as_physical(Some(containing_block)),
+ pbm.padding.to_physical(containing_block_writing_mode),
+ pbm.border.to_physical(containing_block_writing_mode),
+ margin.to_physical(containing_block_writing_mode),
+ None, /* clearance */
+ )
+ .with_specific_layout_info(specific_layout_info)
+ };
+ positioning_context.layout_collected_children(layout_context, &mut new_fragment);
+
+ // 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(
+ &new_fragment.content_rect.origin.to_vector(),
+ PositioningContextLength::zero(),
+ );
+
+ for_nearest_containing_block_for_all_descendants
+ .extend(positioning_context.for_nearest_containing_block_for_all_descendants);
+
+ let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
+ context.base.set_fragment(fragment.clone());
+ fragment
+ }
+}
+
+#[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 AxisResult {
+ size: SizeConstraint,
+ margin_start: Au,
+ margin_end: Au,
+}
+
+struct AbsoluteAxisSolver<'a> {
+ 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<LengthPercentageOrAuto<'a>>,
+ static_position_rect_axis: RectAxis,
+ alignment: AlignFlags,
+ flip_anchor: bool,
+ is_table: 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.to_used_value(self.containing_size),
+ (None, Some(end)) => end.to_used_value(self.containing_size),
+ (Some(start), Some(end)) => {
+ start.to_used_value(self.containing_size) + end.to_used_value(self.containing_size)
+ },
+ }
+ }
+
+ /// This unifies some of the parts in common in:
+ ///
+ /// * <https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width>
+ /// * <https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height>
+ ///
+ /// … and:
+ ///
+ /// * <https://drafts.csswg.org/css2/visudet.html#abs-replaced-width>
+ /// * <https://drafts.csswg.org/css2/visudet.html#abs-replaced-height>
+ ///
+ /// In the replaced case, `size` is never `Auto`.
+ fn solve(&self, get_content_size: Option<impl FnOnce() -> ContentSizes>) -> AxisResult {
+ let solve_size = |initial_behavior, stretch_size: Au| -> SizeConstraint {
+ let stretch_size = stretch_size.max(Au::zero());
+ if let Some(get_content_size) = get_content_size {
+ SizeConstraint::Definite(self.computed_sizes.resolve(
+ self.axis,
+ initial_behavior,
+ Au::zero,
+ Some(stretch_size),
+ get_content_size,
+ self.is_table,
+ ))
+ } else {
+ self.computed_sizes.resolve_extrinsic(
+ initial_behavior,
+ Au::zero(),
+ Some(stretch_size),
+ )
+ }
+ };
+ if self.box_offsets.either_auto() {
+ let margin_start = self.computed_margin_start.auto_is(Au::zero);
+ let margin_end = self.computed_margin_end.auto_is(Au::zero);
+ let stretch_size = self.containing_size -
+ self.inset_sum() -
+ self.padding_border_sum -
+ margin_start -
+ margin_end;
+ let size = solve_size(Size::FitContent, stretch_size);
+ AxisResult {
+ size,
+ margin_start,
+ margin_end,
+ }
+ } else {
+ let mut free_space = self.containing_size - self.inset_sum() - self.padding_border_sum;
+ let stretch_size = free_space -
+ self.computed_margin_start.auto_is(Au::zero) -
+ self.computed_margin_end.auto_is(Au::zero);
+ let initial_behavior = match self.alignment.value() {
+ AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table => Size::Stretch,
+ AlignFlags::STRETCH => Size::Stretch,
+ _ => Size::FitContent,
+ };
+ let size = solve_size(initial_behavior, stretch_size);
+ if let Some(used_size) = size.to_definite() {
+ free_space -= used_size;
+ } else {
+ free_space = Au::zero();
+ }
+ let (margin_start, margin_end) =
+ match (self.computed_margin_start, self.computed_margin_end) {
+ (AuOrAuto::Auto, AuOrAuto::Auto) => {
+ if self.avoid_negative_margin_start && free_space < Au::zero() {
+ (Au::zero(), free_space)
+ } else {
+ let margin_start = free_space / 2;
+ (margin_start, free_space - margin_start)
+ }
+ },
+ (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => (free_space - end, end),
+ (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
+ (start, free_space - start)
+ },
+ (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
+ (start, end)
+ },
+ };
+ AxisResult {
+ size,
+ margin_start,
+ margin_end,
+ }
+ }
+ }
+
+ fn solve_tentatively(&mut self) -> AxisResult {
+ self.solve(None::<fn() -> ContentSizes>)
+ }
+
+ fn override_size(&mut self, size: Au) {
+ self.computed_sizes.preferred = Size::Numeric(size);
+ self.computed_sizes.min = Size::default();
+ self.computed_sizes.max = Size::default();
+ }
+
+ 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 offsets = LogicalSides1D {
+ start: start.to_used_value(self.containing_size),
+ end: end.to_used_value(self.containing_size),
+ };
+ let alignment_container = RectAxis {
+ origin: offsets.start,
+ length: self.containing_size - offsets.sum(),
+ };
+ (
+ alignment_container,
+ containing_block_writing_mode,
+ false,
+ Some(offsets),
+ )
+ },
+ // 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.to_used_value(self.containing_size),
+ (None, Some(end)) => {
+ return self.containing_size - size - end.to_used_value(self.containing_size);
+ },
+ };
+
+ 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
+ AlignFlags::END | AlignFlags::FLEX_END => AlignFlags::END,
+ // https://drafts.csswg.org/css-align/#valdef-self-position-start
+ // https://drafts.csswg.org/css-align/#valdef-self-position-flex-start
+ _ => 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))
+ }
+}
+
+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,
+ 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),
+ }
+}