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