Skip to main content

layout/
positioned.rs

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