Skip to main content

layout/flow/
mod.rs

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