layout/flexbox/
layout.rs

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