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