layout/flow/
mod.rs

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