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