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.rect.origin += relative_adjustement(&base.style, containing_block)
203                .to_physical_vector(containing_block.style.writing_mode)
204        }
205
206        new_fragment
207    }
208
209    fn forget_unhoisted_boxes(&mut self, fragment: &BoxFragment) {
210        let style = fragment.style();
211        debug_assert!(
212            style.establishes_containing_block_for_absolute_descendants(fragment.base.flags)
213        );
214        if style.establishes_containing_block_for_all_descendants(fragment.base.flags) {
215            self.absolutes.clear();
216        } else {
217            self.absolutes
218                .retain(|hoisted_box| hoisted_box.position() == Position::Fixed);
219        }
220    }
221
222    fn take_boxes_for_fragment(
223        &mut self,
224        new_fragment: &BoxFragment,
225        boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
226        boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
227    ) {
228        let style = new_fragment.style();
229        debug_assert!(
230            style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
231        );
232
233        if style.establishes_containing_block_for_all_descendants(new_fragment.base.flags) {
234            boxes_to_layout_out.append(&mut self.absolutes);
235            return;
236        }
237
238        // TODO: This could potentially use `extract_if` when that is stabilized.
239        let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
240            .absolutes
241            .drain(..)
242            .partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
243        boxes_to_layout_out.append(&mut boxes_to_layout);
244        boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
245    }
246
247    // Lay out the hoisted boxes collected into this `PositioningContext` and add them
248    // to the given `BoxFragment`.
249    pub(crate) fn layout_collected_children(
250        &mut self,
251        layout_context: &LayoutContext,
252        new_fragment: &mut BoxFragment,
253    ) {
254        if self.absolutes.is_empty() {
255            return;
256        }
257
258        // Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and
259        // then these are processed later. In that case and if this fragment doesn't establish a
260        // containing block for absolutes at all, we just do nothing. All hoisted fragments will
261        // later be passed up to a parent PositioningContext.
262        //
263        // Handling this case here, when the PositioningContext is completely ineffectual other than
264        // as a temporary container for hoisted boxes, means that callers can execute less conditional
265        // code.
266        let style = new_fragment.style().clone();
267        if !style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) {
268            return;
269        }
270
271        let padding_rect = PhysicalRect::new(
272            // Ignore the content rect’s position in its own containing block:
273            PhysicalPoint::origin(),
274            new_fragment.base.rect.size,
275        )
276        .outer_rect(new_fragment.padding);
277        let containing_block = DefiniteContainingBlock {
278            size: padding_rect.size.to_logical(style.writing_mode),
279            style: &style,
280        };
281
282        let mut fixed_position_boxes_to_hoist = Vec::new();
283        let mut boxes_to_layout = Vec::new();
284        self.take_boxes_for_fragment(
285            new_fragment,
286            &mut boxes_to_layout,
287            &mut fixed_position_boxes_to_hoist,
288        );
289
290        // Laying out a `position: absolute` child (which only establishes a containing block for
291        // `position: absolute` descendants) can result in more `position: fixed` descendants
292        // collecting in `self.absolutes`. We need to loop here in order to keep either laying them
293        // out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more
294        // when `self.absolutes` is empty.
295        while !boxes_to_layout.is_empty() {
296            HoistedAbsolutelyPositionedBox::layout_many(
297                layout_context,
298                std::mem::take(&mut boxes_to_layout),
299                &mut new_fragment.children,
300                &mut self.absolutes,
301                &containing_block,
302                new_fragment.padding,
303            );
304
305            self.take_boxes_for_fragment(
306                new_fragment,
307                &mut boxes_to_layout,
308                &mut fixed_position_boxes_to_hoist,
309            );
310        }
311
312        // We replace here instead of simply preserving these in `take_boxes_for_fragment`
313        // so that we don't have to continually re-iterate over them when laying out in the
314        // loop above.
315        self.absolutes = fixed_position_boxes_to_hoist;
316    }
317
318    pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
319        debug_assert!(hoisted_box.position().is_absolutely_positioned());
320        self.absolutes.push(hoisted_box);
321    }
322
323    pub(crate) fn append(&mut self, mut other: Self) {
324        if other.absolutes.is_empty() {
325            return;
326        }
327        if self.absolutes.is_empty() {
328            self.absolutes = other.absolutes;
329        } else {
330            self.absolutes.append(&mut other.absolutes)
331        }
332    }
333
334    pub(crate) fn layout_initial_containing_block_children(
335        &mut self,
336        layout_context: &LayoutContext,
337        initial_containing_block: &DefiniteContainingBlock,
338        fragments: &mut Vec<Fragment>,
339    ) {
340        // Laying out a `position: absolute` child (which only establishes a containing block for
341        // `position: absolute` descendants) can result in more `position: fixed` descendants
342        // collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We
343        // know there aren't any more when `self.absolutes` is empty.
344        while !self.absolutes.is_empty() {
345            HoistedAbsolutelyPositionedBox::layout_many(
346                layout_context,
347                mem::take(&mut self.absolutes),
348                fragments,
349                &mut self.absolutes,
350                initial_containing_block,
351                Default::default(),
352            )
353        }
354    }
355
356    /// Get the length of this [PositioningContext].
357    pub(crate) fn len(&self) -> PositioningContextLength {
358        PositioningContextLength(self.absolutes.len())
359    }
360
361    /// Truncate this [PositioningContext] to the given [PositioningContextLength].  This
362    /// is useful for "unhoisting" boxes in this context and returning it to the state at
363    /// the time that [`PositioningContext::len()`] was called.
364    pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
365        self.absolutes.truncate(length.0)
366    }
367}
368
369/// A data structure which stores the size of a positioning context.
370#[derive(Clone, Copy, Debug, PartialEq)]
371pub(crate) struct PositioningContextLength(usize);
372
373impl Zero for PositioningContextLength {
374    fn zero() -> Self {
375        Self(0)
376    }
377
378    fn is_zero(&self) -> bool {
379        self.0.is_zero()
380    }
381}
382
383impl HoistedAbsolutelyPositionedBox {
384    fn position(&self) -> Position {
385        let position = self
386            .absolutely_positioned_box
387            .borrow()
388            .context
389            .style()
390            .clone_position();
391        assert!(position.is_absolutely_positioned());
392        position
393    }
394
395    pub(crate) fn layout_many(
396        layout_context: &LayoutContext,
397        mut boxes: Vec<Self>,
398        fragments: &mut Vec<Fragment>,
399        for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
400        containing_block: &DefiniteContainingBlock,
401        containing_block_padding: PhysicalSides<Au>,
402    ) {
403        if layout_context.use_rayon {
404            let mut new_fragments = Vec::new();
405            let mut new_hoisted_boxes = Vec::new();
406
407            boxes
408                .par_iter_mut()
409                .map(|hoisted_box| {
410                    let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
411                    let new_fragment = hoisted_box.layout(
412                        layout_context,
413                        &mut new_hoisted_boxes,
414                        containing_block,
415                        containing_block_padding,
416                    );
417
418                    hoisted_box.fragment.borrow_mut().fragment = Some(new_fragment.clone());
419                    (new_fragment, new_hoisted_boxes)
420                })
421                .unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
422
423            fragments.extend(new_fragments);
424            for_nearest_containing_block_for_all_descendants
425                .extend(new_hoisted_boxes.into_iter().flatten());
426        } else {
427            fragments.extend(boxes.iter_mut().map(|box_| {
428                let new_fragment = box_.layout(
429                    layout_context,
430                    for_nearest_containing_block_for_all_descendants,
431                    containing_block,
432                    containing_block_padding,
433                );
434
435                box_.fragment.borrow_mut().fragment = Some(new_fragment.clone());
436                new_fragment
437            }))
438        }
439    }
440
441    pub(crate) fn layout(
442        &mut self,
443        layout_context: &LayoutContext,
444        hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
445        containing_block: &DefiniteContainingBlock,
446        containing_block_padding: PhysicalSides<Au>,
447    ) -> Fragment {
448        let cbis = containing_block.size.inline;
449        let cbbs = containing_block.size.block;
450        let containing_block_writing_mode = containing_block.style.writing_mode;
451        let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
452        let context = &absolutely_positioned_box.context;
453        let style = context.style().clone();
454        let layout_style = context.layout_style();
455        let ContentBoxSizesAndPBM {
456            content_box_sizes,
457            pbm,
458            ..
459        } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
460        let containing_block = &containing_block.into();
461        let is_table = layout_style.is_table();
462        let is_table_or_replaced = is_table || context.is_replaced();
463        let preferred_aspect_ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
464
465        // The static position rect was calculated assuming that the containing block would be
466        // established by the content box of some ancestor, but the actual containing block is
467        // established by the padding box. So we need to add the padding of that ancestor.
468        let mut static_position_rect = self
469            .static_position_rect()
470            .outer_rect(-containing_block_padding);
471        static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero());
472        let static_position_rect = static_position_rect.to_logical(containing_block);
473
474        let box_offset = style.box_offsets(containing_block.style.writing_mode);
475
476        // When the "static-position rect" doesn't come into play, we do not do any alignment
477        // in the inline axis.
478        let inline_box_offsets = box_offset.inline_sides().percentages_relative_to(cbis);
479        let inline_alignment = match inline_box_offsets.either_specified() {
480            true => style.clone_justify_self().0,
481            false => self.resolved_alignment.inline,
482        };
483
484        let inline_axis_solver = AbsoluteAxisSolver {
485            axis: Direction::Inline,
486            containing_size: cbis,
487            padding_border_sum: pbm.padding_border_sums.inline,
488            computed_margin_start: pbm.margin.inline_start,
489            computed_margin_end: pbm.margin.inline_end,
490            computed_sizes: content_box_sizes.inline,
491            avoid_negative_margin_start: true,
492            box_offsets: inline_box_offsets,
493            static_position_rect_axis: static_position_rect.get_axis(Direction::Inline),
494            alignment: inline_alignment,
495            flip_anchor: self.original_parent_writing_mode.is_bidi_ltr() !=
496                containing_block_writing_mode.is_bidi_ltr(),
497            is_table_or_replaced,
498        };
499
500        // When the "static-position rect" doesn't come into play, we re-resolve "align-self"
501        // against this containing block.
502        let block_box_offsets = box_offset.block_sides().percentages_relative_to(cbbs);
503        let block_alignment = match block_box_offsets.either_specified() {
504            true => style.clone_align_self().0,
505            false => self.resolved_alignment.block,
506        };
507        let block_axis_solver = AbsoluteAxisSolver {
508            axis: Direction::Block,
509            containing_size: cbbs,
510            padding_border_sum: pbm.padding_border_sums.block,
511            computed_margin_start: pbm.margin.block_start,
512            computed_margin_end: pbm.margin.block_end,
513            computed_sizes: content_box_sizes.block,
514            avoid_negative_margin_start: false,
515            box_offsets: block_box_offsets,
516            static_position_rect_axis: static_position_rect.get_axis(Direction::Block),
517            alignment: block_alignment,
518            flip_anchor: false,
519            is_table_or_replaced,
520        };
521
522        // The block size can depend on layout results, so we only solve it tentatively,
523        // we may have to resolve it properly later on.
524        let block_automatic_size = block_axis_solver.automatic_size();
525        let block_stretch_size = Some(block_axis_solver.stretch_size());
526        let inline_stretch_size = inline_axis_solver.stretch_size();
527        let tentative_block_content_size =
528            context.tentative_block_content_size(preferred_aspect_ratio, inline_stretch_size);
529        let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
530            SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
531                Direction::Block,
532                block_automatic_size,
533                Au::zero,
534                block_stretch_size,
535                || block_content_size,
536                is_table,
537            ))
538        } else {
539            block_axis_solver.computed_sizes.resolve_extrinsic(
540                block_automatic_size,
541                Au::zero(),
542                block_stretch_size,
543            )
544        };
545
546        // The inline axis can be fully resolved, computing intrinsic sizes using the
547        // extrinsic block size.
548        let get_inline_content_size = || {
549            let constraint_space =
550                ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio);
551            context
552                .inline_content_sizes(layout_context, &constraint_space)
553                .sizes
554        };
555        let inline_size = inline_axis_solver.computed_sizes.resolve(
556            Direction::Inline,
557            inline_axis_solver.automatic_size(),
558            Au::zero,
559            Some(inline_stretch_size),
560            get_inline_content_size,
561            is_table,
562        );
563
564        let containing_block_for_children = ContainingBlock {
565            size: ContainingBlockSize {
566                inline: inline_size,
567                block: tentative_block_size,
568            },
569            style: &style,
570        };
571        // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
572        assert_eq!(
573            containing_block_writing_mode.is_horizontal(),
574            style.writing_mode.is_horizontal(),
575            "Mixed horizontal and vertical writing modes are not supported yet"
576        );
577
578        let mut positioning_context = PositioningContext::default();
579        let lazy_block_size = LazySize::new(
580            &block_axis_solver.computed_sizes,
581            Direction::Block,
582            block_automatic_size,
583            Au::zero,
584            block_stretch_size,
585            is_table,
586        );
587        let (layout, is_cached) = context.layout_and_is_cached(
588            layout_context,
589            &mut positioning_context,
590            &containing_block_for_children,
591            containing_block,
592            preferred_aspect_ratio,
593            &lazy_block_size,
594        );
595        let IndependentFormattingContextLayoutResult {
596            content_inline_size_for_table,
597            content_block_size,
598            fragments,
599            specific_layout_info,
600            ..
601        } = layout;
602
603        let content_size = LogicalVec2 {
604            // Tables can become narrower than predicted due to collapsed columns.
605            inline: content_inline_size_for_table.unwrap_or(inline_size),
606
607            // Now we can properly solve the block size.
608            block: lazy_block_size.resolve(|| content_block_size),
609        };
610
611        let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
612        let block_margins = block_axis_solver.solve_margins(content_size.block);
613        let margin = LogicalSides {
614            inline_start: inline_margins.start,
615            inline_end: inline_margins.end,
616            block_start: block_margins.start,
617            block_end: block_margins.end,
618        };
619
620        let pb = pbm.padding + pbm.border;
621        let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
622        let inline_origin = inline_axis_solver.origin_for_margin_box(
623            margin_rect_size.inline,
624            style.writing_mode,
625            self.original_parent_writing_mode,
626            containing_block_writing_mode,
627        );
628        let block_origin = block_axis_solver.origin_for_margin_box(
629            margin_rect_size.block,
630            style.writing_mode,
631            self.original_parent_writing_mode,
632            containing_block_writing_mode,
633        );
634        let content_rect = LogicalRect {
635            start_corner: LogicalVec2 {
636                inline: inline_origin + margin.inline_start + pb.inline_start,
637                block: block_origin + margin.block_start + pb.block_start,
638            },
639            size: content_size,
640        }
641        .as_physical(Some(containing_block));
642
643        let mut adjust_hoisted_boxes = |mut positioning_context: PositioningContext| {
644            // Any hoisted boxes that remain in this positioning context are going to be hoisted
645            // up above this absolutely positioned box. These will necessarily be fixed position
646            // elements, because absolutely positioned elements form containing blocks for all
647            // other elements. If any of them have a static start position though, we need to
648            // adjust it to account for the start corner of this absolute.
649            positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
650                &content_rect.origin.to_vector(),
651                PositioningContextLength::zero(),
652            );
653
654            hoisted_absolutes_from_children.extend(positioning_context.absolutes);
655        };
656
657        if is_cached {
658            if let Some(Fragment::Box(old_fragment)) = context.base.fragments().first() {
659                let old_fragment_borrowed = old_fragment.borrow();
660                if content_rect == old_fragment_borrowed.content_rect() {
661                    // Drain the nested absolutes for which we are a containing block.
662                    // However, we are reusing the fragment, so no need to lay them out again.
663                    positioning_context.forget_unhoisted_boxes(&old_fragment_borrowed);
664                    adjust_hoisted_boxes(positioning_context);
665                    return Fragment::Box(old_fragment.clone());
666                }
667            }
668        }
669
670        let mut new_fragment = BoxFragment::new(
671            context.base_fragment_info(),
672            style,
673            fragments,
674            content_rect,
675            pbm.padding.to_physical(containing_block_writing_mode),
676            pbm.border.to_physical(containing_block_writing_mode),
677            margin.to_physical(containing_block_writing_mode),
678            specific_layout_info,
679        );
680
681        // This is an absolutely positioned element, which means it also establishes a
682        // containing block for absolutes. We lay out any absolutely positioned children
683        // here and pass the rest to `hoisted_absolutes_from_children.`
684        positioning_context.layout_collected_children(layout_context, &mut new_fragment);
685
686        adjust_hoisted_boxes(positioning_context);
687
688        let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
689        context.base.set_fragment(fragment.clone());
690        fragment
691    }
692
693    fn static_position_rect(&self) -> PhysicalRect<Au> {
694        self.adjusted_static_position_rect
695            .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
696    }
697
698    fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
699        self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
700    }
701}
702
703#[derive(Clone, Copy, Debug)]
704struct RectAxis {
705    origin: Au,
706    length: Au,
707}
708
709impl LogicalRect<Au> {
710    fn get_axis(&self, axis: Direction) -> RectAxis {
711        match axis {
712            Direction::Block => RectAxis {
713                origin: self.start_corner.block,
714                length: self.size.block,
715            },
716            Direction::Inline => RectAxis {
717                origin: self.start_corner.inline,
718                length: self.size.inline,
719            },
720        }
721    }
722}
723
724struct AbsoluteAxisSolver {
725    axis: Direction,
726    containing_size: Au,
727    padding_border_sum: Au,
728    computed_margin_start: AuOrAuto,
729    computed_margin_end: AuOrAuto,
730    computed_sizes: Sizes,
731    avoid_negative_margin_start: bool,
732    box_offsets: LogicalSides1D<AuOrAuto>,
733    static_position_rect_axis: RectAxis,
734    alignment: AlignFlags,
735    flip_anchor: bool,
736    is_table_or_replaced: bool,
737}
738
739impl AbsoluteAxisSolver {
740    /// Returns the amount that we need to subtract from the containing block size in order to
741    /// obtain the inset-modified containing block that we will use for sizing purposes.
742    /// (Note that for alignment purposes, we may re-resolve auto insets to a different value.)
743    /// <https://drafts.csswg.org/css-position/#resolving-insets>
744    fn inset_sum(&self) -> Au {
745        match (
746            self.box_offsets.start.non_auto(),
747            self.box_offsets.end.non_auto(),
748        ) {
749            (None, None) => {
750                if self.flip_anchor {
751                    self.containing_size -
752                        self.static_position_rect_axis.origin -
753                        self.static_position_rect_axis.length
754                } else {
755                    self.static_position_rect_axis.origin
756                }
757            },
758            (Some(start), None) => start,
759            (None, Some(end)) => end,
760            (Some(start), Some(end)) => start + end,
761        }
762    }
763
764    /// Returns the size of the inset-modified containing block.
765    /// <https://drafts.csswg.org/css-position-3/#inset-modified-containing-block>
766    #[inline]
767    fn available_space(&self) -> Au {
768        Au::zero().max(self.containing_size - self.inset_sum())
769    }
770
771    #[inline]
772    fn automatic_size(&self) -> Size<Au> {
773        match self.alignment.value() {
774            _ if self.box_offsets.either_auto() => Size::FitContent,
775            AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
776            AlignFlags::STRETCH => Size::Stretch,
777            _ => Size::FitContent,
778        }
779    }
780
781    #[inline]
782    fn stretch_size(&self) -> Au {
783        Au::zero().max(
784            self.available_space() -
785                self.padding_border_sum -
786                self.computed_margin_start.auto_is(Au::zero) -
787                self.computed_margin_end.auto_is(Au::zero),
788        )
789    }
790
791    fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
792        if self.box_offsets.either_auto() {
793            LogicalSides1D::new(
794                self.computed_margin_start.auto_is(Au::zero),
795                self.computed_margin_end.auto_is(Au::zero),
796            )
797        } else {
798            let free_space = self.available_space() - self.padding_border_sum - size;
799            match (self.computed_margin_start, self.computed_margin_end) {
800                (AuOrAuto::Auto, AuOrAuto::Auto) => {
801                    if self.avoid_negative_margin_start && free_space < Au::zero() {
802                        LogicalSides1D::new(Au::zero(), free_space)
803                    } else {
804                        let margin_start = free_space / 2;
805                        LogicalSides1D::new(margin_start, free_space - margin_start)
806                    }
807                },
808                (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
809                    LogicalSides1D::new(free_space - end, end)
810                },
811                (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
812                    LogicalSides1D::new(start, free_space - start)
813                },
814                (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
815                    LogicalSides1D::new(start, end)
816                },
817            }
818        }
819    }
820
821    fn origin_for_margin_box(
822        &self,
823        size: Au,
824        self_writing_mode: WritingMode,
825        original_parent_writing_mode: WritingMode,
826        containing_block_writing_mode: WritingMode,
827    ) -> Au {
828        let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
829            self.box_offsets.start.non_auto(),
830            self.box_offsets.end.non_auto(),
831        ) {
832            (None, None) => (
833                self.static_position_rect_axis,
834                original_parent_writing_mode,
835                self.flip_anchor,
836                None,
837            ),
838            (Some(start), Some(end)) => {
839                let alignment_container = RectAxis {
840                    origin: start,
841                    length: self.available_space(),
842                };
843                (
844                    alignment_container,
845                    containing_block_writing_mode,
846                    false,
847                    Some(LogicalSides1D { start, end }),
848                )
849            },
850            // If a single offset is auto, for alignment purposes it resolves to the amount
851            // that makes the inset-modified containing block be exactly as big as the abspos.
852            // Therefore the free space is zero and the alignment value is irrelevant.
853            (Some(start), None) => return start,
854            (None, Some(end)) => {
855                return self.containing_size - size - end;
856            },
857        };
858
859        assert_eq!(
860            self_writing_mode.is_horizontal(),
861            original_parent_writing_mode.is_horizontal(),
862            "Mixed horizontal and vertical writing modes are not supported yet"
863        );
864        assert_eq!(
865            self_writing_mode.is_horizontal(),
866            containing_block_writing_mode.is_horizontal(),
867            "Mixed horizontal and vertical writing modes are not supported yet"
868        );
869        let self_value_matches_container = || {
870            self.axis == Direction::Block ||
871                self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
872        };
873
874        // Here we resolve the alignment to either start, center, or end.
875        // Note we need to handle both self-alignment values (when some inset isn't auto)
876        // and distributed alignment values (when both insets are auto).
877        // The latter are treated as their fallback alignment.
878        let alignment = match self.alignment.value() {
879            // https://drafts.csswg.org/css-align/#valdef-self-position-center
880            // https://drafts.csswg.org/css-align/#valdef-align-content-space-around
881            // https://drafts.csswg.org/css-align/#valdef-align-content-space-evenly
882            AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
883                AlignFlags::CENTER
884            },
885            // https://drafts.csswg.org/css-align/#valdef-self-position-self-start
886            AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
887            AlignFlags::SELF_START => AlignFlags::END,
888            // https://drafts.csswg.org/css-align/#valdef-self-position-self-end
889            AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
890            AlignFlags::SELF_END => AlignFlags::START,
891            // https://drafts.csswg.org/css-align/#valdef-justify-content-left
892            AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
893            AlignFlags::LEFT => AlignFlags::END,
894            // https://drafts.csswg.org/css-align/#valdef-justify-content-right
895            AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
896            AlignFlags::RIGHT => AlignFlags::START,
897            // https://drafts.csswg.org/css-align/#valdef-self-position-end
898            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-end
899            // https://drafts.csswg.org/css-align/#valdef-justify-self-last-baseline
900            AlignFlags::END | AlignFlags::FLEX_END | AlignFlags::LAST_BASELINE => AlignFlags::END,
901            // https://drafts.csswg.org/css-align/#valdef-self-position-start
902            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-start
903            // https://drafts.csswg.org/css-align/#valdef-justify-self-first-baseline
904            _ => AlignFlags::START,
905        };
906
907        let alignment = match alignment {
908            AlignFlags::START if flip_anchor => AlignFlags::END,
909            AlignFlags::END if flip_anchor => AlignFlags::START,
910            alignment => alignment,
911        };
912
913        let free_space = alignment_container.length - size;
914        let flags = self.alignment.flags();
915        let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
916            AlignFlags::START
917        } else {
918            alignment
919        };
920
921        let origin = match alignment {
922            AlignFlags::START => alignment_container.origin,
923            AlignFlags::CENTER => alignment_container.origin + free_space / 2,
924            AlignFlags::END => alignment_container.origin + free_space,
925            _ => unreachable!(),
926        };
927        if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
928            matches!(
929                self.alignment,
930                AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
931            )
932        {
933            return origin;
934        }
935        let Some(offsets) = offsets else {
936            return origin;
937        };
938
939        // Handle default overflow alignment.
940        // https://drafts.csswg.org/css-align/#auto-safety-position
941        let min = Au::zero().min(offsets.start);
942        let max = self.containing_size - Au::zero().min(offsets.end) - size;
943        origin.clamp_between_extremums(min, Some(max))
944    }
945}
946
947/// <https://drafts.csswg.org/css2/visuren.html#relative-positioning>
948pub(crate) fn relative_adjustement(
949    style: &ComputedValues,
950    containing_block: &ContainingBlock,
951) -> LogicalVec2<Au> {
952    // It's not completely clear what to do with indefinite percentages
953    // (https://github.com/w3c/csswg-drafts/issues/9353), so we match
954    // other browsers and treat them as 'auto' offsets.
955    let cbis = containing_block.size.inline;
956    let cbbs = containing_block.size.block;
957    let box_offsets = style
958        .box_offsets(containing_block.style.writing_mode)
959        .map_inline_and_block_axes(
960            |value| value.map(|value| value.to_used_value(cbis)),
961            |value| match cbbs {
962                SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
963                _ => match value.non_auto().and_then(|value| value.to_length()) {
964                    Some(value) => AuOrAuto::LengthPercentage(value.into()),
965                    None => AuOrAuto::Auto,
966                },
967            },
968        );
969    fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
970        match (start, end) {
971            (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
972            (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
973            (AuOrAuto::LengthPercentage(start), _) => start,
974        }
975    }
976    LogicalVec2 {
977        inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
978        block: adjust(box_offsets.block_start, box_offsets.block_end),
979    }
980}