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