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