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