Skip to main content

layout/flow/
mod.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#![allow(rustdoc::private_intra_doc_links)]
5
6//! Flow layout, also known as block-and-inline layout.
7
8use app_units::{Au, MAX_AU};
9use inline::InlineFormattingContext;
10use layout_api::LayoutNode;
11use malloc_size_of_derive::MallocSizeOf;
12use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
13use script::layout_dom::ServoLayoutNode;
14use servo_arc::Arc as ServoArc;
15use style::Zero;
16use style::computed_values::clear::T as StyleClear;
17use style::context::SharedStyleContext;
18use style::logical_geometry::Direction;
19use style::properties::ComputedValues;
20use style::servo::selector_parser::PseudoElement;
21use style::values::specified::align::AlignFlags;
22use style::values::specified::{Display, TextAlignKeyword};
23
24use crate::cell::ArcRefCell;
25use crate::context::LayoutContext;
26use crate::dom::WeakLayoutBox;
27use crate::flow::float::{Clear, FloatBox, FloatSide, PlacementAmongFloats, SequentialLayoutState};
28use crate::flow::same_formatting_context_block::SameFormattingContextBlock;
29use crate::formatting_contexts::{Baselines, IndependentFormattingContext};
30use crate::fragment_tree::{
31    BaseFragmentInfo, BlockLevelLayoutInfo, BoxFragment, CollapsedBlockMargins, CollapsedMargin,
32    Fragment, FragmentFlags, PositioningFragment,
33};
34use crate::geom::{
35    AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
36    PhysicalSides, ToLogical, ToLogicalWithContainingBlock,
37};
38use crate::layout_box_base::{IndependentFormattingContextLayoutResult, LayoutBoxBase};
39use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
40use crate::sizing::{
41    self, ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, LazySize, Size,
42    SizeConstraint, Sizes,
43};
44use crate::style_ext::{AspectRatio, ContentBoxSizesAndPBM, LayoutStyle, PaddingBorderMargin};
45use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock};
46
47mod construct;
48pub mod float;
49pub mod inline;
50mod root;
51mod same_formatting_context_block;
52
53pub(crate) use construct::{BlockContainerBuilder, BlockLevelCreator};
54pub(crate) use root::BoxTree;
55
56#[derive(Debug, MallocSizeOf)]
57pub(crate) struct BlockFormattingContext {
58    pub contents: BlockContainer,
59    pub contains_floats: bool,
60}
61
62#[derive(Debug, MallocSizeOf)]
63pub(crate) enum BlockContainer {
64    BlockLevelBoxes(Vec<ArcRefCell<BlockLevelBox>>),
65    InlineFormattingContext(InlineFormattingContext),
66}
67
68impl BlockContainer {
69    fn contains_floats(&self) -> bool {
70        match self {
71            BlockContainer::BlockLevelBoxes(boxes) => boxes
72                .iter()
73                .any(|block_level_box| block_level_box.borrow().contains_floats()),
74            BlockContainer::InlineFormattingContext(context) => context.contains_floats,
75        }
76    }
77
78    pub(crate) fn repair_style(
79        &mut self,
80        context: &SharedStyleContext,
81        node: &ServoLayoutNode,
82        new_style: &ServoArc<ComputedValues>,
83    ) {
84        match self {
85            BlockContainer::BlockLevelBoxes(..) => {},
86            BlockContainer::InlineFormattingContext(inline_formatting_context) => {
87                inline_formatting_context.repair_style(context, node, new_style)
88            },
89        }
90    }
91
92    pub(crate) fn subtree_size(&self) -> usize {
93        match self {
94            BlockContainer::BlockLevelBoxes(boxes) => boxes
95                .iter()
96                .map(|block_level_box| block_level_box.borrow().subtree_size())
97                .sum(),
98            BlockContainer::InlineFormattingContext(inline_formatting_context) => {
99                inline_formatting_context.subtree_size()
100            },
101        }
102    }
103}
104
105#[derive(Debug, MallocSizeOf)]
106pub(crate) enum BlockLevelBox {
107    Independent(IndependentFormattingContext),
108    OutOfFlowAbsolutelyPositionedBox(ArcRefCell<AbsolutelyPositionedBox>),
109    OutOfFlowFloatBox(FloatBox),
110    OutsideMarker(OutsideMarker),
111    SameFormattingContextBlock(SameFormattingContextBlock),
112}
113
114impl BlockLevelBox {
115    pub(crate) fn repair_style(
116        &mut self,
117        context: &SharedStyleContext,
118        node: &ServoLayoutNode,
119        new_style: &ServoArc<ComputedValues>,
120    ) {
121        match self {
122            BlockLevelBox::Independent(independent_formatting_context) => {
123                independent_formatting_context.repair_style(context, node, new_style)
124            },
125            BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => positioned_box
126                .borrow_mut()
127                .context
128                .repair_style(context, node, new_style),
129            BlockLevelBox::OutOfFlowFloatBox(float_box) => {
130                float_box.contents.repair_style(context, node, new_style)
131            },
132            BlockLevelBox::OutsideMarker(outside_marker) => {
133                outside_marker.repair_style(context, node, new_style)
134            },
135            BlockLevelBox::SameFormattingContextBlock(same_formatting_context_block) => {
136                same_formatting_context_block.repair_style(context, node, new_style)
137            },
138        }
139    }
140
141    pub(crate) fn with_base<T>(&self, callback: impl FnOnce(&LayoutBoxBase) -> T) -> T {
142        match self {
143            BlockLevelBox::Independent(independent_formatting_context) => {
144                callback(&independent_formatting_context.base)
145            },
146            BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
147                callback(&positioned_box.borrow().context.base)
148            },
149            BlockLevelBox::OutOfFlowFloatBox(float_box) => callback(&float_box.contents.base),
150            BlockLevelBox::OutsideMarker(outside_marker) => callback(&outside_marker.context.base),
151            BlockLevelBox::SameFormattingContextBlock(same_formatting_context_block) => {
152                callback(&same_formatting_context_block.base)
153            },
154        }
155    }
156
157    pub(crate) fn with_base_mut<T>(&mut self, callback: impl FnOnce(&mut LayoutBoxBase) -> T) -> T {
158        match self {
159            BlockLevelBox::Independent(independent_formatting_context) => {
160                callback(&mut independent_formatting_context.base)
161            },
162            BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
163                callback(&mut positioned_box.borrow_mut().context.base)
164            },
165            BlockLevelBox::OutOfFlowFloatBox(float_box) => callback(&mut float_box.contents.base),
166            BlockLevelBox::OutsideMarker(outside_marker) => {
167                callback(&mut outside_marker.context.base)
168            },
169            BlockLevelBox::SameFormattingContextBlock(same_formatting_context_block) => {
170                callback(&mut same_formatting_context_block.base)
171            },
172        }
173    }
174
175    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
176        match self {
177            Self::Independent(independent_formatting_context) => {
178                independent_formatting_context.attached_to_tree(layout_box)
179            },
180            Self::OutOfFlowAbsolutelyPositionedBox(positioned_box) => {
181                positioned_box.borrow().context.attached_to_tree(layout_box)
182            },
183            Self::OutOfFlowFloatBox(float_box) => float_box.contents.attached_to_tree(layout_box),
184            Self::OutsideMarker(outside_marker) => {
185                outside_marker.context.attached_to_tree(layout_box)
186            },
187            Self::SameFormattingContextBlock(same_formatting_context_block) => {
188                same_formatting_context_block
189                    .contents
190                    .attached_to_tree(layout_box)
191            },
192        }
193    }
194
195    fn contains_floats(&self) -> bool {
196        match self {
197            BlockLevelBox::SameFormattingContextBlock(same_formatting_context_block) => {
198                same_formatting_context_block.contains_floats
199            },
200            BlockLevelBox::OutOfFlowFloatBox { .. } => true,
201            _ => false,
202        }
203    }
204
205    fn find_block_margin_collapsing_with_parent(
206        &self,
207        layout_context: &LayoutContext,
208        collected_margin: &mut CollapsedMargin,
209        containing_block: &ContainingBlock,
210    ) -> bool {
211        let layout_style = match self {
212            BlockLevelBox::SameFormattingContextBlock(same_formatting_context_block) => {
213                same_formatting_context_block.layout_style()
214            },
215            BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_) |
216            BlockLevelBox::OutOfFlowFloatBox(_) => return true,
217            BlockLevelBox::OutsideMarker(_) => return false,
218            BlockLevelBox::Independent(context) => {
219                // FIXME: If the element doesn't fit next to floats, it will get clearance.
220                // In that case this should be returning false.
221                context.layout_style()
222            },
223        };
224
225        // FIXME: This should only return false when 'clear' causes clearance.
226        let style = layout_style.style();
227        if style.get_box().clear != StyleClear::None {
228            return false;
229        }
230
231        let ContentBoxSizesAndPBM {
232            content_box_sizes,
233            pbm,
234            ..
235        } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
236        let margin = pbm.margin.auto_is(Au::zero);
237        collected_margin.adjoin_assign(&CollapsedMargin::new(margin.block_start));
238
239        let BlockLevelBox::SameFormattingContextBlock(same_formatting_context_block) = self else {
240            return false;
241        };
242
243        if !pbm.padding.block_start.is_zero() || !pbm.border.block_start.is_zero() {
244            return false;
245        }
246
247        let available_inline_size =
248            containing_block.size.inline - pbm.padding_border_sums.inline - margin.inline_sum();
249        let available_block_size = containing_block.size.block.to_definite().map(|block_size| {
250            Au::zero().max(block_size - pbm.padding_border_sums.block - margin.block_sum())
251        });
252
253        let tentative_block_size = content_box_sizes.block.resolve_extrinsic(
254            Size::FitContent,
255            Au::zero(),
256            available_block_size,
257        );
258
259        let get_inline_content_sizes = || {
260            let constraint_space = ConstraintSpace::new(
261                tentative_block_size,
262                style,
263                None, /* TODO: support preferred aspect ratios on non-replaced boxes */
264            );
265            self.inline_content_sizes(layout_context, &constraint_space)
266                .sizes
267        };
268        let inline_size = content_box_sizes.inline.resolve(
269            Direction::Inline,
270            Size::Stretch,
271            Au::zero,
272            Some(available_inline_size),
273            get_inline_content_sizes,
274            false, /* is_table */
275        );
276
277        let containing_block_for_children = ContainingBlock {
278            size: ContainingBlockSize {
279                inline: inline_size,
280                block: tentative_block_size,
281            },
282            style,
283        };
284
285        if !same_formatting_context_block
286            .contents
287            .find_block_margin_collapsing_with_parent(
288                layout_context,
289                collected_margin,
290                &containing_block_for_children,
291            )
292        {
293            return false;
294        }
295
296        if !tentative_block_size.definite_or_min().is_zero() ||
297            !pbm.padding_border_sums.block.is_zero()
298        {
299            return false;
300        }
301
302        collected_margin.adjoin_assign(&CollapsedMargin::new(margin.block_end));
303
304        true
305    }
306
307    fn subtree_size(&self) -> usize {
308        self.with_base(|base| base.subtree_size())
309    }
310}
311
312#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
313pub(crate) struct CollapsibleWithParentStartMargin(bool);
314
315/// The contentes of a BlockContainer created to render a list marker
316/// for a list that has `list-style-position: outside`.
317#[derive(Debug, MallocSizeOf)]
318pub(crate) struct OutsideMarker {
319    pub list_item_style: ServoArc<ComputedValues>,
320    pub context: IndependentFormattingContext,
321}
322
323impl OutsideMarker {
324    fn layout(
325        &self,
326        layout_context: &LayoutContext<'_>,
327        containing_block: &ContainingBlock<'_>,
328        positioning_context: &mut PositioningContext,
329    ) -> Fragment {
330        let style = &self.context.base.style;
331        let preferred_aspect_ratio = self.context.preferred_aspect_ratio(&LogicalVec2::zero());
332        let constraint_space =
333            ConstraintSpace::new(SizeConstraint::default(), style, preferred_aspect_ratio);
334        let content_sizes = self
335            .context
336            .inline_content_sizes(layout_context, &constraint_space);
337        let containing_block_for_children = ContainingBlock {
338            size: ContainingBlockSize {
339                inline: content_sizes.sizes.max_content,
340                block: SizeConstraint::default(),
341            },
342            style,
343        };
344
345        let layout = self.context.layout(
346            layout_context,
347            positioning_context,
348            &containing_block_for_children,
349            containing_block,
350            preferred_aspect_ratio,
351            &LazySize::intrinsic(),
352        );
353
354        let max_inline_size = layout
355            .fragments
356            .iter()
357            .map(|fragment| {
358                fragment
359                    .base()
360                    .map(|base| base.rect())
361                    .unwrap_or_default()
362                    .to_logical(&containing_block_for_children)
363                    .max_inline_position()
364            })
365            .max()
366            .unwrap_or_default();
367
368        // Position the marker beyond the inline start of the border box list item. This needs to
369        // take into account the border and padding of the item.
370        //
371        // TODO: This is the wrong containing block, as it should be the containing block of the
372        // parent of this list item. What this means in practice is that the writing mode could be
373        // wrong and padding defined as a percentage will be resolved incorrectly.
374        //
375        // TODO: This should use the LayoutStyle of the list item, not the default one. Currently
376        // they are the same, but this could change in the future.
377        let pbm_of_list_item =
378            LayoutStyle::Default(&self.list_item_style).padding_border_margin(containing_block);
379        let content_rect = LogicalRect {
380            start_corner: LogicalVec2 {
381                inline: -max_inline_size -
382                    (pbm_of_list_item.border.inline_start +
383                        pbm_of_list_item.padding.inline_start),
384                block: Zero::zero(),
385            },
386            size: LogicalVec2 {
387                inline: max_inline_size,
388                block: layout.content_block_size,
389            },
390        };
391
392        let mut base_fragment_info = BaseFragmentInfo::anonymous();
393        base_fragment_info.flags |= FragmentFlags::IS_OUTSIDE_LIST_ITEM_MARKER;
394
395        Fragment::Box(
396            BoxFragment::new(
397                base_fragment_info,
398                style.clone(),
399                layout.fragments,
400                content_rect.as_physical(Some(containing_block)),
401                PhysicalSides::zero(),
402                PhysicalSides::zero(),
403                PhysicalSides::zero(),
404                layout.specific_layout_info,
405            )
406            .into(),
407        )
408    }
409
410    fn repair_style(
411        &mut self,
412        context: &SharedStyleContext,
413        node: &ServoLayoutNode,
414        new_style: &ServoArc<ComputedValues>,
415    ) {
416        self.list_item_style = node.parent_style(context);
417        self.context.repair_style(context, node, new_style);
418    }
419}
420
421impl BlockFormattingContext {
422    pub(super) fn layout(
423        &self,
424        layout_context: &LayoutContext,
425        positioning_context: &mut PositioningContext,
426        containing_block: &ContainingBlock,
427        lazy_block_size: &LazySize,
428        base: Option<&LayoutBoxBase>,
429    ) -> IndependentFormattingContextLayoutResult {
430        let mut sequential_layout_state =
431            if self.contains_floats || !layout_context.allow_parallel_layout {
432                Some(SequentialLayoutState::new(containing_block.size.inline))
433            } else {
434                None
435            };
436
437        // Since this is an independent formatting context, we don't ignore block margins when
438        // resolving a stretch block size of the children.
439        // https://drafts.csswg.org/css-sizing-4/#stretch-fit-sizing
440        let ignore_block_margins_for_stretch = LogicalSides1D::new(false, false);
441
442        // Store the current length of the positioning context, because below we may need to
443        // adjust the static positions of the abspos within this BFC.
444        let previous_positioning_context_len = positioning_context.len();
445
446        let mut flow_layout = self.contents.layout(
447            layout_context,
448            positioning_context,
449            containing_block,
450            sequential_layout_state.as_mut(),
451            CollapsibleWithParentStartMargin(false),
452            ignore_block_margins_for_stretch,
453        );
454        debug_assert!(
455            !flow_layout
456                .collapsible_margins_in_children
457                .collapsed_through
458        );
459
460        // The content height of a BFC root should include any float participating in that BFC
461        // (https://drafts.csswg.org/css2/#root-height), we implement this by imagining there is
462        // an element with `clear: both` after the actual contents.
463        let clearance = sequential_layout_state.and_then(|sequential_layout_state| {
464            sequential_layout_state.calculate_clearance(Clear::Both, &CollapsedMargin::zero())
465        });
466
467        let content_block_size = flow_layout.content_block_size +
468            flow_layout.collapsible_margins_in_children.end.solve() +
469            clearance.unwrap_or_default();
470
471        // Buttons center their contents in the block axis. Therefore, create an `AnonymousFragment`
472        // that contains the fragments of the contents, and place it as desired.
473        // TODO: Use `align-content` instead, see https://github.com/w3c/csswg-drafts/issues/14190
474        if let Some(base) = base &&
475            base.base_fragment_info
476                .flags
477                .contains(FragmentFlags::IS_BUTTON)
478        {
479            flow_layout.depends_on_block_constraints = true;
480            let final_block_size = lazy_block_size.resolve(|| content_block_size);
481            let align_fragment_rect = LogicalRect {
482                start_corner: LogicalVec2 {
483                    inline: Au::zero(),
484                    block: Au::zero().max((final_block_size - content_block_size) / 2),
485                },
486                size: LogicalVec2 {
487                    inline: containing_block.size.inline,
488                    block: content_block_size,
489                },
490            }
491            .as_physical(Some(containing_block));
492            positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
493                &align_fragment_rect.origin.to_vector(),
494                previous_positioning_context_len,
495            );
496            let align_fragment = PositioningFragment::new_anonymous(
497                base.style.clone(),
498                align_fragment_rect,
499                flow_layout.fragments,
500                false, /* is_line_box */
501            );
502            flow_layout.fragments = vec![Fragment::Positioning(align_fragment)];
503        }
504
505        IndependentFormattingContextLayoutResult {
506            fragments: flow_layout.fragments,
507            content_block_size,
508            content_inline_size_for_table: None,
509            baselines: flow_layout.baselines,
510            depends_on_block_constraints: flow_layout.depends_on_block_constraints,
511            specific_layout_info: None,
512            collapsible_margins_in_children: CollapsedBlockMargins::zero(),
513        }
514    }
515
516    #[inline]
517    pub(crate) fn layout_style<'a>(&self, base: &'a LayoutBoxBase) -> LayoutStyle<'a> {
518        LayoutStyle::Default(&base.style)
519    }
520
521    pub(crate) fn repair_style(
522        &mut self,
523        context: &SharedStyleContext,
524        node: &ServoLayoutNode,
525        new_style: &ServoArc<ComputedValues>,
526    ) {
527        self.contents.repair_style(context, node, new_style);
528    }
529
530    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
531        self.contents.attached_to_tree(layout_box);
532    }
533}
534
535/// Finds the min/max-content inline size of the block-level children of a block container.
536/// The in-flow boxes will stack vertically, so we only need to consider the maximum size.
537/// But floats can flow horizontally depending on 'clear', so we may need to sum their sizes.
538/// CSS 2 does not define the exact algorithm, this logic is based on the behavior observed
539/// on Gecko and Blink.
540fn compute_inline_content_sizes_for_block_level_boxes(
541    boxes: &[ArcRefCell<BlockLevelBox>],
542    layout_context: &LayoutContext,
543    containing_block: &IndefiniteContainingBlock,
544) -> InlineContentSizesResult {
545    let get_box_info = |box_: &ArcRefCell<BlockLevelBox>| {
546        match &*box_.borrow() {
547            BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(_) |
548            BlockLevelBox::OutsideMarker { .. } => None,
549            BlockLevelBox::OutOfFlowFloatBox(float_box) => {
550                let inline_content_sizes_result = float_box.contents.outer_inline_content_sizes(
551                    layout_context,
552                    containing_block,
553                    &LogicalVec2::zero(),
554                    false, /* auto_block_size_stretches_to_containing_block */
555                );
556                let style = &float_box.contents.style();
557                let container_writing_mode = containing_block.style.writing_mode;
558                Some((
559                    inline_content_sizes_result,
560                    FloatSide::from_style_and_container_writing_mode(style, container_writing_mode),
561                    Clear::from_style_and_container_writing_mode(style, container_writing_mode),
562                ))
563            },
564            BlockLevelBox::SameFormattingContextBlock(same_formatting_context_block) => {
565                let base = &same_formatting_context_block.base;
566                let contents = &same_formatting_context_block.contents;
567                let is_anonymous_block =
568                    matches!(base.style.pseudo(), Some(PseudoElement::ServoAnonymousBox));
569                let inline_content_sizes_result = sizing::outer_inline(
570                    base,
571                    &contents.layout_style(base),
572                    containing_block,
573                    &LogicalVec2::zero(),
574                    false,               /* auto_block_size_stretches_to_containing_block */
575                    false,               /* is_replaced */
576                    !is_anonymous_block, /* establishes_containing_block */
577                    |_| None, /* TODO: support preferred aspect ratios on non-replaced boxes */
578                    |constraint_space| {
579                        base.inline_content_sizes(layout_context, constraint_space, contents)
580                    },
581                    |_aspect_ratio| None,
582                );
583                // A block in the same BFC can overlap floats, it's not moved next to them,
584                // so we shouldn't add its size to the size of the floats.
585                // Instead, we treat it like an independent block with 'clear: both',
586                // except if it's an anonymous block.
587                // Presumably, the exception is because an anonymous block will always have
588                // inline-level contents, which don't overlap floats. However, the same might
589                // also happen with a non-anonymous block, so the logic is a bit arbitrary,
590                // but matches other browsers (see #41280).
591                let clear = if is_anonymous_block {
592                    Clear::None
593                } else {
594                    Clear::Both
595                };
596                Some((inline_content_sizes_result, None, clear))
597            },
598            BlockLevelBox::Independent(independent) => {
599                let inline_content_sizes_result = independent.outer_inline_content_sizes(
600                    layout_context,
601                    containing_block,
602                    &LogicalVec2::zero(),
603                    false, /* auto_block_size_stretches_to_containing_block */
604                );
605                Some((
606                    inline_content_sizes_result,
607                    None,
608                    Clear::from_style_and_container_writing_mode(
609                        independent.style(),
610                        containing_block.style.writing_mode,
611                    ),
612                ))
613            },
614        }
615    };
616
617    /// When iterating the block-level boxes to compute the inline content sizes,
618    /// this struct contains the data accumulated up to the current box.
619    #[derive(Default)]
620    struct AccumulatedData {
621        /// Whether the inline size depends on the block one.
622        depends_on_block_constraints: bool,
623        /// The maximum size seen so far, not including trailing uncleared floats.
624        max_size: ContentSizes,
625        /// The size of the trailing uncleared floats on the inline-start and
626        /// inline-end sides of the containing block.
627        floats: LogicalSides1D<ContentSizes>,
628    }
629
630    impl AccumulatedData {
631        fn max_size_including_uncleared_floats(&self) -> ContentSizes {
632            self.max_size.max(self.floats.start.union(&self.floats.end))
633        }
634        fn clear_floats(&mut self, clear: Clear) {
635            match clear {
636                Clear::InlineStart => {
637                    self.max_size = self.max_size_including_uncleared_floats();
638                    self.floats.start = ContentSizes::default();
639                },
640                Clear::InlineEnd => {
641                    self.max_size = self.max_size_including_uncleared_floats();
642                    self.floats.end = ContentSizes::default();
643                },
644                Clear::Both => {
645                    self.max_size = self.max_size_including_uncleared_floats();
646                    self.floats = LogicalSides1D::default();
647                },
648                Clear::None => {},
649            };
650        }
651    }
652
653    let accumulate =
654        |mut data: AccumulatedData,
655         (inline_content_sizes_result, float, clear): (InlineContentSizesResult, _, _)| {
656            let size = inline_content_sizes_result.sizes.max(ContentSizes::zero());
657            let depends_on_block_constraints =
658                inline_content_sizes_result.depends_on_block_constraints;
659            data.depends_on_block_constraints |= depends_on_block_constraints;
660            data.clear_floats(clear);
661            match float {
662                Some(FloatSide::InlineStart) => data.floats.start.union_assign(&size),
663                Some(FloatSide::InlineEnd) => data.floats.end.union_assign(&size),
664                None => {
665                    data.max_size
666                        .max_assign(data.floats.start.union(&data.floats.end).union(&size));
667                    data.floats = LogicalSides1D::default();
668                },
669            }
670            data
671        };
672
673    let job_counts = boxes
674        .iter()
675        .map(|block_level_box| block_level_box.borrow().subtree_size());
676    let data = if layout_context.should_parallelize_layout(job_counts) {
677        boxes
678            .par_iter()
679            .filter_map(get_box_info)
680            .collect::<Vec<_>>()
681            .into_iter()
682            .fold(AccumulatedData::default(), accumulate)
683    } else {
684        boxes
685            .iter()
686            .filter_map(get_box_info)
687            .fold(AccumulatedData::default(), accumulate)
688    };
689    InlineContentSizesResult {
690        depends_on_block_constraints: data.depends_on_block_constraints,
691        sizes: data.max_size_including_uncleared_floats(),
692    }
693}
694
695impl BlockContainer {
696    fn layout(
697        &self,
698        layout_context: &LayoutContext,
699        positioning_context: &mut PositioningContext,
700        containing_block: &ContainingBlock,
701        sequential_layout_state: Option<&mut SequentialLayoutState>,
702        collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
703        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
704    ) -> IndependentFormattingContextLayoutResult {
705        match self {
706            BlockContainer::BlockLevelBoxes(child_boxes) => layout_block_level_children(
707                layout_context,
708                positioning_context,
709                child_boxes,
710                containing_block,
711                sequential_layout_state,
712                collapsible_with_parent_start_margin,
713                ignore_block_margins_for_stretch,
714            ),
715            BlockContainer::InlineFormattingContext(ifc) => ifc.layout(
716                layout_context,
717                positioning_context,
718                containing_block,
719                sequential_layout_state,
720                collapsible_with_parent_start_margin,
721                ignore_block_margins_for_stretch,
722            ),
723        }
724    }
725
726    #[inline]
727    pub(crate) fn layout_style<'a>(&self, base: &'a LayoutBoxBase) -> LayoutStyle<'a> {
728        LayoutStyle::Default(&base.style)
729    }
730
731    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
732        match self {
733            Self::BlockLevelBoxes(child_boxes) => {
734                for child_box in child_boxes {
735                    child_box.borrow_mut().with_base_mut(|base| {
736                        base.parent_box.replace(layout_box.clone());
737                    });
738                }
739            },
740            Self::InlineFormattingContext(ifc) => ifc.attached_to_tree(layout_box),
741        }
742    }
743
744    fn find_block_margin_collapsing_with_parent(
745        &self,
746        layout_context: &LayoutContext,
747        collected_margin: &mut CollapsedMargin,
748        containing_block_for_children: &ContainingBlock,
749    ) -> bool {
750        match self {
751            BlockContainer::BlockLevelBoxes(boxes) => boxes.iter().all(|block_level_box| {
752                block_level_box
753                    .borrow()
754                    .find_block_margin_collapsing_with_parent(
755                        layout_context,
756                        collected_margin,
757                        containing_block_for_children,
758                    )
759            }),
760            BlockContainer::InlineFormattingContext(context) => context
761                .find_block_margin_collapsing_with_parent(
762                    layout_context,
763                    collected_margin,
764                    containing_block_for_children,
765                ),
766        }
767    }
768}
769
770impl ComputeInlineContentSizes for BlockContainer {
771    fn compute_inline_content_sizes(
772        &self,
773        layout_context: &LayoutContext,
774        constraint_space: &ConstraintSpace,
775    ) -> InlineContentSizesResult {
776        match &self {
777            Self::BlockLevelBoxes(boxes) => compute_inline_content_sizes_for_block_level_boxes(
778                boxes,
779                layout_context,
780                &constraint_space.into(),
781            ),
782            Self::InlineFormattingContext(context) => {
783                context.compute_inline_content_sizes(layout_context, constraint_space)
784            },
785        }
786    }
787}
788
789fn layout_block_level_children(
790    layout_context: &LayoutContext,
791    positioning_context: &mut PositioningContext,
792    child_boxes: &[ArcRefCell<BlockLevelBox>],
793    containing_block: &ContainingBlock,
794    mut sequential_layout_state: Option<&mut SequentialLayoutState>,
795    collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
796    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
797) -> IndependentFormattingContextLayoutResult {
798    let mut placement_state =
799        PlacementState::new(collapsible_with_parent_start_margin, containing_block);
800
801    let fragments = match sequential_layout_state {
802        Some(ref mut sequential_layout_state) => layout_block_level_children_sequentially(
803            layout_context,
804            positioning_context,
805            child_boxes,
806            sequential_layout_state,
807            &mut placement_state,
808            ignore_block_margins_for_stretch,
809        ),
810        None => layout_block_level_children_in_parallel(
811            layout_context,
812            positioning_context,
813            child_boxes,
814            &mut placement_state,
815            ignore_block_margins_for_stretch,
816        ),
817    };
818
819    let depends_on_block_constraints = fragments.iter().any(|fragment| {
820        fragment.base().is_some_and(|base| {
821            base.flags.contains(
822                FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
823            )
824        })
825    });
826
827    let (content_block_size, collapsible_margins_in_children, baselines) = placement_state.finish();
828    IndependentFormattingContextLayoutResult {
829        fragments,
830        content_block_size,
831        collapsible_margins_in_children,
832        baselines,
833        depends_on_block_constraints,
834        content_inline_size_for_table: None,
835        specific_layout_info: None,
836    }
837}
838
839fn layout_block_level_children_in_parallel(
840    layout_context: &LayoutContext,
841    positioning_context: &mut PositioningContext,
842    child_boxes: &[ArcRefCell<BlockLevelBox>],
843    placement_state: &mut PlacementState,
844    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
845) -> Vec<Fragment> {
846    let mut layout_results: Vec<(Fragment, PositioningContext)> =
847        Vec::with_capacity(child_boxes.len());
848
849    child_boxes
850        .par_iter()
851        .map(|child_box| {
852            let mut child_positioning_context = PositioningContext::default();
853            let fragment = child_box.borrow().layout(
854                layout_context,
855                &mut child_positioning_context,
856                placement_state.containing_block,
857                /* sequential_layout_state = */ None,
858                /* collapsible_with_parent_start_margin = */ None,
859                ignore_block_margins_for_stretch,
860                false, /* has_inline_parent */
861            );
862            (fragment, child_positioning_context)
863        })
864        .collect_into_vec(&mut layout_results);
865
866    layout_results
867        .into_iter()
868        .map(|(mut fragment, mut child_positioning_context)| {
869            placement_state.place_fragment_and_update_baseline(&mut fragment, None);
870            child_positioning_context.adjust_static_position_of_hoisted_fragments(
871                &fragment,
872                PositioningContextLength::zero(),
873            );
874            positioning_context.append(child_positioning_context);
875            fragment
876        })
877        .collect()
878}
879
880fn layout_block_level_children_sequentially(
881    layout_context: &LayoutContext,
882    positioning_context: &mut PositioningContext,
883    child_boxes: &[ArcRefCell<BlockLevelBox>],
884    sequential_layout_state: &mut SequentialLayoutState,
885    placement_state: &mut PlacementState,
886    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
887) -> Vec<Fragment> {
888    // Because floats are involved, we do layout for this block formatting context in tree
889    // order without parallelism. This enables mutable access to a `SequentialLayoutState` that
890    // tracks every float encountered so far (again in tree order).
891    child_boxes
892        .iter()
893        .map(|child_box| {
894            layout_block_level_child(
895                layout_context,
896                positioning_context,
897                &child_box.borrow(),
898                Some(sequential_layout_state),
899                placement_state,
900                ignore_block_margins_for_stretch,
901                false, /* has_inline_parent */
902            )
903        })
904        .collect()
905}
906
907fn layout_block_level_child(
908    layout_context: &LayoutContext,
909    positioning_context: &mut PositioningContext,
910    child_box: &BlockLevelBox,
911    mut sequential_layout_state: Option<&mut SequentialLayoutState>,
912    placement_state: &mut PlacementState,
913    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
914    has_inline_parent: bool,
915) -> Fragment {
916    let positioning_context_length_before_layout = positioning_context.len();
917    let mut fragment = child_box.layout(
918        layout_context,
919        positioning_context,
920        placement_state.containing_block,
921        sequential_layout_state.as_deref_mut(),
922        Some(CollapsibleWithParentStartMargin(
923            placement_state.next_in_flow_margin_collapses_with_parent_start_margin,
924        )),
925        ignore_block_margins_for_stretch,
926        has_inline_parent,
927    );
928
929    placement_state.place_fragment_and_update_baseline(&mut fragment, sequential_layout_state);
930    positioning_context.adjust_static_position_of_hoisted_fragments(
931        &fragment,
932        positioning_context_length_before_layout,
933    );
934
935    fragment
936}
937
938impl BlockLevelBox {
939    #[allow(clippy::too_many_arguments)]
940    fn layout(
941        &self,
942        layout_context: &LayoutContext,
943        positioning_context: &mut PositioningContext,
944        containing_block: &ContainingBlock,
945        sequential_layout_state: Option<&mut SequentialLayoutState>,
946        collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
947        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
948        has_inline_parent: bool,
949    ) -> Fragment {
950        let fragment = match self {
951            BlockLevelBox::SameFormattingContextBlock(same_formatting_context_block) => {
952                Fragment::Box(
953                    same_formatting_context_block.layout_in_flow_non_replaced_block_level_cached(
954                        layout_context,
955                        positioning_context,
956                        containing_block,
957                        sequential_layout_state,
958                        collapsible_with_parent_start_margin,
959                        ignore_block_margins_for_stretch,
960                        has_inline_parent,
961                    ),
962                )
963            },
964            BlockLevelBox::Independent(independent) => Fragment::Box(
965                positioning_context
966                    .layout_maybe_position_relative_fragment(
967                        layout_context,
968                        containing_block,
969                        &independent.base,
970                        |positioning_context| {
971                            independent.layout_in_flow_block_level(
972                                layout_context,
973                                positioning_context,
974                                containing_block,
975                                sequential_layout_state,
976                                ignore_block_margins_for_stretch,
977                                has_inline_parent,
978                            )
979                        },
980                    )
981                    .into(),
982            ),
983            BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => {
984                // The static position of zero here is incorrect, however we do not know
985                // the correct positioning until later, in place_block_level_fragment, and
986                // this value will be adjusted there.
987                let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
988                    box_.clone(),
989                    // This is incorrect, however we do not know the correct positioning
990                    // until later, in PlacementState::place_fragment, and this value will be
991                    // adjusted there
992                    PhysicalRect::zero(),
993                    LogicalVec2 {
994                        inline: AlignFlags::START,
995                        block: AlignFlags::START,
996                    },
997                    containing_block.style.writing_mode,
998                );
999                let hoisted_fragment = hoisted_box.fragment.clone();
1000                positioning_context.push(hoisted_box);
1001                Fragment::AbsoluteOrFixedPositionedPlaceholder(hoisted_fragment)
1002            },
1003            BlockLevelBox::OutOfFlowFloatBox(float_box) => Fragment::Float(
1004                float_box
1005                    .layout(layout_context, positioning_context, containing_block)
1006                    .into(),
1007            ),
1008            BlockLevelBox::OutsideMarker(outside_marker) => {
1009                outside_marker.layout(layout_context, containing_block, positioning_context)
1010            },
1011        };
1012
1013        self.with_base(|base| base.set_fragment(fragment.clone()));
1014
1015        fragment
1016    }
1017
1018    fn inline_content_sizes(
1019        &self,
1020        layout_context: &LayoutContext,
1021        constraint_space: &ConstraintSpace,
1022    ) -> InlineContentSizesResult {
1023        let independent_formatting_context = match self {
1024            BlockLevelBox::Independent(independent) => independent,
1025            BlockLevelBox::OutOfFlowAbsolutelyPositionedBox(box_) => &box_.borrow().context,
1026            BlockLevelBox::OutOfFlowFloatBox(float_box) => &float_box.contents,
1027            BlockLevelBox::OutsideMarker(outside_marker) => &outside_marker.context,
1028            BlockLevelBox::SameFormattingContextBlock(same_formatting_context_block) => {
1029                return same_formatting_context_block
1030                    .inline_content_sizes(layout_context, constraint_space);
1031            },
1032        };
1033        independent_formatting_context.inline_content_sizes(layout_context, constraint_space)
1034    }
1035}
1036
1037impl IndependentFormattingContext {
1038    /// Lay out an in-flow block-level box that establishes an independent
1039    /// formatting context in its containing formatting context.
1040    ///
1041    /// - <https://drafts.csswg.org/css2/visudet.html#blockwidth>
1042    /// - <https://drafts.csswg.org/css2/visudet.html#block-replaced-width>
1043    /// - <https://drafts.csswg.org/css2/visudet.html#normal-block>
1044    /// - <https://drafts.csswg.org/css2/visudet.html#inline-replaced-height>
1045    pub(crate) fn layout_in_flow_block_level(
1046        &self,
1047        layout_context: &LayoutContext,
1048        positioning_context: &mut PositioningContext,
1049        containing_block: &ContainingBlock,
1050        sequential_layout_state: Option<&mut SequentialLayoutState>,
1051        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
1052        has_inline_parent: bool,
1053    ) -> BoxFragment {
1054        if let Some(sequential_layout_state) = sequential_layout_state {
1055            return self.layout_in_flow_block_level_sequentially(
1056                layout_context,
1057                positioning_context,
1058                containing_block,
1059                sequential_layout_state,
1060                ignore_block_margins_for_stretch,
1061                has_inline_parent,
1062            );
1063        }
1064
1065        let get_inline_content_sizes = |constraint_space: &ConstraintSpace| {
1066            self.inline_content_sizes(layout_context, constraint_space)
1067                .sizes
1068        };
1069        let layout_style = self.layout_style();
1070        let ContainingBlockPaddingAndBorder {
1071            containing_block: containing_block_for_children,
1072            pbm,
1073            block_sizes,
1074            depends_on_block_constraints,
1075            available_block_size,
1076            justify_self,
1077            preferred_aspect_ratio,
1078        } = solve_containing_block_padding_and_border_for_in_flow_box(
1079            containing_block,
1080            &layout_style,
1081            get_inline_content_sizes,
1082            ignore_block_margins_for_stretch,
1083            Some(self),
1084            has_inline_parent,
1085        );
1086
1087        let lazy_block_size = LazySize::new(
1088            &block_sizes,
1089            Direction::Block,
1090            Size::FitContent,
1091            Au::zero,
1092            available_block_size,
1093            layout_style.is_table(),
1094        );
1095
1096        let layout = self.layout(
1097            layout_context,
1098            positioning_context,
1099            &containing_block_for_children,
1100            containing_block,
1101            preferred_aspect_ratio,
1102            &lazy_block_size,
1103        );
1104
1105        let inline_size = layout
1106            .content_inline_size_for_table
1107            .unwrap_or(containing_block_for_children.size.inline);
1108        let block_size = lazy_block_size.resolve(|| layout.content_block_size);
1109
1110        let ResolvedMargins {
1111            margin,
1112            effective_margin_inline_start,
1113        } = solve_margins(containing_block, &pbm, inline_size, justify_self);
1114
1115        let content_rect = LogicalRect {
1116            start_corner: LogicalVec2 {
1117                block: pbm.padding.block_start + pbm.border.block_start,
1118                inline: pbm.padding.inline_start +
1119                    pbm.border.inline_start +
1120                    effective_margin_inline_start,
1121            },
1122            size: LogicalVec2 {
1123                block: block_size,
1124                inline: inline_size,
1125            },
1126        };
1127
1128        let block_margins_collapsed_with_children = CollapsedBlockMargins::from_margin(&margin);
1129        let containing_block_writing_mode = containing_block.style.writing_mode;
1130
1131        let mut base_fragment_info = self.base.base_fragment_info;
1132        if depends_on_block_constraints {
1133            base_fragment_info.flags.insert(
1134                FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
1135            );
1136        }
1137        BoxFragment::new(
1138            base_fragment_info,
1139            self.base.style.clone(),
1140            layout.fragments,
1141            content_rect.as_physical(Some(containing_block)),
1142            pbm.padding.to_physical(containing_block_writing_mode),
1143            pbm.border.to_physical(containing_block_writing_mode),
1144            margin.to_physical(containing_block_writing_mode),
1145            layout.specific_layout_info,
1146        )
1147        .with_baselines(layout.baselines)
1148        .with_block_level_layout_info(block_margins_collapsed_with_children, None)
1149    }
1150
1151    /// Lay out a normal in flow non-replaced block that establishes an independent
1152    /// formatting context in its containing formatting context but handling sequential
1153    /// layout concerns, such clearing and placing the content next to floats.
1154    fn layout_in_flow_block_level_sequentially(
1155        &self,
1156        layout_context: &LayoutContext<'_>,
1157        positioning_context: &mut PositioningContext,
1158        containing_block: &ContainingBlock<'_>,
1159        sequential_layout_state: &mut SequentialLayoutState,
1160        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
1161        has_inline_parent: bool,
1162    ) -> BoxFragment {
1163        let style = &self.base.style;
1164        let containing_block_writing_mode = containing_block.style.writing_mode;
1165        let ContentBoxSizesAndPBM {
1166            content_box_sizes,
1167            pbm,
1168            depends_on_block_constraints,
1169            ..
1170        } = self
1171            .layout_style()
1172            .content_box_sizes_and_padding_border_margin(&containing_block.into());
1173
1174        let (margin_block_start, margin_block_end) =
1175            solve_block_margins_for_in_flow_block_level(&pbm);
1176        let collapsed_margin_block_start = CollapsedMargin::new(margin_block_start);
1177
1178        // From https://drafts.csswg.org/css2/#floats:
1179        // "The border box of a table, a block-level replaced element, or an element in
1180        //  the normal flow that establishes a new block formatting context (such as an
1181        //  element with overflow other than visible) must not overlap the margin box of
1182        //  any floats in the same block formatting context as the element itself. If
1183        //  necessary, implementations should clear the said element by placing it below
1184        //  any preceding floats, but may place it adjacent to such floats if there is
1185        //  sufficient space. They may even make the border box of said element narrower
1186        //  than defined by section 10.3.3. CSS 2 does not define when a UA may put said
1187        //  element next to the float or by how much said element may become narrower."
1188        let mut content_size;
1189        let mut layout;
1190        let mut placement_rect;
1191
1192        // First compute the clear position required by the 'clear' property.
1193        // The code below may then add extra clearance when the element can't fit
1194        // next to floats not covered by 'clear'.
1195        let clear_position = sequential_layout_state.calculate_clear_position(
1196            Clear::from_style_and_container_writing_mode(style, containing_block_writing_mode),
1197            &collapsed_margin_block_start,
1198        );
1199        let ceiling = clear_position.unwrap_or_else(|| {
1200            sequential_layout_state.position_without_clearance(&collapsed_margin_block_start)
1201        });
1202
1203        // Then compute a tentative block size.
1204        let pbm_sums = pbm.sums_auto_is_zero(ignore_block_margins_for_stretch);
1205        let available_block_size = containing_block
1206            .size
1207            .block
1208            .to_definite()
1209            .map(|block_size| Au::zero().max(block_size - pbm_sums.block));
1210        let is_table = self.is_table();
1211        let preferred_aspect_ratio = self.preferred_aspect_ratio(&pbm.padding_border_sums);
1212
1213        #[derive(Default)]
1214        struct Cache {
1215            min_block_size: Au,
1216            max_block_size: Option<Au>,
1217            tentative_block_size: SizeConstraint,
1218            depends_on_stretch_size: bool,
1219        }
1220        let mut cache = Cache {
1221            depends_on_stretch_size: true,
1222            ..Default::default()
1223        };
1224
1225        let update_cache = |cache: &mut Cache, stretch_size| {
1226            let tentative_block_content_size = self
1227                .tentative_block_content_size_with_dependency(preferred_aspect_ratio, stretch_size);
1228            let (preferred_block_size, min_block_size, max_block_size, depends_on_stretch_size) =
1229                if let Some(result) = tentative_block_content_size {
1230                    let (block_content_size, depends_on_stretch_size) = result;
1231                    let (preferred, min, max) = content_box_sizes.block.resolve_each(
1232                        Size::FitContent,
1233                        Au::zero,
1234                        available_block_size,
1235                        || block_content_size,
1236                        is_table,
1237                    );
1238                    (Some(preferred), min, max, depends_on_stretch_size)
1239                } else {
1240                    let (preferred, min, max) = content_box_sizes.block.resolve_each_extrinsic(
1241                        Size::FitContent,
1242                        Au::zero(),
1243                        available_block_size,
1244                    );
1245                    (preferred, min, max, false)
1246                };
1247            cache.min_block_size = min_block_size;
1248            cache.max_block_size = max_block_size;
1249            cache.tentative_block_size =
1250                SizeConstraint::new(preferred_block_size, min_block_size, max_block_size);
1251            cache.depends_on_stretch_size = depends_on_stretch_size;
1252        };
1253
1254        // With the tentative block size we can compute the inline min/max-content sizes.
1255        let get_inline_content_sizes = |cache: &Cache| {
1256            let constraint_space =
1257                ConstraintSpace::new(cache.tentative_block_size, style, preferred_aspect_ratio);
1258            self.inline_content_sizes(layout_context, &constraint_space)
1259                .sizes
1260        };
1261
1262        let justify_self = resolve_justify_self(style, containing_block.style, has_inline_parent);
1263        let automatic_inline_size = automatic_inline_size(justify_self, Some(self));
1264        let compute_inline_size = |cache: &mut Cache, stretch_size| {
1265            if cache.depends_on_stretch_size {
1266                update_cache(cache, stretch_size);
1267            }
1268            content_box_sizes.inline.resolve(
1269                Direction::Inline,
1270                automatic_inline_size,
1271                Au::zero,
1272                Some(stretch_size),
1273                || get_inline_content_sizes(cache),
1274                is_table,
1275            )
1276        };
1277
1278        let get_lazy_block_size = || {
1279            LazySize::new(
1280                &content_box_sizes.block,
1281                Direction::Block,
1282                Size::FitContent,
1283                Au::zero,
1284                available_block_size,
1285                is_table,
1286            )
1287        };
1288
1289        // The final inline size can depend on the available space, which depends on where
1290        // we are placing the box, since floats reduce the available space.
1291        // Here we assume that `compute_inline_size()` is a monotonically increasing function
1292        // with respect to the available space. Therefore, if we get the same result for 0
1293        // and for MAX_AU, it means that the function is constant.
1294        // TODO: `compute_inline_size()` may not be monotonic with `calc-size()`. For example,
1295        // `calc-size(stretch, (1px / (size + 1px) + sign(size)) * 1px)` would result in 1px
1296        // both when the available space is zero and infinity, but it's not constant.
1297        let inline_size_with_max_available_space = compute_inline_size(&mut cache, MAX_AU);
1298        let inline_size_with_no_available_space = compute_inline_size(&mut cache, Au::zero());
1299        if inline_size_with_no_available_space == inline_size_with_max_available_space {
1300            // If the inline size doesn't depend on the available inline space, we can just
1301            // compute it with an available inline space of zero. Then, after layout we can
1302            // compute the block size, and finally place among floats.
1303            let inline_size = inline_size_with_no_available_space;
1304            let lazy_block_size = get_lazy_block_size();
1305            layout = self.layout(
1306                layout_context,
1307                positioning_context,
1308                &ContainingBlock {
1309                    size: ContainingBlockSize {
1310                        inline: inline_size,
1311                        // `cache.tentative_block_size` can only depend on the inline stretch size
1312                        // for replaced elements, whose layout doesn't use the block size of the
1313                        // containing block for children.
1314                        block: cache.tentative_block_size,
1315                    },
1316                    style,
1317                },
1318                containing_block,
1319                preferred_aspect_ratio,
1320                &lazy_block_size,
1321            );
1322
1323            content_size = LogicalVec2 {
1324                block: lazy_block_size.resolve(|| layout.content_block_size),
1325                inline: layout.content_inline_size_for_table.unwrap_or(inline_size),
1326            };
1327
1328            let mut placement = PlacementAmongFloats::new(
1329                &sequential_layout_state.floats,
1330                ceiling,
1331                content_size + pbm.padding_border_sums,
1332                &pbm,
1333            );
1334            placement_rect = placement.place();
1335        } else {
1336            // If the inline size depends on the available space, then we need to iterate
1337            // the various placement candidates, resolve both the inline and block sizes
1338            // on each one placement area, and then check if the box actually fits it.
1339            // As an optimization, we first compute a lower bound of the final box size,
1340            // and skip placement candidates where not even the lower bound would fit.
1341            let minimum_size_of_block = LogicalVec2 {
1342                // For the lower bound of the inline size, simply assume no available space.
1343                // TODO: this won't work for things like `calc-size(stretch, 100px - size)`,
1344                // which should result in a bigger size when the available space gets smaller.
1345                inline: inline_size_with_no_available_space,
1346                // For the lower bound of the block size, also use the cached data that was
1347                // computed with no inline available space. If there is a dependency, it will
1348                // be monotonically increasing.
1349                // TODO: won't work e.g. for `block-size: calc-size(max-content, 100px - size)`
1350                // on a stretchable replaced element with an aspect ratio of 1/1: when the
1351                // inline available space is 0, it will resolve to 100px, but for 100px it
1352                // will resolve to 0.
1353                block: match cache.tentative_block_size {
1354                    // If we were able to resolve the preferred and maximum block sizes,
1355                    // use the tentative block size (it takes the 3 sizes into account).
1356                    SizeConstraint::Definite(size) if cache.max_block_size.is_some() => size,
1357                    // Oherwise the preferred or maximum block size might end up being zero,
1358                    // so can only rely on the minimum block size.
1359                    _ => cache.min_block_size,
1360                },
1361            } + pbm.padding_border_sums;
1362            let mut placement = PlacementAmongFloats::new(
1363                &sequential_layout_state.floats,
1364                ceiling,
1365                minimum_size_of_block,
1366                &pbm,
1367            );
1368
1369            loop {
1370                // First try to place the block using the minimum size as the object size.
1371                placement_rect = placement.place();
1372                let available_inline_size =
1373                    placement_rect.size.inline - pbm.padding_border_sums.inline;
1374                let proposed_inline_size = compute_inline_size(&mut cache, available_inline_size);
1375
1376                // Now lay out the block using the inline size we calculated from the placement.
1377                // Later we'll check to see if the resulting block size is compatible with the
1378                // placement.
1379                let positioning_context_length = positioning_context.len();
1380                let lazy_block_size = get_lazy_block_size();
1381                layout = self.layout(
1382                    layout_context,
1383                    positioning_context,
1384                    &ContainingBlock {
1385                        size: ContainingBlockSize {
1386                            inline: proposed_inline_size,
1387                            block: cache.tentative_block_size,
1388                        },
1389                        style,
1390                    },
1391                    containing_block,
1392                    preferred_aspect_ratio,
1393                    &lazy_block_size,
1394                );
1395
1396                let inline_size = if let Some(inline_size) = layout.content_inline_size_for_table {
1397                    // This is a table that ended up being smaller than predicted because of
1398                    // collapsed columns. Note we don't backtrack to consider areas that we
1399                    // previously thought weren't big enough.
1400                    // TODO: Should `minimum_size_of_block.inline` be zero for tables?
1401                    debug_assert!(inline_size < proposed_inline_size);
1402                    inline_size
1403                } else {
1404                    proposed_inline_size
1405                };
1406                content_size = LogicalVec2 {
1407                    block: lazy_block_size.resolve(|| layout.content_block_size),
1408                    inline: inline_size,
1409                };
1410
1411                // Now we know the block size of this attempted layout of a box with block
1412                // size of auto. Try to fit it into our precalculated placement among the
1413                // floats. If it fits, then we can stop trying layout candidates.
1414                if placement.try_to_expand_for_auto_block_size(
1415                    content_size.block + pbm.padding_border_sums.block,
1416                    &placement_rect.size,
1417                ) {
1418                    break;
1419                }
1420
1421                // The previous attempt to lay out this independent formatting context
1422                // among the floats did not work, so we must unhoist any boxes from that
1423                // attempt.
1424                positioning_context.truncate(&positioning_context_length);
1425            }
1426        }
1427
1428        // Only set clearance if we would have cleared or the placement among floats moves
1429        // the block further in the block direction. These two situations are the ones that
1430        // prevent margin collapse.
1431        let has_clearance = clear_position.is_some() || placement_rect.start_corner.block > ceiling;
1432        let clearance = has_clearance.then(|| {
1433            placement_rect.start_corner.block -
1434                sequential_layout_state
1435                    .position_with_zero_clearance(&collapsed_margin_block_start)
1436        });
1437
1438        let ((margin_inline_start, margin_inline_end), effective_margin_inline_start) =
1439            solve_inline_margins_avoiding_floats(
1440                sequential_layout_state,
1441                containing_block,
1442                &pbm,
1443                content_size.inline + pbm.padding_border_sums.inline,
1444                placement_rect,
1445                justify_self,
1446            );
1447
1448        let margin = LogicalSides {
1449            inline_start: margin_inline_start,
1450            inline_end: margin_inline_end,
1451            block_start: margin_block_start,
1452            block_end: margin_block_end,
1453        };
1454
1455        // Clearance prevents margin collapse between this block and previous ones,
1456        // so in that case collapse margins before adjoining them below.
1457        if clearance.is_some() {
1458            sequential_layout_state.commit_margin();
1459        }
1460        sequential_layout_state.adjoin_assign(&collapsed_margin_block_start);
1461
1462        // Margins can never collapse into independent formatting contexts.
1463        sequential_layout_state.commit_margin();
1464        sequential_layout_state.advance_block_position(
1465            pbm.padding_border_sums.block + content_size.block + clearance.unwrap_or_else(Au::zero),
1466        );
1467        sequential_layout_state.adjoin_assign(&CollapsedMargin::new(margin.block_end));
1468
1469        let content_rect = LogicalRect {
1470            start_corner: LogicalVec2 {
1471                block: pbm.padding.block_start +
1472                    pbm.border.block_start +
1473                    clearance.unwrap_or_else(Au::zero),
1474                inline: pbm.padding.inline_start +
1475                    pbm.border.inline_start +
1476                    effective_margin_inline_start,
1477            },
1478            size: content_size,
1479        };
1480
1481        let mut base_fragment_info = self.base.base_fragment_info;
1482        if depends_on_block_constraints {
1483            base_fragment_info.flags.insert(
1484                FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
1485            );
1486        }
1487
1488        BoxFragment::new(
1489            base_fragment_info,
1490            style.clone(),
1491            layout.fragments,
1492            content_rect.as_physical(Some(containing_block)),
1493            pbm.padding.to_physical(containing_block_writing_mode),
1494            pbm.border.to_physical(containing_block_writing_mode),
1495            margin.to_physical(containing_block_writing_mode),
1496            layout.specific_layout_info,
1497        )
1498        .with_baselines(layout.baselines)
1499        .with_block_level_layout_info(CollapsedBlockMargins::from_margin(&margin), clearance)
1500    }
1501}
1502
1503struct ContainingBlockPaddingAndBorder<'a> {
1504    containing_block: ContainingBlock<'a>,
1505    pbm: PaddingBorderMargin,
1506    block_sizes: Sizes,
1507    depends_on_block_constraints: bool,
1508    available_block_size: Option<Au>,
1509    justify_self: AlignFlags,
1510    preferred_aspect_ratio: Option<AspectRatio>,
1511}
1512
1513struct ResolvedMargins {
1514    /// Used value for the margin properties, as exposed in getComputedStyle().
1515    pub margin: LogicalSides<Au>,
1516
1517    /// Distance between the border box and the containing block on the inline-start side.
1518    /// This is typically the same as the inline-start margin, but can be greater when
1519    /// the box is justified within the free space in the containing block.
1520    /// The reason we aren't just adjusting the used margin-inline-start is that
1521    /// this shouldn't be observable via getComputedStyle().
1522    /// <https://drafts.csswg.org/css-align/#justify-self-property>
1523    pub effective_margin_inline_start: Au,
1524}
1525
1526/// Given the style for an in-flow box and its containing block, determine the containing
1527/// block for its children.
1528/// Note that in the presence of floats, this shouldn't be used for a block-level box
1529/// that establishes an independent formatting context (or is replaced), since the
1530/// inline size could then be incorrect.
1531fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
1532    containing_block: &ContainingBlock<'_>,
1533    layout_style: &'a LayoutStyle,
1534    get_inline_content_sizes: impl FnOnce(&ConstraintSpace) -> ContentSizes,
1535    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
1536    context: Option<&IndependentFormattingContext>,
1537    has_inline_parent: bool,
1538) -> ContainingBlockPaddingAndBorder<'a> {
1539    let style = layout_style.style();
1540    if matches!(style.pseudo(), Some(PseudoElement::ServoAnonymousBox)) {
1541        // <https://drafts.csswg.org/css2/#anonymous-block-level>
1542        // > Anonymous block boxes are ignored when resolving percentage values that would
1543        // > refer to it: the closest non-anonymous ancestor box is used instead.
1544        let containing_block_for_children = ContainingBlock {
1545            size: ContainingBlockSize {
1546                inline: containing_block.size.inline,
1547                block: containing_block.size.block,
1548            },
1549            style,
1550        };
1551        // <https://drafts.csswg.org/css2/#anonymous-block-level>
1552        // > Non-inherited properties have their initial value.
1553        return ContainingBlockPaddingAndBorder {
1554            containing_block: containing_block_for_children,
1555            pbm: PaddingBorderMargin::zero(),
1556            block_sizes: Sizes::default(),
1557            depends_on_block_constraints: false,
1558            // The available block size may actually be definite, but it should be irrelevant
1559            // since the sizing properties are set to their initial value.
1560            available_block_size: None,
1561            // The initial `justify-self` is `auto`, but use `normal` (behaving as `stretch`).
1562            // This is being discussed in <https://github.com/w3c/csswg-drafts/issues/11461>.
1563            justify_self: AlignFlags::NORMAL,
1564            preferred_aspect_ratio: None,
1565        };
1566    }
1567
1568    let ContentBoxSizesAndPBM {
1569        content_box_sizes,
1570        pbm,
1571        depends_on_block_constraints,
1572        ..
1573    } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
1574
1575    let pbm_sums = pbm.sums_auto_is_zero(ignore_block_margins_for_stretch);
1576    let available_inline_size = Au::zero().max(containing_block.size.inline - pbm_sums.inline);
1577    let available_block_size = containing_block
1578        .size
1579        .block
1580        .to_definite()
1581        .map(|block_size| Au::zero().max(block_size - pbm_sums.block));
1582
1583    // TODO: support preferred aspect ratios on boxes that don't establish an independent
1584    // formatting context.
1585    let preferred_aspect_ratio =
1586        context.and_then(|context| context.preferred_aspect_ratio(&pbm.padding_border_sums));
1587    let is_table = layout_style.is_table();
1588
1589    // https://drafts.csswg.org/css2/#the-height-property
1590    // https://drafts.csswg.org/css2/visudet.html#min-max-heights
1591    let tentative_block_content_size = context.and_then(|context| {
1592        context.tentative_block_content_size(preferred_aspect_ratio, available_inline_size)
1593    });
1594    let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
1595        SizeConstraint::Definite(content_box_sizes.block.resolve(
1596            Direction::Block,
1597            Size::FitContent,
1598            Au::zero,
1599            available_block_size,
1600            || block_content_size,
1601            is_table,
1602        ))
1603    } else {
1604        content_box_sizes.block.resolve_extrinsic(
1605            Size::FitContent,
1606            Au::zero(),
1607            available_block_size,
1608        )
1609    };
1610
1611    // https://drafts.csswg.org/css2/#the-width-property
1612    // https://drafts.csswg.org/css2/visudet.html#min-max-widths
1613    let get_inline_content_sizes = || {
1614        get_inline_content_sizes(&ConstraintSpace::new(
1615            tentative_block_size,
1616            style,
1617            preferred_aspect_ratio,
1618        ))
1619    };
1620    let justify_self = resolve_justify_self(style, containing_block.style, has_inline_parent);
1621    let inline_size = content_box_sizes.inline.resolve(
1622        Direction::Inline,
1623        automatic_inline_size(justify_self, context),
1624        Au::zero,
1625        Some(available_inline_size),
1626        get_inline_content_sizes,
1627        is_table,
1628    );
1629
1630    let containing_block_for_children = ContainingBlock {
1631        size: ContainingBlockSize {
1632            inline: inline_size,
1633            block: tentative_block_size,
1634        },
1635        style,
1636    };
1637    // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
1638    assert_eq!(
1639        containing_block.style.writing_mode.is_horizontal(),
1640        containing_block_for_children
1641            .style
1642            .writing_mode
1643            .is_horizontal(),
1644        "Vertical writing modes are not supported yet"
1645    );
1646    ContainingBlockPaddingAndBorder {
1647        containing_block: containing_block_for_children,
1648        pbm,
1649        block_sizes: content_box_sizes.block,
1650        depends_on_block_constraints,
1651        available_block_size,
1652        justify_self,
1653        preferred_aspect_ratio,
1654    }
1655}
1656
1657/// Given the containing block and size of an in-flow box, determine the margins.
1658/// Note that in the presence of floats, this shouldn't be used for a block-level box
1659/// that establishes an independent formatting context (or is replaced), since the
1660/// margins could then be incorrect.
1661fn solve_margins(
1662    containing_block: &ContainingBlock<'_>,
1663    pbm: &PaddingBorderMargin,
1664    inline_size: Au,
1665    justify_self: AlignFlags,
1666) -> ResolvedMargins {
1667    let (inline_margins, effective_margin_inline_start) =
1668        solve_inline_margins_for_in_flow_block_level(
1669            containing_block,
1670            pbm,
1671            inline_size,
1672            justify_self,
1673        );
1674    let block_margins = solve_block_margins_for_in_flow_block_level(pbm);
1675    ResolvedMargins {
1676        margin: LogicalSides {
1677            inline_start: inline_margins.0,
1678            inline_end: inline_margins.1,
1679            block_start: block_margins.0,
1680            block_end: block_margins.1,
1681        },
1682        effective_margin_inline_start,
1683    }
1684}
1685
1686/// Resolves 'auto' margins of an in-flow block-level box in the block axis.
1687/// <https://drafts.csswg.org/css2/#normal-block>
1688/// <https://drafts.csswg.org/css2/#block-root-margin>
1689fn solve_block_margins_for_in_flow_block_level(pbm: &PaddingBorderMargin) -> (Au, Au) {
1690    (
1691        pbm.margin.block_start.auto_is(Au::zero),
1692        pbm.margin.block_end.auto_is(Au::zero),
1693    )
1694}
1695
1696/// Resolves the `justify-self` value, preserving flags.
1697fn resolve_justify_self(
1698    style: &ComputedValues,
1699    containing_block_style: &ComputedValues,
1700    has_inline_parent: bool,
1701) -> AlignFlags {
1702    // `justify-self: auto` behaves as the computed `justify-items` value of the parent box.
1703    // The parent box is generally the containing block, but it can also be an inline box.
1704    // In that case, since `justify-items` doesn't apply to inline boxes, we need to treat
1705    // `justify-self: auto` as `normal`.
1706    // See the resolution in <https://github.com/w3c/csswg-drafts/issues/11462>.
1707    let alignment = match style.clone_justify_self().0 {
1708        AlignFlags::AUTO if has_inline_parent => AlignFlags::NORMAL,
1709        AlignFlags::AUTO => containing_block_style.clone_justify_items().computed.0.0,
1710        alignment => alignment,
1711    };
1712    let is_ltr = |style: &ComputedValues| style.writing_mode.line_left_is_inline_start();
1713    let alignment_value = match alignment.value() {
1714        AlignFlags::LEFT if is_ltr(containing_block_style) => AlignFlags::START,
1715        AlignFlags::LEFT => AlignFlags::END,
1716        AlignFlags::RIGHT if is_ltr(containing_block_style) => AlignFlags::END,
1717        AlignFlags::RIGHT => AlignFlags::START,
1718        AlignFlags::SELF_START if is_ltr(containing_block_style) == is_ltr(style) => {
1719            AlignFlags::START
1720        },
1721        AlignFlags::SELF_START => AlignFlags::END,
1722        AlignFlags::SELF_END if is_ltr(containing_block_style) == is_ltr(style) => AlignFlags::END,
1723        AlignFlags::SELF_END => AlignFlags::START,
1724        alignment_value => alignment_value,
1725    };
1726    alignment.flags() | alignment_value
1727}
1728
1729/// Determines the automatic size for the inline axis of a block-level box.
1730/// <https://drafts.csswg.org/css-sizing-3/#automatic-size>
1731#[inline]
1732fn automatic_inline_size<T>(
1733    justify_self: AlignFlags,
1734    context: Option<&IndependentFormattingContext>,
1735) -> Size<T> {
1736    let normal_stretches = || {
1737        !context.is_some_and(|context| {
1738            context
1739                .base
1740                .base_fragment_info
1741                .flags
1742                .intersects(FragmentFlags::IS_REPLACED | FragmentFlags::IS_WIDGET) ||
1743                context.is_table()
1744        })
1745    };
1746    match justify_self {
1747        AlignFlags::STRETCH => Size::Stretch,
1748        AlignFlags::NORMAL if normal_stretches() => Size::Stretch,
1749        _ => Size::FitContent,
1750    }
1751}
1752
1753/// Justifies a block-level box, distributing the free space according to `justify-self`.
1754/// Note `<center>` and `<div align>` are implemented via internal 'text-align' values,
1755/// which are also handled here.
1756/// The provided free space should already take margins into account. In particular,
1757/// it should be zero if there is an auto margin.
1758/// <https://drafts.csswg.org/css-align/#justify-block>
1759fn justify_self_alignment(
1760    containing_block: &ContainingBlock,
1761    free_space: Au,
1762    justify_self: AlignFlags,
1763) -> Au {
1764    let mut alignment = justify_self.value();
1765    let is_safe = justify_self.flags() == AlignFlags::SAFE || alignment == AlignFlags::NORMAL;
1766    if is_safe && free_space <= Au::zero() {
1767        alignment = AlignFlags::START
1768    }
1769    match alignment {
1770        AlignFlags::NORMAL => {},
1771        AlignFlags::CENTER => return free_space / 2,
1772        AlignFlags::END => return free_space,
1773        _ => return Au::zero(),
1774    }
1775
1776    // For `justify-self: normal`, fall back to the special 'text-align' values.
1777    let style = containing_block.style;
1778    match style.clone_text_align() {
1779        TextAlignKeyword::MozCenter => free_space / 2,
1780        TextAlignKeyword::MozLeft if !style.writing_mode.line_left_is_inline_start() => free_space,
1781        TextAlignKeyword::MozRight if style.writing_mode.line_left_is_inline_start() => free_space,
1782        _ => Au::zero(),
1783    }
1784}
1785
1786/// Resolves 'auto' margins of an in-flow block-level box in the inline axis,
1787/// distributing the free space in the containing block.
1788///
1789/// This is based on CSS2.1 ยง 10.3.3 <https://drafts.csswg.org/css2/#blockwidth>
1790/// but without adjusting the margins in "over-contrained" cases, as mandated by
1791/// <https://drafts.csswg.org/css-align/#justify-block>.
1792///
1793/// Note that in the presence of floats, this shouldn't be used for a block-level box
1794/// that establishes an independent formatting context (or is replaced).
1795///
1796/// In addition to the used margins, it also returns the effective margin-inline-start
1797/// (see ContainingBlockPaddingAndBorder).
1798fn solve_inline_margins_for_in_flow_block_level(
1799    containing_block: &ContainingBlock,
1800    pbm: &PaddingBorderMargin,
1801    inline_size: Au,
1802    justify_self: AlignFlags,
1803) -> ((Au, Au), Au) {
1804    let free_space = containing_block.size.inline - pbm.padding_border_sums.inline - inline_size;
1805    let mut justification = Au::zero();
1806    let inline_margins = match (pbm.margin.inline_start, pbm.margin.inline_end) {
1807        (AuOrAuto::Auto, AuOrAuto::Auto) => {
1808            let start = Au::zero().max(free_space / 2);
1809            (start, free_space - start)
1810        },
1811        (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
1812            (Au::zero().max(free_space - end), end)
1813        },
1814        (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => (start, free_space - start),
1815        (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
1816            // In the cases above, the free space is zero after taking 'auto' margins into account.
1817            // But here we may still have some free space to perform 'justify-self' alignment.
1818            // This aligns the margin box within the containing block, or in other words,
1819            // aligns the border box within the margin-shrunken containing block.
1820            justification =
1821                justify_self_alignment(containing_block, free_space - start - end, justify_self);
1822            (start, end)
1823        },
1824    };
1825    let effective_margin_inline_start = inline_margins.0 + justification;
1826    (inline_margins, effective_margin_inline_start)
1827}
1828
1829/// Resolves 'auto' margins of an in-flow block-level box in the inline axis
1830/// similarly to |solve_inline_margins_for_in_flow_block_level|. However,
1831/// they align within the provided rect (instead of the containing block),
1832/// to avoid overlapping floats.
1833/// In addition to the used margins, it also returns the effective
1834/// margin-inline-start (see ContainingBlockPaddingAndBorder).
1835/// It may differ from the used inline-start margin if the computed value
1836/// wasn't 'auto' and there are floats to avoid or the box is justified.
1837/// See <https://github.com/w3c/csswg-drafts/issues/9174>
1838fn solve_inline_margins_avoiding_floats(
1839    sequential_layout_state: &SequentialLayoutState,
1840    containing_block: &ContainingBlock,
1841    pbm: &PaddingBorderMargin,
1842    inline_size: Au,
1843    placement_rect: LogicalRect<Au>,
1844    justify_self: AlignFlags,
1845) -> ((Au, Au), Au) {
1846    // PlacementAmongFloats should guarantee that the inline size of the placement rect
1847    // is at least as big as `inline_size`. However, that may fail when dealing with
1848    // huge sizes that need to be saturated to MAX_AU, so floor by zero. See #37312.
1849    let free_space = Au::zero().max(placement_rect.size.inline - inline_size);
1850    let cb_info = &sequential_layout_state.floats.containing_block_info;
1851    let start_adjustment = placement_rect.start_corner.inline - cb_info.inline_start;
1852    let end_adjustment = cb_info.inline_end - placement_rect.max_inline_position();
1853    let mut justification = Au::zero();
1854    let inline_margins = match (pbm.margin.inline_start, pbm.margin.inline_end) {
1855        (AuOrAuto::Auto, AuOrAuto::Auto) => {
1856            let half = free_space / 2;
1857            (start_adjustment + half, end_adjustment + free_space - half)
1858        },
1859        (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => (start_adjustment + free_space, end),
1860        (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => (start, end_adjustment + free_space),
1861        (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
1862            // The spec says 'justify-self' aligns the margin box within the float-shrunken
1863            // containing block. That's wrong (https://github.com/w3c/csswg-drafts/issues/9963),
1864            // and Blink and WebKit are broken anyways. So we match Gecko instead: this aligns
1865            // the border box within the instersection of the float-shrunken containing-block
1866            // and the margin-shrunken containing-block.
1867            justification = justify_self_alignment(containing_block, free_space, justify_self);
1868            (start, end)
1869        },
1870    };
1871    let effective_margin_inline_start = inline_margins.0.max(start_adjustment) + justification;
1872    (inline_margins, effective_margin_inline_start)
1873}
1874
1875/// State that we maintain when placing blocks.
1876///
1877/// In parallel mode, this placement is done after all child blocks are laid out. In
1878/// sequential mode, this is done right after each block is laid out.
1879struct PlacementState<'container> {
1880    next_in_flow_margin_collapses_with_parent_start_margin: bool,
1881    last_in_flow_margin_collapses_with_parent_end_margin: bool,
1882    start_margin: CollapsedMargin,
1883    current_margin: CollapsedMargin,
1884    current_block_direction_position: Au,
1885    inflow_baselines: Baselines,
1886    is_inline_block_context: bool,
1887
1888    /// If this [`PlacementState`] is laying out a list item with an outside marker. Record the
1889    /// block size of that marker, because the content block size of the list item needs to be at
1890    /// least as tall as the marker size -- even though the marker doesn't advance the block
1891    /// position of the placement.
1892    marker_block_size: Option<Au>,
1893
1894    /// The [`ContainingBlock`] of the container into which this [`PlacementState`] is laying out
1895    /// fragments. This is used to convert between physical and logical geometry.
1896    containing_block: &'container ContainingBlock<'container>,
1897}
1898
1899impl<'container> PlacementState<'container> {
1900    fn new(
1901        collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
1902        containing_block: &'container ContainingBlock<'container>,
1903    ) -> PlacementState<'container> {
1904        let is_inline_block_context =
1905            containing_block.style.get_box().clone_display() == Display::InlineBlock;
1906        PlacementState {
1907            next_in_flow_margin_collapses_with_parent_start_margin:
1908                collapsible_with_parent_start_margin.0,
1909            last_in_flow_margin_collapses_with_parent_end_margin: true,
1910            start_margin: CollapsedMargin::zero(),
1911            current_margin: CollapsedMargin::zero(),
1912            current_block_direction_position: Au::zero(),
1913            inflow_baselines: Baselines::default(),
1914            is_inline_block_context,
1915            marker_block_size: None,
1916            containing_block,
1917        }
1918    }
1919
1920    fn place_fragment_and_update_baseline(
1921        &mut self,
1922        fragment: &mut Fragment,
1923        sequential_layout_state: Option<&mut SequentialLayoutState>,
1924    ) {
1925        self.place_fragment(fragment, sequential_layout_state);
1926
1927        let box_fragment = match fragment {
1928            Fragment::LayoutRoot(..) | Fragment::Box(..) => fragment
1929                .retrieve_box_fragment()
1930                .expect("Should be guaranteed by surrounding check"),
1931            _ => return,
1932        };
1933
1934        // From <https://drafts.csswg.org/css-align-3/#baseline-export>:
1935        // > When finding the first/last baseline set of an inline-block, any baselines
1936        // > contributed by table boxes must be skipped. (This quirk is a legacy behavior from
1937        // > [CSS2].)
1938        if self.is_inline_block_context && box_fragment.is_table_wrapper() {
1939            return;
1940        }
1941
1942        let box_block_offset = box_fragment
1943            .content_rect()
1944            .origin
1945            .to_logical(self.containing_block)
1946            .block;
1947        let box_fragment_baselines =
1948            box_fragment.baselines(self.containing_block.style.writing_mode);
1949        if let (None, Some(first)) = (self.inflow_baselines.first, box_fragment_baselines.first) {
1950            self.inflow_baselines.first = Some(first + box_block_offset);
1951        }
1952        if let Some(last) = box_fragment_baselines.last {
1953            self.inflow_baselines.last = Some(last + box_block_offset);
1954        }
1955    }
1956
1957    /// Place a single [Fragment] in a block level context using the state so far and
1958    /// information gathered from the [Fragment] itself.
1959    fn place_fragment(
1960        &mut self,
1961        fragment: &mut Fragment,
1962        sequential_layout_state: Option<&mut SequentialLayoutState>,
1963    ) {
1964        match fragment {
1965            Fragment::LayoutRoot(..) | Fragment::Box(..) => {
1966                let fragment = fragment
1967                    .retrieve_box_fragment()
1968                    .expect("Should be guaranteed by surrounding condition");
1969
1970                // If this child is a marker positioned outside of a list item, then record its
1971                // size, but also ensure that it doesn't advance the block position of the placment.
1972                // This ensures item content is placed next to the marker.
1973                //
1974                // This is a pretty big hack because it doesn't properly handle all interactions
1975                // between the marker and the item. For instance the marker should be positioned at
1976                // the baseline of list item content and the first line of the item content should
1977                // be at least as tall as the marker -- not the entire list item itself.
1978                let is_outside_marker = fragment
1979                    .base
1980                    .flags
1981                    .contains(FragmentFlags::IS_OUTSIDE_LIST_ITEM_MARKER);
1982                if is_outside_marker {
1983                    assert!(self.marker_block_size.is_none());
1984                    self.marker_block_size = Some(
1985                        fragment
1986                            .content_rect()
1987                            .size
1988                            .to_logical(self.containing_block.style.writing_mode)
1989                            .block,
1990                    );
1991                    return;
1992                }
1993
1994                let BlockLevelLayoutInfo {
1995                    clearance,
1996                    block_margins_collapsed_with_children: fragment_block_margins,
1997                } = *fragment
1998                    .block_level_layout_info
1999                    .clone()
2000                    .expect("A block-level fragment should have a BlockLevelLayoutInfo.");
2001                let mut fragment_block_size = fragment
2002                    .border_rect()
2003                    .size
2004                    .to_logical(self.containing_block.style.writing_mode)
2005                    .block;
2006
2007                // We use `last_in_flow_margin_collapses_with_parent_end_margin` to implement
2008                // this quote from https://drafts.csswg.org/css2/#collapsing-margins
2009                // > If the top and bottom margins of an element with clearance are adjoining,
2010                // > its margins collapse with the adjoining margins of following siblings but that
2011                // > resulting margin does not collapse with the bottom margin of the parent block.
2012                if let Some(clearance) = clearance {
2013                    fragment_block_size += clearance;
2014                    // Margins can't be adjoining if they are separated by clearance.
2015                    // Setting `next_in_flow_margin_collapses_with_parent_start_margin` to false
2016                    // prevents collapsing with the start margin of the parent, and will set
2017                    // `collapsed_through` to false, preventing the parent from collapsing through.
2018                    self.current_block_direction_position += self.current_margin.solve();
2019                    self.current_margin = CollapsedMargin::zero();
2020                    self.next_in_flow_margin_collapses_with_parent_start_margin = false;
2021                    if fragment_block_margins.collapsed_through {
2022                        self.last_in_flow_margin_collapses_with_parent_end_margin = false;
2023                    }
2024                } else if !fragment_block_margins.collapsed_through {
2025                    self.last_in_flow_margin_collapses_with_parent_end_margin = true;
2026                }
2027
2028                if self.next_in_flow_margin_collapses_with_parent_start_margin {
2029                    debug_assert!(self.current_margin.solve().is_zero());
2030                    self.start_margin
2031                        .adjoin_assign(&fragment_block_margins.start);
2032                    if fragment_block_margins.collapsed_through {
2033                        self.start_margin.adjoin_assign(&fragment_block_margins.end);
2034                        return;
2035                    }
2036                    self.next_in_flow_margin_collapses_with_parent_start_margin = false;
2037                } else {
2038                    self.current_margin
2039                        .adjoin_assign(&fragment_block_margins.start);
2040                }
2041
2042                fragment.base.translate_rect(
2043                    LogicalVec2 {
2044                        inline: Au::zero(),
2045                        block: self.current_margin.solve() + self.current_block_direction_position,
2046                    }
2047                    .to_physical_size(self.containing_block.style.writing_mode),
2048                );
2049
2050                if fragment_block_margins.collapsed_through {
2051                    // `fragment_block_size` is typically zero when collapsing through,
2052                    // but we still need to consider it in case there is clearance.
2053                    self.current_block_direction_position += fragment_block_size;
2054                    self.current_margin
2055                        .adjoin_assign(&fragment_block_margins.end);
2056                } else {
2057                    self.current_block_direction_position +=
2058                        self.current_margin.solve() + fragment_block_size;
2059                    self.current_margin = fragment_block_margins.end;
2060                }
2061            },
2062            Fragment::AbsoluteOrFixedPositionedPlaceholder(fragment) => {
2063                // The alignment of absolutes in block flow layout is always "start", so the size of
2064                // the static position rectangle does not matter.
2065                fragment.borrow_mut().original_static_position_rect = LogicalRect {
2066                    start_corner: LogicalVec2 {
2067                        block: (self.current_margin.solve() +
2068                            self.current_block_direction_position),
2069                        inline: Au::zero(),
2070                    },
2071                    size: LogicalVec2::zero(),
2072                }
2073                .as_physical(Some(self.containing_block));
2074            },
2075            Fragment::Float(box_fragment) => {
2076                let sequential_layout_state = sequential_layout_state
2077                    .expect("Found float fragment without SequentialLayoutState");
2078                let block_offset_from_containing_block_top =
2079                    self.current_block_direction_position + self.current_margin.solve();
2080                sequential_layout_state.place_float_fragment(
2081                    box_fragment,
2082                    self.containing_block,
2083                    self.start_margin,
2084                    block_offset_from_containing_block_top,
2085                );
2086            },
2087            Fragment::Positioning(_) => {},
2088            _ => unreachable!("Unexpected Fragment type encountered during flow layout"),
2089        }
2090    }
2091
2092    fn finish(mut self) -> (Au, CollapsedBlockMargins, Baselines) {
2093        if !self.last_in_flow_margin_collapses_with_parent_end_margin {
2094            self.current_block_direction_position += self.current_margin.solve();
2095            self.current_margin = CollapsedMargin::zero();
2096        }
2097        let (total_block_size, collapsed_through) = match self.marker_block_size {
2098            Some(marker_block_size) => (
2099                self.current_block_direction_position.max(marker_block_size),
2100                // If this is a list item (even empty) with an outside marker, then it
2101                // should not collapse through.
2102                false,
2103            ),
2104            None => (
2105                self.current_block_direction_position,
2106                self.next_in_flow_margin_collapses_with_parent_start_margin,
2107            ),
2108        };
2109
2110        (
2111            total_block_size,
2112            CollapsedBlockMargins {
2113                collapsed_through,
2114                start: self.start_margin,
2115                end: self.current_margin,
2116            },
2117            self.inflow_baselines,
2118        )
2119    }
2120}
2121
2122pub(crate) struct IndependentFloatOrAtomicLayoutResult {
2123    pub fragment: BoxFragment,
2124    pub baselines: Baselines,
2125    pub pbm_sums: LogicalSides<Au>,
2126}
2127
2128impl IndependentFormattingContext {
2129    pub(crate) fn layout_float_or_atomic_inline(
2130        &self,
2131        layout_context: &LayoutContext,
2132        child_positioning_context: &mut PositioningContext,
2133        containing_block: &ContainingBlock,
2134    ) -> IndependentFloatOrAtomicLayoutResult {
2135        let style = self.style();
2136        let container_writing_mode = containing_block.style.writing_mode;
2137        let layout_style = self.layout_style();
2138        let content_box_sizes_and_pbm =
2139            layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
2140        let pbm = &content_box_sizes_and_pbm.pbm;
2141        let margin = pbm.margin.auto_is(Au::zero);
2142        let pbm_sums = pbm.padding + pbm.border + margin;
2143        let preferred_aspect_ratio = self.preferred_aspect_ratio(&pbm.padding_border_sums);
2144        let is_table = self.is_table();
2145
2146        let available_inline_size =
2147            Au::zero().max(containing_block.size.inline - pbm_sums.inline_sum());
2148        let available_block_size = containing_block
2149            .size
2150            .block
2151            .to_definite()
2152            .map(|block_size| Au::zero().max(block_size - pbm_sums.block_sum()));
2153
2154        let tentative_block_content_size =
2155            self.tentative_block_content_size(preferred_aspect_ratio, available_inline_size);
2156        let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
2157            SizeConstraint::Definite(content_box_sizes_and_pbm.content_box_sizes.block.resolve(
2158                Direction::Block,
2159                Size::FitContent,
2160                Au::zero,
2161                available_block_size,
2162                || block_content_size,
2163                is_table,
2164            ))
2165        } else {
2166            content_box_sizes_and_pbm
2167                .content_box_sizes
2168                .block
2169                .resolve_extrinsic(Size::FitContent, Au::zero(), available_block_size)
2170        };
2171
2172        let get_content_size = || {
2173            let constraint_space =
2174                ConstraintSpace::new(tentative_block_size, style, preferred_aspect_ratio);
2175            self.inline_content_sizes(layout_context, &constraint_space)
2176                .sizes
2177        };
2178
2179        let inline_size = content_box_sizes_and_pbm.content_box_sizes.inline.resolve(
2180            Direction::Inline,
2181            Size::FitContent,
2182            Au::zero,
2183            Some(available_inline_size),
2184            get_content_size,
2185            is_table,
2186        );
2187
2188        let containing_block_for_children = ContainingBlock {
2189            size: ContainingBlockSize {
2190                inline: inline_size,
2191                block: tentative_block_size,
2192            },
2193            style,
2194        };
2195        assert_eq!(
2196            container_writing_mode.is_horizontal(),
2197            style.writing_mode.is_horizontal(),
2198            "Mixed horizontal and vertical writing modes are not supported yet"
2199        );
2200
2201        let lazy_block_size = LazySize::new(
2202            &content_box_sizes_and_pbm.content_box_sizes.block,
2203            Direction::Block,
2204            Size::FitContent,
2205            Au::zero,
2206            available_block_size,
2207            is_table,
2208        );
2209
2210        let IndependentFormattingContextLayoutResult {
2211            content_inline_size_for_table,
2212            content_block_size,
2213            fragments,
2214            baselines,
2215            specific_layout_info,
2216            ..
2217        } = self.layout(
2218            layout_context,
2219            child_positioning_context,
2220            &containing_block_for_children,
2221            containing_block,
2222            preferred_aspect_ratio,
2223            &lazy_block_size,
2224        );
2225
2226        let content_size = LogicalVec2 {
2227            inline: content_inline_size_for_table.unwrap_or(inline_size),
2228            block: lazy_block_size.resolve(|| content_block_size),
2229        }
2230        .to_physical_size(container_writing_mode);
2231        let content_rect = PhysicalRect::new(PhysicalPoint::zero(), content_size);
2232
2233        let mut base_fragment_info = self.base_fragment_info();
2234        if content_box_sizes_and_pbm.depends_on_block_constraints {
2235            base_fragment_info.flags.insert(
2236                FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
2237            );
2238        }
2239
2240        // Floats can have clearance, but it's handled internally by the float placement logic,
2241        // so there's no need to store it explicitly in the fragment.
2242        // And atomic inlines don't have clearance.
2243        let fragment = BoxFragment::new(
2244            base_fragment_info,
2245            style.clone(),
2246            fragments,
2247            content_rect,
2248            pbm.padding.to_physical(container_writing_mode),
2249            pbm.border.to_physical(container_writing_mode),
2250            margin.to_physical(container_writing_mode),
2251            specific_layout_info,
2252        );
2253
2254        IndependentFloatOrAtomicLayoutResult {
2255            fragment,
2256            baselines,
2257            pbm_sums,
2258        }
2259    }
2260}