layout/flow/
mod.rs

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