Skip to main content

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;
6use std::ops::Range;
7use std::sync::Arc;
8
9use app_units::Au;
10use malloc_size_of_derive::MallocSizeOf;
11use rayon::iter::IntoParallelRefMutIterator;
12use rayon::prelude::{IndexedParallelIterator, ParallelIterator};
13use servo_arc::Arc as ServoArc;
14use style::Zero;
15use style::computed_values::position::T as Position;
16use style::logical_geometry::{LogicalAxis, PhysicalSide, WritingMode};
17use style::properties::ComputedValues;
18use style::values::specified::align::AlignFlags;
19
20use crate::cell::{ArcRefCell, RefOrAtomicRef};
21use crate::context::LayoutContext;
22use crate::dom_traversal::{Contents, NodeAndStyleInfo};
23use crate::formatting_contexts::IndependentFormattingContext;
24use crate::fragment_tree::{
25    BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment, LayoutRootFragment,
26};
27use crate::geom::{
28    AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
29    PhysicalSize, PhysicalVec, ToLogical, ToLogicalWithContainingBlock,
30};
31use crate::layout_box_base::{IndependentFormattingContextLayoutResult, LayoutBoxBase};
32use crate::sizing::{LazySize, Size, SizeConstraint, Sizes};
33use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
34use crate::{
35    ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock,
36    PropagatedBoxTreeData,
37};
38
39#[derive(Debug, MallocSizeOf)]
40pub(crate) struct AbsolutelyPositionedBox {
41    pub context: IndependentFormattingContext,
42}
43
44#[derive(Clone, MallocSizeOf)]
45pub(crate) struct HoistedAbsolutelyPositionedBox {
46    absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
47    /// A reference to a Fragment which is shared between this `HoistedAbsolutelyPositionedBox`
48    /// and its placeholder `AbsoluteOrFixedPositionedFragment` in the original tree position.
49    /// This will be used later in order to paint this hoisted box in tree order.
50    pub fragment: ArcRefCell<HoistedSharedFragment>,
51    /// The adjusted "static-position rect" of this absolutely positioned box. This is
52    /// defined by the layout mode from which the box originates. This is the
53    /// [`HoistedSharedFragment::original_static_position_rect`] adjusted by the offests
54    /// of ancestors between the tree position of the absolute and the
55    /// [`PostioningContext`] that holds this [`HoistedAbsolutelyPositionedBox`].
56    ///
57    /// If the value is `None`, the original static position rect has not been adjusted yet.
58    ///
59    /// See <https://drafts.csswg.org/css-position-3/#staticpos-rect>
60    pub adjusted_static_position_rect: Option<PhysicalRect<Au>>,
61    /// The resolved alignment values used for aligning this absolutely positioned element
62    /// if the "static-position rect" ends up being the "inset-modified containing block".
63    /// These values are dependent on the layout mode (currently only interesting for
64    /// flexbox).
65    pub resolved_alignment: LogicalVec2<AlignFlags>,
66    /// This is the [`WritingMode`] of the original parent of the element that created this
67    /// hoisted absolutely-positioned fragment. This helps to interpret the offset for
68    /// static positioning. If the writing mode is right-to-left or bottom-to-top, the static
69    /// offset needs to be adjusted by the absolutely positioned element's inline size.
70    pub original_parent_writing_mode: WritingMode,
71}
72
73impl AbsolutelyPositionedBox {
74    pub fn new(context: IndependentFormattingContext) -> Self {
75        Self { context }
76    }
77
78    pub fn construct(
79        context: &LayoutContext,
80        node_info: &NodeAndStyleInfo,
81        display_inside: DisplayInside,
82        contents: Contents,
83    ) -> Self {
84        Self {
85            context: IndependentFormattingContext::construct(
86                context,
87                node_info,
88                display_inside,
89                contents,
90                // Text decorations are not propagated to any out-of-flow descendants. In addition,
91                // absolutes don't affect the size of ancestors so it is fine to allow descendent
92                // tables to resolve percentage columns.
93                PropagatedBoxTreeData::default(),
94            ),
95        }
96    }
97
98    pub(crate) fn to_hoisted(
99        absolutely_positioned_box: ArcRefCell<Self>,
100        static_position_rect: PhysicalRect<Au>,
101        resolved_alignment: LogicalVec2<AlignFlags>,
102        original_parent_writing_mode: WritingMode,
103    ) -> HoistedAbsolutelyPositionedBox {
104        HoistedAbsolutelyPositionedBox {
105            fragment: ArcRefCell::new(HoistedSharedFragment::new(static_position_rect)),
106            adjusted_static_position_rect: None,
107            resolved_alignment,
108            original_parent_writing_mode,
109            absolutely_positioned_box,
110        }
111    }
112}
113
114#[derive(Clone, Default, MallocSizeOf)]
115pub(crate) struct PositioningContext {
116    absolutes: Vec<HoistedAbsolutelyPositionedBox>,
117}
118
119impl PositioningContext {
120    #[inline]
121    pub(crate) fn is_empty(&self) -> bool {
122        self.absolutes.is_empty()
123    }
124
125    #[inline]
126    pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> {
127        Self::new_for_style_and_fragment_flags(
128            &layout_box_base.style,
129            &layout_box_base.base_fragment_info.flags,
130        )
131    }
132
133    fn new_for_style_and_fragment_flags(
134        style: &ComputedValues,
135        flags: &FragmentFlags,
136    ) -> Option<Self> {
137        if style.establishes_containing_block_for_absolute_descendants(*flags) {
138            Some(Self::default())
139        } else {
140            None
141        }
142    }
143
144    /// Absolute and fixed position fragments are hoisted up to their containing blocks
145    /// from their tree position. When these fragments have static inset start positions,
146    /// that position (relative to the ancestor containing block) needs to be included
147    /// with the hoisted fragment so that it can be laid out properly at the containing
148    /// block.
149    ///
150    /// This function is used to update the static position of hoisted boxes added after
151    /// the given index at every level of the fragment tree as the hoisted fragments move
152    /// up to their containing blocks. Once an ancestor fragment is laid out, this
153    /// function can be used to aggregate its offset to any descendent boxes that are
154    /// being hoisted. In this case, the appropriate index to use is the result of
155    /// [`PositioningContext::len()`] cached before laying out the [`Fragment`].
156    pub(crate) fn adjust_static_position_of_hoisted_fragments(
157        &mut self,
158        parent_fragment: &Fragment,
159        index: PositioningContextLength,
160    ) {
161        let Some(base) = parent_fragment.base() else {
162            return;
163        };
164        self.adjust_static_position_of_hoisted_fragments_with_offset(
165            &base.rect().origin.to_vector(),
166            index,
167        );
168    }
169
170    /// See documentation for [PositioningContext::adjust_static_position_of_hoisted_fragments].
171    pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset(
172        &mut self,
173        offset: &PhysicalVec<Au>,
174        index: PositioningContextLength,
175    ) {
176        self.adjust_static_position_of_hoisted_fragments_in_range(offset, &(index..self.len()))
177    }
178
179    /// See documentation for [PositioningContext::adjust_static_position_of_hoisted_fragments].
180    pub(crate) fn adjust_static_position_of_hoisted_fragments_in_range(
181        &mut self,
182        offset: &PhysicalVec<Au>,
183        range: &Range<PositioningContextLength>,
184    ) {
185        for hoisted_box in &mut self.absolutes[range.start.0..range.end.0] {
186            hoisted_box.adjust_static_position_with_offset(offset);
187        }
188    }
189
190    /// Given `fragment_layout_fn`, a closure which lays out a fragment in a provided
191    /// `PositioningContext`, create a new positioning context if necessary for the fragment and
192    /// lay out the fragment and all its children. Returns the newly created `BoxFragment`.
193    pub(crate) fn layout_maybe_position_relative_fragment(
194        &mut self,
195        layout_context: &LayoutContext,
196        containing_block: &ContainingBlock,
197        base: &LayoutBoxBase,
198        fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
199    ) -> BoxFragment {
200        // If a new `PositioningContext` isn't necessary, simply create the fragment using
201        // the given closure and the current `PositioningContext`.
202        let establishes_containing_block_for_absolutes = base
203            .style
204            .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
205        if !establishes_containing_block_for_absolutes {
206            return fragment_layout_fn(self);
207        }
208
209        let mut new_context = PositioningContext::default();
210        let mut new_fragment = fragment_layout_fn(&mut new_context);
211
212        // Lay out all of the absolutely positioned children for this fragment, and, if it
213        // isn't a containing block for fixed elements, then pass those up to the parent.
214        new_context.layout_collected_children(layout_context, &mut new_fragment);
215        self.append(new_context);
216
217        if base.style.get_box().position == Position::Relative {
218            new_fragment.base.translate_rect(
219                relative_adjustement(&base.style, containing_block)
220                    .to_physical_vector(containing_block.style.writing_mode)
221                    .into(),
222            );
223        }
224
225        new_fragment
226    }
227
228    fn forget_unhoisted_boxes(&mut self, fragment: &BoxFragment) {
229        let style = fragment.style();
230        debug_assert!(
231            style.establishes_containing_block_for_absolute_descendants(fragment.base.flags)
232        );
233        if style.establishes_containing_block_for_all_descendants(fragment.base.flags) {
234            self.absolutes.clear();
235        } else {
236            self.absolutes
237                .retain(|hoisted_box| hoisted_box.position() == Position::Fixed);
238        }
239    }
240
241    fn take_boxes_for_fragment(
242        &mut self,
243        new_fragment: &BoxFragment,
244        boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
245        boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
246    ) {
247        let style = new_fragment.style();
248        debug_assert!(
249            style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
250        );
251
252        if style.establishes_containing_block_for_all_descendants(new_fragment.base.flags) {
253            boxes_to_layout_out.append(&mut self.absolutes);
254            return;
255        }
256
257        // TODO: This could potentially use `extract_if` when that is stabilized.
258        let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
259            .absolutes
260            .drain(..)
261            .partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
262        boxes_to_layout_out.append(&mut boxes_to_layout);
263        boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
264    }
265
266    // Lay out the hoisted boxes collected into this `PositioningContext` and add them
267    // to the given `BoxFragment`.
268    pub(crate) fn layout_collected_children(
269        &mut self,
270        layout_context: &LayoutContext,
271        new_fragment: &mut BoxFragment,
272    ) {
273        if self.absolutes.is_empty() {
274            return;
275        }
276
277        // Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and
278        // then these are processed later. In that case and if this fragment doesn't establish a
279        // containing block for absolutes at all, we just do nothing. All hoisted fragments will
280        // later be passed up to a parent PositioningContext.
281        //
282        // Handling this case here, when the PositioningContext is completely ineffectual other than
283        // as a temporary container for hoisted boxes, means that callers can execute less conditional
284        // code.
285        let style = new_fragment.style().clone();
286        if !style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) {
287            return;
288        }
289
290        let mut fixed_position_boxes_to_hoist = Vec::new();
291        let mut boxes_to_layout = Vec::new();
292        self.take_boxes_for_fragment(
293            new_fragment,
294            &mut boxes_to_layout,
295            &mut fixed_position_boxes_to_hoist,
296        );
297
298        // The children are moved out and then replaced later so that it can be borrowed mutably
299        // while a reference to the fragment is passed to `layout_many`.
300        let mut children = std::mem::take(&mut new_fragment.children);
301
302        // Laying out a `position: absolute` child (which only establishes a containing block for
303        // `position: absolute` descendants) can result in more `position: fixed` descendants
304        // collecting in `self.absolutes`. We need to loop here in order to keep either laying them
305        // out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more
306        // when `self.absolutes` is empty.
307        while !boxes_to_layout.is_empty() {
308            HoistedAbsolutelyPositionedBox::layout_many(
309                layout_context,
310                std::mem::take(&mut boxes_to_layout),
311                &mut children,
312                &mut self.absolutes,
313                &AbsoluteContainingBlock::Fragment(new_fragment),
314            );
315
316            self.take_boxes_for_fragment(
317                new_fragment,
318                &mut boxes_to_layout,
319                &mut fixed_position_boxes_to_hoist,
320            );
321        }
322
323        // Replace the children as they were temporarily removed to satisfy the borrow checker.
324        new_fragment.children = children;
325
326        // We replace here instead of simply preserving these in `take_boxes_for_fragment`
327        // so that we don't have to continually re-iterate over them when laying out in the
328        // loop above.
329        self.absolutes = fixed_position_boxes_to_hoist;
330    }
331
332    pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
333        debug_assert!(hoisted_box.position().is_absolutely_positioned());
334        self.absolutes.push(hoisted_box);
335    }
336
337    pub(crate) fn append(&mut self, mut other: Self) {
338        if other.absolutes.is_empty() {
339            return;
340        }
341        if self.absolutes.is_empty() {
342            self.absolutes = other.absolutes;
343        } else {
344            self.absolutes.append(&mut other.absolutes)
345        }
346    }
347
348    pub(crate) fn layout_initial_containing_block_children(
349        &mut self,
350        layout_context: &LayoutContext,
351        initial_containing_block: &DefiniteContainingBlock,
352        fragments: &mut Vec<Fragment>,
353    ) {
354        // Laying out a `position: absolute` child (which only establishes a containing block for
355        // `position: absolute` descendants) can result in more `position: fixed` descendants
356        // collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We
357        // know there aren't any more when `self.absolutes` is empty.
358        while !self.absolutes.is_empty() {
359            HoistedAbsolutelyPositionedBox::layout_many(
360                layout_context,
361                mem::take(&mut self.absolutes),
362                fragments,
363                &mut self.absolutes,
364                &AbsoluteContainingBlock::InitialContainingBlock(
365                    initial_containing_block
366                        .size
367                        .to_physical_size(initial_containing_block.style.writing_mode),
368                    initial_containing_block.style,
369                ),
370            )
371        }
372    }
373
374    /// Get the length of this [PositioningContext].
375    pub(crate) fn len(&self) -> PositioningContextLength {
376        PositioningContextLength(self.absolutes.len())
377    }
378
379    /// Truncate this [PositioningContext] to the given [PositioningContextLength].  This
380    /// is useful for "unhoisting" boxes in this context and returning it to the state at
381    /// the time that [`PositioningContext::len()`] was called.
382    pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
383        self.absolutes.truncate(length.0)
384    }
385}
386
387/// A data structure which stores the size of a positioning context.
388#[derive(Clone, Copy, Debug, PartialEq)]
389pub(crate) struct PositioningContextLength(usize);
390
391impl Zero for PositioningContextLength {
392    fn zero() -> Self {
393        Self(0)
394    }
395
396    fn is_zero(&self) -> bool {
397        self.0.is_zero()
398    }
399}
400
401impl HoistedAbsolutelyPositionedBox {
402    fn position(&self) -> Position {
403        let position = self
404            .absolutely_positioned_box
405            .borrow()
406            .context
407            .style()
408            .get_box()
409            .position;
410        assert!(position.is_absolutely_positioned());
411        position
412    }
413
414    fn layout_many(
415        layout_context: &LayoutContext,
416        mut boxes: Vec<Self>,
417        fragments: &mut Vec<Fragment>,
418        for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
419        absolute_containing_block: &AbsoluteContainingBlock,
420    ) {
421        let job_sizes = boxes.iter().map(|hoisted_box| {
422            hoisted_box
423                .absolutely_positioned_box
424                .borrow()
425                .context
426                .subtree_size()
427        });
428        if layout_context.should_parallelize_layout(job_sizes) {
429            let mut new_fragments = Vec::new();
430            let mut new_hoisted_boxes = Vec::new();
431
432            boxes
433                .par_iter_mut()
434                .map(|hoisted_box| {
435                    let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
436                    let new_fragment = hoisted_box.layout(
437                        layout_context,
438                        &mut new_hoisted_boxes,
439                        absolute_containing_block,
440                    );
441                    (new_fragment, new_hoisted_boxes)
442                })
443                .unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
444
445            fragments.extend(new_fragments);
446            for_nearest_containing_block_for_all_descendants
447                .extend(new_hoisted_boxes.into_iter().flatten());
448        } else {
449            fragments.extend(boxes.iter_mut().map(|hoisted_box| {
450                hoisted_box.layout(
451                    layout_context,
452                    for_nearest_containing_block_for_all_descendants,
453                    absolute_containing_block,
454                )
455            }))
456        }
457    }
458
459    fn layout(
460        &mut self,
461        layout_context: &LayoutContext,
462        hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
463        absolute_containing_block: &AbsoluteContainingBlock,
464    ) -> Fragment {
465        let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
466        let independent_formatting_context = &absolutely_positioned_box.context;
467
468        let containing_block_rect =
469            absolute_containing_block.rect_for_item(independent_formatting_context.style());
470        let containing_block_style = absolute_containing_block.style();
471        let containing_block = DefiniteContainingBlock {
472            size: containing_block_rect
473                .size
474                .to_logical(containing_block_style.writing_mode),
475            style: &containing_block_style,
476        };
477
478        // Static position adjustment: content box -> containing block
479        //
480        // The static position rect is in coordinates relative to the content box of some
481        // ancestor. Convert it into coordinates relative to the containing block of the
482        // absolute. For grid containers, we resolve the node's grid area. For all other
483        // containers, the containing block is just the padding box.
484        let containing_block_origin = containing_block_rect.origin.to_vector();
485        let fully_adjusted_static_position_rect = {
486            let mut static_position_rect = self
487                .static_position_rect()
488                .translate(-containing_block_origin);
489            static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero());
490            static_position_rect.to_logical(&(&containing_block).into())
491        };
492
493        let (box_fragment, mut positioning_context) = independent_formatting_context
494            .layout_as_absolute(
495                layout_context,
496                &fully_adjusted_static_position_rect,
497                &containing_block,
498                containing_block_origin,
499                self.resolved_alignment,
500                self.original_parent_writing_mode,
501            );
502
503        // An absolutely-positioned box can be a layout root if it does not hoist any
504        // fixed positioned boxes out of it. This condition ensures isolation from parent
505        // layout meaning that laying out the absolutely positioned box again, will not
506        // affect ancestor layout.
507        let is_layout_root = positioning_context.is_empty();
508
509        // Any hoisted boxes that remain in this positioning context are going to be hoisted
510        // up above this absolutely positioned box. These will necessarily be fixed position
511        // elements, because absolutely positioned elements form containing blocks for all
512        // other elements. If any of them have a static start position though, we need to
513        // adjust it to account for the start corner of this absolute.
514        positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
515            &box_fragment.content_rect().origin.to_vector(),
516            PositioningContextLength::zero(),
517        );
518        hoisted_absolutes_from_children.extend(positioning_context.absolutes);
519
520        let fragment = Fragment::Box(box_fragment);
521        self.fragment.borrow_mut().fragment = Some(fragment.clone());
522
523        let fragment = match is_layout_root {
524            false => fragment,
525            true => Fragment::LayoutRoot(LayoutRootFragment {
526                fragment: self.fragment.clone(),
527            }),
528        };
529
530        independent_formatting_context
531            .base
532            .set_fragment(fragment.clone());
533
534        *independent_formatting_context
535            .layout_root_layout_inputs
536            .borrow_mut() = is_layout_root.then(|| {
537            Box::new(LayoutRootLayoutInputs {
538                fully_adjusted_static_position_rect,
539                resolved_alignment: self.resolved_alignment,
540                containing_block_size: containing_block.size,
541                containing_block_style: containing_block.style.clone(),
542                containing_block_origin,
543                original_parent_writing_mode: self.original_parent_writing_mode,
544            })
545        });
546
547        fragment
548    }
549
550    fn static_position_rect(&self) -> PhysicalRect<Au> {
551        self.adjusted_static_position_rect
552            .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
553    }
554
555    fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
556        self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
557    }
558}
559
560impl IndependentFormattingContext {
561    pub(crate) fn layout_as_absolute(
562        &self,
563        layout_context: &LayoutContext,
564        static_position_rect: &LogicalRect<Au>,
565        containing_block: &DefiniteContainingBlock,
566        containing_block_origin: PhysicalVec<Au>,
567        resolved_alignment: LogicalVec2<AlignFlags>,
568        original_parent_writing_mode: WritingMode,
569    ) -> (Arc<BoxFragment>, PositioningContext) {
570        let cbis = containing_block.size.inline;
571        let cbbs = containing_block.size.block;
572        let containing_block_writing_mode = containing_block.style.writing_mode;
573        let style = self.style().clone();
574        let layout_style = self.layout_style();
575        let ContentBoxSizesAndPBM {
576            content_box_sizes,
577            pbm,
578            ..
579        } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
580        let is_table = layout_style.is_table();
581        let is_table_or_replaced = is_table || self.is_replaced();
582        let preferred_aspect_ratio = self.preferred_aspect_ratio(&pbm.padding_border_sums);
583
584        let box_offset = style.box_offsets(containing_block.style.writing_mode);
585
586        // When the "static-position rect" doesn't come into play, we do not do any alignment
587        // in the inline axis.
588        let inline_box_offsets = box_offset.inline_sides().percentages_relative_to(cbis);
589        let inline_alignment = match inline_box_offsets.either_specified() {
590            true => style.get_justify_self().0,
591            false => resolved_alignment.inline,
592        };
593
594        let inline_axis_solver = AbsoluteAxisSolver {
595            axis: LogicalAxis::Inline,
596            containing_size: cbis,
597            padding_border_sum: pbm.padding_border_sums.inline,
598            computed_margin_start: pbm.margin.inline_start,
599            computed_margin_end: pbm.margin.inline_end,
600            computed_sizes: content_box_sizes.inline,
601            avoid_negative_margin_start: true,
602            box_offsets: inline_box_offsets,
603            static_position_rect_axis: static_position_rect.get_axis(LogicalAxis::Inline),
604            alignment: inline_alignment,
605            flip_anchor: original_parent_writing_mode.is_bidi_ltr() !=
606                containing_block_writing_mode.is_bidi_ltr(),
607            is_table_or_replaced,
608        };
609
610        // When the "static-position rect" doesn't come into play, we re-resolve "align-self"
611        // against this containing block.
612        let block_box_offsets = box_offset.block_sides().percentages_relative_to(cbbs);
613        let block_alignment = match block_box_offsets.either_specified() {
614            true => style.get_align_self().0,
615            false => resolved_alignment.block,
616        };
617        let block_axis_solver = AbsoluteAxisSolver {
618            axis: LogicalAxis::Block,
619            containing_size: cbbs,
620            padding_border_sum: pbm.padding_border_sums.block,
621            computed_margin_start: pbm.margin.block_start,
622            computed_margin_end: pbm.margin.block_end,
623            computed_sizes: content_box_sizes.block,
624            avoid_negative_margin_start: false,
625            box_offsets: block_box_offsets,
626            static_position_rect_axis: static_position_rect.get_axis(LogicalAxis::Block),
627            alignment: block_alignment,
628            flip_anchor: false,
629            is_table_or_replaced,
630        };
631
632        // The block size can depend on layout results, so we only solve it tentatively,
633        // we may have to resolve it properly later on.
634        let block_automatic_size = block_axis_solver.automatic_size();
635        let block_stretch_size = Some(block_axis_solver.stretch_size());
636        let inline_stretch_size = Some(inline_axis_solver.stretch_size());
637        let tentative_block_content_size =
638            self.tentative_block_content_size(preferred_aspect_ratio, inline_stretch_size);
639        let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
640            SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
641                LogicalAxis::Block,
642                block_automatic_size,
643                Au::zero,
644                block_stretch_size,
645                || block_content_size,
646                is_table,
647            ))
648        } else {
649            block_axis_solver.computed_sizes.resolve_extrinsic(
650                block_automatic_size,
651                Au::zero(),
652                block_stretch_size,
653            )
654        };
655
656        // The inline axis can be fully resolved, computing intrinsic sizes using the
657        // extrinsic block size.
658        let get_inline_content_size = || {
659            let constraint_space =
660                ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio);
661            self.inline_content_sizes(layout_context, &constraint_space)
662                .sizes
663        };
664        let inline_size = inline_axis_solver.computed_sizes.resolve(
665            LogicalAxis::Inline,
666            inline_axis_solver.automatic_size(),
667            Au::zero,
668            inline_stretch_size,
669            get_inline_content_size,
670            is_table,
671        );
672
673        let containing_block_for_children = ContainingBlock {
674            size: ContainingBlockSize {
675                inline: inline_size,
676                block: tentative_block_size,
677            },
678            style: &style,
679        };
680        // TODO: https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
681
682        let mut positioning_context = PositioningContext::default();
683        let lazy_block_size = LazySize::new(
684            &block_axis_solver.computed_sizes,
685            LogicalAxis::Block,
686            block_automatic_size,
687            Au::zero,
688            block_stretch_size,
689            is_table,
690        );
691
692        let containing_block = &containing_block.into();
693        let (layout, is_cached) = self.layout_and_is_cached(
694            layout_context,
695            &mut positioning_context,
696            &containing_block_for_children,
697            containing_block,
698            preferred_aspect_ratio,
699            &lazy_block_size,
700        );
701        let IndependentFormattingContextLayoutResult {
702            content_inline_size_for_table,
703            content_block_size,
704            fragments,
705            specific_layout_info,
706            ..
707        } = layout;
708
709        let content_size = LogicalVec2 {
710            // Tables can become narrower than predicted due to collapsed columns.
711            inline: content_inline_size_for_table.unwrap_or(inline_size),
712
713            // Now we can properly solve the block size.
714            block: lazy_block_size.resolve(|| content_block_size),
715        };
716
717        let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
718        let block_margins = block_axis_solver.solve_margins(content_size.block);
719        let margin = LogicalSides {
720            inline_start: inline_margins.start,
721            inline_end: inline_margins.end,
722            block_start: block_margins.start,
723            block_end: block_margins.end,
724        };
725
726        let pb = pbm.padding + pbm.border;
727        let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
728        let inline_origin = inline_axis_solver.origin_for_margin_box(
729            margin_rect_size.inline,
730            style.writing_mode,
731            original_parent_writing_mode,
732            containing_block_writing_mode,
733        );
734        let block_origin = block_axis_solver.origin_for_margin_box(
735            margin_rect_size.block,
736            style.writing_mode,
737            original_parent_writing_mode,
738            containing_block_writing_mode,
739        );
740        let content_rect = LogicalRect {
741            start_corner: LogicalVec2 {
742                inline: inline_origin + margin.inline_start + pb.inline_start,
743                block: block_origin + margin.block_start + pb.block_start,
744            },
745            size: content_size,
746        }
747        .as_physical(Some(containing_block))
748        .translate(containing_block_origin);
749
750        if is_cached &&
751            let Some(old_fragment) = self.base.fragments().first() &&
752            let Some(old_box_fragment) = old_fragment
753                .retrieve_box_fragment()
754                .map(|fragment| fragment.clone()) &&
755            content_rect == old_box_fragment.content_rect()
756        {
757            // Drain the nested absolutes for which we are a containing block.
758            // However, we are reusing the fragment, so no need to lay them out again.
759            positioning_context.forget_unhoisted_boxes(&old_box_fragment);
760            return (old_box_fragment, positioning_context);
761        }
762
763        let mut new_box_fragment = BoxFragment::new(
764            self.base_fragment_info(),
765            style,
766            fragments,
767            content_rect,
768            pbm.padding.to_physical(containing_block_writing_mode),
769            pbm.border.to_physical(containing_block_writing_mode),
770            margin.to_physical(containing_block_writing_mode),
771            specific_layout_info,
772        );
773
774        // This is an absolutely positioned element, which means it also establishes a
775        // containing block for absolutes. We lay out any absolutely positioned children
776        // here and pass the rest to `hoisted_absolutes_from_children.`
777        positioning_context.layout_collected_children(layout_context, &mut new_box_fragment);
778        (new_box_fragment.into(), positioning_context)
779    }
780}
781
782#[derive(Clone, Copy, Debug)]
783struct RectAxis {
784    origin: Au,
785    length: Au,
786}
787
788impl LogicalRect<Au> {
789    fn get_axis(&self, axis: LogicalAxis) -> RectAxis {
790        match axis {
791            LogicalAxis::Block => RectAxis {
792                origin: self.start_corner.block,
793                length: self.size.block,
794            },
795            LogicalAxis::Inline => RectAxis {
796                origin: self.start_corner.inline,
797                length: self.size.inline,
798            },
799        }
800    }
801}
802
803struct AbsoluteAxisSolver {
804    axis: LogicalAxis,
805    containing_size: Au,
806    padding_border_sum: Au,
807    computed_margin_start: AuOrAuto,
808    computed_margin_end: AuOrAuto,
809    computed_sizes: Sizes,
810    avoid_negative_margin_start: bool,
811    box_offsets: LogicalSides1D<AuOrAuto>,
812    static_position_rect_axis: RectAxis,
813    alignment: AlignFlags,
814    flip_anchor: bool,
815    is_table_or_replaced: bool,
816}
817
818impl AbsoluteAxisSolver {
819    /// Returns the amount that we need to subtract from the containing block size in order to
820    /// obtain the inset-modified containing block that we will use for sizing purposes.
821    /// (Note that for alignment purposes, we may re-resolve auto insets to a different value.)
822    /// <https://drafts.csswg.org/css-position/#resolving-insets>
823    fn inset_sum(&self) -> Au {
824        match (
825            self.box_offsets.start.non_auto(),
826            self.box_offsets.end.non_auto(),
827        ) {
828            (None, None) => {
829                if self.flip_anchor {
830                    self.containing_size -
831                        self.static_position_rect_axis.origin -
832                        self.static_position_rect_axis.length
833                } else {
834                    self.static_position_rect_axis.origin
835                }
836            },
837            (Some(start), None) => start,
838            (None, Some(end)) => end,
839            (Some(start), Some(end)) => start + end,
840        }
841    }
842
843    /// Returns the size of the inset-modified containing block.
844    /// <https://drafts.csswg.org/css-position-3/#inset-modified-containing-block>
845    #[inline]
846    fn available_space(&self) -> Au {
847        Au::zero().max(self.containing_size - self.inset_sum())
848    }
849
850    #[inline]
851    fn automatic_size(&self) -> Size<Au> {
852        match self.alignment.value() {
853            _ if self.box_offsets.either_auto() => Size::FitContent,
854            AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
855            AlignFlags::STRETCH => Size::Stretch,
856            _ => Size::FitContent,
857        }
858    }
859
860    #[inline]
861    fn stretch_size(&self) -> Au {
862        Au::zero().max(
863            self.available_space() -
864                self.padding_border_sum -
865                self.computed_margin_start.auto_is(Au::zero) -
866                self.computed_margin_end.auto_is(Au::zero),
867        )
868    }
869
870    fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
871        if self.box_offsets.either_auto() {
872            LogicalSides1D::new(
873                self.computed_margin_start.auto_is(Au::zero),
874                self.computed_margin_end.auto_is(Au::zero),
875            )
876        } else {
877            let free_space = self.available_space() - self.padding_border_sum - size;
878            match (self.computed_margin_start, self.computed_margin_end) {
879                (AuOrAuto::Auto, AuOrAuto::Auto) => {
880                    if self.avoid_negative_margin_start && free_space < Au::zero() {
881                        LogicalSides1D::new(Au::zero(), free_space)
882                    } else {
883                        let margin_start = free_space / 2;
884                        LogicalSides1D::new(margin_start, free_space - margin_start)
885                    }
886                },
887                (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
888                    LogicalSides1D::new(free_space - end, end)
889                },
890                (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
891                    LogicalSides1D::new(start, free_space - start)
892                },
893                (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
894                    LogicalSides1D::new(start, end)
895                },
896            }
897        }
898    }
899
900    fn origin_for_margin_box(
901        &self,
902        size: Au,
903        self_writing_mode: WritingMode,
904        original_parent_writing_mode: WritingMode,
905        containing_block_writing_mode: WritingMode,
906    ) -> Au {
907        let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
908            self.box_offsets.start.non_auto(),
909            self.box_offsets.end.non_auto(),
910        ) {
911            (None, None) => (
912                self.static_position_rect_axis,
913                original_parent_writing_mode,
914                self.flip_anchor,
915                None,
916            ),
917            (Some(start), Some(end)) => {
918                let alignment_container = RectAxis {
919                    origin: start,
920                    length: self.available_space(),
921                };
922                (
923                    alignment_container,
924                    containing_block_writing_mode,
925                    false,
926                    Some(LogicalSides1D { start, end }),
927                )
928            },
929            // If a single offset is auto, for alignment purposes it resolves to the amount
930            // that makes the inset-modified containing block be exactly as big as the abspos.
931            // Therefore the free space is zero and the alignment value is irrelevant.
932            (Some(start), None) => return start,
933            (None, Some(end)) => {
934                return self.containing_size - size - end;
935            },
936        };
937
938        // TODO: https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
939
940        let self_value_matches_container = || {
941            self.axis == LogicalAxis::Block ||
942                self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
943        };
944
945        // Here we resolve the alignment to either start, center, or end.
946        // Note we need to handle both self-alignment values (when some inset isn't auto)
947        // and distributed alignment values (when both insets are auto).
948        // The latter are treated as their fallback alignment.
949        let alignment = match self.alignment.value() {
950            // https://drafts.csswg.org/css-align/#valdef-self-position-center
951            // https://drafts.csswg.org/css-align/#valdef-align-content-space-around
952            // https://drafts.csswg.org/css-align/#valdef-align-content-space-evenly
953            AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
954                AlignFlags::CENTER
955            },
956            // https://drafts.csswg.org/css-align/#valdef-self-position-self-start
957            AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
958            AlignFlags::SELF_START => AlignFlags::END,
959            // https://drafts.csswg.org/css-align/#valdef-self-position-self-end
960            AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
961            AlignFlags::SELF_END => AlignFlags::START,
962            // - Horizontal writing mode (block axis vertical): the keywords are
963            //   perpendicular to the axis, so they behave as `start`.
964            // - Vertical writing mode (block axis horizontal): align to the
965            //   physical edge. `left` is the block-start edge in `vertical-lr`
966            //   and the block-end edge in `vertical-rl`; `right` is the reverse.
967            // https://drafts.csswg.org/css-align/#valdef-justify-content-left
968            AlignFlags::LEFT if self.axis == LogicalAxis::Block => {
969                if alignment_container_writing_mode.is_horizontal() ||
970                    alignment_container_writing_mode.block_start_physical_side() ==
971                        PhysicalSide::Left
972                {
973                    AlignFlags::START
974                } else {
975                    AlignFlags::END
976                }
977            },
978            // https://drafts.csswg.org/css-align/#valdef-justify-content-right
979            AlignFlags::RIGHT if self.axis == LogicalAxis::Block => {
980                if alignment_container_writing_mode.is_horizontal() ||
981                    alignment_container_writing_mode.block_start_physical_side() ==
982                        PhysicalSide::Right
983                {
984                    AlignFlags::START
985                } else {
986                    AlignFlags::END
987                }
988            },
989            // On the inline axis these keywords are line-left / line-right.
990            AlignFlags::LEFT if alignment_container_writing_mode.line_left_is_inline_start() => {
991                AlignFlags::START
992            },
993            AlignFlags::LEFT => AlignFlags::END,
994            AlignFlags::RIGHT if alignment_container_writing_mode.line_left_is_inline_start() => {
995                AlignFlags::END
996            },
997            AlignFlags::RIGHT => AlignFlags::START,
998            // https://drafts.csswg.org/css-align/#valdef-self-position-end
999            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-end
1000            // https://drafts.csswg.org/css-align/#valdef-justify-self-last-baseline
1001            AlignFlags::END | AlignFlags::FLEX_END | AlignFlags::LAST_BASELINE => AlignFlags::END,
1002            // https://drafts.csswg.org/css-align/#valdef-self-position-start
1003            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-start
1004            // https://drafts.csswg.org/css-align/#valdef-justify-self-first-baseline
1005            _ => AlignFlags::START,
1006        };
1007
1008        let alignment = match alignment {
1009            AlignFlags::START if flip_anchor => AlignFlags::END,
1010            AlignFlags::END if flip_anchor => AlignFlags::START,
1011            alignment => alignment,
1012        };
1013
1014        let free_space = alignment_container.length - size;
1015        let flags = self.alignment.flags();
1016        let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
1017            AlignFlags::START
1018        } else {
1019            alignment
1020        };
1021
1022        let origin = match alignment {
1023            AlignFlags::START => alignment_container.origin,
1024            AlignFlags::CENTER => alignment_container.origin + free_space / 2,
1025            AlignFlags::END => alignment_container.origin + free_space,
1026            _ => unreachable!(),
1027        };
1028        if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
1029            matches!(
1030                self.alignment,
1031                AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
1032            )
1033        {
1034            return origin;
1035        }
1036        let Some(offsets) = offsets else {
1037            return origin;
1038        };
1039
1040        // Handle default overflow alignment.
1041        // https://drafts.csswg.org/css-align/#auto-safety-position
1042        let min = Au::zero().min(offsets.start);
1043        let max = self.containing_size - Au::zero().min(offsets.end) - size;
1044        origin.clamp_between_extremums(min, Some(max))
1045    }
1046}
1047
1048/// <https://drafts.csswg.org/css2/visuren.html#relative-positioning>
1049pub(crate) fn relative_adjustement(
1050    style: &ComputedValues,
1051    containing_block: &ContainingBlock,
1052) -> LogicalVec2<Au> {
1053    // It's not completely clear what to do with indefinite percentages
1054    // (https://github.com/w3c/csswg-drafts/issues/9353), so we match
1055    // other browsers and treat them as 'auto' offsets.
1056    let cbis = containing_block.size.inline;
1057    let cbbs = containing_block.size.block;
1058    let box_offsets = style
1059        .box_offsets(containing_block.style.writing_mode)
1060        .map_inline_and_block_axes(
1061            |value| value.map(|value| value.to_used_value(cbis)),
1062            |value| match cbbs {
1063                SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
1064                _ => match value.non_auto().and_then(|value| value.to_length()) {
1065                    Some(value) => AuOrAuto::LengthPercentage(value.into()),
1066                    None => AuOrAuto::Auto,
1067                },
1068            },
1069        );
1070    fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
1071        match (start, end) {
1072            (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
1073            (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
1074            (AuOrAuto::LengthPercentage(start), _) => start,
1075        }
1076    }
1077    LogicalVec2 {
1078        inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
1079        block: adjust(box_offsets.block_start, box_offsets.block_end),
1080    }
1081}
1082
1083/// These are the recorded layout inputs that were used when laying out an
1084/// absolutely-positioned element. They can be re-used when the absolutely-positioned
1085/// element is a viable layout root (no escaping fixed position elements, currently). The
1086/// information here is enough to re-run layout for an absolute.
1087#[derive(MallocSizeOf)]
1088pub(crate) struct LayoutRootLayoutInputs {
1089    /// The fully adjusted static position rectangle used to lay out the absolute. This is
1090    /// adjusted by the containing blocks of all of the boxes that come between an
1091    /// absolute's tree position and its layout containing block.
1092    fully_adjusted_static_position_rect: LogicalRect<Au>,
1093    /// The resolved alignment to use when laying out the absolute. This comes from the
1094    /// original box.
1095    resolved_alignment: LogicalVec2<AlignFlags>,
1096    /// This is the containing block size of the absolute's containing block. This is
1097    /// stored here because it's easier to access than the parent box.
1098    containing_block_size: LogicalVec2<Au>,
1099    /// This is the containing block origin of the absolute's containing block (relative
1100    /// to the containing block element's content box). This is stored here because it's
1101    /// easier to access than the parent box.
1102    containing_block_origin: PhysicalVec<Au>,
1103    /// This is the style of the containing block. This is stored here because it's easier
1104    /// to access than the parent box.
1105    #[conditional_malloc_size_of]
1106    containing_block_style: ServoArc<ComputedValues>,
1107    /// This is the writing mode of the absolute's tree parent.
1108    original_parent_writing_mode: WritingMode,
1109}
1110
1111impl std::fmt::Debug for LayoutRootLayoutInputs {
1112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1113        f.debug_struct("LayoutRootLayoutInputs")
1114            .field("containing_block_size", &self.containing_block_size)
1115            .finish()
1116    }
1117}
1118
1119impl LayoutRootLayoutInputs {
1120    /// Re-run layout for the given inputs. This is used to run layout again at layout
1121    /// roots.
1122    pub(crate) fn layout(
1123        &self,
1124        layout_context: &LayoutContext,
1125        context: &IndependentFormattingContext,
1126        shared_fragment: &ArcRefCell<HoistedSharedFragment>,
1127    ) -> Result<(), ()> {
1128        let containing_block = DefiniteContainingBlock {
1129            size: self.containing_block_size,
1130            style: &self.containing_block_style,
1131        };
1132        let (box_fragment, positioning_context) = context.layout_as_absolute(
1133            layout_context,
1134            &self.fully_adjusted_static_position_rect,
1135            &containing_block,
1136            self.containing_block_origin,
1137            self.resolved_alignment,
1138            self.original_parent_writing_mode,
1139        );
1140
1141        if !positioning_context.is_empty() {
1142            return Err(());
1143        }
1144
1145        shared_fragment.borrow_mut().fragment = Some(Fragment::Box(box_fragment));
1146        Ok(())
1147    }
1148}
1149
1150enum AbsoluteContainingBlock<'a> {
1151    Fragment(&'a BoxFragment),
1152    InitialContainingBlock(PhysicalSize<Au>, &'a ServoArc<ComputedValues>),
1153}
1154
1155impl<'a> AbsoluteContainingBlock<'a> {
1156    fn rect_for_item(&self, item_style: &ComputedValues) -> PhysicalRect<Au> {
1157        match self {
1158            AbsoluteContainingBlock::Fragment(box_fragment) => {
1159                box_fragment.containing_block_rect_for_absolute(item_style)
1160            },
1161            AbsoluteContainingBlock::InitialContainingBlock(size, _) => {
1162                PhysicalRect::new(PhysicalPoint::zero(), *size)
1163            },
1164        }
1165    }
1166
1167    fn style(&self) -> RefOrAtomicRef<'_, ServoArc<ComputedValues>> {
1168        match self {
1169            AbsoluteContainingBlock::Fragment(box_fragment) => {
1170                RefOrAtomicRef::AtomicRef(box_fragment.style())
1171            },
1172            AbsoluteContainingBlock::InitialContainingBlock(_, style) => {
1173                RefOrAtomicRef::Ref(*style)
1174            },
1175        }
1176    }
1177}