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 =
1389            automatic_inline_size(justify_self, is_table, self.is_replaced());
1390        let compute_inline_size = |stretch_size| {
1391            content_box_sizes.inline.resolve(
1392                Direction::Inline,
1393                automatic_inline_size,
1394                Au::zero,
1395                Some(stretch_size),
1396                get_inline_content_sizes,
1397                is_table,
1398            )
1399        };
1400
1401        let get_lazy_block_size = || {
1402            LazySize::new(
1403                &content_box_sizes.block,
1404                Direction::Block,
1405                Size::FitContent,
1406                Au::zero,
1407                available_block_size,
1408                is_table,
1409            )
1410        };
1411
1412        // The final inline size can depend on the available space, which depends on where
1413        // we are placing the box, since floats reduce the available space.
1414        // Here we assume that `compute_inline_size()` is a monotonically increasing function
1415        // with respect to the available space. Therefore, if we get the same result for 0
1416        // and for MAX_AU, it means that the function is constant.
1417        // TODO: `compute_inline_size()` may not be monotonic with `calc-size()`. For example,
1418        // `calc-size(stretch, (1px / (size + 1px) + sign(size)) * 1px)` would result in 1px
1419        // both when the available space is zero and infinity, but it's not constant.
1420        let inline_size_with_no_available_space = compute_inline_size(Au::zero());
1421        if inline_size_with_no_available_space == compute_inline_size(MAX_AU) {
1422            // If the inline size doesn't depend on the available inline space, we can just
1423            // compute it with an available inline space of zero. Then, after layout we can
1424            // compute the block size, and finally place among floats.
1425            let inline_size = inline_size_with_no_available_space;
1426            let lazy_block_size = get_lazy_block_size();
1427            layout = self.layout(
1428                layout_context,
1429                positioning_context,
1430                &ContainingBlock {
1431                    size: ContainingBlockSize {
1432                        inline: inline_size,
1433                        block: tentative_block_size,
1434                    },
1435                    style,
1436                },
1437                containing_block,
1438                preferred_aspect_ratio,
1439                &lazy_block_size,
1440            );
1441
1442            content_size = LogicalVec2 {
1443                block: lazy_block_size.resolve(|| layout.content_block_size),
1444                inline: layout.content_inline_size_for_table.unwrap_or(inline_size),
1445            };
1446
1447            let mut placement = PlacementAmongFloats::new(
1448                &sequential_layout_state.floats,
1449                ceiling,
1450                content_size + pbm.padding_border_sums,
1451                &pbm,
1452            );
1453            placement_rect = placement.place();
1454        } else {
1455            // If the inline size depends on the available space, then we need to iterate
1456            // the various placement candidates, resolve both the inline and block sizes
1457            // on each one placement area, and then check if the box actually fits it.
1458            // As an optimization, we first compute a lower bound of the final box size,
1459            // and skip placement candidates where not even the lower bound would fit.
1460            let minimum_size_of_block = LogicalVec2 {
1461                // For the lower bound of the inline size, simply assume no available space.
1462                // TODO: this won't work for things like `calc-size(stretch, 100px - size)`,
1463                // which should result in a bigger size when the available space gets smaller.
1464                inline: inline_size_with_no_available_space,
1465                block: match tentative_block_size {
1466                    // If we were able to resolve the preferred and maximum block sizes,
1467                    // use the tentative block size (it takes the 3 sizes into account).
1468                    SizeConstraint::Definite(size) if max_block_size.is_some() => size,
1469                    // Oherwise the preferred or maximum block size might end up being zero,
1470                    // so can only rely on the minimum block size.
1471                    _ => min_block_size,
1472                },
1473            } + pbm.padding_border_sums;
1474            let mut placement = PlacementAmongFloats::new(
1475                &sequential_layout_state.floats,
1476                ceiling,
1477                minimum_size_of_block,
1478                &pbm,
1479            );
1480
1481            loop {
1482                // First try to place the block using the minimum size as the object size.
1483                placement_rect = placement.place();
1484                let available_inline_size =
1485                    placement_rect.size.inline - pbm.padding_border_sums.inline;
1486                let proposed_inline_size = compute_inline_size(available_inline_size);
1487
1488                // Now lay out the block using the inline size we calculated from the placement.
1489                // Later we'll check to see if the resulting block size is compatible with the
1490                // placement.
1491                let positioning_context_length = positioning_context.len();
1492                let lazy_block_size = get_lazy_block_size();
1493                layout = self.layout(
1494                    layout_context,
1495                    positioning_context,
1496                    &ContainingBlock {
1497                        size: ContainingBlockSize {
1498                            inline: proposed_inline_size,
1499                            block: tentative_block_size,
1500                        },
1501                        style,
1502                    },
1503                    containing_block,
1504                    preferred_aspect_ratio,
1505                    &lazy_block_size,
1506                );
1507
1508                let inline_size = if let Some(inline_size) = layout.content_inline_size_for_table {
1509                    // This is a table that ended up being smaller than predicted because of
1510                    // collapsed columns. Note we don't backtrack to consider areas that we
1511                    // previously thought weren't big enough.
1512                    // TODO: Should `minimum_size_of_block.inline` be zero for tables?
1513                    debug_assert!(inline_size < proposed_inline_size);
1514                    inline_size
1515                } else {
1516                    proposed_inline_size
1517                };
1518                content_size = LogicalVec2 {
1519                    block: lazy_block_size.resolve(|| layout.content_block_size),
1520                    inline: inline_size,
1521                };
1522
1523                // Now we know the block size of this attempted layout of a box with block
1524                // size of auto. Try to fit it into our precalculated placement among the
1525                // floats. If it fits, then we can stop trying layout candidates.
1526                if placement.try_to_expand_for_auto_block_size(
1527                    content_size.block + pbm.padding_border_sums.block,
1528                    &placement_rect.size,
1529                ) {
1530                    break;
1531                }
1532
1533                // The previous attempt to lay out this independent formatting context
1534                // among the floats did not work, so we must unhoist any boxes from that
1535                // attempt.
1536                positioning_context.truncate(&positioning_context_length);
1537            }
1538        }
1539
1540        // Only set clearance if we would have cleared or the placement among floats moves
1541        // the block further in the block direction. These two situations are the ones that
1542        // prevent margin collapse.
1543        let has_clearance = clear_position.is_some() || placement_rect.start_corner.block > ceiling;
1544        let clearance = has_clearance.then(|| {
1545            placement_rect.start_corner.block -
1546                sequential_layout_state
1547                    .position_with_zero_clearance(&collapsed_margin_block_start)
1548        });
1549
1550        let ((margin_inline_start, margin_inline_end), effective_margin_inline_start) =
1551            solve_inline_margins_avoiding_floats(
1552                sequential_layout_state,
1553                containing_block,
1554                &pbm,
1555                content_size.inline + pbm.padding_border_sums.inline,
1556                placement_rect,
1557                justify_self,
1558            );
1559
1560        let margin = LogicalSides {
1561            inline_start: margin_inline_start,
1562            inline_end: margin_inline_end,
1563            block_start: margin_block_start,
1564            block_end: margin_block_end,
1565        };
1566
1567        // Clearance prevents margin collapse between this block and previous ones,
1568        // so in that case collapse margins before adjoining them below.
1569        if clearance.is_some() {
1570            sequential_layout_state.collapse_margins();
1571        }
1572        sequential_layout_state.adjoin_assign(&collapsed_margin_block_start);
1573
1574        // Margins can never collapse into independent formatting contexts.
1575        sequential_layout_state.collapse_margins();
1576        sequential_layout_state.advance_block_position(
1577            pbm.padding_border_sums.block + content_size.block + clearance.unwrap_or_else(Au::zero),
1578        );
1579        sequential_layout_state.adjoin_assign(&CollapsedMargin::new(margin.block_end));
1580
1581        let content_rect = LogicalRect {
1582            start_corner: LogicalVec2 {
1583                block: pbm.padding.block_start +
1584                    pbm.border.block_start +
1585                    clearance.unwrap_or_else(Au::zero),
1586                inline: pbm.padding.inline_start +
1587                    pbm.border.inline_start +
1588                    effective_margin_inline_start,
1589            },
1590            size: content_size,
1591        };
1592
1593        let mut base_fragment_info = self.base.base_fragment_info;
1594        if depends_on_block_constraints {
1595            base_fragment_info.flags.insert(
1596                FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
1597            );
1598        }
1599
1600        BoxFragment::new(
1601            base_fragment_info,
1602            style.clone(),
1603            layout.fragments,
1604            content_rect.as_physical(Some(containing_block)),
1605            pbm.padding.to_physical(containing_block_writing_mode),
1606            pbm.border.to_physical(containing_block_writing_mode),
1607            margin.to_physical(containing_block_writing_mode),
1608            layout.specific_layout_info,
1609        )
1610        .with_baselines(layout.baselines)
1611        .with_block_level_layout_info(CollapsedBlockMargins::from_margin(&margin), clearance)
1612    }
1613}
1614
1615struct ContainingBlockPaddingAndBorder<'a> {
1616    containing_block: ContainingBlock<'a>,
1617    pbm: PaddingBorderMargin,
1618    block_sizes: Sizes,
1619    depends_on_block_constraints: bool,
1620    available_block_size: Option<Au>,
1621    justify_self: AlignFlags,
1622    preferred_aspect_ratio: Option<AspectRatio>,
1623}
1624
1625struct ResolvedMargins {
1626    /// Used value for the margin properties, as exposed in getComputedStyle().
1627    pub margin: LogicalSides<Au>,
1628
1629    /// Distance between the border box and the containing block on the inline-start side.
1630    /// This is typically the same as the inline-start margin, but can be greater when
1631    /// the box is justified within the free space in the containing block.
1632    /// The reason we aren't just adjusting the used margin-inline-start is that
1633    /// this shouldn't be observable via getComputedStyle().
1634    /// <https://drafts.csswg.org/css-align/#justify-self-property>
1635    pub effective_margin_inline_start: Au,
1636}
1637
1638/// Given the style for an in-flow box and its containing block, determine the containing
1639/// block for its children.
1640/// Note that in the presence of floats, this shouldn't be used for a block-level box
1641/// that establishes an independent formatting context (or is replaced), since the
1642/// inline size could then be incorrect.
1643fn solve_containing_block_padding_and_border_for_in_flow_box<'a>(
1644    containing_block: &ContainingBlock<'_>,
1645    layout_style: &'a LayoutStyle,
1646    get_inline_content_sizes: impl FnOnce(&ConstraintSpace) -> ContentSizes,
1647    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
1648    context: Option<&IndependentFormattingContext>,
1649) -> ContainingBlockPaddingAndBorder<'a> {
1650    let style = layout_style.style();
1651    if matches!(style.pseudo(), Some(PseudoElement::ServoAnonymousBox)) {
1652        // <https://drafts.csswg.org/css2/#anonymous-block-level>
1653        // > Anonymous block boxes are ignored when resolving percentage values that would
1654        // > refer to it: the closest non-anonymous ancestor box is used instead.
1655        let containing_block_for_children = ContainingBlock {
1656            size: ContainingBlockSize {
1657                inline: containing_block.size.inline,
1658                block: containing_block.size.block,
1659            },
1660            style,
1661        };
1662        // <https://drafts.csswg.org/css2/#anonymous-block-level>
1663        // > Non-inherited properties have their initial value.
1664        return ContainingBlockPaddingAndBorder {
1665            containing_block: containing_block_for_children,
1666            pbm: PaddingBorderMargin::zero(),
1667            block_sizes: Sizes::default(),
1668            depends_on_block_constraints: false,
1669            // The available block size may actually be definite, but it should be irrelevant
1670            // since the sizing properties are set to their initial value.
1671            available_block_size: None,
1672            // The initial `justify-self` is `auto`, but use `normal` (behaving as `stretch`).
1673            // This is being discussed in <https://github.com/w3c/csswg-drafts/issues/11461>.
1674            justify_self: AlignFlags::NORMAL,
1675            preferred_aspect_ratio: None,
1676        };
1677    }
1678
1679    let ContentBoxSizesAndPBM {
1680        content_box_sizes,
1681        pbm,
1682        depends_on_block_constraints,
1683        ..
1684    } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
1685
1686    let pbm_sums = pbm.sums_auto_is_zero(ignore_block_margins_for_stretch);
1687    let available_inline_size = Au::zero().max(containing_block.size.inline - pbm_sums.inline);
1688    let available_block_size = containing_block
1689        .size
1690        .block
1691        .to_definite()
1692        .map(|block_size| Au::zero().max(block_size - pbm_sums.block));
1693
1694    // TODO: support preferred aspect ratios on boxes that don't establish an independent
1695    // formatting context.
1696    let preferred_aspect_ratio =
1697        context.and_then(|context| context.preferred_aspect_ratio(&pbm.padding_border_sums));
1698    let is_table = layout_style.is_table();
1699
1700    // https://drafts.csswg.org/css2/#the-height-property
1701    // https://drafts.csswg.org/css2/visudet.html#min-max-heights
1702    let tentative_block_content_size =
1703        context.and_then(|context| context.tentative_block_content_size(preferred_aspect_ratio));
1704    let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
1705        SizeConstraint::Definite(content_box_sizes.block.resolve(
1706            Direction::Block,
1707            Size::FitContent,
1708            Au::zero,
1709            available_block_size,
1710            || block_content_size,
1711            is_table,
1712        ))
1713    } else {
1714        content_box_sizes.block.resolve_extrinsic(
1715            Size::FitContent,
1716            Au::zero(),
1717            available_block_size,
1718        )
1719    };
1720
1721    // https://drafts.csswg.org/css2/#the-width-property
1722    // https://drafts.csswg.org/css2/visudet.html#min-max-widths
1723    let get_inline_content_sizes = || {
1724        get_inline_content_sizes(&ConstraintSpace::new(
1725            tentative_block_size,
1726            style,
1727            preferred_aspect_ratio,
1728        ))
1729    };
1730    let justify_self = resolve_justify_self(style, containing_block.style);
1731    let is_replaced = context.is_some_and(|context| context.is_replaced());
1732    let inline_size = content_box_sizes.inline.resolve(
1733        Direction::Inline,
1734        automatic_inline_size(justify_self, is_table, is_replaced),
1735        Au::zero,
1736        Some(available_inline_size),
1737        get_inline_content_sizes,
1738        is_table,
1739    );
1740
1741    let containing_block_for_children = ContainingBlock {
1742        size: ContainingBlockSize {
1743            inline: inline_size,
1744            block: tentative_block_size,
1745        },
1746        style,
1747    };
1748    // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
1749    assert_eq!(
1750        containing_block.style.writing_mode.is_horizontal(),
1751        containing_block_for_children
1752            .style
1753            .writing_mode
1754            .is_horizontal(),
1755        "Vertical writing modes are not supported yet"
1756    );
1757    ContainingBlockPaddingAndBorder {
1758        containing_block: containing_block_for_children,
1759        pbm,
1760        block_sizes: content_box_sizes.block,
1761        depends_on_block_constraints,
1762        available_block_size,
1763        justify_self,
1764        preferred_aspect_ratio,
1765    }
1766}
1767
1768/// Given the containing block and size of an in-flow box, determine the margins.
1769/// Note that in the presence of floats, this shouldn't be used for a block-level box
1770/// that establishes an independent formatting context (or is replaced), since the
1771/// margins could then be incorrect.
1772fn solve_margins(
1773    containing_block: &ContainingBlock<'_>,
1774    pbm: &PaddingBorderMargin,
1775    inline_size: Au,
1776    justify_self: AlignFlags,
1777) -> ResolvedMargins {
1778    let (inline_margins, effective_margin_inline_start) =
1779        solve_inline_margins_for_in_flow_block_level(
1780            containing_block,
1781            pbm,
1782            inline_size,
1783            justify_self,
1784        );
1785    let block_margins = solve_block_margins_for_in_flow_block_level(pbm);
1786    ResolvedMargins {
1787        margin: LogicalSides {
1788            inline_start: inline_margins.0,
1789            inline_end: inline_margins.1,
1790            block_start: block_margins.0,
1791            block_end: block_margins.1,
1792        },
1793        effective_margin_inline_start,
1794    }
1795}
1796
1797/// Resolves 'auto' margins of an in-flow block-level box in the block axis.
1798/// <https://drafts.csswg.org/css2/#normal-block>
1799/// <https://drafts.csswg.org/css2/#block-root-margin>
1800fn solve_block_margins_for_in_flow_block_level(pbm: &PaddingBorderMargin) -> (Au, Au) {
1801    (
1802        pbm.margin.block_start.auto_is(Au::zero),
1803        pbm.margin.block_end.auto_is(Au::zero),
1804    )
1805}
1806
1807/// Resolves the `justify-self` value, preserving flags.
1808fn resolve_justify_self(style: &ComputedValues, parent_style: &ComputedValues) -> AlignFlags {
1809    let is_ltr = |style: &ComputedValues| style.writing_mode.line_left_is_inline_start();
1810    let alignment = match style.clone_justify_self().0.0 {
1811        AlignFlags::AUTO => parent_style.clone_justify_items().computed.0,
1812        alignment => alignment,
1813    };
1814    let alignment_value = match alignment.value() {
1815        AlignFlags::LEFT if is_ltr(parent_style) => AlignFlags::START,
1816        AlignFlags::LEFT => AlignFlags::END,
1817        AlignFlags::RIGHT if is_ltr(parent_style) => AlignFlags::END,
1818        AlignFlags::RIGHT => AlignFlags::START,
1819        AlignFlags::SELF_START if is_ltr(parent_style) == is_ltr(style) => AlignFlags::START,
1820        AlignFlags::SELF_START => AlignFlags::END,
1821        AlignFlags::SELF_END if is_ltr(parent_style) == is_ltr(style) => AlignFlags::END,
1822        AlignFlags::SELF_END => AlignFlags::START,
1823        alignment_value => alignment_value,
1824    };
1825    alignment.flags() | alignment_value
1826}
1827
1828/// Determines the automatic size for the inline axis of a block-level box.
1829/// <https://drafts.csswg.org/css-sizing-3/#automatic-size>
1830#[inline]
1831fn automatic_inline_size<T>(
1832    justify_self: AlignFlags,
1833    is_table: bool,
1834    is_replaced: bool,
1835) -> Size<T> {
1836    // TODO: Normal alignment shouldn't stretch widgets.
1837    match justify_self {
1838        AlignFlags::STRETCH => Size::Stretch,
1839        AlignFlags::NORMAL if !is_table && !is_replaced => Size::Stretch,
1840        _ => Size::FitContent,
1841    }
1842}
1843
1844/// Justifies a block-level box, distributing the free space according to `justify-self`.
1845/// Note `<center>` and `<div align>` are implemented via internal 'text-align' values,
1846/// which are also handled here.
1847/// The provided free space should already take margins into account. In particular,
1848/// it should be zero if there is an auto margin.
1849/// <https://drafts.csswg.org/css-align/#justify-block>
1850fn justify_self_alignment(
1851    containing_block: &ContainingBlock,
1852    free_space: Au,
1853    justify_self: AlignFlags,
1854) -> Au {
1855    let mut alignment = justify_self.value();
1856    let is_safe = justify_self.flags() == AlignFlags::SAFE || alignment == AlignFlags::NORMAL;
1857    if is_safe && free_space <= Au::zero() {
1858        alignment = AlignFlags::START
1859    }
1860    match alignment {
1861        AlignFlags::NORMAL => {},
1862        AlignFlags::CENTER => return free_space / 2,
1863        AlignFlags::END => return free_space,
1864        _ => return Au::zero(),
1865    }
1866
1867    // For `justify-self: normal`, fall back to the special 'text-align' values.
1868    let style = containing_block.style;
1869    match style.clone_text_align() {
1870        TextAlignKeyword::MozCenter => free_space / 2,
1871        TextAlignKeyword::MozLeft if !style.writing_mode.line_left_is_inline_start() => free_space,
1872        TextAlignKeyword::MozRight if style.writing_mode.line_left_is_inline_start() => free_space,
1873        _ => Au::zero(),
1874    }
1875}
1876
1877/// Resolves 'auto' margins of an in-flow block-level box in the inline axis,
1878/// distributing the free space in the containing block.
1879///
1880/// This is based on CSS2.1 ยง 10.3.3 <https://drafts.csswg.org/css2/#blockwidth>
1881/// but without adjusting the margins in "over-contrained" cases, as mandated by
1882/// <https://drafts.csswg.org/css-align/#justify-block>.
1883///
1884/// Note that in the presence of floats, this shouldn't be used for a block-level box
1885/// that establishes an independent formatting context (or is replaced).
1886///
1887/// In addition to the used margins, it also returns the effective margin-inline-start
1888/// (see ContainingBlockPaddingAndBorder).
1889fn solve_inline_margins_for_in_flow_block_level(
1890    containing_block: &ContainingBlock,
1891    pbm: &PaddingBorderMargin,
1892    inline_size: Au,
1893    justify_self: AlignFlags,
1894) -> ((Au, Au), Au) {
1895    let free_space = containing_block.size.inline - pbm.padding_border_sums.inline - inline_size;
1896    let mut justification = Au::zero();
1897    let inline_margins = match (pbm.margin.inline_start, pbm.margin.inline_end) {
1898        (AuOrAuto::Auto, AuOrAuto::Auto) => {
1899            let start = Au::zero().max(free_space / 2);
1900            (start, free_space - start)
1901        },
1902        (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
1903            (Au::zero().max(free_space - end), end)
1904        },
1905        (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => (start, free_space - start),
1906        (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
1907            // In the cases above, the free space is zero after taking 'auto' margins into account.
1908            // But here we may still have some free space to perform 'justify-self' alignment.
1909            // This aligns the margin box within the containing block, or in other words,
1910            // aligns the border box within the margin-shrunken containing block.
1911            justification =
1912                justify_self_alignment(containing_block, free_space - start - end, justify_self);
1913            (start, end)
1914        },
1915    };
1916    let effective_margin_inline_start = inline_margins.0 + justification;
1917    (inline_margins, effective_margin_inline_start)
1918}
1919
1920/// Resolves 'auto' margins of an in-flow block-level box in the inline axis
1921/// similarly to |solve_inline_margins_for_in_flow_block_level|. However,
1922/// they align within the provided rect (instead of the containing block),
1923/// to avoid overlapping floats.
1924/// In addition to the used margins, it also returns the effective
1925/// margin-inline-start (see ContainingBlockPaddingAndBorder).
1926/// It may differ from the used inline-start margin if the computed value
1927/// wasn't 'auto' and there are floats to avoid or the box is justified.
1928/// See <https://github.com/w3c/csswg-drafts/issues/9174>
1929fn solve_inline_margins_avoiding_floats(
1930    sequential_layout_state: &SequentialLayoutState,
1931    containing_block: &ContainingBlock,
1932    pbm: &PaddingBorderMargin,
1933    inline_size: Au,
1934    placement_rect: LogicalRect<Au>,
1935    justify_self: AlignFlags,
1936) -> ((Au, Au), Au) {
1937    // PlacementAmongFloats should guarantee that the inline size of the placement rect
1938    // is at least as big as `inline_size`. However, that may fail when dealing with
1939    // huge sizes that need to be saturated to MAX_AU, so floor by zero. See #37312.
1940    let free_space = Au::zero().max(placement_rect.size.inline - inline_size);
1941    let cb_info = &sequential_layout_state.floats.containing_block_info;
1942    let start_adjustment = placement_rect.start_corner.inline - cb_info.inline_start;
1943    let end_adjustment = cb_info.inline_end - placement_rect.max_inline_position();
1944    let mut justification = Au::zero();
1945    let inline_margins = match (pbm.margin.inline_start, pbm.margin.inline_end) {
1946        (AuOrAuto::Auto, AuOrAuto::Auto) => {
1947            let half = free_space / 2;
1948            (start_adjustment + half, end_adjustment + free_space - half)
1949        },
1950        (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => (start_adjustment + free_space, end),
1951        (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => (start, end_adjustment + free_space),
1952        (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
1953            // The spec says 'justify-self' aligns the margin box within the float-shrunken
1954            // containing block. That's wrong (https://github.com/w3c/csswg-drafts/issues/9963),
1955            // and Blink and WebKit are broken anyways. So we match Gecko instead: this aligns
1956            // the border box within the instersection of the float-shrunken containing-block
1957            // and the margin-shrunken containing-block.
1958            justification = justify_self_alignment(containing_block, free_space, justify_self);
1959            (start, end)
1960        },
1961    };
1962    let effective_margin_inline_start = inline_margins.0.max(start_adjustment) + justification;
1963    (inline_margins, effective_margin_inline_start)
1964}
1965
1966/// State that we maintain when placing blocks.
1967///
1968/// In parallel mode, this placement is done after all child blocks are laid out. In
1969/// sequential mode, this is done right after each block is laid out.
1970struct PlacementState<'container> {
1971    next_in_flow_margin_collapses_with_parent_start_margin: bool,
1972    last_in_flow_margin_collapses_with_parent_end_margin: bool,
1973    start_margin: CollapsedMargin,
1974    current_margin: CollapsedMargin,
1975    current_block_direction_position: Au,
1976    inflow_baselines: Baselines,
1977    is_inline_block_context: bool,
1978
1979    /// If this [`PlacementState`] is laying out a list item with an outside marker. Record the
1980    /// block size of that marker, because the content block size of the list item needs to be at
1981    /// least as tall as the marker size -- even though the marker doesn't advance the block
1982    /// position of the placement.
1983    marker_block_size: Option<Au>,
1984
1985    /// The [`ContainingBlock`] of the container into which this [`PlacementState`] is laying out
1986    /// fragments. This is used to convert between physical and logical geometry.
1987    containing_block: &'container ContainingBlock<'container>,
1988}
1989
1990impl<'container> PlacementState<'container> {
1991    fn new(
1992        collapsible_with_parent_start_margin: CollapsibleWithParentStartMargin,
1993        containing_block: &'container ContainingBlock<'container>,
1994    ) -> PlacementState<'container> {
1995        let is_inline_block_context =
1996            containing_block.style.get_box().clone_display() == Display::InlineBlock;
1997        PlacementState {
1998            next_in_flow_margin_collapses_with_parent_start_margin:
1999                collapsible_with_parent_start_margin.0,
2000            last_in_flow_margin_collapses_with_parent_end_margin: true,
2001            start_margin: CollapsedMargin::zero(),
2002            current_margin: CollapsedMargin::zero(),
2003            current_block_direction_position: Au::zero(),
2004            inflow_baselines: Baselines::default(),
2005            is_inline_block_context,
2006            marker_block_size: None,
2007            containing_block,
2008        }
2009    }
2010
2011    fn place_fragment_and_update_baseline(
2012        &mut self,
2013        fragment: &mut Fragment,
2014        sequential_layout_state: Option<&mut SequentialLayoutState>,
2015    ) {
2016        self.place_fragment(fragment, sequential_layout_state);
2017
2018        let box_fragment = match fragment {
2019            Fragment::Box(box_fragment) => box_fragment,
2020            _ => return,
2021        };
2022        let box_fragment = box_fragment.borrow();
2023
2024        // From <https://drafts.csswg.org/css-align-3/#baseline-export>:
2025        // > When finding the first/last baseline set of an inline-block, any baselines
2026        // > contributed by table boxes must be skipped. (This quirk is a legacy behavior from
2027        // > [CSS2].)
2028        if self.is_inline_block_context && box_fragment.is_table_wrapper() {
2029            return;
2030        }
2031
2032        let box_block_offset = box_fragment
2033            .content_rect
2034            .origin
2035            .to_logical(self.containing_block)
2036            .block;
2037        let box_fragment_baselines =
2038            box_fragment.baselines(self.containing_block.style.writing_mode);
2039        if let (None, Some(first)) = (self.inflow_baselines.first, box_fragment_baselines.first) {
2040            self.inflow_baselines.first = Some(first + box_block_offset);
2041        }
2042        if let Some(last) = box_fragment_baselines.last {
2043            self.inflow_baselines.last = Some(last + box_block_offset);
2044        }
2045    }
2046
2047    /// Place a single [Fragment] in a block level context using the state so far and
2048    /// information gathered from the [Fragment] itself.
2049    fn place_fragment(
2050        &mut self,
2051        fragment: &mut Fragment,
2052        sequential_layout_state: Option<&mut SequentialLayoutState>,
2053    ) {
2054        match fragment {
2055            Fragment::Box(fragment) => {
2056                // If this child is a marker positioned outside of a list item, then record its
2057                // size, but also ensure that it doesn't advance the block position of the placment.
2058                // This ensures item content is placed next to the marker.
2059                //
2060                // This is a pretty big hack because it doesn't properly handle all interactions
2061                // between the marker and the item. For instance the marker should be positioned at
2062                // the baseline of list item content and the first line of the item content should
2063                // be at least as tall as the marker -- not the entire list item itself.
2064                let fragment = &mut *fragment.borrow_mut();
2065                let is_outside_marker = fragment
2066                    .base
2067                    .flags
2068                    .contains(FragmentFlags::IS_OUTSIDE_LIST_ITEM_MARKER);
2069                if is_outside_marker {
2070                    assert!(self.marker_block_size.is_none());
2071                    self.marker_block_size = Some(
2072                        fragment
2073                            .content_rect
2074                            .size
2075                            .to_logical(self.containing_block.style.writing_mode)
2076                            .block,
2077                    );
2078                    return;
2079                }
2080
2081                let BlockLevelLayoutInfo {
2082                    clearance,
2083                    block_margins_collapsed_with_children: fragment_block_margins,
2084                } = &**fragment
2085                    .block_level_layout_info
2086                    .as_ref()
2087                    .expect("A block-level fragment should have a BlockLevelLayoutInfo.");
2088                let mut fragment_block_size = fragment
2089                    .border_rect()
2090                    .size
2091                    .to_logical(self.containing_block.style.writing_mode)
2092                    .block;
2093
2094                // We use `last_in_flow_margin_collapses_with_parent_end_margin` to implement
2095                // this quote from https://drafts.csswg.org/css2/#collapsing-margins
2096                // > If the top and bottom margins of an element with clearance are adjoining,
2097                // > its margins collapse with the adjoining margins of following siblings but that
2098                // > resulting margin does not collapse with the bottom margin of the parent block.
2099                if let Some(clearance) = *clearance {
2100                    fragment_block_size += clearance;
2101                    // Margins can't be adjoining if they are separated by clearance.
2102                    // Setting `next_in_flow_margin_collapses_with_parent_start_margin` to false
2103                    // prevents collapsing with the start margin of the parent, and will set
2104                    // `collapsed_through` to false, preventing the parent from collapsing through.
2105                    self.current_block_direction_position += self.current_margin.solve();
2106                    self.current_margin = CollapsedMargin::zero();
2107                    self.next_in_flow_margin_collapses_with_parent_start_margin = false;
2108                    if fragment_block_margins.collapsed_through {
2109                        self.last_in_flow_margin_collapses_with_parent_end_margin = false;
2110                    }
2111                } else if !fragment_block_margins.collapsed_through {
2112                    self.last_in_flow_margin_collapses_with_parent_end_margin = true;
2113                }
2114
2115                if self.next_in_flow_margin_collapses_with_parent_start_margin {
2116                    debug_assert!(self.current_margin.solve().is_zero());
2117                    self.start_margin
2118                        .adjoin_assign(&fragment_block_margins.start);
2119                    if fragment_block_margins.collapsed_through {
2120                        self.start_margin.adjoin_assign(&fragment_block_margins.end);
2121                        return;
2122                    }
2123                    self.next_in_flow_margin_collapses_with_parent_start_margin = false;
2124                } else {
2125                    self.current_margin
2126                        .adjoin_assign(&fragment_block_margins.start);
2127                }
2128
2129                fragment.content_rect.origin += LogicalVec2 {
2130                    inline: Au::zero(),
2131                    block: self.current_margin.solve() + self.current_block_direction_position,
2132                }
2133                .to_physical_size(self.containing_block.style.writing_mode);
2134
2135                if fragment_block_margins.collapsed_through {
2136                    // `fragment_block_size` is typically zero when collapsing through,
2137                    // but we still need to consider it in case there is clearance.
2138                    self.current_block_direction_position += fragment_block_size;
2139                    self.current_margin
2140                        .adjoin_assign(&fragment_block_margins.end);
2141                } else {
2142                    self.current_block_direction_position +=
2143                        self.current_margin.solve() + fragment_block_size;
2144                    self.current_margin = fragment_block_margins.end;
2145                }
2146            },
2147            Fragment::AbsoluteOrFixedPositioned(fragment) => {
2148                // The alignment of absolutes in block flow layout is always "start", so the size of
2149                // the static position rectangle does not matter.
2150                fragment.borrow_mut().original_static_position_rect = LogicalRect {
2151                    start_corner: LogicalVec2 {
2152                        block: (self.current_margin.solve() +
2153                            self.current_block_direction_position),
2154                        inline: Au::zero(),
2155                    },
2156                    size: LogicalVec2::zero(),
2157                }
2158                .as_physical(Some(self.containing_block));
2159            },
2160            Fragment::Float(box_fragment) => {
2161                let sequential_layout_state = sequential_layout_state
2162                    .expect("Found float fragment without SequentialLayoutState");
2163                let block_offset_from_containing_block_top =
2164                    self.current_block_direction_position + self.current_margin.solve();
2165                let box_fragment = &mut *box_fragment.borrow_mut();
2166                sequential_layout_state.place_float_fragment(
2167                    box_fragment,
2168                    self.containing_block,
2169                    self.start_margin,
2170                    block_offset_from_containing_block_top,
2171                );
2172            },
2173            Fragment::Positioning(_) => {},
2174            _ => unreachable!(),
2175        }
2176    }
2177
2178    fn finish(mut self) -> (Au, CollapsedBlockMargins, Baselines) {
2179        if !self.last_in_flow_margin_collapses_with_parent_end_margin {
2180            self.current_block_direction_position += self.current_margin.solve();
2181            self.current_margin = CollapsedMargin::zero();
2182        }
2183        let (total_block_size, collapsed_through) = match self.marker_block_size {
2184            Some(marker_block_size) => (
2185                self.current_block_direction_position.max(marker_block_size),
2186                // If this is a list item (even empty) with an outside marker, then it
2187                // should not collapse through.
2188                false,
2189            ),
2190            None => (
2191                self.current_block_direction_position,
2192                self.next_in_flow_margin_collapses_with_parent_start_margin,
2193            ),
2194        };
2195
2196        (
2197            total_block_size,
2198            CollapsedBlockMargins {
2199                collapsed_through,
2200                start: self.start_margin,
2201                end: self.current_margin,
2202            },
2203            self.inflow_baselines,
2204        )
2205    }
2206}
2207
2208pub(crate) struct IndependentFloatOrAtomicLayoutResult {
2209    pub fragment: BoxFragment,
2210    pub baselines: Baselines,
2211    pub pbm_sums: LogicalSides<Au>,
2212}
2213
2214impl IndependentFormattingContext {
2215    pub(crate) fn layout_float_or_atomic_inline(
2216        &self,
2217        layout_context: &LayoutContext,
2218        child_positioning_context: &mut PositioningContext,
2219        containing_block: &ContainingBlock,
2220    ) -> IndependentFloatOrAtomicLayoutResult {
2221        let style = self.style();
2222        let container_writing_mode = containing_block.style.writing_mode;
2223        let layout_style = self.layout_style();
2224        let content_box_sizes_and_pbm =
2225            layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
2226        let pbm = &content_box_sizes_and_pbm.pbm;
2227        let margin = pbm.margin.auto_is(Au::zero);
2228        let pbm_sums = pbm.padding + pbm.border + margin;
2229        let preferred_aspect_ratio = self.preferred_aspect_ratio(&pbm.padding_border_sums);
2230        let is_table = self.is_table();
2231
2232        let available_inline_size =
2233            Au::zero().max(containing_block.size.inline - pbm_sums.inline_sum());
2234        let available_block_size = containing_block
2235            .size
2236            .block
2237            .to_definite()
2238            .map(|block_size| Au::zero().max(block_size - pbm_sums.block_sum()));
2239
2240        let tentative_block_content_size =
2241            self.tentative_block_content_size(preferred_aspect_ratio);
2242        let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
2243            SizeConstraint::Definite(content_box_sizes_and_pbm.content_box_sizes.block.resolve(
2244                Direction::Block,
2245                Size::FitContent,
2246                Au::zero,
2247                available_block_size,
2248                || block_content_size,
2249                is_table,
2250            ))
2251        } else {
2252            content_box_sizes_and_pbm
2253                .content_box_sizes
2254                .block
2255                .resolve_extrinsic(Size::FitContent, Au::zero(), available_block_size)
2256        };
2257
2258        let get_content_size = || {
2259            let constraint_space =
2260                ConstraintSpace::new(tentative_block_size, style, preferred_aspect_ratio);
2261            self.inline_content_sizes(layout_context, &constraint_space)
2262                .sizes
2263        };
2264
2265        let inline_size = content_box_sizes_and_pbm.content_box_sizes.inline.resolve(
2266            Direction::Inline,
2267            Size::FitContent,
2268            Au::zero,
2269            Some(available_inline_size),
2270            get_content_size,
2271            is_table,
2272        );
2273
2274        let containing_block_for_children = ContainingBlock {
2275            size: ContainingBlockSize {
2276                inline: inline_size,
2277                block: tentative_block_size,
2278            },
2279            style,
2280        };
2281        assert_eq!(
2282            container_writing_mode.is_horizontal(),
2283            style.writing_mode.is_horizontal(),
2284            "Mixed horizontal and vertical writing modes are not supported yet"
2285        );
2286
2287        let lazy_block_size = LazySize::new(
2288            &content_box_sizes_and_pbm.content_box_sizes.block,
2289            Direction::Block,
2290            Size::FitContent,
2291            Au::zero,
2292            available_block_size,
2293            is_table,
2294        );
2295
2296        let CacheableLayoutResult {
2297            content_inline_size_for_table,
2298            content_block_size,
2299            fragments,
2300            baselines,
2301            specific_layout_info,
2302            ..
2303        } = self.layout(
2304            layout_context,
2305            child_positioning_context,
2306            &containing_block_for_children,
2307            containing_block,
2308            preferred_aspect_ratio,
2309            &lazy_block_size,
2310        );
2311
2312        let content_size = LogicalVec2 {
2313            inline: content_inline_size_for_table.unwrap_or(inline_size),
2314            block: lazy_block_size.resolve(|| content_block_size),
2315        }
2316        .to_physical_size(container_writing_mode);
2317        let content_rect = PhysicalRect::new(PhysicalPoint::zero(), content_size);
2318
2319        let mut base_fragment_info = self.base_fragment_info();
2320        if content_box_sizes_and_pbm.depends_on_block_constraints {
2321            base_fragment_info.flags.insert(
2322                FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
2323            );
2324        }
2325
2326        // Floats can have clearance, but it's handled internally by the float placement logic,
2327        // so there's no need to store it explicitly in the fragment.
2328        // And atomic inlines don't have clearance.
2329        let fragment = BoxFragment::new(
2330            base_fragment_info,
2331            style.clone(),
2332            fragments,
2333            content_rect,
2334            pbm.padding.to_physical(container_writing_mode),
2335            pbm.border.to_physical(container_writing_mode),
2336            margin.to_physical(container_writing_mode),
2337            specific_layout_info,
2338        );
2339
2340        IndependentFloatOrAtomicLayoutResult {
2341            fragment,
2342            baselines,
2343            pbm_sums,
2344        }
2345    }
2346}