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 start_offset = match &parent_fragment {
152            Fragment::Box(fragment) | Fragment::Float(fragment) => {
153                fragment.borrow().content_rect.origin
154            },
155            Fragment::AbsoluteOrFixedPositioned(_) => return,
156            Fragment::Positioning(fragment) => fragment.borrow().rect.origin,
157            _ => unreachable!(),
158        };
159        self.adjust_static_position_of_hoisted_fragments_with_offset(
160            &start_offset.to_vector(),
161            index,
162        );
163    }
164
165    /// See documentation for [PositioningContext::adjust_static_position_of_hoisted_fragments].
166    pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset(
167        &mut self,
168        offset: &PhysicalVec<Au>,
169        index: PositioningContextLength,
170    ) {
171        self.absolutes
172            .iter_mut()
173            .skip(index.0)
174            .for_each(|hoisted_box| {
175                hoisted_box.adjust_static_position_with_offset(offset);
176            })
177    }
178
179    /// Given `fragment_layout_fn`, a closure which lays out a fragment in a provided
180    /// `PositioningContext`, create a new positioning context if necessary for the fragment and
181    /// lay out the fragment and all its children. Returns the newly created `BoxFragment`.
182    pub(crate) fn layout_maybe_position_relative_fragment(
183        &mut self,
184        layout_context: &LayoutContext,
185        containing_block: &ContainingBlock,
186        base: &LayoutBoxBase,
187        fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
188    ) -> BoxFragment {
189        // If a new `PositioningContext` isn't necessary, simply create the fragment using
190        // the given closure and the current `PositioningContext`.
191        let establishes_containing_block_for_absolutes = base
192            .style
193            .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
194        if !establishes_containing_block_for_absolutes {
195            return fragment_layout_fn(self);
196        }
197
198        let mut new_context = PositioningContext::default();
199        let mut new_fragment = fragment_layout_fn(&mut new_context);
200
201        // Lay out all of the absolutely positioned children for this fragment, and, if it
202        // isn't a containing block for fixed elements, then pass those up to the parent.
203        new_context.layout_collected_children(layout_context, &mut new_fragment);
204        self.append(new_context);
205
206        if base.style.clone_position() == Position::Relative {
207            new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block)
208                .to_physical_vector(containing_block.style.writing_mode)
209        }
210
211        new_fragment
212    }
213
214    fn take_boxes_for_fragment(
215        &mut self,
216        new_fragment: &BoxFragment,
217        boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
218        boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
219    ) {
220        debug_assert!(
221            new_fragment
222                .style
223                .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
224        );
225
226        if new_fragment
227            .style
228            .establishes_containing_block_for_all_descendants(new_fragment.base.flags)
229        {
230            boxes_to_layout_out.append(&mut self.absolutes);
231            return;
232        }
233
234        // TODO: This could potentially use `extract_if` when that is stabilized.
235        let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
236            .absolutes
237            .drain(..)
238            .partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
239        boxes_to_layout_out.append(&mut boxes_to_layout);
240        boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
241    }
242
243    // Lay out the hoisted boxes collected into this `PositioningContext` and add them
244    // to the given `BoxFragment`.
245    pub(crate) fn layout_collected_children(
246        &mut self,
247        layout_context: &LayoutContext,
248        new_fragment: &mut BoxFragment,
249    ) {
250        if self.absolutes.is_empty() {
251            return;
252        }
253
254        // Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and
255        // then these are processed later. In that case and if this fragment doesn't establish a
256        // containing block for absolutes at all, we just do nothing. All hoisted fragments will
257        // later be passed up to a parent PositioningContext.
258        //
259        // Handling this case here, when the PositioningContext is completely ineffectual other than
260        // as a temporary container for hoisted boxes, means that callers can execute less conditional
261        // code.
262        if !new_fragment
263            .style
264            .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
265        {
266            return;
267        }
268
269        let padding_rect = PhysicalRect::new(
270            // Ignore the content rect’s position in its own containing block:
271            PhysicalPoint::origin(),
272            new_fragment.content_rect.size,
273        )
274        .outer_rect(new_fragment.padding);
275        let containing_block = DefiniteContainingBlock {
276            size: padding_rect
277                .size
278                .to_logical(new_fragment.style.writing_mode),
279            style: &new_fragment.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 tentative_block_content_size =
527            context.tentative_block_content_size(preferred_aspect_ratio);
528        let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
529            SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
530                Direction::Block,
531                block_automatic_size,
532                Au::zero,
533                block_stretch_size,
534                || block_content_size,
535                is_table,
536            ))
537        } else {
538            block_axis_solver.computed_sizes.resolve_extrinsic(
539                block_automatic_size,
540                Au::zero(),
541                block_stretch_size,
542            )
543        };
544
545        // The inline axis can be fully resolved, computing intrinsic sizes using the
546        // extrinsic block size.
547        let get_inline_content_size = || {
548            let constraint_space =
549                ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio);
550            context
551                .inline_content_sizes(layout_context, &constraint_space)
552                .sizes
553        };
554        let inline_size = inline_axis_solver.computed_sizes.resolve(
555            Direction::Inline,
556            inline_axis_solver.automatic_size(),
557            Au::zero,
558            Some(inline_axis_solver.stretch_size()),
559            get_inline_content_size,
560            is_table,
561        );
562
563        let containing_block_for_children = ContainingBlock {
564            size: ContainingBlockSize {
565                inline: inline_size,
566                block: tentative_block_size,
567            },
568            style: &style,
569        };
570        // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
571        assert_eq!(
572            containing_block_writing_mode.is_horizontal(),
573            style.writing_mode.is_horizontal(),
574            "Mixed horizontal and vertical writing modes are not supported yet"
575        );
576
577        let mut positioning_context = PositioningContext::default();
578        let lazy_block_size = LazySize::new(
579            &block_axis_solver.computed_sizes,
580            Direction::Block,
581            block_automatic_size,
582            Au::zero,
583            block_stretch_size,
584            is_table,
585        );
586        let CacheableLayoutResult {
587            content_inline_size_for_table,
588            content_block_size,
589            fragments,
590            specific_layout_info,
591            ..
592        } = context.layout(
593            layout_context,
594            &mut positioning_context,
595            &containing_block_for_children,
596            containing_block,
597            preferred_aspect_ratio,
598            &lazy_block_size,
599        );
600
601        let content_size = LogicalVec2 {
602            // Tables can become narrower than predicted due to collapsed columns.
603            inline: content_inline_size_for_table.unwrap_or(inline_size),
604
605            // Now we can properly solve the block size.
606            block: lazy_block_size.resolve(|| content_block_size),
607        };
608
609        let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
610        let block_margins = block_axis_solver.solve_margins(content_size.block);
611        let margin = LogicalSides {
612            inline_start: inline_margins.start,
613            inline_end: inline_margins.end,
614            block_start: block_margins.start,
615            block_end: block_margins.end,
616        };
617
618        let pb = pbm.padding + pbm.border;
619        let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
620        let inline_origin = inline_axis_solver.origin_for_margin_box(
621            margin_rect_size.inline,
622            style.writing_mode,
623            self.original_parent_writing_mode,
624            containing_block_writing_mode,
625        );
626        let block_origin = block_axis_solver.origin_for_margin_box(
627            margin_rect_size.block,
628            style.writing_mode,
629            self.original_parent_writing_mode,
630            containing_block_writing_mode,
631        );
632
633        let content_rect = LogicalRect {
634            start_corner: LogicalVec2 {
635                inline: inline_origin + margin.inline_start + pb.inline_start,
636                block: block_origin + margin.block_start + pb.block_start,
637            },
638            size: content_size,
639        };
640        let mut new_fragment = BoxFragment::new(
641            context.base_fragment_info(),
642            style,
643            fragments,
644            content_rect.as_physical(Some(containing_block)),
645            pbm.padding.to_physical(containing_block_writing_mode),
646            pbm.border.to_physical(containing_block_writing_mode),
647            margin.to_physical(containing_block_writing_mode),
648            specific_layout_info,
649        );
650
651        // This is an absolutely positioned element, which means it also establishes a
652        // containing block for absolutes. We lay out any absolutely positioned children
653        // here and pass the rest to `hoisted_absolutes_from_children.`
654        positioning_context.layout_collected_children(layout_context, &mut new_fragment);
655
656        // Any hoisted boxes that remain in this positioning context are going to be hoisted
657        // up above this absolutely positioned box. These will necessarily be fixed position
658        // elements, because absolutely positioned elements form containing blocks for all
659        // other elements. If any of them have a static start position though, we need to
660        // adjust it to account for the start corner of this absolute.
661        positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
662            &new_fragment.content_rect.origin.to_vector(),
663            PositioningContextLength::zero(),
664        );
665
666        hoisted_absolutes_from_children.extend(positioning_context.absolutes);
667
668        let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
669        context.base.set_fragment(fragment.clone());
670        fragment
671    }
672
673    fn static_position_rect(&self) -> PhysicalRect<Au> {
674        self.adjusted_static_position_rect
675            .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
676    }
677
678    fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
679        self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
680    }
681}
682
683#[derive(Clone, Copy, Debug)]
684struct RectAxis {
685    origin: Au,
686    length: Au,
687}
688
689impl LogicalRect<Au> {
690    fn get_axis(&self, axis: Direction) -> RectAxis {
691        match axis {
692            Direction::Block => RectAxis {
693                origin: self.start_corner.block,
694                length: self.size.block,
695            },
696            Direction::Inline => RectAxis {
697                origin: self.start_corner.inline,
698                length: self.size.inline,
699            },
700        }
701    }
702}
703
704struct AbsoluteAxisSolver {
705    axis: Direction,
706    containing_size: Au,
707    padding_border_sum: Au,
708    computed_margin_start: AuOrAuto,
709    computed_margin_end: AuOrAuto,
710    computed_sizes: Sizes,
711    avoid_negative_margin_start: bool,
712    box_offsets: LogicalSides1D<AuOrAuto>,
713    static_position_rect_axis: RectAxis,
714    alignment: AlignFlags,
715    flip_anchor: bool,
716    is_table_or_replaced: bool,
717}
718
719impl AbsoluteAxisSolver {
720    /// Returns the amount that we need to subtract from the containing block size in order to
721    /// obtain the inset-modified containing block that we will use for sizing purposes.
722    /// (Note that for alignment purposes, we may re-resolve auto insets to a different value.)
723    /// <https://drafts.csswg.org/css-position/#resolving-insets>
724    fn inset_sum(&self) -> Au {
725        match (
726            self.box_offsets.start.non_auto(),
727            self.box_offsets.end.non_auto(),
728        ) {
729            (None, None) => {
730                if self.flip_anchor {
731                    self.containing_size -
732                        self.static_position_rect_axis.origin -
733                        self.static_position_rect_axis.length
734                } else {
735                    self.static_position_rect_axis.origin
736                }
737            },
738            (Some(start), None) => start,
739            (None, Some(end)) => end,
740            (Some(start), Some(end)) => start + end,
741        }
742    }
743
744    /// Returns the size of the inset-modified containing block.
745    /// <https://drafts.csswg.org/css-position-3/#inset-modified-containing-block>
746    #[inline]
747    fn available_space(&self) -> Au {
748        Au::zero().max(self.containing_size - self.inset_sum())
749    }
750
751    #[inline]
752    fn automatic_size(&self) -> Size<Au> {
753        match self.alignment.value() {
754            _ if self.box_offsets.either_auto() => Size::FitContent,
755            AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
756            AlignFlags::STRETCH => Size::Stretch,
757            _ => Size::FitContent,
758        }
759    }
760
761    #[inline]
762    fn stretch_size(&self) -> Au {
763        Au::zero().max(
764            self.available_space() -
765                self.padding_border_sum -
766                self.computed_margin_start.auto_is(Au::zero) -
767                self.computed_margin_end.auto_is(Au::zero),
768        )
769    }
770
771    fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
772        if self.box_offsets.either_auto() {
773            LogicalSides1D::new(
774                self.computed_margin_start.auto_is(Au::zero),
775                self.computed_margin_end.auto_is(Au::zero),
776            )
777        } else {
778            let free_space = self.available_space() - self.padding_border_sum - size;
779            match (self.computed_margin_start, self.computed_margin_end) {
780                (AuOrAuto::Auto, AuOrAuto::Auto) => {
781                    if self.avoid_negative_margin_start && free_space < Au::zero() {
782                        LogicalSides1D::new(Au::zero(), free_space)
783                    } else {
784                        let margin_start = free_space / 2;
785                        LogicalSides1D::new(margin_start, free_space - margin_start)
786                    }
787                },
788                (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
789                    LogicalSides1D::new(free_space - end, end)
790                },
791                (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
792                    LogicalSides1D::new(start, free_space - start)
793                },
794                (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
795                    LogicalSides1D::new(start, end)
796                },
797            }
798        }
799    }
800
801    fn origin_for_margin_box(
802        &self,
803        size: Au,
804        self_writing_mode: WritingMode,
805        original_parent_writing_mode: WritingMode,
806        containing_block_writing_mode: WritingMode,
807    ) -> Au {
808        let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
809            self.box_offsets.start.non_auto(),
810            self.box_offsets.end.non_auto(),
811        ) {
812            (None, None) => (
813                self.static_position_rect_axis,
814                original_parent_writing_mode,
815                self.flip_anchor,
816                None,
817            ),
818            (Some(start), Some(end)) => {
819                let alignment_container = RectAxis {
820                    origin: start,
821                    length: self.available_space(),
822                };
823                (
824                    alignment_container,
825                    containing_block_writing_mode,
826                    false,
827                    Some(LogicalSides1D { start, end }),
828                )
829            },
830            // If a single offset is auto, for alignment purposes it resolves to the amount
831            // that makes the inset-modified containing block be exactly as big as the abspos.
832            // Therefore the free space is zero and the alignment value is irrelevant.
833            (Some(start), None) => return start,
834            (None, Some(end)) => {
835                return self.containing_size - size - end;
836            },
837        };
838
839        assert_eq!(
840            self_writing_mode.is_horizontal(),
841            original_parent_writing_mode.is_horizontal(),
842            "Mixed horizontal and vertical writing modes are not supported yet"
843        );
844        assert_eq!(
845            self_writing_mode.is_horizontal(),
846            containing_block_writing_mode.is_horizontal(),
847            "Mixed horizontal and vertical writing modes are not supported yet"
848        );
849        let self_value_matches_container = || {
850            self.axis == Direction::Block ||
851                self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
852        };
853
854        // Here we resolve the alignment to either start, center, or end.
855        // Note we need to handle both self-alignment values (when some inset isn't auto)
856        // and distributed alignment values (when both insets are auto).
857        // The latter are treated as their fallback alignment.
858        let alignment = match self.alignment.value() {
859            // https://drafts.csswg.org/css-align/#valdef-self-position-center
860            // https://drafts.csswg.org/css-align/#valdef-align-content-space-around
861            // https://drafts.csswg.org/css-align/#valdef-align-content-space-evenly
862            AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
863                AlignFlags::CENTER
864            },
865            // https://drafts.csswg.org/css-align/#valdef-self-position-self-start
866            AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
867            AlignFlags::SELF_START => AlignFlags::END,
868            // https://drafts.csswg.org/css-align/#valdef-self-position-self-end
869            AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
870            AlignFlags::SELF_END => AlignFlags::START,
871            // https://drafts.csswg.org/css-align/#valdef-justify-content-left
872            AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
873            AlignFlags::LEFT => AlignFlags::END,
874            // https://drafts.csswg.org/css-align/#valdef-justify-content-right
875            AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
876            AlignFlags::RIGHT => AlignFlags::START,
877            // https://drafts.csswg.org/css-align/#valdef-self-position-end
878            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-end
879            AlignFlags::END | AlignFlags::FLEX_END => AlignFlags::END,
880            // https://drafts.csswg.org/css-align/#valdef-self-position-start
881            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-start
882            _ => AlignFlags::START,
883        };
884
885        let alignment = match alignment {
886            AlignFlags::START if flip_anchor => AlignFlags::END,
887            AlignFlags::END if flip_anchor => AlignFlags::START,
888            alignment => alignment,
889        };
890
891        let free_space = alignment_container.length - size;
892        let flags = self.alignment.flags();
893        let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
894            AlignFlags::START
895        } else {
896            alignment
897        };
898
899        let origin = match alignment {
900            AlignFlags::START => alignment_container.origin,
901            AlignFlags::CENTER => alignment_container.origin + free_space / 2,
902            AlignFlags::END => alignment_container.origin + free_space,
903            _ => unreachable!(),
904        };
905        if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
906            matches!(
907                self.alignment,
908                AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
909            )
910        {
911            return origin;
912        }
913        let Some(offsets) = offsets else {
914            return origin;
915        };
916
917        // Handle default overflow alignment.
918        // https://drafts.csswg.org/css-align/#auto-safety-position
919        let min = Au::zero().min(offsets.start);
920        let max = self.containing_size - Au::zero().min(offsets.end) - size;
921        origin.clamp_between_extremums(min, Some(max))
922    }
923}
924
925/// <https://drafts.csswg.org/css2/visuren.html#relative-positioning>
926pub(crate) fn relative_adjustement(
927    style: &ComputedValues,
928    containing_block: &ContainingBlock,
929) -> LogicalVec2<Au> {
930    // It's not completely clear what to do with indefinite percentages
931    // (https://github.com/w3c/csswg-drafts/issues/9353), so we match
932    // other browsers and treat them as 'auto' offsets.
933    let cbis = containing_block.size.inline;
934    let cbbs = containing_block.size.block;
935    let box_offsets = style
936        .box_offsets(containing_block.style.writing_mode)
937        .map_inline_and_block_axes(
938            |value| value.map(|value| value.to_used_value(cbis)),
939            |value| match cbbs {
940                SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
941                _ => match value.non_auto().and_then(|value| value.to_length()) {
942                    Some(value) => AuOrAuto::LengthPercentage(value.into()),
943                    None => AuOrAuto::Auto,
944                },
945            },
946        );
947    fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
948        match (start, end) {
949            (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
950            (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
951            (AuOrAuto::LengthPercentage(start), _) => start,
952        }
953    }
954    LogicalVec2 {
955        inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
956        block: adjust(box_offsets.block_start, box_offsets.block_end),
957    }
958}