Skip to main content

layout/
positioned.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::mem;
6
7use app_units::Au;
8use malloc_size_of_derive::MallocSizeOf;
9use rayon::iter::IntoParallelRefMutIterator;
10use rayon::prelude::{IndexedParallelIterator, ParallelIterator};
11use style::Zero;
12use style::computed_values::position::T as Position;
13use style::logical_geometry::{Direction, WritingMode};
14use style::properties::ComputedValues;
15use style::values::specified::align::AlignFlags;
16
17use crate::cell::ArcRefCell;
18use crate::context::LayoutContext;
19use crate::dom_traversal::{Contents, NodeAndStyleInfo};
20use crate::formatting_contexts::IndependentFormattingContext;
21use crate::fragment_tree::{BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment};
22use crate::geom::{
23    AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
24    PhysicalSides, PhysicalSize, PhysicalVec, ToLogical, ToLogicalWithContainingBlock,
25};
26use crate::layout_box_base::{IndependentFormattingContextLayoutResult, LayoutBoxBase};
27use crate::sizing::{LazySize, Size, SizeConstraint, Sizes};
28use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
29use crate::{
30    ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock,
31    PropagatedBoxTreeData,
32};
33
34#[derive(Debug, MallocSizeOf)]
35pub(crate) struct AbsolutelyPositionedBox {
36    pub context: IndependentFormattingContext,
37}
38
39#[derive(Clone, MallocSizeOf)]
40pub(crate) struct HoistedAbsolutelyPositionedBox {
41    absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
42    /// A reference to a Fragment which is shared between this `HoistedAbsolutelyPositionedBox`
43    /// and its placeholder `AbsoluteOrFixedPositionedFragment` in the original tree position.
44    /// This will be used later in order to paint this hoisted box in tree order.
45    pub fragment: ArcRefCell<HoistedSharedFragment>,
46    /// The adjusted "static-position rect" of this absolutely positioned box. This is
47    /// defined by the layout mode from which the box originates. This is the
48    /// [`HoistedSharedFragment::original_static_position_rect`] adjusted by the offests
49    /// of ancestors between the tree position of the absolute and the
50    /// [`PostioningContext`] that holds this [`HoistedAbsolutelyPositionedBox`].
51    ///
52    /// If the value is `None`, the original static position rect has not been adjusted yet.
53    ///
54    /// See <https://drafts.csswg.org/css-position-3/#staticpos-rect>
55    pub adjusted_static_position_rect: Option<PhysicalRect<Au>>,
56    /// The resolved alignment values used for aligning this absolutely positioned element
57    /// if the "static-position rect" ends up being the "inset-modified containing block".
58    /// These values are dependent on the layout mode (currently only interesting for
59    /// flexbox).
60    pub resolved_alignment: LogicalVec2<AlignFlags>,
61    /// This is the [`WritingMode`] of the original parent of the element that created this
62    /// hoisted absolutely-positioned fragment. This helps to interpret the offset for
63    /// static positioning. If the writing mode is right-to-left or bottom-to-top, the static
64    /// offset needs to be adjusted by the absolutely positioned element's inline size.
65    pub original_parent_writing_mode: WritingMode,
66}
67
68impl AbsolutelyPositionedBox {
69    pub fn new(context: IndependentFormattingContext) -> Self {
70        Self { context }
71    }
72
73    pub fn construct(
74        context: &LayoutContext,
75        node_info: &NodeAndStyleInfo,
76        display_inside: DisplayInside,
77        contents: Contents,
78    ) -> Self {
79        Self {
80            context: IndependentFormattingContext::construct(
81                context,
82                node_info,
83                display_inside,
84                contents,
85                // Text decorations are not propagated to any out-of-flow descendants. In addition,
86                // absolutes don't affect the size of ancestors so it is fine to allow descendent
87                // tables to resolve percentage columns.
88                PropagatedBoxTreeData::default(),
89            ),
90        }
91    }
92
93    pub(crate) fn to_hoisted(
94        absolutely_positioned_box: ArcRefCell<Self>,
95        static_position_rect: PhysicalRect<Au>,
96        resolved_alignment: LogicalVec2<AlignFlags>,
97        original_parent_writing_mode: WritingMode,
98    ) -> HoistedAbsolutelyPositionedBox {
99        HoistedAbsolutelyPositionedBox {
100            fragment: ArcRefCell::new(HoistedSharedFragment::new(static_position_rect)),
101            adjusted_static_position_rect: None,
102            resolved_alignment,
103            original_parent_writing_mode,
104            absolutely_positioned_box,
105        }
106    }
107}
108
109#[derive(Clone, Default, MallocSizeOf)]
110pub(crate) struct PositioningContext {
111    absolutes: Vec<HoistedAbsolutelyPositionedBox>,
112}
113
114impl PositioningContext {
115    #[inline]
116    pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> {
117        Self::new_for_style_and_fragment_flags(
118            &layout_box_base.style,
119            &layout_box_base.base_fragment_info.flags,
120        )
121    }
122
123    fn new_for_style_and_fragment_flags(
124        style: &ComputedValues,
125        flags: &FragmentFlags,
126    ) -> Option<Self> {
127        if style.establishes_containing_block_for_absolute_descendants(*flags) {
128            Some(Self::default())
129        } else {
130            None
131        }
132    }
133
134    /// Absolute and fixed position fragments are hoisted up to their containing blocks
135    /// from their tree position. When these fragments have static inset start positions,
136    /// that position (relative to the ancestor containing block) needs to be included
137    /// with the hoisted fragment so that it can be laid out properly at the containing
138    /// block.
139    ///
140    /// This function is used to update the static position of hoisted boxes added after
141    /// the given index at every level of the fragment tree as the hoisted fragments move
142    /// up to their containing blocks. Once an ancestor fragment is laid out, this
143    /// function can be used to aggregate its offset to any descendent boxes that are
144    /// being hoisted. In this case, the appropriate index to use is the result of
145    /// [`PositioningContext::len()`] cached before laying out the [`Fragment`].
146    pub(crate) fn adjust_static_position_of_hoisted_fragments(
147        &mut self,
148        parent_fragment: &Fragment,
149        index: PositioningContextLength,
150    ) {
151        let Some(base) = parent_fragment.base() else {
152            return;
153        };
154        self.adjust_static_position_of_hoisted_fragments_with_offset(
155            &base.rect().origin.to_vector(),
156            index,
157        );
158    }
159
160    /// See documentation for [PositioningContext::adjust_static_position_of_hoisted_fragments].
161    pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset(
162        &mut self,
163        offset: &PhysicalVec<Au>,
164        index: PositioningContextLength,
165    ) {
166        self.absolutes
167            .iter_mut()
168            .skip(index.0)
169            .for_each(|hoisted_box| {
170                hoisted_box.adjust_static_position_with_offset(offset);
171            })
172    }
173
174    /// Given `fragment_layout_fn`, a closure which lays out a fragment in a provided
175    /// `PositioningContext`, create a new positioning context if necessary for the fragment and
176    /// lay out the fragment and all its children. Returns the newly created `BoxFragment`.
177    pub(crate) fn layout_maybe_position_relative_fragment(
178        &mut self,
179        layout_context: &LayoutContext,
180        containing_block: &ContainingBlock,
181        base: &LayoutBoxBase,
182        fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
183    ) -> BoxFragment {
184        // If a new `PositioningContext` isn't necessary, simply create the fragment using
185        // the given closure and the current `PositioningContext`.
186        let establishes_containing_block_for_absolutes = base
187            .style
188            .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
189        if !establishes_containing_block_for_absolutes {
190            return fragment_layout_fn(self);
191        }
192
193        let mut new_context = PositioningContext::default();
194        let mut new_fragment = fragment_layout_fn(&mut new_context);
195
196        // Lay out all of the absolutely positioned children for this fragment, and, if it
197        // isn't a containing block for fixed elements, then pass those up to the parent.
198        new_context.layout_collected_children(layout_context, &mut new_fragment);
199        self.append(new_context);
200
201        if base.style.clone_position() == Position::Relative {
202            new_fragment.base.translate_rect(
203                relative_adjustement(&base.style, containing_block)
204                    .to_physical_vector(containing_block.style.writing_mode)
205                    .into(),
206            );
207        }
208
209        new_fragment
210    }
211
212    fn forget_unhoisted_boxes(&mut self, fragment: &BoxFragment) {
213        let style = fragment.style();
214        debug_assert!(
215            style.establishes_containing_block_for_absolute_descendants(fragment.base.flags)
216        );
217        if style.establishes_containing_block_for_all_descendants(fragment.base.flags) {
218            self.absolutes.clear();
219        } else {
220            self.absolutes
221                .retain(|hoisted_box| hoisted_box.position() == Position::Fixed);
222        }
223    }
224
225    fn take_boxes_for_fragment(
226        &mut self,
227        new_fragment: &BoxFragment,
228        boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
229        boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
230    ) {
231        let style = new_fragment.style();
232        debug_assert!(
233            style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
234        );
235
236        if style.establishes_containing_block_for_all_descendants(new_fragment.base.flags) {
237            boxes_to_layout_out.append(&mut self.absolutes);
238            return;
239        }
240
241        // TODO: This could potentially use `extract_if` when that is stabilized.
242        let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
243            .absolutes
244            .drain(..)
245            .partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
246        boxes_to_layout_out.append(&mut boxes_to_layout);
247        boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
248    }
249
250    // Lay out the hoisted boxes collected into this `PositioningContext` and add them
251    // to the given `BoxFragment`.
252    pub(crate) fn layout_collected_children(
253        &mut self,
254        layout_context: &LayoutContext,
255        new_fragment: &mut BoxFragment,
256    ) {
257        if self.absolutes.is_empty() {
258            return;
259        }
260
261        // Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and
262        // then these are processed later. In that case and if this fragment doesn't establish a
263        // containing block for absolutes at all, we just do nothing. All hoisted fragments will
264        // later be passed up to a parent PositioningContext.
265        //
266        // Handling this case here, when the PositioningContext is completely ineffectual other than
267        // as a temporary container for hoisted boxes, means that callers can execute less conditional
268        // code.
269        let style = new_fragment.style().clone();
270        if !style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) {
271            return;
272        }
273
274        let padding_rect = PhysicalRect::new(
275            // Ignore the content rect’s position in its own containing block:
276            PhysicalPoint::origin(),
277            new_fragment.base.rect().size,
278        )
279        .outer_rect(new_fragment.padding);
280        let containing_block = DefiniteContainingBlock {
281            size: padding_rect.size.to_logical(style.writing_mode),
282            style: &style,
283        };
284
285        let mut fixed_position_boxes_to_hoist = Vec::new();
286        let mut boxes_to_layout = Vec::new();
287        self.take_boxes_for_fragment(
288            new_fragment,
289            &mut boxes_to_layout,
290            &mut fixed_position_boxes_to_hoist,
291        );
292
293        // Laying out a `position: absolute` child (which only establishes a containing block for
294        // `position: absolute` descendants) can result in more `position: fixed` descendants
295        // collecting in `self.absolutes`. We need to loop here in order to keep either laying them
296        // out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more
297        // when `self.absolutes` is empty.
298        while !boxes_to_layout.is_empty() {
299            HoistedAbsolutelyPositionedBox::layout_many(
300                layout_context,
301                std::mem::take(&mut boxes_to_layout),
302                &mut new_fragment.children,
303                &mut self.absolutes,
304                &containing_block,
305                new_fragment.padding,
306            );
307
308            self.take_boxes_for_fragment(
309                new_fragment,
310                &mut boxes_to_layout,
311                &mut fixed_position_boxes_to_hoist,
312            );
313        }
314
315        // We replace here instead of simply preserving these in `take_boxes_for_fragment`
316        // so that we don't have to continually re-iterate over them when laying out in the
317        // loop above.
318        self.absolutes = fixed_position_boxes_to_hoist;
319    }
320
321    pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
322        debug_assert!(hoisted_box.position().is_absolutely_positioned());
323        self.absolutes.push(hoisted_box);
324    }
325
326    pub(crate) fn append(&mut self, mut other: Self) {
327        if other.absolutes.is_empty() {
328            return;
329        }
330        if self.absolutes.is_empty() {
331            self.absolutes = other.absolutes;
332        } else {
333            self.absolutes.append(&mut other.absolutes)
334        }
335    }
336
337    pub(crate) fn layout_initial_containing_block_children(
338        &mut self,
339        layout_context: &LayoutContext,
340        initial_containing_block: &DefiniteContainingBlock,
341        fragments: &mut Vec<Fragment>,
342    ) {
343        // Laying out a `position: absolute` child (which only establishes a containing block for
344        // `position: absolute` descendants) can result in more `position: fixed` descendants
345        // collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We
346        // know there aren't any more when `self.absolutes` is empty.
347        while !self.absolutes.is_empty() {
348            HoistedAbsolutelyPositionedBox::layout_many(
349                layout_context,
350                mem::take(&mut self.absolutes),
351                fragments,
352                &mut self.absolutes,
353                initial_containing_block,
354                Default::default(),
355            )
356        }
357    }
358
359    /// Get the length of this [PositioningContext].
360    pub(crate) fn len(&self) -> PositioningContextLength {
361        PositioningContextLength(self.absolutes.len())
362    }
363
364    /// Truncate this [PositioningContext] to the given [PositioningContextLength].  This
365    /// is useful for "unhoisting" boxes in this context and returning it to the state at
366    /// the time that [`PositioningContext::len()`] was called.
367    pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
368        self.absolutes.truncate(length.0)
369    }
370}
371
372/// A data structure which stores the size of a positioning context.
373#[derive(Clone, Copy, Debug, PartialEq)]
374pub(crate) struct PositioningContextLength(usize);
375
376impl Zero for PositioningContextLength {
377    fn zero() -> Self {
378        Self(0)
379    }
380
381    fn is_zero(&self) -> bool {
382        self.0.is_zero()
383    }
384}
385
386impl HoistedAbsolutelyPositionedBox {
387    fn position(&self) -> Position {
388        let position = self
389            .absolutely_positioned_box
390            .borrow()
391            .context
392            .style()
393            .clone_position();
394        assert!(position.is_absolutely_positioned());
395        position
396    }
397
398    pub(crate) fn layout_many(
399        layout_context: &LayoutContext,
400        mut boxes: Vec<Self>,
401        fragments: &mut Vec<Fragment>,
402        for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
403        containing_block: &DefiniteContainingBlock,
404        containing_block_padding: PhysicalSides<Au>,
405    ) {
406        if layout_context.use_rayon {
407            let mut new_fragments = Vec::new();
408            let mut new_hoisted_boxes = Vec::new();
409
410            boxes
411                .par_iter_mut()
412                .map(|hoisted_box| {
413                    let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
414                    let new_fragment = hoisted_box.layout(
415                        layout_context,
416                        &mut new_hoisted_boxes,
417                        containing_block,
418                        containing_block_padding,
419                    );
420
421                    hoisted_box.fragment.borrow_mut().fragment = Some(new_fragment.clone());
422                    (new_fragment, new_hoisted_boxes)
423                })
424                .unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
425
426            fragments.extend(new_fragments);
427            for_nearest_containing_block_for_all_descendants
428                .extend(new_hoisted_boxes.into_iter().flatten());
429        } else {
430            fragments.extend(boxes.iter_mut().map(|box_| {
431                let new_fragment = box_.layout(
432                    layout_context,
433                    for_nearest_containing_block_for_all_descendants,
434                    containing_block,
435                    containing_block_padding,
436                );
437
438                box_.fragment.borrow_mut().fragment = Some(new_fragment.clone());
439                new_fragment
440            }))
441        }
442    }
443
444    pub(crate) fn layout(
445        &mut self,
446        layout_context: &LayoutContext,
447        hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
448        containing_block: &DefiniteContainingBlock,
449        containing_block_padding: PhysicalSides<Au>,
450    ) -> Fragment {
451        let cbis = containing_block.size.inline;
452        let cbbs = containing_block.size.block;
453        let containing_block_writing_mode = containing_block.style.writing_mode;
454        let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
455        let context = &absolutely_positioned_box.context;
456        let style = context.style().clone();
457        let layout_style = context.layout_style();
458        let ContentBoxSizesAndPBM {
459            content_box_sizes,
460            pbm,
461            ..
462        } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
463        let containing_block = &containing_block.into();
464        let is_table = layout_style.is_table();
465        let is_table_or_replaced = is_table || context.is_replaced();
466        let preferred_aspect_ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
467
468        // The static position rect was calculated assuming that the containing block would be
469        // established by the content box of some ancestor, but the actual containing block is
470        // established by the padding box. So we need to add the padding of that ancestor.
471        let mut static_position_rect = self
472            .static_position_rect()
473            .outer_rect(-containing_block_padding);
474        static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero());
475        let static_position_rect = static_position_rect.to_logical(containing_block);
476
477        let box_offset = style.box_offsets(containing_block.style.writing_mode);
478
479        // When the "static-position rect" doesn't come into play, we do not do any alignment
480        // in the inline axis.
481        let inline_box_offsets = box_offset.inline_sides().percentages_relative_to(cbis);
482        let inline_alignment = match inline_box_offsets.either_specified() {
483            true => style.clone_justify_self().0,
484            false => self.resolved_alignment.inline,
485        };
486
487        let inline_axis_solver = AbsoluteAxisSolver {
488            axis: Direction::Inline,
489            containing_size: cbis,
490            padding_border_sum: pbm.padding_border_sums.inline,
491            computed_margin_start: pbm.margin.inline_start,
492            computed_margin_end: pbm.margin.inline_end,
493            computed_sizes: content_box_sizes.inline,
494            avoid_negative_margin_start: true,
495            box_offsets: inline_box_offsets,
496            static_position_rect_axis: static_position_rect.get_axis(Direction::Inline),
497            alignment: inline_alignment,
498            flip_anchor: self.original_parent_writing_mode.is_bidi_ltr() !=
499                containing_block_writing_mode.is_bidi_ltr(),
500            is_table_or_replaced,
501        };
502
503        // When the "static-position rect" doesn't come into play, we re-resolve "align-self"
504        // against this containing block.
505        let block_box_offsets = box_offset.block_sides().percentages_relative_to(cbbs);
506        let block_alignment = match block_box_offsets.either_specified() {
507            true => style.clone_align_self().0,
508            false => self.resolved_alignment.block,
509        };
510        let block_axis_solver = AbsoluteAxisSolver {
511            axis: Direction::Block,
512            containing_size: cbbs,
513            padding_border_sum: pbm.padding_border_sums.block,
514            computed_margin_start: pbm.margin.block_start,
515            computed_margin_end: pbm.margin.block_end,
516            computed_sizes: content_box_sizes.block,
517            avoid_negative_margin_start: false,
518            box_offsets: block_box_offsets,
519            static_position_rect_axis: static_position_rect.get_axis(Direction::Block),
520            alignment: block_alignment,
521            flip_anchor: false,
522            is_table_or_replaced,
523        };
524
525        // The block size can depend on layout results, so we only solve it tentatively,
526        // we may have to resolve it properly later on.
527        let block_automatic_size = block_axis_solver.automatic_size();
528        let block_stretch_size = Some(block_axis_solver.stretch_size());
529        let inline_stretch_size = inline_axis_solver.stretch_size();
530        let tentative_block_content_size =
531            context.tentative_block_content_size(preferred_aspect_ratio, inline_stretch_size);
532        let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
533            SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
534                Direction::Block,
535                block_automatic_size,
536                Au::zero,
537                block_stretch_size,
538                || block_content_size,
539                is_table,
540            ))
541        } else {
542            block_axis_solver.computed_sizes.resolve_extrinsic(
543                block_automatic_size,
544                Au::zero(),
545                block_stretch_size,
546            )
547        };
548
549        // The inline axis can be fully resolved, computing intrinsic sizes using the
550        // extrinsic block size.
551        let get_inline_content_size = || {
552            let constraint_space =
553                ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio);
554            context
555                .inline_content_sizes(layout_context, &constraint_space)
556                .sizes
557        };
558        let inline_size = inline_axis_solver.computed_sizes.resolve(
559            Direction::Inline,
560            inline_axis_solver.automatic_size(),
561            Au::zero,
562            Some(inline_stretch_size),
563            get_inline_content_size,
564            is_table,
565        );
566
567        let containing_block_for_children = ContainingBlock {
568            size: ContainingBlockSize {
569                inline: inline_size,
570                block: tentative_block_size,
571            },
572            style: &style,
573        };
574        // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
575        assert_eq!(
576            containing_block_writing_mode.is_horizontal(),
577            style.writing_mode.is_horizontal(),
578            "Mixed horizontal and vertical writing modes are not supported yet"
579        );
580
581        let mut positioning_context = PositioningContext::default();
582        let lazy_block_size = LazySize::new(
583            &block_axis_solver.computed_sizes,
584            Direction::Block,
585            block_automatic_size,
586            Au::zero,
587            block_stretch_size,
588            is_table,
589        );
590        let (layout, is_cached) = context.layout_and_is_cached(
591            layout_context,
592            &mut positioning_context,
593            &containing_block_for_children,
594            containing_block,
595            preferred_aspect_ratio,
596            &lazy_block_size,
597        );
598        let IndependentFormattingContextLayoutResult {
599            content_inline_size_for_table,
600            content_block_size,
601            fragments,
602            specific_layout_info,
603            ..
604        } = layout;
605
606        let content_size = LogicalVec2 {
607            // Tables can become narrower than predicted due to collapsed columns.
608            inline: content_inline_size_for_table.unwrap_or(inline_size),
609
610            // Now we can properly solve the block size.
611            block: lazy_block_size.resolve(|| content_block_size),
612        };
613
614        let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
615        let block_margins = block_axis_solver.solve_margins(content_size.block);
616        let margin = LogicalSides {
617            inline_start: inline_margins.start,
618            inline_end: inline_margins.end,
619            block_start: block_margins.start,
620            block_end: block_margins.end,
621        };
622
623        let pb = pbm.padding + pbm.border;
624        let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
625        let inline_origin = inline_axis_solver.origin_for_margin_box(
626            margin_rect_size.inline,
627            style.writing_mode,
628            self.original_parent_writing_mode,
629            containing_block_writing_mode,
630        );
631        let block_origin = block_axis_solver.origin_for_margin_box(
632            margin_rect_size.block,
633            style.writing_mode,
634            self.original_parent_writing_mode,
635            containing_block_writing_mode,
636        );
637        let content_rect = LogicalRect {
638            start_corner: LogicalVec2 {
639                inline: inline_origin + margin.inline_start + pb.inline_start,
640                block: block_origin + margin.block_start + pb.block_start,
641            },
642            size: content_size,
643        }
644        .as_physical(Some(containing_block));
645
646        let mut adjust_hoisted_boxes = |mut positioning_context: PositioningContext| {
647            // Any hoisted boxes that remain in this positioning context are going to be hoisted
648            // up above this absolutely positioned box. These will necessarily be fixed position
649            // elements, because absolutely positioned elements form containing blocks for all
650            // other elements. If any of them have a static start position though, we need to
651            // adjust it to account for the start corner of this absolute.
652            positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
653                &content_rect.origin.to_vector(),
654                PositioningContextLength::zero(),
655            );
656
657            hoisted_absolutes_from_children.extend(positioning_context.absolutes);
658        };
659
660        if is_cached &&
661            let Some(Fragment::Box(old_fragment)) = context.base.fragments().first() &&
662            content_rect == old_fragment.content_rect()
663        {
664            // Drain the nested absolutes for which we are a containing block.
665            // However, we are reusing the fragment, so no need to lay them out again.
666            positioning_context.forget_unhoisted_boxes(old_fragment);
667            adjust_hoisted_boxes(positioning_context);
668            return Fragment::Box(old_fragment.clone());
669        }
670
671        let mut new_fragment = BoxFragment::new(
672            context.base_fragment_info(),
673            style,
674            fragments,
675            content_rect,
676            pbm.padding.to_physical(containing_block_writing_mode),
677            pbm.border.to_physical(containing_block_writing_mode),
678            margin.to_physical(containing_block_writing_mode),
679            specific_layout_info,
680        );
681
682        // This is an absolutely positioned element, which means it also establishes a
683        // containing block for absolutes. We lay out any absolutely positioned children
684        // here and pass the rest to `hoisted_absolutes_from_children.`
685        positioning_context.layout_collected_children(layout_context, &mut new_fragment);
686
687        adjust_hoisted_boxes(positioning_context);
688
689        let fragment = Fragment::Box(new_fragment.into());
690        context.base.set_fragment(fragment.clone());
691        fragment
692    }
693
694    fn static_position_rect(&self) -> PhysicalRect<Au> {
695        self.adjusted_static_position_rect
696            .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
697    }
698
699    fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
700        self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
701    }
702}
703
704#[derive(Clone, Copy, Debug)]
705struct RectAxis {
706    origin: Au,
707    length: Au,
708}
709
710impl LogicalRect<Au> {
711    fn get_axis(&self, axis: Direction) -> RectAxis {
712        match axis {
713            Direction::Block => RectAxis {
714                origin: self.start_corner.block,
715                length: self.size.block,
716            },
717            Direction::Inline => RectAxis {
718                origin: self.start_corner.inline,
719                length: self.size.inline,
720            },
721        }
722    }
723}
724
725struct AbsoluteAxisSolver {
726    axis: Direction,
727    containing_size: Au,
728    padding_border_sum: Au,
729    computed_margin_start: AuOrAuto,
730    computed_margin_end: AuOrAuto,
731    computed_sizes: Sizes,
732    avoid_negative_margin_start: bool,
733    box_offsets: LogicalSides1D<AuOrAuto>,
734    static_position_rect_axis: RectAxis,
735    alignment: AlignFlags,
736    flip_anchor: bool,
737    is_table_or_replaced: bool,
738}
739
740impl AbsoluteAxisSolver {
741    /// Returns the amount that we need to subtract from the containing block size in order to
742    /// obtain the inset-modified containing block that we will use for sizing purposes.
743    /// (Note that for alignment purposes, we may re-resolve auto insets to a different value.)
744    /// <https://drafts.csswg.org/css-position/#resolving-insets>
745    fn inset_sum(&self) -> Au {
746        match (
747            self.box_offsets.start.non_auto(),
748            self.box_offsets.end.non_auto(),
749        ) {
750            (None, None) => {
751                if self.flip_anchor {
752                    self.containing_size -
753                        self.static_position_rect_axis.origin -
754                        self.static_position_rect_axis.length
755                } else {
756                    self.static_position_rect_axis.origin
757                }
758            },
759            (Some(start), None) => start,
760            (None, Some(end)) => end,
761            (Some(start), Some(end)) => start + end,
762        }
763    }
764
765    /// Returns the size of the inset-modified containing block.
766    /// <https://drafts.csswg.org/css-position-3/#inset-modified-containing-block>
767    #[inline]
768    fn available_space(&self) -> Au {
769        Au::zero().max(self.containing_size - self.inset_sum())
770    }
771
772    #[inline]
773    fn automatic_size(&self) -> Size<Au> {
774        match self.alignment.value() {
775            _ if self.box_offsets.either_auto() => Size::FitContent,
776            AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
777            AlignFlags::STRETCH => Size::Stretch,
778            _ => Size::FitContent,
779        }
780    }
781
782    #[inline]
783    fn stretch_size(&self) -> Au {
784        Au::zero().max(
785            self.available_space() -
786                self.padding_border_sum -
787                self.computed_margin_start.auto_is(Au::zero) -
788                self.computed_margin_end.auto_is(Au::zero),
789        )
790    }
791
792    fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
793        if self.box_offsets.either_auto() {
794            LogicalSides1D::new(
795                self.computed_margin_start.auto_is(Au::zero),
796                self.computed_margin_end.auto_is(Au::zero),
797            )
798        } else {
799            let free_space = self.available_space() - self.padding_border_sum - size;
800            match (self.computed_margin_start, self.computed_margin_end) {
801                (AuOrAuto::Auto, AuOrAuto::Auto) => {
802                    if self.avoid_negative_margin_start && free_space < Au::zero() {
803                        LogicalSides1D::new(Au::zero(), free_space)
804                    } else {
805                        let margin_start = free_space / 2;
806                        LogicalSides1D::new(margin_start, free_space - margin_start)
807                    }
808                },
809                (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
810                    LogicalSides1D::new(free_space - end, end)
811                },
812                (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
813                    LogicalSides1D::new(start, free_space - start)
814                },
815                (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
816                    LogicalSides1D::new(start, end)
817                },
818            }
819        }
820    }
821
822    fn origin_for_margin_box(
823        &self,
824        size: Au,
825        self_writing_mode: WritingMode,
826        original_parent_writing_mode: WritingMode,
827        containing_block_writing_mode: WritingMode,
828    ) -> Au {
829        let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
830            self.box_offsets.start.non_auto(),
831            self.box_offsets.end.non_auto(),
832        ) {
833            (None, None) => (
834                self.static_position_rect_axis,
835                original_parent_writing_mode,
836                self.flip_anchor,
837                None,
838            ),
839            (Some(start), Some(end)) => {
840                let alignment_container = RectAxis {
841                    origin: start,
842                    length: self.available_space(),
843                };
844                (
845                    alignment_container,
846                    containing_block_writing_mode,
847                    false,
848                    Some(LogicalSides1D { start, end }),
849                )
850            },
851            // If a single offset is auto, for alignment purposes it resolves to the amount
852            // that makes the inset-modified containing block be exactly as big as the abspos.
853            // Therefore the free space is zero and the alignment value is irrelevant.
854            (Some(start), None) => return start,
855            (None, Some(end)) => {
856                return self.containing_size - size - end;
857            },
858        };
859
860        assert_eq!(
861            self_writing_mode.is_horizontal(),
862            original_parent_writing_mode.is_horizontal(),
863            "Mixed horizontal and vertical writing modes are not supported yet"
864        );
865        assert_eq!(
866            self_writing_mode.is_horizontal(),
867            containing_block_writing_mode.is_horizontal(),
868            "Mixed horizontal and vertical writing modes are not supported yet"
869        );
870        let self_value_matches_container = || {
871            self.axis == Direction::Block ||
872                self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
873        };
874
875        // Here we resolve the alignment to either start, center, or end.
876        // Note we need to handle both self-alignment values (when some inset isn't auto)
877        // and distributed alignment values (when both insets are auto).
878        // The latter are treated as their fallback alignment.
879        let alignment = match self.alignment.value() {
880            // https://drafts.csswg.org/css-align/#valdef-self-position-center
881            // https://drafts.csswg.org/css-align/#valdef-align-content-space-around
882            // https://drafts.csswg.org/css-align/#valdef-align-content-space-evenly
883            AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
884                AlignFlags::CENTER
885            },
886            // https://drafts.csswg.org/css-align/#valdef-self-position-self-start
887            AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
888            AlignFlags::SELF_START => AlignFlags::END,
889            // https://drafts.csswg.org/css-align/#valdef-self-position-self-end
890            AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
891            AlignFlags::SELF_END => AlignFlags::START,
892            // https://drafts.csswg.org/css-align/#valdef-justify-content-left
893            AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
894            AlignFlags::LEFT => AlignFlags::END,
895            // https://drafts.csswg.org/css-align/#valdef-justify-content-right
896            AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
897            AlignFlags::RIGHT => AlignFlags::START,
898            // https://drafts.csswg.org/css-align/#valdef-self-position-end
899            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-end
900            // https://drafts.csswg.org/css-align/#valdef-justify-self-last-baseline
901            AlignFlags::END | AlignFlags::FLEX_END | AlignFlags::LAST_BASELINE => AlignFlags::END,
902            // https://drafts.csswg.org/css-align/#valdef-self-position-start
903            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-start
904            // https://drafts.csswg.org/css-align/#valdef-justify-self-first-baseline
905            _ => AlignFlags::START,
906        };
907
908        let alignment = match alignment {
909            AlignFlags::START if flip_anchor => AlignFlags::END,
910            AlignFlags::END if flip_anchor => AlignFlags::START,
911            alignment => alignment,
912        };
913
914        let free_space = alignment_container.length - size;
915        let flags = self.alignment.flags();
916        let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
917            AlignFlags::START
918        } else {
919            alignment
920        };
921
922        let origin = match alignment {
923            AlignFlags::START => alignment_container.origin,
924            AlignFlags::CENTER => alignment_container.origin + free_space / 2,
925            AlignFlags::END => alignment_container.origin + free_space,
926            _ => unreachable!(),
927        };
928        if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
929            matches!(
930                self.alignment,
931                AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
932            )
933        {
934            return origin;
935        }
936        let Some(offsets) = offsets else {
937            return origin;
938        };
939
940        // Handle default overflow alignment.
941        // https://drafts.csswg.org/css-align/#auto-safety-position
942        let min = Au::zero().min(offsets.start);
943        let max = self.containing_size - Au::zero().min(offsets.end) - size;
944        origin.clamp_between_extremums(min, Some(max))
945    }
946}
947
948/// <https://drafts.csswg.org/css2/visuren.html#relative-positioning>
949pub(crate) fn relative_adjustement(
950    style: &ComputedValues,
951    containing_block: &ContainingBlock,
952) -> LogicalVec2<Au> {
953    // It's not completely clear what to do with indefinite percentages
954    // (https://github.com/w3c/csswg-drafts/issues/9353), so we match
955    // other browsers and treat them as 'auto' offsets.
956    let cbis = containing_block.size.inline;
957    let cbbs = containing_block.size.block;
958    let box_offsets = style
959        .box_offsets(containing_block.style.writing_mode)
960        .map_inline_and_block_axes(
961            |value| value.map(|value| value.to_used_value(cbis)),
962            |value| match cbbs {
963                SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
964                _ => match value.non_auto().and_then(|value| value.to_length()) {
965                    Some(value) => AuOrAuto::LengthPercentage(value.into()),
966                    None => AuOrAuto::Auto,
967                },
968            },
969        );
970    fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
971        match (start, end) {
972            (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
973            (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
974            (AuOrAuto::LengthPercentage(start), _) => start,
975        }
976    }
977    LogicalVec2 {
978        inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
979        block: adjust(box_offsets.block_start, box_offsets.block_end),
980    }
981}