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