Skip to main content

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