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