Skip to main content

layout/flow/
mod.rs

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