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