layout/flow/
mod.rs

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