layout/flexbox/
layout.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
5use std::cell::{Cell, LazyCell};
6use std::cmp::Ordering;
7
8use app_units::Au;
9use atomic_refcell::AtomicRef;
10use itertools::izip;
11use rayon::iter::{
12    IndexedParallelIterator, IntoParallelRefIterator, ParallelDrainRange, ParallelIterator,
13};
14use style::Zero;
15use style::computed_values::position::T as Position;
16use style::logical_geometry::Direction;
17use style::properties::ComputedValues;
18use style::properties::longhands::align_items::computed_value::T as AlignItems;
19use style::properties::longhands::box_sizing::computed_value::T as BoxSizing;
20use style::properties::longhands::flex_wrap::computed_value::T as FlexWrap;
21use style::values::computed::LengthPercentage;
22use style::values::generics::flex::GenericFlexBasis as FlexBasis;
23use style::values::generics::length::LengthPercentageOrNormal;
24use style::values::specified::align::AlignFlags;
25
26use super::geom::{FlexAxis, FlexRelativeRect, FlexRelativeSides, FlexRelativeVec2};
27use super::{FlexContainer, FlexContainerConfig, FlexItemBox, FlexLevelBox};
28use crate::cell::ArcRefCell;
29use crate::context::LayoutContext;
30use crate::dom::WeakLayoutBox;
31use crate::formatting_contexts::Baselines;
32use crate::fragment_tree::{
33    BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
34};
35use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2};
36use crate::layout_box_base::IndependentFormattingContextLayoutResult;
37use crate::positioned::{
38    AbsolutelyPositionedBox, PositioningContext, PositioningContextLength, relative_adjustement,
39};
40use crate::sizing::{
41    ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult, IntrinsicSizingMode,
42    LazySize, Size, SizeConstraint, Sizes,
43};
44use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, LayoutStyle};
45use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize, IndefiniteContainingBlock};
46
47/// Layout parameters and intermediate results about a flex container,
48/// grouped to avoid passing around many parameters
49struct FlexContext<'a> {
50    config: FlexContainerConfig,
51    layout_context: &'a LayoutContext<'a>,
52    containing_block: &'a ContainingBlock<'a>, // For items
53    container_inner_size_constraint: FlexRelativeVec2<SizeConstraint>,
54}
55
56/// A flex item with some intermediate results
57struct FlexItem<'a> {
58    box_: &'a FlexItemBox,
59
60    /// The preferred, min and max inner cross sizes.
61    content_cross_sizes: Sizes,
62
63    padding: FlexRelativeSides<Au>,
64    border: FlexRelativeSides<Au>,
65    margin: FlexRelativeSides<AuOrAuto>,
66
67    /// Sum of padding, border, and margin (with `auto` assumed to be zero) in each axis.
68    /// This is the difference between an outer and inner size.
69    pbm_auto_is_zero: FlexRelativeVec2<Au>,
70
71    /// <https://drafts.csswg.org/css-flexbox/#algo-main-item>
72    flex_base_size: Au,
73
74    /// Whether the [`Self::flex_base_size`] comes from a definite `flex-basis`.
75    /// If false and the container main size is also indefinite, percentages in the item's
76    /// content that resolve against its main size should be indefinite.
77    flex_base_size_is_definite: bool,
78
79    /// <https://drafts.csswg.org/css-flexbox/#algo-main-item>
80    hypothetical_main_size: Au,
81
82    /// The used min main size of the flex item.
83    /// <https://drafts.csswg.org/css-flexbox/#min-main-size-property>
84    content_min_main_size: Au,
85
86    /// The used max main size of the flex item.
87    /// <https://drafts.csswg.org/css-flexbox/#max-main-size-property>
88    content_max_main_size: Option<Au>,
89
90    /// This is `align-self`, defaulting to `align-items` if `auto`
91    align_self: AlignItems,
92
93    /// Whether or not the size of this [`FlexItem`] depends on its block constraints.
94    depends_on_block_constraints: bool,
95
96    /// <https://drafts.csswg.org/css-sizing-4/#preferred-aspect-ratio>
97    preferred_aspect_ratio: Option<AspectRatio>,
98
99    /// The automatic size in the cross axis.
100    /// <https://drafts.csswg.org/css-sizing-3/#automatic-size>
101    automatic_cross_size: Size<Au>,
102    automatic_cross_size_for_intrinsic_sizing: Size<Au>,
103}
104
105/// Child of a FlexContainer. Can either be absolutely positioned, or not. If not,
106/// a placeholder is used and flex content is stored outside of this enum.
107enum FlexContent {
108    AbsolutelyPositionedBox(ArcRefCell<AbsolutelyPositionedBox>),
109    FlexItemPlaceholder,
110}
111
112/// Return type of `FlexItem::layout`
113struct FlexItemLayoutResult {
114    hypothetical_cross_size: Au,
115    fragments: Vec<Fragment>,
116    positioning_context: PositioningContext,
117
118    /// Baselines from the item’s content, relative to the item’s margin box.
119    /// Used only for baseline propagation to parent layout contexts.
120    content_baselines_for_parent_relative_to_margin_box: Baselines,
121
122    /// This is the single baseline this item uses for flex alignment.
123    /// Either the first or the last baseline or None, depending on ‘align-self’.
124    flex_alignment_baseline_relative_to_margin_box: Option<Au>,
125
126    // The content size of this layout in the block axis. This is known before layout
127    // for replaced elements, but for non-replaced it's only known after layout.
128    content_block_size: Au,
129
130    // The containing block size used to generate this layout.
131    containing_block_size: ContainingBlockSize,
132
133    // Whether or not this layout depended on block constraints.
134    depends_on_block_constraints: bool,
135
136    // The specific layout info that this flex item had.
137    specific_layout_info: Option<SpecificLayoutInfo>,
138}
139
140/// A data structure to hold all of the information about a flex item that has been placed
141/// into a flex line. This happens once the item is laid out and its line has been determined.
142struct FlexLineItem<'a> {
143    /// The items that are placed in this line.
144    item: FlexItem<'a>,
145
146    /// The layout results of the initial layout pass for a flex line. These may be replaced
147    /// if necessary due to the use of `align-content: stretch` or `align-self: stretch`.
148    layout_result: FlexItemLayoutResult,
149
150    /// The used main size of this item in its line.
151    used_main_size: Au,
152}
153
154impl FlexLineItem<'_> {
155    fn get_or_synthesize_baseline_with_cross_size(&self, cross_size: Au) -> Au {
156        self.layout_result
157            .flex_alignment_baseline_relative_to_margin_box
158            .unwrap_or_else(|| {
159                self.item
160                    .synthesized_baseline_relative_to_margin_box(cross_size)
161            })
162    }
163
164    #[allow(clippy::too_many_arguments)]
165    fn collect_fragment(
166        mut self,
167        initial_flex_layout: &InitialFlexLineLayout,
168        item_used_size: FlexRelativeVec2<Au>,
169        item_margin: FlexRelativeSides<Au>,
170        item_main_interval: Au,
171        final_line_cross_size: Au,
172        shared_alignment_baseline: &Option<Au>,
173        flex_context: &mut FlexContext,
174        all_baselines: &mut Baselines,
175        main_position_cursor: &mut Au,
176    ) -> (ArcRefCell<BoxFragment>, PositioningContext) {
177        // https://drafts.csswg.org/css-flexbox/#algo-main-align
178        // “Align the items along the main-axis”
179        *main_position_cursor +=
180            item_margin.main_start + self.item.border.main_start + self.item.padding.main_start;
181        let item_content_main_start_position = *main_position_cursor;
182
183        *main_position_cursor += item_used_size.main +
184            self.item.padding.main_end +
185            self.item.border.main_end +
186            item_margin.main_end +
187            item_main_interval;
188
189        // https://drafts.csswg.org/css-flexbox/#algo-cross-align
190        let item_content_cross_start_position = self.item.align_along_cross_axis(
191            &item_margin,
192            &item_used_size.cross,
193            final_line_cross_size,
194            self.layout_result
195                .flex_alignment_baseline_relative_to_margin_box
196                .unwrap_or_default(),
197            shared_alignment_baseline.unwrap_or_default(),
198            flex_context.config.flex_wrap_is_reversed,
199        );
200
201        let start_corner = FlexRelativeVec2 {
202            main: item_content_main_start_position,
203            cross: item_content_cross_start_position,
204        };
205
206        // Need to collect both baselines from baseline participation and other baselines.
207        let final_line_size = FlexRelativeVec2 {
208            main: initial_flex_layout.line_size.main,
209            cross: final_line_cross_size,
210        };
211        let content_rect = flex_context.rect_to_flow_relative(
212            final_line_size,
213            FlexRelativeRect {
214                start_corner,
215                size: item_used_size,
216            },
217        );
218
219        let adjust_baseline = |baseline: Au| {
220            baseline + item_content_cross_start_position -
221                self.item.border.cross_start -
222                self.item.padding.cross_start -
223                item_margin.cross_start
224        };
225
226        let baselines = self
227            .layout_result
228            .content_baselines_for_parent_relative_to_margin_box;
229        if flex_context.config.flex_direction_is_reversed {
230            if let Some(last_baseline) = baselines.last {
231                all_baselines
232                    .last
233                    .get_or_insert_with(|| adjust_baseline(last_baseline));
234            }
235            if let Some(first_baseline) = baselines.first {
236                all_baselines.first = Some(adjust_baseline(first_baseline));
237            }
238        } else {
239            if let Some(first_baseline) = baselines.first {
240                all_baselines
241                    .first
242                    .get_or_insert_with(|| adjust_baseline(first_baseline));
243            }
244            if let Some(last_baseline) = baselines.last {
245                all_baselines.last = Some(adjust_baseline(last_baseline));
246            }
247        }
248
249        let mut fragment_info = self.item.box_.base_fragment_info();
250        fragment_info
251            .flags
252            .insert(FragmentFlags::IS_FLEX_OR_GRID_ITEM);
253        if self.item.depends_on_block_constraints {
254            fragment_info.flags.insert(
255                FragmentFlags::SIZE_DEPENDS_ON_BLOCK_CONSTRAINTS_AND_CAN_BE_CHILD_OF_FLEX_ITEM,
256            );
257        }
258        let flags = fragment_info.flags;
259
260        let containing_block = flex_context.containing_block;
261        let container_writing_mode = containing_block.style.writing_mode;
262        let style = self.item.box_.style();
263
264        let mut fragment = BoxFragment::new(
265            fragment_info,
266            style.clone(),
267            self.layout_result.fragments,
268            content_rect.as_physical(Some(flex_context.containing_block)),
269            flex_context
270                .sides_to_flow_relative(self.item.padding)
271                .to_physical(container_writing_mode),
272            flex_context
273                .sides_to_flow_relative(self.item.border)
274                .to_physical(container_writing_mode),
275            flex_context
276                .sides_to_flow_relative(item_margin)
277                .to_physical(container_writing_mode),
278            self.layout_result.specific_layout_info,
279        );
280
281        // If this flex item establishes a containing block for absolutely-positioned
282        // descendants, then lay out any relevant absolutely-positioned children. This
283        // will remove those children from `self.positioning_context`.
284        if style.establishes_containing_block_for_absolute_descendants(flags) {
285            self.layout_result
286                .positioning_context
287                .layout_collected_children(flex_context.layout_context, &mut fragment);
288        }
289
290        if style.clone_position() == Position::Relative {
291            fragment.base.rect.origin += relative_adjustement(style, containing_block)
292                .to_physical_size(containing_block.style.writing_mode)
293        }
294
295        let fragment = ArcRefCell::new(fragment);
296        self.item
297            .box_
298            .independent_formatting_context
299            .base
300            .set_fragment(Fragment::Box(fragment.clone()));
301        (fragment, self.layout_result.positioning_context)
302    }
303}
304
305/// Once the final cross size of a line is known, the line can go through their final
306/// layout and this the return value. See [`InitialFlexLineLayout::finish_with_final_cross_size`].
307struct FinalFlexLineLayout {
308    /// The final cross size of this flex line.
309    cross_size: Au,
310    /// The [`BoxFragment`]s and [`PositioningContext`]s of all flex items,
311    /// one per flex item in "order-modified document order."
312    item_fragments: Vec<(ArcRefCell<BoxFragment>, PositioningContext)>,
313    /// The 'shared alignment baseline' of this flex line. This is the baseline used for
314    /// baseline-aligned items if there are any, otherwise `None`.
315    shared_alignment_baseline: Option<Au>,
316    /// This is the baseline of the first and last items with compatible writing mode, regardless of
317    /// whether they particpate in baseline alignement. This is used as a fallback baseline for the
318    /// container, if there are no items participating in baseline alignment in the first or last
319    /// flex lines.
320    all_baselines: Baselines,
321}
322
323impl FlexContainerConfig {
324    fn resolve_reversable_flex_alignment(
325        &self,
326        align_flags: AlignFlags,
327        reversed: bool,
328    ) -> AlignFlags {
329        match (align_flags.value(), reversed) {
330            (AlignFlags::FLEX_START, false) => AlignFlags::START | align_flags.flags(),
331            (AlignFlags::FLEX_START, true) => AlignFlags::END | align_flags.flags(),
332            (AlignFlags::FLEX_END, false) => AlignFlags::END | align_flags.flags(),
333            (AlignFlags::FLEX_END, true) => AlignFlags::START | align_flags.flags(),
334            (_, _) => align_flags,
335        }
336    }
337
338    fn resolve_align_self_for_child(&self, child_style: &ComputedValues) -> AlignFlags {
339        self.resolve_reversable_flex_alignment(
340            child_style
341                .resolve_align_self(self.align_items, AlignFlags::STRETCH)
342                .0,
343            self.flex_wrap_is_reversed,
344        )
345    }
346
347    fn resolve_justify_content_for_child(&self) -> AlignFlags {
348        self.resolve_reversable_flex_alignment(
349            self.justify_content.primary(),
350            self.flex_direction_is_reversed,
351        )
352    }
353
354    fn sides_to_flex_relative<T>(&self, sides: LogicalSides<T>) -> FlexRelativeSides<T> {
355        self.main_start_cross_start_sides_are
356            .sides_to_flex_relative(sides)
357    }
358
359    fn sides_to_flow_relative<T>(&self, sides: FlexRelativeSides<T>) -> LogicalSides<T> {
360        self.main_start_cross_start_sides_are
361            .sides_to_flow_relative(sides)
362    }
363}
364
365impl FlexContext<'_> {
366    #[inline]
367    fn sides_to_flow_relative<T>(&self, x: FlexRelativeSides<T>) -> LogicalSides<T> {
368        self.config.sides_to_flow_relative(x)
369    }
370
371    #[inline]
372    fn rect_to_flow_relative(
373        &self,
374        base_rect_size: FlexRelativeVec2<Au>,
375        rect: FlexRelativeRect<Au>,
376    ) -> LogicalRect<Au> {
377        super::geom::rect_to_flow_relative(
378            self.config.flex_axis,
379            self.config.main_start_cross_start_sides_are,
380            base_rect_size,
381            rect,
382        )
383    }
384}
385
386#[derive(Debug, Default)]
387struct DesiredFlexFractionAndGrowOrShrinkFactor {
388    desired_flex_fraction: f32,
389    flex_grow_or_shrink_factor: f32,
390}
391
392#[derive(Default)]
393struct FlexItemBoxInlineContentSizesInfo {
394    outer_flex_base_size: Au,
395    outer_min_main_size: Au,
396    outer_max_main_size: Option<Au>,
397    min_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor,
398    max_flex_factors: DesiredFlexFractionAndGrowOrShrinkFactor,
399    min_content_main_size_for_multiline_container: Au,
400    depends_on_block_constraints: bool,
401}
402
403impl ComputeInlineContentSizes for FlexContainer {
404    #[servo_tracing::instrument(name = "FlexContainer::compute_inline_content_sizes", skip_all)]
405    fn compute_inline_content_sizes(
406        &self,
407        layout_context: &LayoutContext,
408        constraint_space: &ConstraintSpace,
409    ) -> InlineContentSizesResult {
410        match self.config.flex_axis {
411            FlexAxis::Row => {
412                self.main_content_sizes(layout_context, &constraint_space.into(), || {
413                    unreachable!(
414                        "Unexpected FlexContext query during row flex intrinsic size calculation."
415                    )
416                })
417            },
418            FlexAxis::Column => self.cross_content_sizes(layout_context, &constraint_space.into()),
419        }
420    }
421}
422
423impl FlexContainer {
424    fn cross_content_sizes(
425        &self,
426        layout_context: &LayoutContext,
427        containing_block_for_children: &IndefiniteContainingBlock,
428    ) -> InlineContentSizesResult {
429        // <https://drafts.csswg.org/css-flexbox/#intrinsic-cross-sizes>
430        assert_eq!(
431            self.config.flex_axis,
432            FlexAxis::Column,
433            "The cross axis should be the inline one"
434        );
435        let mut sizes = ContentSizes::zero();
436        let mut depends_on_block_constraints = false;
437        for kid in self.children.iter() {
438            let kid = &*kid.borrow();
439            match kid {
440                FlexLevelBox::FlexItem(item) => {
441                    // TODO: For the max-content size we should distribute items into
442                    // columns, and sum the column sizes and gaps.
443                    // TODO: Use the proper automatic minimum size.
444                    let ifc = &item.independent_formatting_context;
445                    let result = ifc.outer_inline_content_sizes(
446                        layout_context,
447                        containing_block_for_children,
448                        &LogicalVec2::zero(),
449                        false, /* auto_block_size_stretches_to_containing_block */
450                    );
451                    sizes.max_assign(result.sizes);
452                    depends_on_block_constraints |= result.depends_on_block_constraints;
453                },
454                FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => {},
455            }
456        }
457        InlineContentSizesResult {
458            sizes,
459            depends_on_block_constraints,
460        }
461    }
462
463    fn main_content_sizes<'a>(
464        &self,
465        layout_context: &LayoutContext,
466        containing_block_for_children: &IndefiniteContainingBlock,
467        flex_context_getter: impl Fn() -> &'a FlexContext<'a>,
468    ) -> InlineContentSizesResult {
469        // - TODO: calculate intrinsic cross sizes when container is a column
470        // (and check for ‘writing-mode’?)
471        // - TODO: Collapsed flex items need to be skipped for intrinsic size calculation.
472
473        // <https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes>
474        // > It is calculated, considering only non-collapsed flex items, by:
475        // > 1. For each flex item, subtract its outer flex base size from its max-content
476        // > contribution size.
477        let mut chosen_max_flex_fraction = f32::NEG_INFINITY;
478        let mut chosen_min_flex_fraction = f32::NEG_INFINITY;
479        let mut sum_of_flex_grow_factors = 0.0;
480        let mut sum_of_flex_shrink_factors = 0.0;
481        let mut item_infos = vec![];
482
483        for kid in self.children.iter() {
484            let kid = &*kid.borrow();
485            match kid {
486                FlexLevelBox::FlexItem(item) => {
487                    sum_of_flex_grow_factors += item.style().get_position().flex_grow.0;
488                    sum_of_flex_shrink_factors += item.style().get_position().flex_shrink.0;
489
490                    let info = item.main_content_size_info(
491                        layout_context,
492                        containing_block_for_children,
493                        &self.config,
494                        &flex_context_getter,
495                    );
496
497                    // > 2. Place all flex items into lines of infinite length. Within
498                    // > each line, find the greatest (most positive) desired flex
499                    // > fraction among all the flex items. This is the line’s chosen flex
500                    // > fraction.
501                    chosen_max_flex_fraction =
502                        chosen_max_flex_fraction.max(info.max_flex_factors.desired_flex_fraction);
503                    chosen_min_flex_fraction =
504                        chosen_min_flex_fraction.max(info.min_flex_factors.desired_flex_fraction);
505
506                    item_infos.push(info)
507                },
508                FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => {},
509            }
510        }
511
512        let normalize_flex_fraction = |chosen_flex_fraction| {
513            if chosen_flex_fraction > 0.0 && sum_of_flex_grow_factors < 1.0 {
514                // > 3. If the chosen flex fraction is positive, and the sum of the line’s
515                // > flex grow factors is less than 1, > divide the chosen flex fraction by that
516                // > sum.
517                chosen_flex_fraction / sum_of_flex_grow_factors
518            } else if chosen_flex_fraction < 0.0 && sum_of_flex_shrink_factors < 1.0 {
519                // > If the chosen flex fraction is negative, and the sum of the line’s flex
520                // > shrink factors is less than 1, > multiply the chosen flex fraction by that
521                // > sum.
522                chosen_flex_fraction * sum_of_flex_shrink_factors
523            } else {
524                chosen_flex_fraction
525            }
526        };
527
528        let chosen_min_flex_fraction = normalize_flex_fraction(chosen_min_flex_fraction);
529        let chosen_max_flex_fraction = normalize_flex_fraction(chosen_max_flex_fraction);
530
531        let main_gap = match self.config.flex_axis {
532            FlexAxis::Row => self.style.clone_column_gap(),
533            FlexAxis::Column => self.style.clone_row_gap(),
534        };
535        let main_gap = match main_gap {
536            LengthPercentageOrNormal::LengthPercentage(length_percentage) => {
537                length_percentage.to_used_value(Au::zero())
538            },
539            LengthPercentageOrNormal::Normal => Au::zero(),
540        };
541        let extra_space_from_main_gap = main_gap * (item_infos.len() as i32 - 1);
542        let mut container_max_content_size = extra_space_from_main_gap;
543        let mut container_min_content_size = if self.config.flex_wrap == FlexWrap::Nowrap {
544            extra_space_from_main_gap
545        } else {
546            Au::zero()
547        };
548        let mut container_depends_on_block_constraints = false;
549
550        for FlexItemBoxInlineContentSizesInfo {
551            outer_flex_base_size,
552            outer_min_main_size,
553            outer_max_main_size,
554            min_flex_factors,
555            max_flex_factors,
556            min_content_main_size_for_multiline_container,
557            depends_on_block_constraints,
558        } in item_infos.iter()
559        {
560            // > 4. Add each item’s flex base size to the product of its flex grow factor (scaled flex shrink
561            // > factor, if shrinking) and the chosen flex fraction, then clamp that result by the max main size
562            // > floored by the min main size.
563            // > 5. The flex container’s max-content size is the largest sum (among all the lines) of the
564            // > afore-calculated sizes of all items within a single line.
565            container_max_content_size += (*outer_flex_base_size +
566                Au::from_f32_px(
567                    max_flex_factors.flex_grow_or_shrink_factor * chosen_max_flex_fraction,
568                ))
569            .clamp_between_extremums(*outer_min_main_size, *outer_max_main_size);
570
571            // > The min-content main size of a single-line flex container is calculated
572            // > identically to the max-content main size, except that the flex items’
573            // > min-content contributions are used instead of their max-content contributions.
574            //
575            // > However, for a multi-line container, the min-content main size is simply the
576            // > largest min-content contribution of all the non-collapsed flex items in the
577            // > flex container. For this purpose, each item’s contribution is capped by the
578            // > item’s flex base size if the item is not growable, floored by the item’s flex
579            // > base size if the item is not shrinkable, and then further clamped by the item’s
580            // > min and max main sizes.
581            if self.config.flex_wrap == FlexWrap::Nowrap {
582                container_min_content_size += (*outer_flex_base_size +
583                    Au::from_f32_px(
584                        min_flex_factors.flex_grow_or_shrink_factor * chosen_min_flex_fraction,
585                    ))
586                .clamp_between_extremums(*outer_min_main_size, *outer_max_main_size);
587            } else {
588                container_min_content_size
589                    .max_assign(*min_content_main_size_for_multiline_container);
590            }
591
592            container_depends_on_block_constraints |= depends_on_block_constraints;
593        }
594
595        InlineContentSizesResult {
596            sizes: ContentSizes {
597                min_content: container_min_content_size,
598                max_content: container_max_content_size,
599            },
600            depends_on_block_constraints: container_depends_on_block_constraints,
601        }
602    }
603
604    /// <https://drafts.csswg.org/css-flexbox/#layout-algorithm>
605    #[servo_tracing::instrument(
606        name = "FlexContainer::layout",
607        skip_all,
608        fields(self_address = self as *const _ as usize)
609    )]
610    pub(crate) fn layout(
611        &self,
612        layout_context: &LayoutContext,
613        positioning_context: &mut PositioningContext,
614        containing_block: &ContainingBlock,
615        lazy_block_size: &LazySize,
616    ) -> IndependentFormattingContextLayoutResult {
617        let mut flex_context = FlexContext {
618            config: self.config.clone(),
619            layout_context,
620            containing_block,
621            // https://drafts.csswg.org/css-flexbox/#definite-sizes
622            container_inner_size_constraint: self.config.flex_axis.vec2_to_flex_relative(
623                LogicalVec2 {
624                    inline: SizeConstraint::Definite(containing_block.size.inline),
625                    block: containing_block.size.block,
626                },
627            ),
628        };
629
630        // “Determine the main size of the flex container”
631        // https://drafts.csswg.org/css-flexbox/#algo-main-container
632        let container_main_size = match self.config.flex_axis {
633            FlexAxis::Row => containing_block.size.inline,
634            FlexAxis::Column => lazy_block_size.resolve(|| {
635                let mut containing_block = IndefiniteContainingBlock::from(containing_block);
636                containing_block.size.block = None;
637                self.main_content_sizes(layout_context, &containing_block, || &flex_context)
638                    .sizes
639                    .max_content
640            }),
641        };
642
643        // Actual length may be less, but we guess that usually not by a lot
644        let mut flex_items = Vec::with_capacity(self.children.len());
645
646        // Absolutely-positioned children of the flex container may be interleaved
647        // with flex items. We need to preserve their relative order for correct painting order,
648        // which is the order of `Fragment`s in this function’s return value.
649        //
650        // Example:
651        // absolutely_positioned_items_with_original_order = [Some(item), Some(item), None, Some(item), None]
652        // flex_items                                      =                         [item,             item]
653        let absolutely_positioned_items_with_original_order = self
654            .children
655            .iter()
656            .map(|arcrefcell| {
657                let borrowed = arcrefcell.borrow();
658                match &*borrowed {
659                    FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(absolutely_positioned) => {
660                        FlexContent::AbsolutelyPositionedBox(absolutely_positioned.clone())
661                    },
662                    FlexLevelBox::FlexItem(_) => {
663                        let item = AtomicRef::map(borrowed, |child| match child {
664                            FlexLevelBox::FlexItem(item) => item,
665                            _ => unreachable!(),
666                        });
667                        flex_items.push(item);
668                        FlexContent::FlexItemPlaceholder
669                    },
670                }
671            })
672            .collect::<Vec<_>>();
673
674        let flex_item_boxes = flex_items.iter().map(|child| &**child);
675        let flex_items = flex_item_boxes
676            .map(|flex_item_box| FlexItem::new(&flex_context, flex_item_box))
677            .collect::<Vec<_>>();
678
679        let row_gap = self.style.clone_row_gap();
680        let column_gap = self.style.clone_column_gap();
681        let (cross_gap, main_gap) = match flex_context.config.flex_axis {
682            FlexAxis::Row => (row_gap, column_gap),
683            FlexAxis::Column => (column_gap, row_gap),
684        };
685        let cross_gap = match cross_gap {
686            LengthPercentageOrNormal::LengthPercentage(length_percent) => length_percent
687                .maybe_to_used_value(
688                    flex_context
689                        .container_inner_size_constraint
690                        .cross
691                        .to_definite(),
692                )
693                .unwrap_or_default(),
694            LengthPercentageOrNormal::Normal => Au::zero(),
695        };
696        let main_gap = match main_gap {
697            LengthPercentageOrNormal::LengthPercentage(length_percent) => length_percent
698                .maybe_to_used_value(
699                    flex_context
700                        .container_inner_size_constraint
701                        .main
702                        .to_definite(),
703                )
704                .unwrap_or_default(),
705            LengthPercentageOrNormal::Normal => Au::zero(),
706        };
707
708        // “Resolve the flexible lengths of all the flex items to find their *used main size*.”
709        // https://drafts.csswg.org/css-flexbox/#algo-flex
710        let initial_line_layouts = do_initial_flex_line_layout(
711            &mut flex_context,
712            container_main_size,
713            flex_items,
714            main_gap,
715        );
716
717        let line_count = initial_line_layouts.len();
718        let content_cross_size = initial_line_layouts
719            .iter()
720            .map(|layout| layout.line_size.cross)
721            .sum::<Au>() +
722            cross_gap * (line_count as i32 - 1);
723        let content_block_size = match self.config.flex_axis {
724            FlexAxis::Row => content_cross_size,
725            FlexAxis::Column => container_main_size,
726        };
727
728        // https://drafts.csswg.org/css-flexbox/#algo-cross-container
729        let container_cross_size = match self.config.flex_axis {
730            FlexAxis::Row => lazy_block_size.resolve(|| content_cross_size),
731            FlexAxis::Column => containing_block.size.inline,
732        };
733
734        let container_size = FlexRelativeVec2 {
735            main: container_main_size,
736            cross: container_cross_size,
737        };
738
739        let mut remaining_free_cross_space = container_cross_size - content_cross_size;
740
741        // Implement fallback alignment.
742        //
743        // In addition to the spec at https://www.w3.org/TR/css-align-3/ this implementation follows
744        // the resolution of https://github.com/w3c/csswg-drafts/issues/10154
745        let num_lines = initial_line_layouts.len();
746        let resolved_align_content: AlignFlags = {
747            // Computed value from the style system
748            let align_content_style = flex_context.config.align_content.primary();
749            let mut is_safe = align_content_style.flags() == AlignFlags::SAFE;
750
751            // From https://drafts.csswg.org/css-align/#distribution-flex
752            // > `normal` behaves as `stretch`.
753            let mut resolved_align_content = match align_content_style.value() {
754                AlignFlags::NORMAL => AlignFlags::STRETCH,
755                align_content => align_content,
756            };
757
758            // From https://drafts.csswg.org/css-flexbox/#algo-line-align:
759            // > Some alignments can only be fulfilled in certain situations or are
760            // > limited in how much space they can consume; for example, space-between
761            // > can only operate when there is more than one alignment subject, and
762            // > baseline alignment, once fulfilled, might not be enough to absorb all
763            // > the excess space. In these cases a fallback alignment takes effect (as
764            // > defined below) to fully consume the excess space.
765            let fallback_is_needed = match resolved_align_content {
766                _ if remaining_free_cross_space <= Au::zero() => true,
767                AlignFlags::STRETCH => num_lines < 1,
768                AlignFlags::SPACE_BETWEEN | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
769                    num_lines < 2
770                },
771                _ => false,
772            };
773
774            if fallback_is_needed {
775                (resolved_align_content, is_safe) = match resolved_align_content {
776                    AlignFlags::STRETCH => (AlignFlags::FLEX_START, false),
777                    AlignFlags::SPACE_BETWEEN => (AlignFlags::FLEX_START, true),
778                    AlignFlags::SPACE_AROUND => (AlignFlags::CENTER, true),
779                    AlignFlags::SPACE_EVENLY => (AlignFlags::CENTER, true),
780                    _ => (resolved_align_content, is_safe),
781                }
782            };
783
784            // 2. If free space is negative the "safe" alignment variants all fallback to Start alignment
785            if remaining_free_cross_space <= Au::zero() && is_safe {
786                resolved_align_content = AlignFlags::START;
787            }
788
789            resolved_align_content
790        };
791
792        // Implement "unsafe" alignment. "safe" alignment is handled by the fallback process above.
793        let flex_wrap_is_reversed = flex_context.config.flex_wrap_is_reversed;
794        let resolved_align_content = self
795            .config
796            .resolve_reversable_flex_alignment(resolved_align_content, flex_wrap_is_reversed);
797        let mut cross_start_position_cursor = match resolved_align_content {
798            AlignFlags::START if flex_wrap_is_reversed => remaining_free_cross_space,
799            AlignFlags::START => Au::zero(),
800            AlignFlags::END if flex_wrap_is_reversed => Au::zero(),
801            AlignFlags::END => remaining_free_cross_space,
802            AlignFlags::CENTER => remaining_free_cross_space / 2,
803            AlignFlags::STRETCH => Au::zero(),
804            AlignFlags::SPACE_BETWEEN => Au::zero(),
805            AlignFlags::SPACE_AROUND => remaining_free_cross_space / num_lines as i32 / 2,
806            AlignFlags::SPACE_EVENLY => remaining_free_cross_space / (num_lines as i32 + 1),
807
808            // TODO: Implement all alignments. Note: not all alignment values are valid for content distribution
809            _ => Au::zero(),
810        };
811
812        let inline_axis_is_main_axis = self.config.flex_axis == FlexAxis::Row;
813        let mut baseline_alignment_participating_baselines = Baselines::default();
814        let mut all_baselines = Baselines::default();
815        let flex_item_fragments: Vec<_> = initial_line_layouts
816            .into_iter()
817            .enumerate()
818            .flat_map(|(index, initial_line_layout)| {
819                // We call `allocate_free_cross_space_for_flex_line` for each line to avoid having
820                // leftover space when the number of lines doesn't evenly divide the total free space,
821                // considering the precision of app units.
822                let (space_to_add_to_line, space_to_add_after_line) =
823                    allocate_free_cross_space_for_flex_line(
824                        resolved_align_content,
825                        remaining_free_cross_space,
826                        (num_lines - index) as i32,
827                    );
828                remaining_free_cross_space -= space_to_add_to_line + space_to_add_after_line;
829
830                let final_line_cross_size =
831                    initial_line_layout.line_size.cross + space_to_add_to_line;
832                let mut final_line_layout = initial_line_layout.finish_with_final_cross_size(
833                    &mut flex_context,
834                    main_gap,
835                    final_line_cross_size,
836                );
837
838                let line_cross_start_position = cross_start_position_cursor;
839                cross_start_position_cursor = line_cross_start_position +
840                    final_line_cross_size +
841                    space_to_add_after_line +
842                    cross_gap;
843
844                let flow_relative_line_position =
845                    match (self.config.flex_axis, flex_wrap_is_reversed) {
846                        (FlexAxis::Row, false) => LogicalVec2 {
847                            block: line_cross_start_position,
848                            inline: Au::zero(),
849                        },
850                        (FlexAxis::Row, true) => LogicalVec2 {
851                            block: container_cross_size -
852                                line_cross_start_position -
853                                final_line_layout.cross_size,
854                            inline: Au::zero(),
855                        },
856                        (FlexAxis::Column, false) => LogicalVec2 {
857                            block: Au::zero(),
858                            inline: line_cross_start_position,
859                        },
860                        (FlexAxis::Column, true) => LogicalVec2 {
861                            block: Au::zero(),
862                            inline: container_cross_size -
863                                line_cross_start_position -
864                                final_line_cross_size,
865                        },
866                    };
867
868                if inline_axis_is_main_axis {
869                    let line_shared_alignment_baseline = final_line_layout
870                        .shared_alignment_baseline
871                        .map(|baseline| baseline + flow_relative_line_position.block);
872                    if index == 0 {
873                        baseline_alignment_participating_baselines.first =
874                            line_shared_alignment_baseline;
875                    }
876                    if index == num_lines - 1 {
877                        baseline_alignment_participating_baselines.last =
878                            line_shared_alignment_baseline;
879                    }
880                }
881
882                let line_all_baselines = final_line_layout
883                    .all_baselines
884                    .offset(flow_relative_line_position.block);
885                if index == 0 {
886                    all_baselines.first = line_all_baselines.first;
887                }
888                if index == num_lines - 1 {
889                    all_baselines.last = line_all_baselines.last;
890                }
891
892                let physical_line_position =
893                    flow_relative_line_position.to_physical_size(self.style.writing_mode);
894                for (fragment, _) in &mut final_line_layout.item_fragments {
895                    fragment.borrow_mut().base.rect.origin += physical_line_position;
896                }
897                final_line_layout.item_fragments
898            })
899            .collect();
900
901        let mut flex_item_fragments = flex_item_fragments.into_iter();
902        let fragments = absolutely_positioned_items_with_original_order
903            .into_iter()
904            .map(|child_as_abspos| match child_as_abspos {
905                FlexContent::AbsolutelyPositionedBox(absolutely_positioned_box) => self
906                    .create_absolutely_positioned_flex_child_fragment(
907                        absolutely_positioned_box,
908                        containing_block,
909                        container_size,
910                        positioning_context,
911                    ),
912                FlexContent::FlexItemPlaceholder => {
913                    // The `flex_item_fragments` iterator yields one fragment
914                    // per flex item, in the original order.
915                    let (fragment, mut child_positioning_context) =
916                        flex_item_fragments.next().unwrap();
917                    let fragment = Fragment::Box(fragment);
918                    child_positioning_context.adjust_static_position_of_hoisted_fragments(
919                        &fragment,
920                        PositioningContextLength::zero(),
921                    );
922                    positioning_context.append(child_positioning_context);
923                    fragment
924                },
925            })
926            .collect::<Vec<_>>();
927
928        // There should be no more flex items
929        assert!(flex_item_fragments.next().is_none());
930
931        let baselines = Baselines {
932            first: baseline_alignment_participating_baselines
933                .first
934                .or(all_baselines.first),
935            last: baseline_alignment_participating_baselines
936                .last
937                .or(all_baselines.last),
938        };
939
940        // TODO: `depends_on_block_constraints` could be false in some corner cases
941        // in order to improve performance.
942        // - In a single-line column container where all items have the grow and shrink
943        //   factors set to zero and the flex basis doesn't depend on block constraints,
944        //   and `justify-content` is `start` or equivalent.
945        //   This is unlikely because the flex shrink factor defaults to 1.
946        // - In a single-line row container where all items have `align-self: start` or
947        //   equivalent, and the cross size doesn't depend on block constraints.
948        //   This is unlikely because `align-self` stretches by default.
949        // - In a multi-line row container where `align-content` is `start` or equivalent,
950        //   and no item cross size depends on block constraints.
951        //   This is unlikely because `align-content` defaults to `stretch`.
952        let depends_on_block_constraints = true;
953
954        IndependentFormattingContextLayoutResult {
955            fragments,
956            content_block_size,
957            content_inline_size_for_table: None,
958            baselines,
959            depends_on_block_constraints,
960            specific_layout_info: None,
961            collapsible_margins_in_children: CollapsedBlockMargins::zero(),
962        }
963    }
964
965    /// Create a absolutely positioned flex child fragment, using the rules the
966    /// specification dictates. This should take into account the alignment and
967    /// justification values of the container and the child to position it within a
968    /// "inset-modified containing block," which may be either the "static-position
969    /// rectangle" that's calculated below or a modified version of the absolute's
970    /// containing block adjusted by the insets specified in the item's style.
971    ///
972    /// From <https://drafts.csswg.org/css-flexbox/#abspos-items>:
973    /// > The cross-axis edges of the static-position rectangle of an
974    /// > absolutely-positioned child of a flex container are the content edges of the
975    /// > flex container The main-axis edges of the static-position rectangle are where
976    /// > the margin edges of the child would be positioned if it were the sole flex item
977    /// > in the flex container, assuming both the child and the flex container were
978    /// > fixed-size boxes of their used size. (For this purpose, auto margins are
979    /// > treated as zero.)
980    fn create_absolutely_positioned_flex_child_fragment(
981        &self,
982        absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
983        containing_block: &ContainingBlock,
984        container_size: FlexRelativeVec2<Au>,
985        positioning_context: &mut PositioningContext,
986    ) -> Fragment {
987        let alignment = {
988            let fragment = absolutely_positioned_box.borrow();
989            let make_flex_only_values_directional_for_absolutes =
990                |value: AlignFlags, reversed: bool| match (value.value(), reversed) {
991                    (AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH, true) => {
992                        AlignFlags::END | AlignFlags::SAFE
993                    },
994                    (AlignFlags::STRETCH, false) => AlignFlags::START | AlignFlags::SAFE,
995                    (AlignFlags::SPACE_BETWEEN, false) => AlignFlags::START | AlignFlags::SAFE,
996                    (AlignFlags::SPACE_BETWEEN, true) => AlignFlags::END | AlignFlags::SAFE,
997                    _ => value,
998                };
999            let cross = make_flex_only_values_directional_for_absolutes(
1000                self.config
1001                    .resolve_align_self_for_child(fragment.context.style()),
1002                self.config.flex_wrap_is_reversed,
1003            );
1004            let main = make_flex_only_values_directional_for_absolutes(
1005                self.config.resolve_justify_content_for_child(),
1006                self.config.flex_direction_is_reversed,
1007            );
1008
1009            FlexRelativeVec2 { cross, main }
1010        };
1011        let logical_alignment = self.config.flex_axis.vec2_to_flow_relative(alignment);
1012
1013        let static_position_rect = LogicalRect {
1014            start_corner: LogicalVec2::zero(),
1015            size: self.config.flex_axis.vec2_to_flow_relative(container_size),
1016        }
1017        .as_physical(Some(containing_block));
1018
1019        let hoisted_box = AbsolutelyPositionedBox::to_hoisted(
1020            absolutely_positioned_box,
1021            static_position_rect,
1022            logical_alignment,
1023            self.config.writing_mode,
1024        );
1025        let hoisted_fragment = hoisted_box.fragment.clone();
1026        positioning_context.push(hoisted_box);
1027        Fragment::AbsoluteOrFixedPositioned(hoisted_fragment)
1028    }
1029
1030    #[inline]
1031    pub(crate) fn layout_style(&self) -> LayoutStyle<'_> {
1032        LayoutStyle::Default(&self.style)
1033    }
1034
1035    pub(crate) fn attached_to_tree(&self, layout_box: WeakLayoutBox) {
1036        for child in &self.children {
1037            child.borrow_mut().with_base_mut(|base| {
1038                base.parent_box.replace(layout_box.clone());
1039            });
1040        }
1041    }
1042}
1043
1044/// Align all flex lines per `align-content` according to
1045/// <https://drafts.csswg.org/css-flexbox/#algo-line-align>. Returns the space to add to
1046/// each line or the space to add after each line.
1047fn allocate_free_cross_space_for_flex_line(
1048    resolved_align_content: AlignFlags,
1049    remaining_free_cross_space: Au,
1050    remaining_line_count: i32,
1051) -> (Au, Au) {
1052    if remaining_free_cross_space.is_zero() {
1053        return (Au::zero(), Au::zero());
1054    }
1055
1056    match resolved_align_content {
1057        AlignFlags::START => (Au::zero(), Au::zero()),
1058        AlignFlags::FLEX_START => (Au::zero(), Au::zero()),
1059        AlignFlags::END => (Au::zero(), Au::zero()),
1060        AlignFlags::FLEX_END => (Au::zero(), Au::zero()),
1061        AlignFlags::CENTER => (Au::zero(), Au::zero()),
1062        AlignFlags::STRETCH => (
1063            remaining_free_cross_space / remaining_line_count,
1064            Au::zero(),
1065        ),
1066        AlignFlags::SPACE_BETWEEN => {
1067            if remaining_line_count > 1 {
1068                (
1069                    Au::zero(),
1070                    remaining_free_cross_space / (remaining_line_count - 1),
1071                )
1072            } else {
1073                (Au::zero(), Au::zero())
1074            }
1075        },
1076        AlignFlags::SPACE_AROUND => (
1077            Au::zero(),
1078            remaining_free_cross_space / remaining_line_count,
1079        ),
1080        AlignFlags::SPACE_EVENLY => (
1081            Au::zero(),
1082            remaining_free_cross_space / (remaining_line_count + 1),
1083        ),
1084
1085        // TODO: Implement all alignments. Note: not all alignment values are valid for content distribution
1086        _ => (Au::zero(), Au::zero()),
1087    }
1088}
1089
1090impl<'a> FlexItem<'a> {
1091    fn new(flex_context: &FlexContext, box_: &'a FlexItemBox) -> Self {
1092        let containing_block = IndefiniteContainingBlock::from(flex_context.containing_block);
1093        let content_box_sizes_and_pbm = box_
1094            .independent_formatting_context
1095            .layout_style()
1096            .content_box_sizes_and_padding_border_margin(&containing_block);
1097        box_.to_flex_item(
1098            flex_context.layout_context,
1099            &containing_block,
1100            &content_box_sizes_and_pbm,
1101            &flex_context.config,
1102            &|| flex_context,
1103        )
1104    }
1105}
1106
1107fn cross_axis_is_item_block_axis(
1108    container_is_horizontal: bool,
1109    item_is_horizontal: bool,
1110    flex_axis: FlexAxis,
1111) -> bool {
1112    let item_is_orthogonal = item_is_horizontal != container_is_horizontal;
1113    let container_is_row = flex_axis == FlexAxis::Row;
1114
1115    container_is_row ^ item_is_orthogonal
1116}
1117
1118/// Whether an item with a computed preferred cross size of `auto` will stretch
1119/// to fill the cross size of its flex line.
1120/// <https://drafts.csswg.org/css-flexbox/#stretched>
1121fn item_with_auto_cross_size_stretches_to_line_size(
1122    align_self: AlignItems,
1123    margin: &FlexRelativeSides<AuOrAuto>,
1124) -> bool {
1125    align_self.0.value() == AlignFlags::STRETCH &&
1126        !margin.cross_start.is_auto() &&
1127        !margin.cross_end.is_auto()
1128}
1129
1130/// "Collect flex items into flex lines"
1131/// <https://drafts.csswg.org/css-flexbox/#algo-line-break>
1132fn do_initial_flex_line_layout<'items>(
1133    flex_context: &mut FlexContext,
1134    container_main_size: Au,
1135    mut items: Vec<FlexItem<'items>>,
1136    main_gap: Au,
1137) -> Vec<InitialFlexLineLayout<'items>> {
1138    let construct_line = |(items, outer_hypothetical_main_size)| {
1139        InitialFlexLineLayout::new(
1140            flex_context,
1141            items,
1142            outer_hypothetical_main_size,
1143            container_main_size,
1144            main_gap,
1145        )
1146    };
1147
1148    if flex_context.config.container_is_single_line {
1149        let outer_hypothetical_main_sizes_sum = items
1150            .iter()
1151            .map(|item| item.hypothetical_main_size + item.pbm_auto_is_zero.main)
1152            .sum();
1153        return vec![construct_line((items, outer_hypothetical_main_sizes_sum))];
1154    }
1155
1156    let mut lines = Vec::new();
1157    let mut line_size_so_far = Au::zero();
1158    let mut line_so_far_is_empty = true;
1159    let mut index = 0;
1160
1161    while let Some(item) = items.get(index) {
1162        let item_size = item.hypothetical_main_size + item.pbm_auto_is_zero.main;
1163        let mut line_size_would_be = line_size_so_far + item_size;
1164        if !line_so_far_is_empty {
1165            line_size_would_be += main_gap;
1166        }
1167        let item_fits = line_size_would_be <= container_main_size;
1168        if item_fits || line_so_far_is_empty {
1169            line_size_so_far = line_size_would_be;
1170            line_so_far_is_empty = false;
1171            index += 1;
1172            continue;
1173        }
1174
1175        // We found something that doesn’t fit. This line ends *before* this item.
1176        let remaining = items.split_off(index);
1177        lines.push((items, line_size_so_far));
1178        items = remaining;
1179
1180        // The next line has this item.
1181        line_size_so_far = item_size;
1182        index = 1;
1183    }
1184
1185    // We didn't reach the end of the last line, so add all remaining items there.
1186    lines.push((items, line_size_so_far));
1187
1188    if flex_context.layout_context.use_rayon {
1189        lines.par_drain(..).map(construct_line).collect()
1190    } else {
1191        lines.into_iter().map(construct_line).collect()
1192    }
1193}
1194
1195/// The result of splitting the flex items into lines using their intrinsic sizes and doing an
1196/// initial layout of each item. A final layout still needs to happen after this is produced to
1197/// handle stretching.
1198struct InitialFlexLineLayout<'a> {
1199    /// The items that are placed in this line.
1200    items: Vec<FlexLineItem<'a>>,
1201
1202    /// The initial size of this flex line, not taking into account `align-content: stretch`.
1203    line_size: FlexRelativeVec2<Au>,
1204
1205    /// The free space available to this line after the initial layout.
1206    free_space_in_main_axis: Au,
1207}
1208
1209impl InitialFlexLineLayout<'_> {
1210    fn new<'items>(
1211        flex_context: &FlexContext,
1212        items: Vec<FlexItem<'items>>,
1213        outer_hypothetical_main_sizes_sum: Au,
1214        container_main_size: Au,
1215        main_gap: Au,
1216    ) -> InitialFlexLineLayout<'items> {
1217        let item_count = items.len();
1218        let (item_used_main_sizes, free_space_in_main_axis) = Self::resolve_flexible_lengths(
1219            &items,
1220            outer_hypothetical_main_sizes_sum,
1221            container_main_size - main_gap * (item_count as i32 - 1),
1222        );
1223
1224        // https://drafts.csswg.org/css-flexbox/#algo-cross-item
1225        let layout_results: Vec<_> = if flex_context.layout_context.use_rayon {
1226            items
1227                .par_iter()
1228                .zip(&item_used_main_sizes)
1229                .map(|(item, used_main_size)| item.layout(*used_main_size, flex_context, None))
1230                .collect()
1231        } else {
1232            items
1233                .iter()
1234                .zip(&item_used_main_sizes)
1235                .map(|(item, used_main_size)| item.layout(*used_main_size, flex_context, None))
1236                .collect()
1237        };
1238
1239        let items: Vec<_> = izip!(
1240            items.into_iter(),
1241            layout_results.into_iter(),
1242            item_used_main_sizes.into_iter()
1243        )
1244        .map(|(item, layout_result, used_main_size)| FlexLineItem {
1245            item,
1246            layout_result,
1247            used_main_size,
1248        })
1249        .collect();
1250
1251        // https://drafts.csswg.org/css-flexbox/#algo-cross-line
1252        let line_cross_size = Self::cross_size(&items, flex_context);
1253        let line_size = FlexRelativeVec2 {
1254            main: container_main_size,
1255            cross: line_cross_size,
1256        };
1257
1258        InitialFlexLineLayout {
1259            items,
1260            line_size,
1261            free_space_in_main_axis,
1262        }
1263    }
1264
1265    /// Return the *main size* of each item, and the line’s remainaing free space
1266    /// <https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths>
1267    fn resolve_flexible_lengths<'items>(
1268        items: &'items [FlexItem<'items>],
1269        outer_hypothetical_main_sizes_sum: Au,
1270        container_main_size: Au,
1271    ) -> (Vec<Au>, Au) {
1272        struct FlexibleLengthResolutionItem<'items> {
1273            item: &'items FlexItem<'items>,
1274            frozen: Cell<bool>,
1275            target_main_size: Cell<Au>,
1276            flex_factor: f32,
1277            min_max_violation_kind: Cell<Ordering>,
1278        }
1279
1280        // > 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
1281        // > items on the line. If the sum is less than the flex container’s inner main
1282        // > size, use the flex grow factor for the rest of this algorithm; otherwise, use
1283        // > the flex shrink factor.
1284        let grow = outer_hypothetical_main_sizes_sum < container_main_size;
1285
1286        let mut frozen_count = 0;
1287        let items: Vec<_> = items
1288            .iter()
1289            .map(|item| {
1290                // > 2. Each item in the flex line has a target main size, initially set to its
1291                // > flex base size. Each item is initially unfrozen and may become frozen.
1292                let target_main_size = Cell::new(item.flex_base_size);
1293
1294                // > 3. Size inflexible items. Freeze, setting its target main size to its hypothetical main size…
1295                // > - any item that has a flex factor of zero
1296                // > - if using the flex grow factor: any item that has a flex base size
1297                // >   greater than its hypothetical main size
1298                // > - if using the flex shrink factor: any item that has a flex base size
1299                // >   smaller than its hypothetical main size
1300                let flex_factor = if grow {
1301                    item.box_.style().get_position().flex_grow.0
1302                } else {
1303                    item.box_.style().get_position().flex_shrink.0
1304                };
1305
1306                let is_inflexible = flex_factor == 0. ||
1307                    if grow {
1308                        item.flex_base_size > item.hypothetical_main_size
1309                    } else {
1310                        item.flex_base_size < item.hypothetical_main_size
1311                    };
1312
1313                let frozen = Cell::new(false);
1314                if is_inflexible {
1315                    frozen_count += 1;
1316                    frozen.set(true);
1317                    target_main_size.set(item.hypothetical_main_size);
1318                }
1319
1320                FlexibleLengthResolutionItem {
1321                    item,
1322                    frozen,
1323                    target_main_size,
1324                    flex_factor,
1325                    // The actual violation will be computed later.
1326                    min_max_violation_kind: Cell::new(Ordering::Equal),
1327                }
1328            })
1329            .collect();
1330
1331        let unfrozen_items = || items.iter().filter(|item| !item.frozen.get());
1332        let main_sizes = |items: Vec<FlexibleLengthResolutionItem>| {
1333            items
1334                .into_iter()
1335                .map(|item| item.target_main_size.get())
1336                .collect()
1337        };
1338
1339        // https://drafts.csswg.org/css-flexbox/#initial-free-space
1340        // > 4. Calculate initial free space. Sum the outer sizes of all items on the line, and
1341        // > subtract this from the flex container’s inner main size. For frozen items, use
1342        // > their outer target main size; for other items, use their outer flex base size.
1343        let free_space = |all_items_frozen| {
1344            let items_size = items
1345                .iter()
1346                .map(|item| {
1347                    item.item.pbm_auto_is_zero.main +
1348                        if all_items_frozen || item.frozen.get() {
1349                            item.target_main_size.get()
1350                        } else {
1351                            item.item.flex_base_size
1352                        }
1353                })
1354                .sum();
1355            container_main_size - items_size
1356        };
1357
1358        let initial_free_space = free_space(false);
1359        loop {
1360            // https://drafts.csswg.org/css-flexbox/#remaining-free-space
1361            let mut remaining_free_space = free_space(false);
1362            // > 5. a. Check for flexible items. If all the flex items on the line are
1363            // >       frozen, free space has been distributed; exit this loop.
1364            if frozen_count >= items.len() {
1365                return (main_sizes(items), remaining_free_space);
1366            }
1367
1368            // > 5. b. Calculate the remaining free space as for initial free space, above. If the
1369            // > sum of the unfrozen flex items’ flex factors is less than one, multiply the
1370            // > initial free space by this sum. If the magnitude of this value is less than
1371            // > the magnitude of the remaining free space, use this as the remaining free
1372            // > space.
1373            let unfrozen_items_flex_factor_sum =
1374                unfrozen_items().map(|item| item.flex_factor).sum();
1375            if unfrozen_items_flex_factor_sum < 1. {
1376                let multiplied = initial_free_space.scale_by(unfrozen_items_flex_factor_sum);
1377                if multiplied.abs() < remaining_free_space.abs() {
1378                    remaining_free_space = multiplied
1379                }
1380            }
1381
1382            // > 5. c. If the remaining free space is non-zero, distribute it proportional
1383            // to the flex factors:
1384            //
1385            // FIXME: is it a problem if floating point precision errors accumulate
1386            // and we get not-quite-zero remaining free space when we should get zero here?
1387            if !remaining_free_space.is_zero() {
1388                // > If using the flex grow factor:
1389                // > For every unfrozen item on the line, find the ratio of the item’s flex grow factor to
1390                // > the sum of the flex grow factors of all unfrozen items on the line. Set the item’s
1391                // > target main size to its flex base size plus a fraction of the remaining free space
1392                // > proportional to the ratio.
1393                if grow {
1394                    for item in unfrozen_items() {
1395                        let ratio = item.flex_factor / unfrozen_items_flex_factor_sum;
1396                        item.target_main_size
1397                            .set(item.item.flex_base_size + remaining_free_space.scale_by(ratio));
1398                    }
1399                // > If using the flex shrink factor
1400                // > For every unfrozen item on the line, multiply its flex shrink factor by its inner flex
1401                // > base size, and note this as its scaled flex shrink factor. Find the ratio of the
1402                // > item’s scaled flex shrink factor to the sum of the scaled flex shrink factors of all
1403                // > unfrozen items on the line. Set the item’s target main size to its flex base size
1404                // > minus a fraction of the absolute value of the remaining free space proportional to the
1405                // > ratio. Note this may result in a negative inner main size; it will be corrected in the
1406                // > next step.
1407                } else {
1408                    // https://drafts.csswg.org/css-flexbox/#scaled-flex-shrink-factor
1409                    let scaled_shrink_factor = |item: &FlexibleLengthResolutionItem| {
1410                        item.item.flex_base_size.scale_by(item.flex_factor)
1411                    };
1412                    let scaled_shrink_factors_sum: Au =
1413                        unfrozen_items().map(scaled_shrink_factor).sum();
1414                    if scaled_shrink_factors_sum > Au::zero() {
1415                        for item in unfrozen_items() {
1416                            let ratio = scaled_shrink_factor(item).0 as f32 /
1417                                scaled_shrink_factors_sum.0 as f32;
1418                            item.target_main_size.set(
1419                                item.item.flex_base_size -
1420                                    remaining_free_space.abs().scale_by(ratio),
1421                            );
1422                        }
1423                    }
1424                }
1425            }
1426
1427            // > 5. d. Fix min/max violations. Clamp each non-frozen item’s target main size
1428            // > by its used min and max main sizes and floor its content-box size at zero.
1429            // > If the item’s target main size was made smaller by this, it’s a max
1430            // > violation. If the item’s target main size was made larger by this, it’s a
1431            // > min violation.
1432            let mut total_violation = Au::zero();
1433            for item in unfrozen_items() {
1434                let unclamped = item.target_main_size.get();
1435                let clamped = unclamped.clamp_between_extremums(
1436                    item.item.content_min_main_size,
1437                    item.item.content_max_main_size,
1438                );
1439                item.target_main_size.set(clamped);
1440                // We represent min violations with Ordering::Greater, and max violations
1441                // with Ordering::Less.
1442                item.min_max_violation_kind.set(clamped.cmp(&unclamped));
1443                total_violation += clamped - unclamped;
1444            }
1445
1446            // > 5. e. Freeze over-flexed items. The total violation is the sum of the
1447            // > adjustments from the previous step ∑(clamped size - unclamped size). If the
1448            // > total violation is:
1449            // > - Zero:  Freeze all items.
1450            // > - Positive: Freeze all the items with min violations.
1451            // > - Negative:  Freeze all the items with max violations.
1452            match total_violation.cmp(&Au::zero()) {
1453                Ordering::Equal => {
1454                    // “Freeze all items.”
1455                    // Return instead, as that’s what the next loop iteration would do.
1456                    let remaining_free_space = free_space(true);
1457                    return (main_sizes(items), remaining_free_space);
1458                },
1459                total_violation_kind => {
1460                    for item in unfrozen_items() {
1461                        if item.min_max_violation_kind.get() == total_violation_kind {
1462                            item.frozen.set(true);
1463                            frozen_count += 1;
1464                        }
1465                    }
1466                },
1467            }
1468        }
1469    }
1470
1471    /// <https://drafts.csswg.org/css-flexbox/#algo-cross-line>
1472    fn cross_size<'items>(items: &'items [FlexLineItem<'items>], flex_context: &FlexContext) -> Au {
1473        if flex_context.config.container_is_single_line {
1474            if let SizeConstraint::Definite(size) =
1475                flex_context.container_inner_size_constraint.cross
1476            {
1477                return size;
1478            }
1479        }
1480
1481        let mut max_ascent = Au::zero();
1482        let mut max_descent = Au::zero();
1483        let mut max_outer_hypothetical_cross_size = Au::zero();
1484        for item in items.iter() {
1485            // TODO: check inline-axis is parallel to main axis, check no auto cross margins
1486            if matches!(
1487                item.item.align_self.0.value(),
1488                AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
1489            ) {
1490                let baseline = item.get_or_synthesize_baseline_with_cross_size(
1491                    item.layout_result.hypothetical_cross_size,
1492                );
1493                let hypothetical_margin_box_cross_size =
1494                    item.layout_result.hypothetical_cross_size + item.item.pbm_auto_is_zero.cross;
1495                max_ascent = max_ascent.max(baseline);
1496                max_descent = max_descent.max(hypothetical_margin_box_cross_size - baseline);
1497            } else {
1498                max_outer_hypothetical_cross_size = max_outer_hypothetical_cross_size.max(
1499                    item.layout_result.hypothetical_cross_size + item.item.pbm_auto_is_zero.cross,
1500                );
1501            }
1502        }
1503
1504        // https://drafts.csswg.org/css-flexbox/#baseline-participation
1505        let largest = max_outer_hypothetical_cross_size.max(max_ascent + max_descent);
1506        match flex_context.container_inner_size_constraint.cross {
1507            SizeConstraint::MinMax(min, max) if flex_context.config.container_is_single_line => {
1508                largest.clamp_between_extremums(min, max)
1509            },
1510            _ => largest,
1511        }
1512    }
1513
1514    fn finish_with_final_cross_size(
1515        mut self,
1516        flex_context: &mut FlexContext,
1517        main_gap: Au,
1518        final_line_cross_size: Au,
1519    ) -> FinalFlexLineLayout {
1520        // FIXME: Collapse `visibility: collapse` items
1521        // This involves “restart layout from the beginning” with a modified second round,
1522        // which will make structuring the code… interesting.
1523        // https://drafts.csswg.org/css-flexbox/#algo-visibility
1524
1525        // Distribute any remaining main free space to auto margins according to
1526        // https://drafts.csswg.org/css-flexbox/#algo-main-align.
1527        let auto_margins_count = self
1528            .items
1529            .iter()
1530            .map(|item| {
1531                item.item.margin.main_start.is_auto() as u32 +
1532                    item.item.margin.main_end.is_auto() as u32
1533            })
1534            .sum::<u32>();
1535        let (space_distributed_to_auto_main_margins, free_space_in_main_axis) =
1536            if self.free_space_in_main_axis > Au::zero() && auto_margins_count > 0 {
1537                (
1538                    self.free_space_in_main_axis / auto_margins_count as i32,
1539                    Au::zero(),
1540                )
1541            } else {
1542                (Au::zero(), self.free_space_in_main_axis)
1543            };
1544
1545        // Determine the used cross size of each flex item
1546        // https://drafts.csswg.org/css-flexbox/#algo-stretch
1547        let item_count = self.items.len();
1548        let mut shared_alignment_baseline = None;
1549        let mut item_used_cross_sizes = Vec::with_capacity(item_count);
1550        let mut item_margins = Vec::with_capacity(item_count);
1551        for item in self.items.iter_mut() {
1552            let cross_axis = match flex_context.config.flex_axis {
1553                FlexAxis::Row => Direction::Block,
1554                FlexAxis::Column => Direction::Inline,
1555            };
1556            let layout = &mut item.layout_result;
1557            let get_content_size = || match cross_axis {
1558                Direction::Block => layout.content_block_size.into(),
1559                Direction::Inline => item
1560                    .item
1561                    .inline_content_sizes(flex_context, item.used_main_size),
1562            };
1563            let used_cross_size = item.item.content_cross_sizes.resolve(
1564                cross_axis,
1565                item.item.automatic_cross_size,
1566                Au::zero,
1567                Some(Au::zero().max(final_line_cross_size - item.item.pbm_auto_is_zero.cross)),
1568                get_content_size,
1569                // Tables have a special sizing in the block axis in that handles collapsed rows,
1570                // but it would prevent stretching. So we only recognize tables in the inline axis.
1571                // The interaction of collapsed table tracks and the flexbox algorithms is unclear,
1572                // see https://github.com/w3c/csswg-drafts/issues/11408.
1573                item.item.box_.independent_formatting_context.is_table() &&
1574                    cross_axis == Direction::Inline,
1575            );
1576            item_used_cross_sizes.push(used_cross_size);
1577
1578            // “If the flex item has `align-self: stretch`, redo layout for its contents,
1579            // treating this used size as its definite cross size so that percentage-sized
1580            // children can be resolved.”
1581            // However, as resolved in https://github.com/w3c/csswg-drafts/issues/11784,
1582            // we do that when the cross size is `stretch`. We also need to do it if the
1583            // inline size changes, which may happen with a `fit-content` cross size.
1584            let needs_new_layout = match cross_axis {
1585                Direction::Block => {
1586                    (match item.item.content_cross_sizes.preferred {
1587                        Size::Initial => item.item.automatic_cross_size == Size::Stretch,
1588                        Size::Stretch => true,
1589                        _ => false,
1590                    }) && SizeConstraint::Definite(used_cross_size) !=
1591                        layout.containing_block_size.block &&
1592                        layout.depends_on_block_constraints
1593                },
1594                Direction::Inline => used_cross_size != layout.containing_block_size.inline,
1595            };
1596            if needs_new_layout {
1597                #[cfg(feature = "tracing")]
1598                tracing::warn!(
1599                    name: "Flex item stretch cache miss",
1600                    cached_inline = ?layout.containing_block_size.inline,
1601                    cached_block = ?layout.containing_block_size.block,
1602                    required_cross_size = ?used_cross_size,
1603                    cross_axis = ?cross_axis,
1604                    depends_on_block_constraints = layout.depends_on_block_constraints,
1605                );
1606                *layout =
1607                    item.item
1608                        .layout(item.used_main_size, flex_context, Some(used_cross_size));
1609            }
1610
1611            let baseline = item.get_or_synthesize_baseline_with_cross_size(used_cross_size);
1612            if matches!(
1613                item.item.align_self.0.value(),
1614                AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
1615            ) {
1616                shared_alignment_baseline =
1617                    Some(shared_alignment_baseline.unwrap_or(baseline).max(baseline));
1618            }
1619            item.layout_result
1620                .flex_alignment_baseline_relative_to_margin_box = Some(baseline);
1621
1622            item_margins.push(item.item.resolve_auto_margins(
1623                flex_context,
1624                final_line_cross_size,
1625                used_cross_size,
1626                space_distributed_to_auto_main_margins,
1627            ));
1628        }
1629
1630        // Align the items along the main-axis per justify-content.
1631        // Implement fallback alignment.
1632        //
1633        // In addition to the spec at https://www.w3.org/TR/css-align-3/ this implementation follows
1634        // the resolution of https://github.com/w3c/csswg-drafts/issues/10154
1635        let resolved_justify_content: AlignFlags = {
1636            let justify_content_style = flex_context.config.justify_content.primary();
1637
1638            // Inital values from the style system
1639            let mut resolved_justify_content = justify_content_style.value();
1640            let mut is_safe = justify_content_style.flags() == AlignFlags::SAFE;
1641
1642            // Fallback occurs in two cases:
1643
1644            // 1. If there is only a single item being aligned and alignment is a distributed alignment keyword
1645            //    https://www.w3.org/TR/css-align-3/#distribution-values
1646            if item_count <= 1 || free_space_in_main_axis <= Au::zero() {
1647                (resolved_justify_content, is_safe) = match resolved_justify_content {
1648                    AlignFlags::STRETCH => (AlignFlags::FLEX_START, true),
1649                    AlignFlags::SPACE_BETWEEN => (AlignFlags::FLEX_START, true),
1650                    AlignFlags::SPACE_AROUND => (AlignFlags::CENTER, true),
1651                    AlignFlags::SPACE_EVENLY => (AlignFlags::CENTER, true),
1652                    _ => (resolved_justify_content, is_safe),
1653                }
1654            };
1655
1656            // 2. If free space is negative the "safe" alignment variants all fallback to Start alignment
1657            if free_space_in_main_axis <= Au::zero() && is_safe {
1658                resolved_justify_content = AlignFlags::START;
1659            }
1660
1661            resolved_justify_content
1662        };
1663
1664        // Implement "unsafe" alignment. "safe" alignment is handled by the fallback process above.
1665        let main_start_position = match resolved_justify_content {
1666            AlignFlags::START => Au::zero(),
1667            AlignFlags::FLEX_START => {
1668                if flex_context.config.flex_direction_is_reversed {
1669                    free_space_in_main_axis
1670                } else {
1671                    Au::zero()
1672                }
1673            },
1674            AlignFlags::END => free_space_in_main_axis,
1675            AlignFlags::FLEX_END => {
1676                if flex_context.config.flex_direction_is_reversed {
1677                    Au::zero()
1678                } else {
1679                    free_space_in_main_axis
1680                }
1681            },
1682            AlignFlags::CENTER => free_space_in_main_axis / 2,
1683            AlignFlags::STRETCH => Au::zero(),
1684            AlignFlags::SPACE_BETWEEN => Au::zero(),
1685            AlignFlags::SPACE_AROUND => (free_space_in_main_axis / item_count as i32) / 2,
1686            AlignFlags::SPACE_EVENLY => free_space_in_main_axis / (item_count + 1) as i32,
1687
1688            // TODO: Implement all alignments. Note: not all alignment values are valid for content distribution
1689            _ => Au::zero(),
1690        };
1691
1692        let item_main_interval = match resolved_justify_content {
1693            AlignFlags::START => Au::zero(),
1694            AlignFlags::FLEX_START => Au::zero(),
1695            AlignFlags::END => Au::zero(),
1696            AlignFlags::FLEX_END => Au::zero(),
1697            AlignFlags::CENTER => Au::zero(),
1698            AlignFlags::STRETCH => Au::zero(),
1699            AlignFlags::SPACE_BETWEEN => free_space_in_main_axis / (item_count - 1) as i32,
1700            AlignFlags::SPACE_AROUND => free_space_in_main_axis / item_count as i32,
1701            AlignFlags::SPACE_EVENLY => free_space_in_main_axis / (item_count + 1) as i32,
1702
1703            // TODO: Implement all alignments. Note: not all alignment values are valid for content distribution
1704            _ => Au::zero(),
1705        };
1706        let item_main_interval = item_main_interval + main_gap;
1707
1708        let mut all_baselines = Baselines::default();
1709        let mut main_position_cursor = main_start_position;
1710
1711        let items = std::mem::take(&mut self.items);
1712        let item_fragments = izip!(items, item_margins, item_used_cross_sizes.iter())
1713            .map(|(item, item_margin, item_used_cross_size)| {
1714                let item_used_size = FlexRelativeVec2 {
1715                    main: item.used_main_size,
1716                    cross: *item_used_cross_size,
1717                };
1718                item.collect_fragment(
1719                    &self,
1720                    item_used_size,
1721                    item_margin,
1722                    item_main_interval,
1723                    final_line_cross_size,
1724                    &shared_alignment_baseline,
1725                    flex_context,
1726                    &mut all_baselines,
1727                    &mut main_position_cursor,
1728                )
1729            })
1730            .collect();
1731
1732        FinalFlexLineLayout {
1733            cross_size: final_line_cross_size,
1734            item_fragments,
1735            all_baselines,
1736            shared_alignment_baseline,
1737        }
1738    }
1739}
1740
1741impl FlexItem<'_> {
1742    /// Return the hypothetical cross size together with laid out contents of the fragment.
1743    /// From <https://drafts.csswg.org/css-flexbox/#algo-cross-item>:
1744    /// > performing layout as if it were an in-flow block-level box with the used main
1745    /// > size and the given available space, treating `auto` as `fit-content`.
1746    #[servo_tracing::instrument(
1747        name = "FlexItem::layout",
1748        skip_all,
1749        fields(
1750            self_address = self as *const _ as usize,
1751            box_address = self.box_ as *const _ as usize,
1752        )
1753    )]
1754    #[allow(clippy::too_many_arguments)]
1755    fn layout(
1756        &self,
1757        used_main_size: Au,
1758        flex_context: &FlexContext,
1759        used_cross_size_override: Option<Au>,
1760    ) -> FlexItemLayoutResult {
1761        let containing_block = flex_context.containing_block;
1762        let independent_formatting_context = &self.box_.independent_formatting_context;
1763        let is_table = independent_formatting_context.is_table();
1764        let mut positioning_context = PositioningContext::default();
1765        let item_writing_mode = independent_formatting_context.style().writing_mode;
1766        let item_is_horizontal = item_writing_mode.is_horizontal();
1767        let flex_axis = flex_context.config.flex_axis;
1768        let cross_axis_is_item_block_axis = cross_axis_is_item_block_axis(
1769            containing_block.style.writing_mode.is_horizontal(),
1770            item_is_horizontal,
1771            flex_axis,
1772        );
1773
1774        let (inline_size, block_size) = if cross_axis_is_item_block_axis {
1775            let cross_size = match used_cross_size_override {
1776                Some(s) => SizeConstraint::Definite(s),
1777                None => {
1778                    let inline_stretch_size =
1779                        Au::zero().max(containing_block.size.inline - self.pbm_auto_is_zero.main);
1780                    let block_stretch_size = containing_block
1781                        .size
1782                        .block
1783                        .to_definite()
1784                        .map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
1785                    let tentative_block_content_size = independent_formatting_context
1786                        .tentative_block_content_size(
1787                            self.preferred_aspect_ratio,
1788                            inline_stretch_size,
1789                        );
1790                    if let Some(block_content_size) = tentative_block_content_size {
1791                        SizeConstraint::Definite(self.content_cross_sizes.resolve(
1792                            Direction::Block,
1793                            Size::FitContent,
1794                            Au::zero,
1795                            block_stretch_size,
1796                            || block_content_size,
1797                            is_table,
1798                        ))
1799                    } else {
1800                        self.content_cross_sizes.resolve_extrinsic(
1801                            Size::FitContent,
1802                            Au::zero(),
1803                            block_stretch_size,
1804                        )
1805                    }
1806                },
1807            };
1808            (used_main_size, cross_size)
1809        } else {
1810            let cross_size = used_cross_size_override.unwrap_or_else(|| {
1811                let stretch_size =
1812                    Au::zero().max(containing_block.size.inline - self.pbm_auto_is_zero.cross);
1813                self.content_cross_sizes.resolve(
1814                    Direction::Inline,
1815                    Size::FitContent,
1816                    Au::zero,
1817                    Some(stretch_size),
1818                    || self.inline_content_sizes(flex_context, used_main_size),
1819                    is_table,
1820                )
1821            });
1822            // The main size of a flex item is considered to be definite if its flex basis is definite
1823            // or the flex container has a definite main size.
1824            // <https://drafts.csswg.org/css-flexbox-1/#definite-sizes>
1825            let main_size = if self.flex_base_size_is_definite ||
1826                flex_context
1827                    .container_inner_size_constraint
1828                    .main
1829                    .is_definite()
1830            {
1831                SizeConstraint::Definite(used_main_size)
1832            } else {
1833                SizeConstraint::default()
1834            };
1835            (cross_size, main_size)
1836        };
1837
1838        let item_style = independent_formatting_context.style();
1839        let item_as_containing_block = ContainingBlock {
1840            size: ContainingBlockSize {
1841                inline: inline_size,
1842                block: block_size,
1843            },
1844            style: item_style,
1845        };
1846
1847        let lazy_block_size = if !cross_axis_is_item_block_axis {
1848            used_main_size.into()
1849        } else if let Some(cross_size) = used_cross_size_override {
1850            cross_size.into()
1851        } else {
1852            let stretch_size = containing_block
1853                .size
1854                .block
1855                .to_definite()
1856                .map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
1857            LazySize::new(
1858                &self.content_cross_sizes,
1859                Direction::Block,
1860                Size::FitContent,
1861                Au::zero,
1862                stretch_size,
1863                is_table,
1864            )
1865        };
1866
1867        let layout = independent_formatting_context.layout(
1868            flex_context.layout_context,
1869            &mut positioning_context,
1870            &item_as_containing_block,
1871            containing_block,
1872            self.preferred_aspect_ratio,
1873            &lazy_block_size,
1874        );
1875        let IndependentFormattingContextLayoutResult {
1876            fragments,
1877            content_block_size,
1878            baselines: content_box_baselines,
1879            depends_on_block_constraints,
1880            specific_layout_info,
1881            ..
1882        } = layout;
1883
1884        let hypothetical_cross_size = if cross_axis_is_item_block_axis {
1885            lazy_block_size.resolve(|| content_block_size)
1886        } else {
1887            inline_size
1888        };
1889
1890        let item_inline_axis_is_horizontal = item_style.writing_mode.is_horizontal();
1891        let container_inline_axis_is_horizontal = flex_context.config.writing_mode.is_horizontal();
1892        let container_main_axis_is_horizontal = match flex_axis {
1893            FlexAxis::Row => container_inline_axis_is_horizontal,
1894            FlexAxis::Column => !container_inline_axis_is_horizontal,
1895        };
1896        let item_inline_axis_parallel_to_container_inline_axis =
1897            item_inline_axis_is_horizontal == container_inline_axis_is_horizontal;
1898        let item_inline_axis_parallel_to_container_main_axis =
1899            item_inline_axis_is_horizontal == container_main_axis_is_horizontal;
1900
1901        let content_baselines_relative_to_margin_box = content_box_baselines.offset(
1902            self.margin.cross_start.auto_is(Au::zero) +
1903                self.padding.cross_start +
1904                self.border.cross_start,
1905        );
1906
1907        let content_baselines_for_parent_relative_to_margin_box =
1908            if item_inline_axis_parallel_to_container_inline_axis {
1909                content_baselines_relative_to_margin_box
1910            } else {
1911                Baselines::default()
1912            };
1913
1914        let flex_alignment_baseline_relative_to_margin_box =
1915            if item_inline_axis_parallel_to_container_main_axis {
1916                match self.align_self.0.value() {
1917                    // ‘baseline’ computes to ‘first baseline’.
1918                    AlignFlags::BASELINE => content_baselines_relative_to_margin_box.first,
1919                    AlignFlags::LAST_BASELINE => content_baselines_relative_to_margin_box.last,
1920                    _ => None,
1921                }
1922            } else {
1923                None
1924            };
1925
1926        FlexItemLayoutResult {
1927            hypothetical_cross_size,
1928            fragments,
1929            positioning_context,
1930            content_baselines_for_parent_relative_to_margin_box,
1931            flex_alignment_baseline_relative_to_margin_box,
1932            content_block_size,
1933            containing_block_size: item_as_containing_block.size,
1934            depends_on_block_constraints,
1935            specific_layout_info,
1936        }
1937    }
1938
1939    fn synthesized_baseline_relative_to_margin_box(&self, content_size: Au) -> Au {
1940        // If the item does not have a baseline in the necessary axis,
1941        // then one is synthesized from the flex item’s border box.
1942        // https://drafts.csswg.org/css-flexbox/#valdef-align-items-baseline
1943        content_size +
1944            self.margin.cross_start.auto_is(Au::zero) +
1945            self.padding.cross_start +
1946            self.border.cross_start +
1947            self.border.cross_end +
1948            self.padding.cross_end
1949    }
1950
1951    /// Return the cross-start, cross-end, main-start, and main-end margins, with `auto` values resolved.
1952    /// See:
1953    ///
1954    /// - <https://drafts.csswg.org/css-flexbox/#algo-cross-margins>
1955    /// - <https://drafts.csswg.org/css-flexbox/#algo-main-align>
1956    fn resolve_auto_margins(
1957        &self,
1958        flex_context: &FlexContext,
1959        line_cross_size: Au,
1960        item_cross_content_size: Au,
1961        space_distributed_to_auto_main_margins: Au,
1962    ) -> FlexRelativeSides<Au> {
1963        let main_start = self
1964            .margin
1965            .main_start
1966            .auto_is(|| space_distributed_to_auto_main_margins);
1967        let main_end = self
1968            .margin
1969            .main_end
1970            .auto_is(|| space_distributed_to_auto_main_margins);
1971
1972        let auto_count = match (self.margin.cross_start, self.margin.cross_end) {
1973            (AuOrAuto::LengthPercentage(cross_start), AuOrAuto::LengthPercentage(cross_end)) => {
1974                return FlexRelativeSides {
1975                    cross_start,
1976                    cross_end,
1977                    main_start,
1978                    main_end,
1979                };
1980            },
1981            (AuOrAuto::Auto, AuOrAuto::Auto) => 2,
1982            _ => 1,
1983        };
1984        let outer_cross_size = self.pbm_auto_is_zero.cross + item_cross_content_size;
1985        let available = line_cross_size - outer_cross_size;
1986        let cross_start;
1987        let cross_end;
1988        if available > Au::zero() {
1989            let each_auto_margin = available / auto_count;
1990            cross_start = self.margin.cross_start.auto_is(|| each_auto_margin);
1991            cross_end = self.margin.cross_end.auto_is(|| each_auto_margin);
1992        } else {
1993            // “the block-start or inline-start margin (whichever is in the cross axis)”
1994            // This margin is the cross-end on iff `flex-wrap` is `wrap-reverse`,
1995            // cross-start otherwise.
1996            // We know this because:
1997            // https://drafts.csswg.org/css-flexbox/#flex-wrap-property
1998            // “For the values that are not wrap-reverse,
1999            //  the cross-start direction is equivalent to
2000            //  either the inline-start or block-start direction of the current writing mode
2001            //  (whichever is in the cross axis)
2002            //  and the cross-end direction is the opposite direction of cross-start.
2003            //  When flex-wrap is wrap-reverse,
2004            //  the cross-start and cross-end directions are swapped.”
2005            let flex_wrap = flex_context.containing_block.style.get_position().flex_wrap;
2006            let flex_wrap_reverse = match flex_wrap {
2007                FlexWrap::Nowrap | FlexWrap::Wrap => false,
2008                FlexWrap::WrapReverse => true,
2009            };
2010            // “if the block-start or inline-start margin (whichever is in the cross axis) is auto,
2011            //  set it to zero. Set the opposite margin so that the outer cross size of the item
2012            //  equals the cross size of its flex line.”
2013            if flex_wrap_reverse {
2014                cross_start = self.margin.cross_start.auto_is(|| available);
2015                cross_end = self.margin.cross_end.auto_is(Au::zero);
2016            } else {
2017                cross_start = self.margin.cross_start.auto_is(Au::zero);
2018                cross_end = self.margin.cross_end.auto_is(|| available);
2019            }
2020        }
2021
2022        FlexRelativeSides {
2023            cross_start,
2024            cross_end,
2025            main_start,
2026            main_end,
2027        }
2028    }
2029
2030    /// Return the coordinate of the cross-start side of the content area
2031    fn align_along_cross_axis(
2032        &self,
2033        margin: &FlexRelativeSides<Au>,
2034        used_cross_size: &Au,
2035        line_cross_size: Au,
2036        propagated_baseline: Au,
2037        max_propagated_baseline: Au,
2038        wrap_reverse: bool,
2039    ) -> Au {
2040        let ending_alignment = line_cross_size - *used_cross_size - self.pbm_auto_is_zero.cross;
2041        let outer_cross_start =
2042            if self.margin.cross_start.is_auto() || self.margin.cross_end.is_auto() {
2043                Au::zero()
2044            } else {
2045                match self.align_self.0.value() {
2046                    AlignFlags::STRETCH => Au::zero(),
2047                    AlignFlags::CENTER => ending_alignment / 2,
2048                    AlignFlags::BASELINE | AlignFlags::LAST_BASELINE => {
2049                        max_propagated_baseline - propagated_baseline
2050                    },
2051                    AlignFlags::START => {
2052                        if !wrap_reverse {
2053                            Au::zero()
2054                        } else {
2055                            ending_alignment
2056                        }
2057                    },
2058                    AlignFlags::END => {
2059                        if !wrap_reverse {
2060                            ending_alignment
2061                        } else {
2062                            Au::zero()
2063                        }
2064                    },
2065                    _ => Au::zero(),
2066                }
2067            };
2068        outer_cross_start + margin.cross_start + self.border.cross_start + self.padding.cross_start
2069    }
2070
2071    #[inline]
2072    fn inline_content_sizes(&self, flex_context: &FlexContext, block_size: Au) -> ContentSizes {
2073        self.box_.inline_content_sizes(
2074            flex_context,
2075            SizeConstraint::Definite(block_size),
2076            self.preferred_aspect_ratio,
2077        )
2078    }
2079}
2080
2081impl FlexItemBox {
2082    fn to_flex_item<'a>(
2083        &self,
2084        layout_context: &LayoutContext,
2085        containing_block: &IndefiniteContainingBlock,
2086        content_box_sizes_and_pbm: &ContentBoxSizesAndPBM,
2087        config: &FlexContainerConfig,
2088        flex_context_getter: &impl Fn() -> &'a FlexContext<'a>,
2089    ) -> FlexItem<'_> {
2090        let flex_axis = config.flex_axis;
2091        let style = self.style();
2092        let cross_axis_is_item_block_axis = cross_axis_is_item_block_axis(
2093            containing_block.style.writing_mode.is_horizontal(),
2094            style.writing_mode.is_horizontal(),
2095            flex_axis,
2096        );
2097        let main_axis = if cross_axis_is_item_block_axis {
2098            Direction::Inline
2099        } else {
2100            Direction::Block
2101        };
2102        let align_self = AlignItems(config.resolve_align_self_for_child(style));
2103
2104        let ContentBoxSizesAndPBM {
2105            content_box_sizes,
2106            pbm,
2107            depends_on_block_constraints,
2108            preferred_size_computes_to_auto,
2109        } = content_box_sizes_and_pbm;
2110
2111        let preferred_aspect_ratio = self
2112            .independent_formatting_context
2113            .preferred_aspect_ratio(&pbm.padding_border_sums);
2114        let padding = config.sides_to_flex_relative(pbm.padding);
2115        let border = config.sides_to_flex_relative(pbm.border);
2116        let margin = config.sides_to_flex_relative(pbm.margin);
2117        let padding_border = padding.sum_by_axis() + border.sum_by_axis();
2118        let margin_auto_is_zero = config.sides_to_flex_relative(pbm.margin.auto_is(Au::zero));
2119        let pbm_auto_is_zero = FlexRelativeVec2 {
2120            main: padding_border.main,
2121            cross: padding_border.cross,
2122        } + margin_auto_is_zero.sum_by_axis();
2123        let (content_main_sizes, content_cross_sizes, cross_size_computes_to_auto) = match flex_axis
2124        {
2125            FlexAxis::Row => (
2126                &content_box_sizes.inline,
2127                &content_box_sizes.block,
2128                preferred_size_computes_to_auto.block,
2129            ),
2130            FlexAxis::Column => (
2131                &content_box_sizes.block,
2132                &content_box_sizes.inline,
2133                preferred_size_computes_to_auto.inline,
2134            ),
2135        };
2136        let automatic_cross_size = if cross_size_computes_to_auto &&
2137            item_with_auto_cross_size_stretches_to_line_size(align_self, &margin)
2138        {
2139            Size::Stretch
2140        } else {
2141            Size::FitContent
2142        };
2143        let automatic_cross_size_for_intrinsic_sizing = if config.container_is_single_line {
2144            automatic_cross_size
2145        } else {
2146            Size::FitContent
2147        };
2148        let containing_block_size = flex_axis.vec2_to_flex_relative(containing_block.size);
2149        let stretch_size = FlexRelativeVec2 {
2150            main: containing_block_size
2151                .main
2152                .map(|v| Au::zero().max(v - pbm_auto_is_zero.main)),
2153            cross: containing_block_size
2154                .cross
2155                .map(|v| Au::zero().max(v - pbm_auto_is_zero.cross)),
2156        };
2157
2158        let is_table = self.independent_formatting_context.is_table();
2159        let tentative_cross_content_size = if cross_axis_is_item_block_axis {
2160            self.independent_formatting_context
2161                .tentative_block_content_size(
2162                    preferred_aspect_ratio,
2163                    stretch_size.main.unwrap_or_default(),
2164                )
2165        } else {
2166            None
2167        };
2168        let (preferred_cross_size, min_cross_size, max_cross_size) =
2169            if let Some(cross_content_size) = tentative_cross_content_size {
2170                let (preferred, min, max) = content_cross_sizes.resolve_each(
2171                    automatic_cross_size_for_intrinsic_sizing,
2172                    Au::zero,
2173                    stretch_size.cross,
2174                    || cross_content_size,
2175                    is_table,
2176                );
2177                (Some(preferred), min, max)
2178            } else {
2179                content_cross_sizes.resolve_each_extrinsic(
2180                    automatic_cross_size_for_intrinsic_sizing,
2181                    Au::zero(),
2182                    stretch_size.cross,
2183                )
2184            };
2185        let cross_size = SizeConstraint::new(preferred_cross_size, min_cross_size, max_cross_size);
2186
2187        // <https://drafts.csswg.org/css-flexbox/#transferred-size-suggestion>
2188        // > If the item has a preferred aspect ratio and its preferred cross size is definite, then the
2189        // > transferred size suggestion is that size (clamped by its minimum and maximum cross sizes if they
2190        // > are definite), converted through the aspect ratio. It is otherwise undefined.
2191        let transferred_size_suggestion =
2192            LazyCell::new(|| match (preferred_aspect_ratio, cross_size) {
2193                (Some(ratio), SizeConstraint::Definite(cross_size)) => {
2194                    Some(ratio.compute_dependent_size(main_axis, cross_size))
2195                },
2196                _ => None,
2197            });
2198
2199        // <https://drafts.csswg.org/css-flexbox/#algo-main-item>
2200        let flex_base_size_is_definite = Cell::new(true);
2201        let main_content_sizes = LazyCell::new(|| {
2202            let flex_item = &self.independent_formatting_context;
2203            // > B: If the flex item has ...
2204            // >   - a preferred aspect ratio,
2205            // >   - a used flex basis of content, and
2206            // >   - a definite cross size,
2207            // > then the flex base size is calculated from its used cross size and the flex item’s aspect ratio.
2208            if let Some(transferred_size_suggestion) = *transferred_size_suggestion {
2209                return transferred_size_suggestion.into();
2210            }
2211
2212            flex_base_size_is_definite.set(false);
2213
2214            // FIXME: implement cases C, D.
2215
2216            // > E. Otherwise, size the item into the available space using its used flex basis in place of
2217            // > its main size, treating a value of content as max-content. If a cross size is needed to
2218            // > determine the main size (e.g. when the flex item’s main size is in its block axis, or when
2219            // > it has a preferred aspect ratio) and the flex item’s cross size is auto and not definite,
2220            // > in this calculation use fit-content as the flex item’s cross size. The flex base size is
2221            // > the item’s resulting main size.
2222            if cross_axis_is_item_block_axis {
2223                // The main axis is the inline axis, so we can get the content size from the normal
2224                // preferred widths calculation.
2225                let constraint_space =
2226                    ConstraintSpace::new(cross_size, style, preferred_aspect_ratio);
2227                let content_sizes = flex_item
2228                    .inline_content_sizes(layout_context, &constraint_space)
2229                    .sizes;
2230                if let Some(ratio) = preferred_aspect_ratio {
2231                    let transferred_min = ratio.compute_dependent_size(main_axis, min_cross_size);
2232                    let transferred_max =
2233                        max_cross_size.map(|v| ratio.compute_dependent_size(main_axis, v));
2234                    content_sizes
2235                        .map(|size| size.clamp_between_extremums(transferred_min, transferred_max))
2236                } else {
2237                    content_sizes
2238                }
2239            } else {
2240                self.layout_for_block_content_size(
2241                    flex_context_getter(),
2242                    &pbm_auto_is_zero,
2243                    content_box_sizes,
2244                    preferred_aspect_ratio,
2245                    automatic_cross_size_for_intrinsic_sizing,
2246                    IntrinsicSizingMode::Size,
2247                )
2248                .into()
2249            }
2250        });
2251
2252        let flex_base_size = self
2253            .flex_basis(
2254                containing_block_size.main,
2255                content_main_sizes.preferred,
2256                padding_border.main,
2257            )
2258            .resolve_for_preferred(Size::MaxContent, stretch_size.main, &main_content_sizes);
2259        let flex_base_size_is_definite = flex_base_size_is_definite.take();
2260
2261        let content_max_main_size = content_main_sizes
2262            .max
2263            .resolve_for_max(stretch_size.main, &main_content_sizes);
2264
2265        let get_automatic_minimum_size = || {
2266            // This is an implementation of <https://drafts.csswg.org/css-flexbox/#min-size-auto>.
2267            if style.establishes_scroll_container(self.base_fragment_info().flags) {
2268                return Au::zero();
2269            }
2270
2271            // > **specified size suggestion**
2272            // > If the item’s preferred main size is definite and not automatic, then the specified
2273            // > size suggestion is that size. It is otherwise undefined.
2274            let specified_size_suggestion = content_main_sizes
2275                .preferred
2276                .maybe_resolve_extrinsic(stretch_size.main);
2277
2278            let is_replaced = self.independent_formatting_context.is_replaced();
2279
2280            // > **content size suggestion**
2281            // > The content size suggestion is the min-content size in the main axis, clamped, if it has a
2282            // > preferred aspect ratio, by any definite minimum and maximum cross sizes converted through the
2283            // > aspect ratio.
2284            let content_size_suggestion = match preferred_aspect_ratio {
2285                Some(ratio) => main_content_sizes.min_content.clamp_between_extremums(
2286                    ratio.compute_dependent_size(main_axis, min_cross_size),
2287                    max_cross_size.map(|l| ratio.compute_dependent_size(main_axis, l)),
2288                ),
2289                None => main_content_sizes.min_content,
2290            };
2291
2292            // > The content-based minimum size of a flex item is the smaller of its specified size
2293            // > suggestion and its content size suggestion if its specified size suggestion exists;
2294            // > otherwise, the smaller of its transferred size suggestion and its content size
2295            // > suggestion if the element is replaced and its transferred size suggestion exists;
2296            // > otherwise its content size suggestion. In all cases, the size is clamped by the maximum
2297            // > main size if it’s definite.
2298            match (specified_size_suggestion, *transferred_size_suggestion) {
2299                (Some(specified), _) => specified.min(content_size_suggestion),
2300                (_, Some(transferred)) if is_replaced => transferred.min(content_size_suggestion),
2301                _ => content_size_suggestion,
2302            }
2303            .clamp_below_max(content_max_main_size)
2304        };
2305        let content_min_main_size = content_main_sizes.min.resolve_for_min(
2306            get_automatic_minimum_size,
2307            stretch_size.main,
2308            &main_content_sizes,
2309            is_table,
2310        );
2311
2312        FlexItem {
2313            box_: self,
2314            content_cross_sizes: content_cross_sizes.clone(),
2315            padding,
2316            border,
2317            margin: config.sides_to_flex_relative(pbm.margin),
2318            pbm_auto_is_zero,
2319            flex_base_size,
2320            flex_base_size_is_definite,
2321            hypothetical_main_size: flex_base_size
2322                .clamp_between_extremums(content_min_main_size, content_max_main_size),
2323            content_min_main_size,
2324            content_max_main_size,
2325            align_self,
2326            depends_on_block_constraints: *depends_on_block_constraints,
2327            preferred_aspect_ratio,
2328            automatic_cross_size,
2329            automatic_cross_size_for_intrinsic_sizing,
2330        }
2331    }
2332
2333    fn main_content_size_info<'a>(
2334        &self,
2335        layout_context: &LayoutContext,
2336        containing_block: &IndefiniteContainingBlock,
2337        config: &FlexContainerConfig,
2338        flex_context_getter: &impl Fn() -> &'a FlexContext<'a>,
2339    ) -> FlexItemBoxInlineContentSizesInfo {
2340        let content_box_sizes_and_pbm = self
2341            .independent_formatting_context
2342            .layout_style()
2343            .content_box_sizes_and_padding_border_margin(containing_block);
2344
2345        // TODO: when laying out a column container with an indefinite main size,
2346        // we compute the base sizes of the items twice. We should consider caching.
2347        let FlexItem {
2348            flex_base_size,
2349            content_min_main_size,
2350            content_max_main_size,
2351            pbm_auto_is_zero,
2352            preferred_aspect_ratio,
2353            automatic_cross_size_for_intrinsic_sizing,
2354            ..
2355        } = self.to_flex_item(
2356            layout_context,
2357            containing_block,
2358            &content_box_sizes_and_pbm,
2359            config,
2360            flex_context_getter,
2361        );
2362
2363        // Compute the min-content and max-content contributions of the item.
2364        // <https://drafts.csswg.org/css-flexbox/#intrinsic-item-contributions>
2365        let (content_contribution_sizes, depends_on_block_constraints) = match config.flex_axis {
2366            FlexAxis::Row => {
2367                let auto_minimum = LogicalVec2 {
2368                    inline: content_min_main_size,
2369                    block: Au::zero(),
2370                };
2371                let InlineContentSizesResult {
2372                    sizes,
2373                    depends_on_block_constraints,
2374                } = self
2375                    .independent_formatting_context
2376                    .outer_inline_content_sizes(
2377                        layout_context,
2378                        containing_block,
2379                        &auto_minimum,
2380                        automatic_cross_size_for_intrinsic_sizing == Size::Stretch,
2381                    );
2382                (sizes, depends_on_block_constraints)
2383            },
2384            FlexAxis::Column => {
2385                let size = self.layout_for_block_content_size(
2386                    flex_context_getter(),
2387                    &pbm_auto_is_zero,
2388                    &content_box_sizes_and_pbm.content_box_sizes,
2389                    preferred_aspect_ratio,
2390                    automatic_cross_size_for_intrinsic_sizing,
2391                    IntrinsicSizingMode::Contribution,
2392                );
2393                (size.into(), true)
2394            },
2395        };
2396
2397        let outer_flex_base_size = flex_base_size + pbm_auto_is_zero.main;
2398        let outer_min_main_size = content_min_main_size + pbm_auto_is_zero.main;
2399        let outer_max_main_size = content_max_main_size.map(|v| v + pbm_auto_is_zero.main);
2400        let max_flex_factors = self.desired_flex_factors_for_preferred_width(
2401            content_contribution_sizes.max_content,
2402            flex_base_size,
2403            outer_flex_base_size,
2404        );
2405
2406        // > The min-content main size of a single-line flex container is calculated
2407        // > identically to the max-content main size, except that the flex items’
2408        // > min-content contributions are used instead of their max-content contributions.
2409        let min_flex_factors = self.desired_flex_factors_for_preferred_width(
2410            content_contribution_sizes.min_content,
2411            flex_base_size,
2412            outer_flex_base_size,
2413        );
2414
2415        // > However, for a multi-line container, the min-content main size is simply the
2416        // > largest min-content contribution of all the non-collapsed flex items in the
2417        // > flex container. For this purpose, each item’s contribution is capped by the
2418        // > item’s flex base size if the item is not growable, floored by the item’s flex
2419        // > base size if the item is not shrinkable, and then further clamped by the item’s
2420        // > min and max main sizes.
2421        let mut min_content_main_size_for_multiline_container =
2422            content_contribution_sizes.min_content;
2423        let style_position = &self.style().get_position();
2424        if style_position.flex_grow.is_zero() {
2425            min_content_main_size_for_multiline_container.min_assign(outer_flex_base_size);
2426        }
2427        if style_position.flex_shrink.is_zero() {
2428            min_content_main_size_for_multiline_container.max_assign(outer_flex_base_size);
2429        }
2430        min_content_main_size_for_multiline_container =
2431            min_content_main_size_for_multiline_container
2432                .clamp_between_extremums(outer_min_main_size, outer_max_main_size);
2433
2434        FlexItemBoxInlineContentSizesInfo {
2435            outer_flex_base_size,
2436            outer_min_main_size,
2437            outer_max_main_size,
2438            min_flex_factors,
2439            max_flex_factors,
2440            min_content_main_size_for_multiline_container,
2441            depends_on_block_constraints,
2442        }
2443    }
2444
2445    fn desired_flex_factors_for_preferred_width(
2446        &self,
2447        preferred_width: Au,
2448        flex_base_size: Au,
2449        outer_flex_base_size: Au,
2450    ) -> DesiredFlexFractionAndGrowOrShrinkFactor {
2451        let difference = (preferred_width - outer_flex_base_size).to_f32_px();
2452        let (flex_grow_or_scaled_flex_shrink_factor, desired_flex_fraction) = if difference > 0.0 {
2453            // > If that result is positive, divide it by the item’s flex
2454            // > grow factor if the flex grow > factor is ≥ 1, or multiply
2455            // > it by the flex grow factor if the flex grow factor is < 1;
2456            let flex_grow_factor = self.style().get_position().flex_grow.0;
2457
2458            (
2459                flex_grow_factor,
2460                if flex_grow_factor >= 1.0 {
2461                    difference / flex_grow_factor
2462                } else {
2463                    difference * flex_grow_factor
2464                },
2465            )
2466        } else if difference < 0.0 {
2467            // > if the result is negative, divide it by the item’s scaled
2468            // > flex shrink factor (if dividing > by zero, treat the result
2469            // > as negative infinity).
2470            let flex_shrink_factor = self.style().get_position().flex_shrink.0;
2471            let scaled_flex_shrink_factor = flex_shrink_factor * flex_base_size.to_f32_px();
2472
2473            (
2474                scaled_flex_shrink_factor,
2475                if scaled_flex_shrink_factor != 0.0 {
2476                    difference / scaled_flex_shrink_factor
2477                } else {
2478                    f32::NEG_INFINITY
2479                },
2480            )
2481        } else {
2482            (0.0, 0.0)
2483        };
2484
2485        DesiredFlexFractionAndGrowOrShrinkFactor {
2486            desired_flex_fraction,
2487            flex_grow_or_shrink_factor: flex_grow_or_scaled_flex_shrink_factor,
2488        }
2489    }
2490
2491    /// <https://drafts.csswg.org/css-flexbox-1/#flex-basis-property>
2492    /// Returns the used value of the `flex-basis` property, after resolving percentages,
2493    /// resolving `auto`, and taking `box-sizing` into account.
2494    /// Note that a return value of `Size::Initial` represents `flex-basis: content`,
2495    /// not `flex-basis: auto`, since the latter always resolves to something else.
2496    fn flex_basis(
2497        &self,
2498        container_definite_main_size: Option<Au>,
2499        main_preferred_size: Size<Au>,
2500        main_padding_border_sum: Au,
2501    ) -> Size<Au> {
2502        let style_position = &self.independent_formatting_context.style().get_position();
2503        match &style_position.flex_basis {
2504            // https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-content
2505            // > Indicates an automatic size based on the flex item’s content.
2506            FlexBasis::Content => Size::Initial,
2507
2508            FlexBasis::Size(size) => match Size::<LengthPercentage>::from(size.clone()) {
2509                // https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
2510                // > When specified on a flex item, the `auto` keyword retrieves
2511                // > the value of the main size property as the used `flex-basis`.
2512                Size::Initial => main_preferred_size,
2513
2514                // https://drafts.csswg.org/css-flexbox-1/#flex-basis-property
2515                // > For all values other than `auto` and `content` (defined above),
2516                // > `flex-basis` is resolved the same way as `width` in horizontal
2517                // > writing modes, except that if a value would resolve to `auto`
2518                // > for `width`, it instead resolves to `content` for `flex-basis`.
2519                // > For example, percentage values of `flex-basis` are resolved
2520                // > against the flex item’s containing block (i.e. its flex container);
2521                // > and if that containing block’s size is indefinite,
2522                // > the used value for `flex-basis` is `content`.
2523                size => {
2524                    let apply_box_sizing = |length: Au| {
2525                        match style_position.box_sizing {
2526                            BoxSizing::ContentBox => length,
2527                            // This may make `length` negative,
2528                            // but it will be clamped in the hypothetical main size
2529                            BoxSizing::BorderBox => length - main_padding_border_sum,
2530                        }
2531                    };
2532                    size.resolve_percentages_for_preferred(container_definite_main_size)
2533                        .map(apply_box_sizing)
2534                },
2535            },
2536        }
2537    }
2538
2539    #[allow(clippy::too_many_arguments)]
2540    #[servo_tracing::instrument(name = "FlexContainer::layout_for_block_content_size", skip_all)]
2541    fn layout_for_block_content_size(
2542        &self,
2543        flex_context: &FlexContext,
2544        pbm_auto_is_zero: &FlexRelativeVec2<Au>,
2545        content_box_sizes: &LogicalVec2<Sizes>,
2546        preferred_aspect_ratio: Option<AspectRatio>,
2547        automatic_inline_size: Size<Au>,
2548        intrinsic_sizing_mode: IntrinsicSizingMode,
2549    ) -> Au {
2550        let content_block_size = || {
2551            let mut positioning_context = PositioningContext::default();
2552            let style = self.independent_formatting_context.style();
2553
2554            // We are computing the intrinsic block size, so the tentative block size that we use
2555            // as an input to the intrinsic inline sizes needs to ignore the values of the sizing
2556            // properties in the block axis.
2557            let tentative_block_size = SizeConstraint::default();
2558
2559            // TODO: This is wrong if the item writing mode is different from the flex
2560            // container's writing mode.
2561            let inline_size = {
2562                let stretch_size =
2563                    flex_context.containing_block.size.inline - pbm_auto_is_zero.cross;
2564                let get_content_size = || {
2565                    self.inline_content_sizes(
2566                        flex_context,
2567                        tentative_block_size,
2568                        preferred_aspect_ratio,
2569                    )
2570                };
2571                content_box_sizes.inline.resolve(
2572                    Direction::Inline,
2573                    automatic_inline_size,
2574                    Au::zero,
2575                    Some(stretch_size),
2576                    get_content_size,
2577                    false,
2578                )
2579            };
2580            let item_as_containing_block = ContainingBlock {
2581                size: ContainingBlockSize {
2582                    inline: inline_size,
2583                    block: tentative_block_size,
2584                },
2585                style,
2586            };
2587            self.independent_formatting_context
2588                .layout(
2589                    flex_context.layout_context,
2590                    &mut positioning_context,
2591                    &item_as_containing_block,
2592                    flex_context.containing_block,
2593                    preferred_aspect_ratio,
2594                    &LazySize::intrinsic(),
2595                )
2596                .content_block_size
2597        };
2598        match intrinsic_sizing_mode {
2599            IntrinsicSizingMode::Contribution => {
2600                let stretch_size = flex_context
2601                    .containing_block
2602                    .size
2603                    .block
2604                    .to_definite()
2605                    .map(|block_size| block_size - pbm_auto_is_zero.main);
2606                let inner_block_size = content_box_sizes.block.resolve(
2607                    Direction::Block,
2608                    Size::FitContent,
2609                    Au::zero,
2610                    stretch_size,
2611                    || ContentSizes::from(content_block_size()),
2612                    // Tables have a special sizing in the block axis that handles collapsed rows
2613                    // by ignoring the sizing properties and instead relying on the content block size,
2614                    // which should indirectly take sizing properties into account.
2615                    // However, above we laid out the table with a SizeConstraint::default() block size,
2616                    // so the content block size doesn't take sizing properties into account.
2617                    // Therefore, pretending that it's never a table tends to provide a better result.
2618                    false, /* is_table */
2619                );
2620                inner_block_size + pbm_auto_is_zero.main
2621            },
2622            IntrinsicSizingMode::Size => content_block_size(),
2623        }
2624    }
2625
2626    fn inline_content_sizes(
2627        &self,
2628        flex_context: &FlexContext,
2629        block_size: SizeConstraint,
2630        preferred_aspect_ratio: Option<AspectRatio>,
2631    ) -> ContentSizes {
2632        let style = self.independent_formatting_context.style();
2633        let constraint_space = ConstraintSpace::new(block_size, style, preferred_aspect_ratio);
2634        self.independent_formatting_context
2635            .inline_content_sizes(flex_context.layout_context, &constraint_space)
2636            .sizes
2637    }
2638}