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::{Direction, WritingMode};
17use style::properties::ComputedValues;
18use style::values::specified::align::AlignFlags;
19
20use crate::cell::ArcRefCell;
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    PhysicalSides, 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.clone_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 padding_rect = PhysicalRect::new(
291            // Ignore the content rect’s position in its own containing block:
292            PhysicalPoint::origin(),
293            new_fragment.base.rect().size,
294        )
295        .outer_rect(new_fragment.padding);
296        let containing_block = DefiniteContainingBlock {
297            size: padding_rect.size.to_logical(style.writing_mode),
298            style: &style,
299        };
300
301        let mut fixed_position_boxes_to_hoist = Vec::new();
302        let mut boxes_to_layout = Vec::new();
303        self.take_boxes_for_fragment(
304            new_fragment,
305            &mut boxes_to_layout,
306            &mut fixed_position_boxes_to_hoist,
307        );
308
309        // Laying out a `position: absolute` child (which only establishes a containing block for
310        // `position: absolute` descendants) can result in more `position: fixed` descendants
311        // collecting in `self.absolutes`. We need to loop here in order to keep either laying them
312        // out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more
313        // when `self.absolutes` is empty.
314        while !boxes_to_layout.is_empty() {
315            HoistedAbsolutelyPositionedBox::layout_many(
316                layout_context,
317                std::mem::take(&mut boxes_to_layout),
318                &mut new_fragment.children,
319                &mut self.absolutes,
320                &containing_block,
321                new_fragment.padding,
322            );
323
324            self.take_boxes_for_fragment(
325                new_fragment,
326                &mut boxes_to_layout,
327                &mut fixed_position_boxes_to_hoist,
328            );
329        }
330
331        // We replace here instead of simply preserving these in `take_boxes_for_fragment`
332        // so that we don't have to continually re-iterate over them when laying out in the
333        // loop above.
334        self.absolutes = fixed_position_boxes_to_hoist;
335    }
336
337    pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
338        debug_assert!(hoisted_box.position().is_absolutely_positioned());
339        self.absolutes.push(hoisted_box);
340    }
341
342    pub(crate) fn append(&mut self, mut other: Self) {
343        if other.absolutes.is_empty() {
344            return;
345        }
346        if self.absolutes.is_empty() {
347            self.absolutes = other.absolutes;
348        } else {
349            self.absolutes.append(&mut other.absolutes)
350        }
351    }
352
353    pub(crate) fn layout_initial_containing_block_children(
354        &mut self,
355        layout_context: &LayoutContext,
356        initial_containing_block: &DefiniteContainingBlock,
357        fragments: &mut Vec<Fragment>,
358    ) {
359        // Laying out a `position: absolute` child (which only establishes a containing block for
360        // `position: absolute` descendants) can result in more `position: fixed` descendants
361        // collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We
362        // know there aren't any more when `self.absolutes` is empty.
363        while !self.absolutes.is_empty() {
364            HoistedAbsolutelyPositionedBox::layout_many(
365                layout_context,
366                mem::take(&mut self.absolutes),
367                fragments,
368                &mut self.absolutes,
369                initial_containing_block,
370                Default::default(),
371            )
372        }
373    }
374
375    /// Get the length of this [PositioningContext].
376    pub(crate) fn len(&self) -> PositioningContextLength {
377        PositioningContextLength(self.absolutes.len())
378    }
379
380    /// Truncate this [PositioningContext] to the given [PositioningContextLength].  This
381    /// is useful for "unhoisting" boxes in this context and returning it to the state at
382    /// the time that [`PositioningContext::len()`] was called.
383    pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
384        self.absolutes.truncate(length.0)
385    }
386}
387
388/// A data structure which stores the size of a positioning context.
389#[derive(Clone, Copy, Debug, PartialEq)]
390pub(crate) struct PositioningContextLength(usize);
391
392impl Zero for PositioningContextLength {
393    fn zero() -> Self {
394        Self(0)
395    }
396
397    fn is_zero(&self) -> bool {
398        self.0.is_zero()
399    }
400}
401
402impl HoistedAbsolutelyPositionedBox {
403    fn position(&self) -> Position {
404        let position = self
405            .absolutely_positioned_box
406            .borrow()
407            .context
408            .style()
409            .clone_position();
410        assert!(position.is_absolutely_positioned());
411        position
412    }
413
414    pub(crate) 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        containing_block: &DefiniteContainingBlock,
420        containing_block_padding: PhysicalSides<Au>,
421    ) {
422        let job_sizes = boxes.iter().map(|hoisted_box| {
423            hoisted_box
424                .absolutely_positioned_box
425                .borrow()
426                .context
427                .subtree_size()
428        });
429        if layout_context.should_parallelize_layout(job_sizes) {
430            let mut new_fragments = Vec::new();
431            let mut new_hoisted_boxes = Vec::new();
432
433            boxes
434                .par_iter_mut()
435                .map(|hoisted_box| {
436                    let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
437                    let new_fragment = hoisted_box.layout(
438                        layout_context,
439                        &mut new_hoisted_boxes,
440                        containing_block,
441                        containing_block_padding,
442                    );
443                    (new_fragment, new_hoisted_boxes)
444                })
445                .unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
446
447            fragments.extend(new_fragments);
448            for_nearest_containing_block_for_all_descendants
449                .extend(new_hoisted_boxes.into_iter().flatten());
450        } else {
451            fragments.extend(boxes.iter_mut().map(|hoisted_box| {
452                hoisted_box.layout(
453                    layout_context,
454                    for_nearest_containing_block_for_all_descendants,
455                    containing_block,
456                    containing_block_padding,
457                )
458            }))
459        }
460    }
461
462    pub(crate) fn layout(
463        &mut self,
464        layout_context: &LayoutContext,
465        hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
466        containing_block: &DefiniteContainingBlock,
467        containing_block_padding: PhysicalSides<Au>,
468    ) -> Fragment {
469        // The static position rect was calculated assuming that the containing block would be
470        // established by the content box of some ancestor, but the actual containing block is
471        // established by the padding box. So we need to translate the rect by the padding of
472        // that ancestor.
473        let mut static_position_rect = self.static_position_rect().translate(PhysicalVec::new(
474            containing_block_padding.left,
475            containing_block_padding.top,
476        ));
477        static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero());
478        let fully_adjusted_static_position_rect =
479            static_position_rect.to_logical(&containing_block.into());
480
481        let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
482        let independent_formatting_context = &absolutely_positioned_box.context;
483        let (box_fragment, mut positioning_context) = independent_formatting_context
484            .layout_as_absolute(
485                layout_context,
486                &fully_adjusted_static_position_rect,
487                containing_block,
488                self.resolved_alignment,
489                self.original_parent_writing_mode,
490            );
491
492        // An absolutely-positioned box can be a layout root if it does not hoist any
493        // fixed positioned boxes out of it. This condition ensures isolation from parent
494        // layout meaning that laying out the absolutely positioned box again, will not
495        // affect ancestor layout.
496        let is_layout_root = positioning_context.is_empty();
497
498        // Any hoisted boxes that remain in this positioning context are going to be hoisted
499        // up above this absolutely positioned box. These will necessarily be fixed position
500        // elements, because absolutely positioned elements form containing blocks for all
501        // other elements. If any of them have a static start position though, we need to
502        // adjust it to account for the start corner of this absolute.
503        positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
504            &box_fragment.content_rect().origin.to_vector(),
505            PositioningContextLength::zero(),
506        );
507        hoisted_absolutes_from_children.extend(positioning_context.absolutes);
508
509        let fragment = Fragment::Box(box_fragment);
510        self.fragment.borrow_mut().fragment = Some(fragment.clone());
511
512        let fragment = match is_layout_root {
513            false => fragment,
514            true => Fragment::LayoutRoot(LayoutRootFragment {
515                fragment: self.fragment.clone(),
516            }),
517        };
518
519        independent_formatting_context
520            .base
521            .set_fragment(fragment.clone());
522
523        *independent_formatting_context
524            .layout_root_layout_inputs
525            .borrow_mut() = is_layout_root.then(|| {
526            Box::new(LayoutRootLayoutInputs {
527                fully_adjusted_static_position_rect,
528                resolved_alignment: self.resolved_alignment,
529                containing_block_size: containing_block.size,
530                containing_block_style: containing_block.style.clone(),
531                original_parent_writing_mode: self.original_parent_writing_mode,
532            })
533        });
534
535        fragment
536    }
537
538    fn static_position_rect(&self) -> PhysicalRect<Au> {
539        self.adjusted_static_position_rect
540            .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
541    }
542
543    fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
544        self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
545    }
546}
547
548impl IndependentFormattingContext {
549    pub(crate) fn layout_as_absolute(
550        &self,
551        layout_context: &LayoutContext,
552        static_position_rect: &LogicalRect<Au>,
553        containing_block: &DefiniteContainingBlock,
554        resolved_alignment: LogicalVec2<AlignFlags>,
555        original_parent_writing_mode: WritingMode,
556    ) -> (Arc<BoxFragment>, PositioningContext) {
557        let cbis = containing_block.size.inline;
558        let cbbs = containing_block.size.block;
559        let containing_block_writing_mode = containing_block.style.writing_mode;
560        let style = self.style().clone();
561        let layout_style = self.layout_style();
562        let ContentBoxSizesAndPBM {
563            content_box_sizes,
564            pbm,
565            ..
566        } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
567        let is_table = layout_style.is_table();
568        let is_table_or_replaced = is_table || self.is_replaced();
569        let preferred_aspect_ratio = self.preferred_aspect_ratio(&pbm.padding_border_sums);
570
571        let box_offset = style.box_offsets(containing_block.style.writing_mode);
572
573        // When the "static-position rect" doesn't come into play, we do not do any alignment
574        // in the inline axis.
575        let inline_box_offsets = box_offset.inline_sides().percentages_relative_to(cbis);
576        let inline_alignment = match inline_box_offsets.either_specified() {
577            true => style.clone_justify_self().0,
578            false => resolved_alignment.inline,
579        };
580
581        let inline_axis_solver = AbsoluteAxisSolver {
582            axis: Direction::Inline,
583            containing_size: cbis,
584            padding_border_sum: pbm.padding_border_sums.inline,
585            computed_margin_start: pbm.margin.inline_start,
586            computed_margin_end: pbm.margin.inline_end,
587            computed_sizes: content_box_sizes.inline,
588            avoid_negative_margin_start: true,
589            box_offsets: inline_box_offsets,
590            static_position_rect_axis: static_position_rect.get_axis(Direction::Inline),
591            alignment: inline_alignment,
592            flip_anchor: original_parent_writing_mode.is_bidi_ltr() !=
593                containing_block_writing_mode.is_bidi_ltr(),
594            is_table_or_replaced,
595        };
596
597        // When the "static-position rect" doesn't come into play, we re-resolve "align-self"
598        // against this containing block.
599        let block_box_offsets = box_offset.block_sides().percentages_relative_to(cbbs);
600        let block_alignment = match block_box_offsets.either_specified() {
601            true => style.clone_align_self().0,
602            false => resolved_alignment.block,
603        };
604        let block_axis_solver = AbsoluteAxisSolver {
605            axis: Direction::Block,
606            containing_size: cbbs,
607            padding_border_sum: pbm.padding_border_sums.block,
608            computed_margin_start: pbm.margin.block_start,
609            computed_margin_end: pbm.margin.block_end,
610            computed_sizes: content_box_sizes.block,
611            avoid_negative_margin_start: false,
612            box_offsets: block_box_offsets,
613            static_position_rect_axis: static_position_rect.get_axis(Direction::Block),
614            alignment: block_alignment,
615            flip_anchor: false,
616            is_table_or_replaced,
617        };
618
619        // The block size can depend on layout results, so we only solve it tentatively,
620        // we may have to resolve it properly later on.
621        let block_automatic_size = block_axis_solver.automatic_size();
622        let block_stretch_size = Some(block_axis_solver.stretch_size());
623        let inline_stretch_size = inline_axis_solver.stretch_size();
624        let tentative_block_content_size =
625            self.tentative_block_content_size(preferred_aspect_ratio, inline_stretch_size);
626        let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
627            SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
628                Direction::Block,
629                block_automatic_size,
630                Au::zero,
631                block_stretch_size,
632                || block_content_size,
633                is_table,
634            ))
635        } else {
636            block_axis_solver.computed_sizes.resolve_extrinsic(
637                block_automatic_size,
638                Au::zero(),
639                block_stretch_size,
640            )
641        };
642
643        // The inline axis can be fully resolved, computing intrinsic sizes using the
644        // extrinsic block size.
645        let get_inline_content_size = || {
646            let constraint_space =
647                ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio);
648            self.inline_content_sizes(layout_context, &constraint_space)
649                .sizes
650        };
651        let inline_size = inline_axis_solver.computed_sizes.resolve(
652            Direction::Inline,
653            inline_axis_solver.automatic_size(),
654            Au::zero,
655            Some(inline_stretch_size),
656            get_inline_content_size,
657            is_table,
658        );
659
660        let containing_block_for_children = ContainingBlock {
661            size: ContainingBlockSize {
662                inline: inline_size,
663                block: tentative_block_size,
664            },
665            style: &style,
666        };
667        // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
668        assert_eq!(
669            containing_block_writing_mode.is_horizontal(),
670            style.writing_mode.is_horizontal(),
671            "Mixed horizontal and vertical writing modes are not supported yet"
672        );
673
674        let mut positioning_context = PositioningContext::default();
675        let lazy_block_size = LazySize::new(
676            &block_axis_solver.computed_sizes,
677            Direction::Block,
678            block_automatic_size,
679            Au::zero,
680            block_stretch_size,
681            is_table,
682        );
683
684        let containing_block = &containing_block.into();
685        let (layout, is_cached) = self.layout_and_is_cached(
686            layout_context,
687            &mut positioning_context,
688            &containing_block_for_children,
689            containing_block,
690            preferred_aspect_ratio,
691            &lazy_block_size,
692        );
693        let IndependentFormattingContextLayoutResult {
694            content_inline_size_for_table,
695            content_block_size,
696            fragments,
697            specific_layout_info,
698            ..
699        } = layout;
700
701        let content_size = LogicalVec2 {
702            // Tables can become narrower than predicted due to collapsed columns.
703            inline: content_inline_size_for_table.unwrap_or(inline_size),
704
705            // Now we can properly solve the block size.
706            block: lazy_block_size.resolve(|| content_block_size),
707        };
708
709        let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
710        let block_margins = block_axis_solver.solve_margins(content_size.block);
711        let margin = LogicalSides {
712            inline_start: inline_margins.start,
713            inline_end: inline_margins.end,
714            block_start: block_margins.start,
715            block_end: block_margins.end,
716        };
717
718        let pb = pbm.padding + pbm.border;
719        let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
720        let inline_origin = inline_axis_solver.origin_for_margin_box(
721            margin_rect_size.inline,
722            style.writing_mode,
723            original_parent_writing_mode,
724            containing_block_writing_mode,
725        );
726        let block_origin = block_axis_solver.origin_for_margin_box(
727            margin_rect_size.block,
728            style.writing_mode,
729            original_parent_writing_mode,
730            containing_block_writing_mode,
731        );
732        let content_rect = LogicalRect {
733            start_corner: LogicalVec2 {
734                inline: inline_origin + margin.inline_start + pb.inline_start,
735                block: block_origin + margin.block_start + pb.block_start,
736            },
737            size: content_size,
738        }
739        .as_physical(Some(containing_block));
740
741        if is_cached &&
742            let Some(old_fragment) = self.base.fragments().first() &&
743            let Some(old_box_fragment) = old_fragment
744                .retrieve_box_fragment()
745                .map(|fragment| fragment.clone()) &&
746            content_rect == old_box_fragment.content_rect()
747        {
748            // Drain the nested absolutes for which we are a containing block.
749            // However, we are reusing the fragment, so no need to lay them out again.
750            positioning_context.forget_unhoisted_boxes(&old_box_fragment);
751            return (old_box_fragment, positioning_context);
752        }
753
754        let mut new_box_fragment = BoxFragment::new(
755            self.base_fragment_info(),
756            style,
757            fragments,
758            content_rect,
759            pbm.padding.to_physical(containing_block_writing_mode),
760            pbm.border.to_physical(containing_block_writing_mode),
761            margin.to_physical(containing_block_writing_mode),
762            specific_layout_info,
763        );
764
765        // This is an absolutely positioned element, which means it also establishes a
766        // containing block for absolutes. We lay out any absolutely positioned children
767        // here and pass the rest to `hoisted_absolutes_from_children.`
768        positioning_context.layout_collected_children(layout_context, &mut new_box_fragment);
769        (new_box_fragment.into(), positioning_context)
770    }
771}
772
773#[derive(Clone, Copy, Debug)]
774struct RectAxis {
775    origin: Au,
776    length: Au,
777}
778
779impl LogicalRect<Au> {
780    fn get_axis(&self, axis: Direction) -> RectAxis {
781        match axis {
782            Direction::Block => RectAxis {
783                origin: self.start_corner.block,
784                length: self.size.block,
785            },
786            Direction::Inline => RectAxis {
787                origin: self.start_corner.inline,
788                length: self.size.inline,
789            },
790        }
791    }
792}
793
794struct AbsoluteAxisSolver {
795    axis: Direction,
796    containing_size: Au,
797    padding_border_sum: Au,
798    computed_margin_start: AuOrAuto,
799    computed_margin_end: AuOrAuto,
800    computed_sizes: Sizes,
801    avoid_negative_margin_start: bool,
802    box_offsets: LogicalSides1D<AuOrAuto>,
803    static_position_rect_axis: RectAxis,
804    alignment: AlignFlags,
805    flip_anchor: bool,
806    is_table_or_replaced: bool,
807}
808
809impl AbsoluteAxisSolver {
810    /// Returns the amount that we need to subtract from the containing block size in order to
811    /// obtain the inset-modified containing block that we will use for sizing purposes.
812    /// (Note that for alignment purposes, we may re-resolve auto insets to a different value.)
813    /// <https://drafts.csswg.org/css-position/#resolving-insets>
814    fn inset_sum(&self) -> Au {
815        match (
816            self.box_offsets.start.non_auto(),
817            self.box_offsets.end.non_auto(),
818        ) {
819            (None, None) => {
820                if self.flip_anchor {
821                    self.containing_size -
822                        self.static_position_rect_axis.origin -
823                        self.static_position_rect_axis.length
824                } else {
825                    self.static_position_rect_axis.origin
826                }
827            },
828            (Some(start), None) => start,
829            (None, Some(end)) => end,
830            (Some(start), Some(end)) => start + end,
831        }
832    }
833
834    /// Returns the size of the inset-modified containing block.
835    /// <https://drafts.csswg.org/css-position-3/#inset-modified-containing-block>
836    #[inline]
837    fn available_space(&self) -> Au {
838        Au::zero().max(self.containing_size - self.inset_sum())
839    }
840
841    #[inline]
842    fn automatic_size(&self) -> Size<Au> {
843        match self.alignment.value() {
844            _ if self.box_offsets.either_auto() => Size::FitContent,
845            AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
846            AlignFlags::STRETCH => Size::Stretch,
847            _ => Size::FitContent,
848        }
849    }
850
851    #[inline]
852    fn stretch_size(&self) -> Au {
853        Au::zero().max(
854            self.available_space() -
855                self.padding_border_sum -
856                self.computed_margin_start.auto_is(Au::zero) -
857                self.computed_margin_end.auto_is(Au::zero),
858        )
859    }
860
861    fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
862        if self.box_offsets.either_auto() {
863            LogicalSides1D::new(
864                self.computed_margin_start.auto_is(Au::zero),
865                self.computed_margin_end.auto_is(Au::zero),
866            )
867        } else {
868            let free_space = self.available_space() - self.padding_border_sum - size;
869            match (self.computed_margin_start, self.computed_margin_end) {
870                (AuOrAuto::Auto, AuOrAuto::Auto) => {
871                    if self.avoid_negative_margin_start && free_space < Au::zero() {
872                        LogicalSides1D::new(Au::zero(), free_space)
873                    } else {
874                        let margin_start = free_space / 2;
875                        LogicalSides1D::new(margin_start, free_space - margin_start)
876                    }
877                },
878                (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
879                    LogicalSides1D::new(free_space - end, end)
880                },
881                (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
882                    LogicalSides1D::new(start, free_space - start)
883                },
884                (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
885                    LogicalSides1D::new(start, end)
886                },
887            }
888        }
889    }
890
891    fn origin_for_margin_box(
892        &self,
893        size: Au,
894        self_writing_mode: WritingMode,
895        original_parent_writing_mode: WritingMode,
896        containing_block_writing_mode: WritingMode,
897    ) -> Au {
898        let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
899            self.box_offsets.start.non_auto(),
900            self.box_offsets.end.non_auto(),
901        ) {
902            (None, None) => (
903                self.static_position_rect_axis,
904                original_parent_writing_mode,
905                self.flip_anchor,
906                None,
907            ),
908            (Some(start), Some(end)) => {
909                let alignment_container = RectAxis {
910                    origin: start,
911                    length: self.available_space(),
912                };
913                (
914                    alignment_container,
915                    containing_block_writing_mode,
916                    false,
917                    Some(LogicalSides1D { start, end }),
918                )
919            },
920            // If a single offset is auto, for alignment purposes it resolves to the amount
921            // that makes the inset-modified containing block be exactly as big as the abspos.
922            // Therefore the free space is zero and the alignment value is irrelevant.
923            (Some(start), None) => return start,
924            (None, Some(end)) => {
925                return self.containing_size - size - end;
926            },
927        };
928
929        assert_eq!(
930            self_writing_mode.is_horizontal(),
931            original_parent_writing_mode.is_horizontal(),
932            "Mixed horizontal and vertical writing modes are not supported yet"
933        );
934        assert_eq!(
935            self_writing_mode.is_horizontal(),
936            containing_block_writing_mode.is_horizontal(),
937            "Mixed horizontal and vertical writing modes are not supported yet"
938        );
939        let self_value_matches_container = || {
940            self.axis == Direction::Block ||
941                self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
942        };
943
944        // Here we resolve the alignment to either start, center, or end.
945        // Note we need to handle both self-alignment values (when some inset isn't auto)
946        // and distributed alignment values (when both insets are auto).
947        // The latter are treated as their fallback alignment.
948        let alignment = match self.alignment.value() {
949            // https://drafts.csswg.org/css-align/#valdef-self-position-center
950            // https://drafts.csswg.org/css-align/#valdef-align-content-space-around
951            // https://drafts.csswg.org/css-align/#valdef-align-content-space-evenly
952            AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
953                AlignFlags::CENTER
954            },
955            // https://drafts.csswg.org/css-align/#valdef-self-position-self-start
956            AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
957            AlignFlags::SELF_START => AlignFlags::END,
958            // https://drafts.csswg.org/css-align/#valdef-self-position-self-end
959            AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
960            AlignFlags::SELF_END => AlignFlags::START,
961            // https://drafts.csswg.org/css-align/#valdef-justify-content-left
962            AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
963            AlignFlags::LEFT => AlignFlags::END,
964            // https://drafts.csswg.org/css-align/#valdef-justify-content-right
965            AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
966            AlignFlags::RIGHT => AlignFlags::START,
967            // https://drafts.csswg.org/css-align/#valdef-self-position-end
968            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-end
969            // https://drafts.csswg.org/css-align/#valdef-justify-self-last-baseline
970            AlignFlags::END | AlignFlags::FLEX_END | AlignFlags::LAST_BASELINE => AlignFlags::END,
971            // https://drafts.csswg.org/css-align/#valdef-self-position-start
972            // https://drafts.csswg.org/css-align/#valdef-self-position-flex-start
973            // https://drafts.csswg.org/css-align/#valdef-justify-self-first-baseline
974            _ => AlignFlags::START,
975        };
976
977        let alignment = match alignment {
978            AlignFlags::START if flip_anchor => AlignFlags::END,
979            AlignFlags::END if flip_anchor => AlignFlags::START,
980            alignment => alignment,
981        };
982
983        let free_space = alignment_container.length - size;
984        let flags = self.alignment.flags();
985        let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
986            AlignFlags::START
987        } else {
988            alignment
989        };
990
991        let origin = match alignment {
992            AlignFlags::START => alignment_container.origin,
993            AlignFlags::CENTER => alignment_container.origin + free_space / 2,
994            AlignFlags::END => alignment_container.origin + free_space,
995            _ => unreachable!(),
996        };
997        if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
998            matches!(
999                self.alignment,
1000                AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
1001            )
1002        {
1003            return origin;
1004        }
1005        let Some(offsets) = offsets else {
1006            return origin;
1007        };
1008
1009        // Handle default overflow alignment.
1010        // https://drafts.csswg.org/css-align/#auto-safety-position
1011        let min = Au::zero().min(offsets.start);
1012        let max = self.containing_size - Au::zero().min(offsets.end) - size;
1013        origin.clamp_between_extremums(min, Some(max))
1014    }
1015}
1016
1017/// <https://drafts.csswg.org/css2/visuren.html#relative-positioning>
1018pub(crate) fn relative_adjustement(
1019    style: &ComputedValues,
1020    containing_block: &ContainingBlock,
1021) -> LogicalVec2<Au> {
1022    // It's not completely clear what to do with indefinite percentages
1023    // (https://github.com/w3c/csswg-drafts/issues/9353), so we match
1024    // other browsers and treat them as 'auto' offsets.
1025    let cbis = containing_block.size.inline;
1026    let cbbs = containing_block.size.block;
1027    let box_offsets = style
1028        .box_offsets(containing_block.style.writing_mode)
1029        .map_inline_and_block_axes(
1030            |value| value.map(|value| value.to_used_value(cbis)),
1031            |value| match cbbs {
1032                SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
1033                _ => match value.non_auto().and_then(|value| value.to_length()) {
1034                    Some(value) => AuOrAuto::LengthPercentage(value.into()),
1035                    None => AuOrAuto::Auto,
1036                },
1037            },
1038        );
1039    fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
1040        match (start, end) {
1041            (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
1042            (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
1043            (AuOrAuto::LengthPercentage(start), _) => start,
1044        }
1045    }
1046    LogicalVec2 {
1047        inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
1048        block: adjust(box_offsets.block_start, box_offsets.block_end),
1049    }
1050}
1051
1052/// These are the recorded layout inputs that were used when laying out an
1053/// absolutely-positioned element. They can be re-used when the absolutely-positioned
1054/// element is a viable layout root (no escaping fixed position elements, currently). The
1055/// information here is enough to re-run layout for an absolute.
1056#[derive(MallocSizeOf)]
1057pub(crate) struct LayoutRootLayoutInputs {
1058    /// The fully adjusted static position rectangle used to lay out the absolute. This is
1059    /// adjusted by the containing blocks of all of the boxes that come between an
1060    /// absolute's tree position and its layout containing block.
1061    fully_adjusted_static_position_rect: LogicalRect<Au>,
1062    /// The resolved alignment to use when laying out the absolute. This comes from the
1063    /// original box.
1064    resolved_alignment: LogicalVec2<AlignFlags>,
1065    /// This is the containing block size of the absolute's containing block. This is
1066    /// stored here because it's easier to access than the parent box.
1067    containing_block_size: LogicalVec2<Au>,
1068    /// This is the style of the containing block. This is stored here because it's easier
1069    /// to access than the parent box.
1070    #[conditional_malloc_size_of]
1071    containing_block_style: ServoArc<ComputedValues>,
1072    /// This is the writing mode of the absolute's tree parent.
1073    original_parent_writing_mode: WritingMode,
1074}
1075
1076impl std::fmt::Debug for LayoutRootLayoutInputs {
1077    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1078        f.debug_struct("LayoutRootLayoutInputs")
1079            .field("containing_block_size", &self.containing_block_size)
1080            .finish()
1081    }
1082}
1083
1084impl LayoutRootLayoutInputs {
1085    /// Re-run layout for the given inputs. This is used to run layout again at layout
1086    /// roots.
1087    pub(crate) fn layout(
1088        &self,
1089        layout_context: &LayoutContext,
1090        context: &IndependentFormattingContext,
1091        shared_fragment: &ArcRefCell<HoistedSharedFragment>,
1092    ) -> Result<(), ()> {
1093        let containing_block = DefiniteContainingBlock {
1094            size: self.containing_block_size,
1095            style: &self.containing_block_style,
1096        };
1097        let (box_fragment, positioning_context) = context.layout_as_absolute(
1098            layout_context,
1099            &self.fully_adjusted_static_position_rect,
1100            &containing_block,
1101            self.resolved_alignment,
1102            self.original_parent_writing_mode,
1103        );
1104
1105        if !positioning_context.is_empty() {
1106            return Err(());
1107        }
1108
1109        shared_fragment.borrow_mut().fragment = Some(Fragment::Box(box_fragment));
1110        Ok(())
1111    }
1112}