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