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::AbsoluteOrFixedPositionedPlaceholder(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    let job_sizes = lines
1192        .iter()
1193        .map(|line| line.0.iter().map(FlexItem::subtree_size).sum());
1194    if flex_context
1195        .layout_context
1196        .should_parallelize_layout(job_sizes)
1197    {
1198        lines.par_drain(..).map(construct_line).collect()
1199    } else {
1200        lines.into_iter().map(construct_line).collect()
1201    }
1202}
1203
1204/// The result of splitting the flex items into lines using their intrinsic sizes and doing an
1205/// initial layout of each item. A final layout still needs to happen after this is produced to
1206/// handle stretching.
1207struct InitialFlexLineLayout<'a> {
1208    /// The items that are placed in this line.
1209    items: Vec<FlexLineItem<'a>>,
1210
1211    /// The initial size of this flex line, not taking into account `align-content: stretch`.
1212    line_size: FlexRelativeVec2<Au>,
1213
1214    /// The free space available to this line after the initial layout.
1215    free_space_in_main_axis: Au,
1216}
1217
1218impl InitialFlexLineLayout<'_> {
1219    fn new<'items>(
1220        flex_context: &FlexContext,
1221        items: Vec<FlexItem<'items>>,
1222        outer_hypothetical_main_sizes_sum: Au,
1223        container_main_size: Au,
1224        main_gap: Au,
1225    ) -> InitialFlexLineLayout<'items> {
1226        let item_count = items.len();
1227        let (item_used_main_sizes, free_space_in_main_axis) = Self::resolve_flexible_lengths(
1228            &items,
1229            outer_hypothetical_main_sizes_sum,
1230            container_main_size - main_gap * (item_count as i32 - 1),
1231        );
1232
1233        // https://drafts.csswg.org/css-flexbox/#algo-cross-item
1234        let layout_results: Vec<_> = if flex_context
1235            .layout_context
1236            .should_parallelize_layout(items.iter().map(FlexItem::subtree_size))
1237        {
1238            items
1239                .par_iter()
1240                .zip(&item_used_main_sizes)
1241                .map(|(item, used_main_size)| item.layout(*used_main_size, flex_context, None))
1242                .collect()
1243        } else {
1244            items
1245                .iter()
1246                .zip(&item_used_main_sizes)
1247                .map(|(item, used_main_size)| item.layout(*used_main_size, flex_context, None))
1248                .collect()
1249        };
1250
1251        let items: Vec<_> = izip!(
1252            items.into_iter(),
1253            layout_results.into_iter(),
1254            item_used_main_sizes.into_iter()
1255        )
1256        .map(|(item, layout_result, used_main_size)| FlexLineItem {
1257            item,
1258            layout_result,
1259            used_main_size,
1260        })
1261        .collect();
1262
1263        // https://drafts.csswg.org/css-flexbox/#algo-cross-line
1264        let line_cross_size = Self::cross_size(&items, flex_context);
1265        let line_size = FlexRelativeVec2 {
1266            main: container_main_size,
1267            cross: line_cross_size,
1268        };
1269
1270        InitialFlexLineLayout {
1271            items,
1272            line_size,
1273            free_space_in_main_axis,
1274        }
1275    }
1276
1277    /// Return the *main size* of each item, and the line’s remainaing free space
1278    /// <https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths>
1279    fn resolve_flexible_lengths<'items>(
1280        items: &'items [FlexItem<'items>],
1281        outer_hypothetical_main_sizes_sum: Au,
1282        container_main_size: Au,
1283    ) -> (Vec<Au>, Au) {
1284        struct FlexibleLengthResolutionItem<'items> {
1285            item: &'items FlexItem<'items>,
1286            frozen: Cell<bool>,
1287            target_main_size: Cell<Au>,
1288            flex_factor: f32,
1289            min_max_violation_kind: Cell<Ordering>,
1290        }
1291
1292        // > 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
1293        // > items on the line. If the sum is less than the flex container’s inner main
1294        // > size, use the flex grow factor for the rest of this algorithm; otherwise, use
1295        // > the flex shrink factor.
1296        let grow = outer_hypothetical_main_sizes_sum < container_main_size;
1297
1298        let mut frozen_count = 0;
1299        let items: Vec<_> = items
1300            .iter()
1301            .map(|item| {
1302                // > 2. Each item in the flex line has a target main size, initially set to its
1303                // > flex base size. Each item is initially unfrozen and may become frozen.
1304                let target_main_size = Cell::new(item.flex_base_size);
1305
1306                // > 3. Size inflexible items. Freeze, setting its target main size to its hypothetical main size…
1307                // > - any item that has a flex factor of zero
1308                // > - if using the flex grow factor: any item that has a flex base size
1309                // >   greater than its hypothetical main size
1310                // > - if using the flex shrink factor: any item that has a flex base size
1311                // >   smaller than its hypothetical main size
1312                let flex_factor = if grow {
1313                    item.box_.style().get_position().flex_grow.0
1314                } else {
1315                    item.box_.style().get_position().flex_shrink.0
1316                };
1317
1318                let is_inflexible = flex_factor == 0. ||
1319                    if grow {
1320                        item.flex_base_size > item.hypothetical_main_size
1321                    } else {
1322                        item.flex_base_size < item.hypothetical_main_size
1323                    };
1324
1325                let frozen = Cell::new(false);
1326                if is_inflexible {
1327                    frozen_count += 1;
1328                    frozen.set(true);
1329                    target_main_size.set(item.hypothetical_main_size);
1330                }
1331
1332                FlexibleLengthResolutionItem {
1333                    item,
1334                    frozen,
1335                    target_main_size,
1336                    flex_factor,
1337                    // The actual violation will be computed later.
1338                    min_max_violation_kind: Cell::new(Ordering::Equal),
1339                }
1340            })
1341            .collect();
1342
1343        let unfrozen_items = || items.iter().filter(|item| !item.frozen.get());
1344        let main_sizes = |items: Vec<FlexibleLengthResolutionItem>| {
1345            items
1346                .into_iter()
1347                .map(|item| item.target_main_size.get())
1348                .collect()
1349        };
1350
1351        // https://drafts.csswg.org/css-flexbox/#initial-free-space
1352        // > 4. Calculate initial free space. Sum the outer sizes of all items on the line, and
1353        // > subtract this from the flex container’s inner main size. For frozen items, use
1354        // > their outer target main size; for other items, use their outer flex base size.
1355        let free_space = |all_items_frozen| {
1356            let items_size = items
1357                .iter()
1358                .map(|item| {
1359                    item.item.pbm_auto_is_zero.main +
1360                        if all_items_frozen || item.frozen.get() {
1361                            item.target_main_size.get()
1362                        } else {
1363                            item.item.flex_base_size
1364                        }
1365                })
1366                .sum();
1367            container_main_size - items_size
1368        };
1369
1370        let initial_free_space = free_space(false);
1371        loop {
1372            // https://drafts.csswg.org/css-flexbox/#remaining-free-space
1373            let mut remaining_free_space = free_space(false);
1374            // > 5. a. Check for flexible items. If all the flex items on the line are
1375            // >       frozen, free space has been distributed; exit this loop.
1376            if frozen_count >= items.len() {
1377                return (main_sizes(items), remaining_free_space);
1378            }
1379
1380            // > 5. b. Calculate the remaining free space as for initial free space, above. If the
1381            // > sum of the unfrozen flex items’ flex factors is less than one, multiply the
1382            // > initial free space by this sum. If the magnitude of this value is less than
1383            // > the magnitude of the remaining free space, use this as the remaining free
1384            // > space.
1385            let unfrozen_items_flex_factor_sum =
1386                unfrozen_items().map(|item| item.flex_factor).sum();
1387            if unfrozen_items_flex_factor_sum < 1. {
1388                let multiplied = initial_free_space.scale_by(unfrozen_items_flex_factor_sum);
1389                if multiplied.abs() < remaining_free_space.abs() {
1390                    remaining_free_space = multiplied
1391                }
1392            }
1393
1394            // > 5. c. If the remaining free space is non-zero, distribute it proportional
1395            // to the flex factors:
1396            //
1397            // FIXME: is it a problem if floating point precision errors accumulate
1398            // and we get not-quite-zero remaining free space when we should get zero here?
1399            if !remaining_free_space.is_zero() {
1400                // > If using the flex grow factor:
1401                // > For every unfrozen item on the line, find the ratio of the item’s flex grow factor to
1402                // > the sum of the flex grow factors of all unfrozen items on the line. Set the item’s
1403                // > target main size to its flex base size plus a fraction of the remaining free space
1404                // > proportional to the ratio.
1405                if grow {
1406                    for item in unfrozen_items() {
1407                        let ratio = item.flex_factor / unfrozen_items_flex_factor_sum;
1408                        item.target_main_size
1409                            .set(item.item.flex_base_size + remaining_free_space.scale_by(ratio));
1410                    }
1411                // > If using the flex shrink factor
1412                // > For every unfrozen item on the line, multiply its flex shrink factor by its inner flex
1413                // > base size, and note this as its scaled flex shrink factor. Find the ratio of the
1414                // > item’s scaled flex shrink factor to the sum of the scaled flex shrink factors of all
1415                // > unfrozen items on the line. Set the item’s target main size to its flex base size
1416                // > minus a fraction of the absolute value of the remaining free space proportional to the
1417                // > ratio. Note this may result in a negative inner main size; it will be corrected in the
1418                // > next step.
1419                } else {
1420                    // https://drafts.csswg.org/css-flexbox/#scaled-flex-shrink-factor
1421                    let scaled_shrink_factor = |item: &FlexibleLengthResolutionItem| {
1422                        item.item.flex_base_size.scale_by(item.flex_factor)
1423                    };
1424                    let scaled_shrink_factors_sum: Au =
1425                        unfrozen_items().map(scaled_shrink_factor).sum();
1426                    if scaled_shrink_factors_sum > Au::zero() {
1427                        for item in unfrozen_items() {
1428                            let ratio = scaled_shrink_factor(item).0 as f32 /
1429                                scaled_shrink_factors_sum.0 as f32;
1430                            item.target_main_size.set(
1431                                item.item.flex_base_size -
1432                                    remaining_free_space.abs().scale_by(ratio),
1433                            );
1434                        }
1435                    }
1436                }
1437            }
1438
1439            // > 5. d. Fix min/max violations. Clamp each non-frozen item’s target main size
1440            // > by its used min and max main sizes and floor its content-box size at zero.
1441            // > If the item’s target main size was made smaller by this, it’s a max
1442            // > violation. If the item’s target main size was made larger by this, it’s a
1443            // > min violation.
1444            let mut total_violation = Au::zero();
1445            for item in unfrozen_items() {
1446                let unclamped = item.target_main_size.get();
1447                let clamped = unclamped.clamp_between_extremums(
1448                    item.item.content_min_main_size,
1449                    item.item.content_max_main_size,
1450                );
1451                item.target_main_size.set(clamped);
1452                // We represent min violations with Ordering::Greater, and max violations
1453                // with Ordering::Less.
1454                item.min_max_violation_kind.set(clamped.cmp(&unclamped));
1455                total_violation += clamped - unclamped;
1456            }
1457
1458            // > 5. e. Freeze over-flexed items. The total violation is the sum of the
1459            // > adjustments from the previous step ∑(clamped size - unclamped size). If the
1460            // > total violation is:
1461            // > - Zero:  Freeze all items.
1462            // > - Positive: Freeze all the items with min violations.
1463            // > - Negative:  Freeze all the items with max violations.
1464            match total_violation.cmp(&Au::zero()) {
1465                Ordering::Equal => {
1466                    // “Freeze all items.”
1467                    // Return instead, as that’s what the next loop iteration would do.
1468                    let remaining_free_space = free_space(true);
1469                    return (main_sizes(items), remaining_free_space);
1470                },
1471                total_violation_kind => {
1472                    for item in unfrozen_items() {
1473                        if item.min_max_violation_kind.get() == total_violation_kind {
1474                            item.frozen.set(true);
1475                            frozen_count += 1;
1476                        }
1477                    }
1478                },
1479            }
1480        }
1481    }
1482
1483    /// <https://drafts.csswg.org/css-flexbox/#algo-cross-line>
1484    fn cross_size<'items>(items: &'items [FlexLineItem<'items>], flex_context: &FlexContext) -> Au {
1485        if flex_context.config.container_is_single_line &&
1486            let SizeConstraint::Definite(size) =
1487                flex_context.container_inner_size_constraint.cross
1488        {
1489            return size;
1490        }
1491
1492        let mut max_ascent = Au::zero();
1493        let mut max_descent = Au::zero();
1494        let mut max_outer_hypothetical_cross_size = Au::zero();
1495        for item in items.iter() {
1496            // TODO: check inline-axis is parallel to main axis, check no auto cross margins
1497            if matches!(
1498                item.item.align_self.0.value(),
1499                AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
1500            ) {
1501                let baseline = item.get_or_synthesize_baseline_with_cross_size(
1502                    item.layout_result.hypothetical_cross_size,
1503                );
1504                let hypothetical_margin_box_cross_size =
1505                    item.layout_result.hypothetical_cross_size + item.item.pbm_auto_is_zero.cross;
1506                max_ascent = max_ascent.max(baseline);
1507                max_descent = max_descent.max(hypothetical_margin_box_cross_size - baseline);
1508            } else {
1509                max_outer_hypothetical_cross_size = max_outer_hypothetical_cross_size.max(
1510                    item.layout_result.hypothetical_cross_size + item.item.pbm_auto_is_zero.cross,
1511                );
1512            }
1513        }
1514
1515        // https://drafts.csswg.org/css-flexbox/#baseline-participation
1516        let largest = max_outer_hypothetical_cross_size.max(max_ascent + max_descent);
1517        match flex_context.container_inner_size_constraint.cross {
1518            SizeConstraint::MinMax(min, max) if flex_context.config.container_is_single_line => {
1519                largest.clamp_between_extremums(min, max)
1520            },
1521            _ => largest,
1522        }
1523    }
1524
1525    fn finish_with_final_cross_size(
1526        mut self,
1527        flex_context: &mut FlexContext,
1528        main_gap: Au,
1529        final_line_cross_size: Au,
1530    ) -> FinalFlexLineLayout {
1531        // FIXME: Collapse `visibility: collapse` items
1532        // This involves “restart layout from the beginning” with a modified second round,
1533        // which will make structuring the code… interesting.
1534        // https://drafts.csswg.org/css-flexbox/#algo-visibility
1535
1536        // Distribute any remaining main free space to auto margins according to
1537        // https://drafts.csswg.org/css-flexbox/#algo-main-align.
1538        let auto_margins_count = self
1539            .items
1540            .iter()
1541            .map(|item| {
1542                item.item.margin.main_start.is_auto() as u32 +
1543                    item.item.margin.main_end.is_auto() as u32
1544            })
1545            .sum::<u32>();
1546        let (space_distributed_to_auto_main_margins, free_space_in_main_axis) =
1547            if self.free_space_in_main_axis > Au::zero() && auto_margins_count > 0 {
1548                (
1549                    self.free_space_in_main_axis / auto_margins_count as i32,
1550                    Au::zero(),
1551                )
1552            } else {
1553                (Au::zero(), self.free_space_in_main_axis)
1554            };
1555
1556        // Determine the used cross size of each flex item
1557        // https://drafts.csswg.org/css-flexbox/#algo-stretch
1558        let item_count = self.items.len();
1559        let mut shared_alignment_baseline = None;
1560        let mut item_used_cross_sizes = Vec::with_capacity(item_count);
1561        let mut item_margins = Vec::with_capacity(item_count);
1562        for item in self.items.iter_mut() {
1563            let cross_axis = match flex_context.config.flex_axis {
1564                FlexAxis::Row => Direction::Block,
1565                FlexAxis::Column => Direction::Inline,
1566            };
1567            let layout = &mut item.layout_result;
1568            let get_content_size = || match cross_axis {
1569                Direction::Block => layout.content_block_size.into(),
1570                Direction::Inline => item
1571                    .item
1572                    .inline_content_sizes(flex_context, item.used_main_size),
1573            };
1574            let used_cross_size = item.item.content_cross_sizes.resolve(
1575                cross_axis,
1576                item.item.automatic_cross_size,
1577                Au::zero,
1578                Some(Au::zero().max(final_line_cross_size - item.item.pbm_auto_is_zero.cross)),
1579                get_content_size,
1580                // Tables have a special sizing in the block axis in that handles collapsed rows,
1581                // but it would prevent stretching. So we only recognize tables in the inline axis.
1582                // The interaction of collapsed table tracks and the flexbox algorithms is unclear,
1583                // see https://github.com/w3c/csswg-drafts/issues/11408.
1584                item.item.box_.independent_formatting_context.is_table() &&
1585                    cross_axis == Direction::Inline,
1586            );
1587            item_used_cross_sizes.push(used_cross_size);
1588
1589            // “If the flex item has `align-self: stretch`, redo layout for its contents,
1590            // treating this used size as its definite cross size so that percentage-sized
1591            // children can be resolved.”
1592            // However, as resolved in https://github.com/w3c/csswg-drafts/issues/11784,
1593            // we do that when the cross size is `stretch`. We also need to do it if the
1594            // inline size changes, which may happen with a `fit-content` cross size.
1595            let needs_new_layout = match cross_axis {
1596                Direction::Block => {
1597                    (match item.item.content_cross_sizes.preferred {
1598                        Size::Initial => item.item.automatic_cross_size == Size::Stretch,
1599                        Size::Stretch => true,
1600                        _ => false,
1601                    }) && SizeConstraint::Definite(used_cross_size) !=
1602                        layout.containing_block_size.block &&
1603                        layout.depends_on_block_constraints
1604                },
1605                Direction::Inline => used_cross_size != layout.containing_block_size.inline,
1606            };
1607            if needs_new_layout {
1608                #[cfg(feature = "tracing")]
1609                tracing::warn!(
1610                    name: "Flex item stretch cache miss",
1611                    cached_inline = ?layout.containing_block_size.inline,
1612                    cached_block = ?layout.containing_block_size.block,
1613                    required_cross_size = ?used_cross_size,
1614                    cross_axis = ?cross_axis,
1615                    depends_on_block_constraints = layout.depends_on_block_constraints,
1616                );
1617                *layout =
1618                    item.item
1619                        .layout(item.used_main_size, flex_context, Some(used_cross_size));
1620            }
1621
1622            let baseline = item.get_or_synthesize_baseline_with_cross_size(used_cross_size);
1623            if matches!(
1624                item.item.align_self.0.value(),
1625                AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
1626            ) {
1627                shared_alignment_baseline =
1628                    Some(shared_alignment_baseline.unwrap_or(baseline).max(baseline));
1629            }
1630            item.layout_result
1631                .flex_alignment_baseline_relative_to_margin_box = Some(baseline);
1632
1633            item_margins.push(item.item.resolve_auto_margins(
1634                flex_context,
1635                final_line_cross_size,
1636                used_cross_size,
1637                space_distributed_to_auto_main_margins,
1638            ));
1639        }
1640
1641        // Align the items along the main-axis per justify-content.
1642        // Implement fallback alignment.
1643        //
1644        // In addition to the spec at https://www.w3.org/TR/css-align-3/ this implementation follows
1645        // the resolution of https://github.com/w3c/csswg-drafts/issues/10154
1646        let resolved_justify_content: AlignFlags = {
1647            let justify_content_style = flex_context.config.justify_content.primary();
1648
1649            // Inital values from the style system
1650            let mut resolved_justify_content = justify_content_style.value();
1651            let mut is_safe = justify_content_style.flags() == AlignFlags::SAFE;
1652
1653            // Fallback occurs in two cases:
1654
1655            // 1. If there is only a single item being aligned and alignment is a distributed alignment keyword
1656            //    https://www.w3.org/TR/css-align-3/#distribution-values
1657            if item_count <= 1 || free_space_in_main_axis <= Au::zero() {
1658                (resolved_justify_content, is_safe) = match resolved_justify_content {
1659                    AlignFlags::STRETCH => (AlignFlags::FLEX_START, true),
1660                    AlignFlags::SPACE_BETWEEN => (AlignFlags::FLEX_START, true),
1661                    AlignFlags::SPACE_AROUND => (AlignFlags::CENTER, true),
1662                    AlignFlags::SPACE_EVENLY => (AlignFlags::CENTER, true),
1663                    _ => (resolved_justify_content, is_safe),
1664                }
1665            };
1666
1667            // 2. If free space is negative the "safe" alignment variants all fallback to Start alignment
1668            if free_space_in_main_axis <= Au::zero() && is_safe {
1669                resolved_justify_content = AlignFlags::START;
1670            }
1671
1672            resolved_justify_content
1673        };
1674
1675        // Implement "unsafe" alignment. "safe" alignment is handled by the fallback process above.
1676        let main_start_position = match resolved_justify_content {
1677            AlignFlags::START => Au::zero(),
1678            AlignFlags::FLEX_START => {
1679                if flex_context.config.flex_direction_is_reversed {
1680                    free_space_in_main_axis
1681                } else {
1682                    Au::zero()
1683                }
1684            },
1685            AlignFlags::END => free_space_in_main_axis,
1686            AlignFlags::FLEX_END => {
1687                if flex_context.config.flex_direction_is_reversed {
1688                    Au::zero()
1689                } else {
1690                    free_space_in_main_axis
1691                }
1692            },
1693            AlignFlags::CENTER => free_space_in_main_axis / 2,
1694            AlignFlags::STRETCH => Au::zero(),
1695            AlignFlags::SPACE_BETWEEN => Au::zero(),
1696            AlignFlags::SPACE_AROUND => (free_space_in_main_axis / item_count as i32) / 2,
1697            AlignFlags::SPACE_EVENLY => free_space_in_main_axis / (item_count + 1) as i32,
1698
1699            // TODO: Implement all alignments. Note: not all alignment values are valid for content distribution
1700            _ => Au::zero(),
1701        };
1702
1703        let item_main_interval = match resolved_justify_content {
1704            AlignFlags::START => Au::zero(),
1705            AlignFlags::FLEX_START => Au::zero(),
1706            AlignFlags::END => Au::zero(),
1707            AlignFlags::FLEX_END => Au::zero(),
1708            AlignFlags::CENTER => Au::zero(),
1709            AlignFlags::STRETCH => Au::zero(),
1710            AlignFlags::SPACE_BETWEEN => free_space_in_main_axis / (item_count - 1) as i32,
1711            AlignFlags::SPACE_AROUND => free_space_in_main_axis / item_count as i32,
1712            AlignFlags::SPACE_EVENLY => free_space_in_main_axis / (item_count + 1) as i32,
1713
1714            // TODO: Implement all alignments. Note: not all alignment values are valid for content distribution
1715            _ => Au::zero(),
1716        };
1717        let item_main_interval = item_main_interval + main_gap;
1718
1719        let mut all_baselines = Baselines::default();
1720        let mut main_position_cursor = main_start_position;
1721
1722        let items = std::mem::take(&mut self.items);
1723        let item_fragments = izip!(items, item_margins, item_used_cross_sizes.iter())
1724            .map(|(item, item_margin, item_used_cross_size)| {
1725                let item_used_size = FlexRelativeVec2 {
1726                    main: item.used_main_size,
1727                    cross: *item_used_cross_size,
1728                };
1729                item.collect_fragment(
1730                    &self,
1731                    item_used_size,
1732                    item_margin,
1733                    item_main_interval,
1734                    final_line_cross_size,
1735                    &shared_alignment_baseline,
1736                    flex_context,
1737                    &mut all_baselines,
1738                    &mut main_position_cursor,
1739                )
1740            })
1741            .collect();
1742
1743        FinalFlexLineLayout {
1744            cross_size: final_line_cross_size,
1745            item_fragments,
1746            all_baselines,
1747            shared_alignment_baseline,
1748        }
1749    }
1750}
1751
1752impl FlexItem<'_> {
1753    /// Return the hypothetical cross size together with laid out contents of the fragment.
1754    /// From <https://drafts.csswg.org/css-flexbox/#algo-cross-item>:
1755    /// > performing layout as if it were an in-flow block-level box with the used main
1756    /// > size and the given available space, treating `auto` as `fit-content`.
1757    #[servo_tracing::instrument(
1758        name = "FlexItem::layout",
1759        skip_all,
1760        fields(
1761            self_address = self as *const _ as usize,
1762            box_address = self.box_ as *const _ as usize,
1763        )
1764    )]
1765    #[allow(clippy::too_many_arguments)]
1766    fn layout(
1767        &self,
1768        used_main_size: Au,
1769        flex_context: &FlexContext,
1770        used_cross_size_override: Option<Au>,
1771    ) -> FlexItemLayoutResult {
1772        let containing_block = flex_context.containing_block;
1773        let independent_formatting_context = &self.box_.independent_formatting_context;
1774        let is_table = independent_formatting_context.is_table();
1775        let mut positioning_context = PositioningContext::default();
1776        let item_writing_mode = independent_formatting_context.style().writing_mode;
1777        let item_is_horizontal = item_writing_mode.is_horizontal();
1778        let flex_axis = flex_context.config.flex_axis;
1779        let cross_axis_is_item_block_axis = cross_axis_is_item_block_axis(
1780            containing_block.style.writing_mode.is_horizontal(),
1781            item_is_horizontal,
1782            flex_axis,
1783        );
1784
1785        let (inline_size, block_size) = if cross_axis_is_item_block_axis {
1786            let cross_size = match used_cross_size_override {
1787                Some(s) => SizeConstraint::Definite(s),
1788                None => {
1789                    let inline_stretch_size =
1790                        Au::zero().max(containing_block.size.inline - self.pbm_auto_is_zero.main);
1791                    let block_stretch_size = containing_block
1792                        .size
1793                        .block
1794                        .to_definite()
1795                        .map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
1796                    let tentative_block_content_size = independent_formatting_context
1797                        .tentative_block_content_size(
1798                            self.preferred_aspect_ratio,
1799                            inline_stretch_size,
1800                        );
1801                    if let Some(block_content_size) = tentative_block_content_size {
1802                        SizeConstraint::Definite(self.content_cross_sizes.resolve(
1803                            Direction::Block,
1804                            Size::FitContent,
1805                            Au::zero,
1806                            block_stretch_size,
1807                            || block_content_size,
1808                            is_table,
1809                        ))
1810                    } else {
1811                        self.content_cross_sizes.resolve_extrinsic(
1812                            Size::FitContent,
1813                            Au::zero(),
1814                            block_stretch_size,
1815                        )
1816                    }
1817                },
1818            };
1819            (used_main_size, cross_size)
1820        } else {
1821            let cross_size = used_cross_size_override.unwrap_or_else(|| {
1822                let stretch_size =
1823                    Au::zero().max(containing_block.size.inline - self.pbm_auto_is_zero.cross);
1824                self.content_cross_sizes.resolve(
1825                    Direction::Inline,
1826                    Size::FitContent,
1827                    Au::zero,
1828                    Some(stretch_size),
1829                    || self.inline_content_sizes(flex_context, used_main_size),
1830                    is_table,
1831                )
1832            });
1833            // The main size of a flex item is considered to be definite if its flex basis is definite
1834            // or the flex container has a definite main size.
1835            // <https://drafts.csswg.org/css-flexbox-1/#definite-sizes>
1836            //
1837            // Each grid area’s width and height are considered definite
1838            // when laying out the grid items into their respective containing blocks,
1839            // after the grid is sized.
1840            // <https://drafts.csswg.org/css-grid-1/#layout-algorithm>
1841            let is_grid = self.box_.independent_formatting_context.is_grid();
1842            let main_size = if is_grid ||
1843                self.flex_base_size_is_definite ||
1844                flex_context
1845                    .container_inner_size_constraint
1846                    .main
1847                    .is_definite()
1848            {
1849                SizeConstraint::Definite(used_main_size)
1850            } else {
1851                SizeConstraint::default()
1852            };
1853            (cross_size, main_size)
1854        };
1855
1856        let item_style = independent_formatting_context.style();
1857        let item_as_containing_block = ContainingBlock {
1858            size: ContainingBlockSize {
1859                inline: inline_size,
1860                block: block_size,
1861            },
1862            style: item_style,
1863        };
1864
1865        let lazy_block_size = if !cross_axis_is_item_block_axis {
1866            used_main_size.into()
1867        } else if let Some(cross_size) = used_cross_size_override {
1868            cross_size.into()
1869        } else {
1870            let stretch_size = containing_block
1871                .size
1872                .block
1873                .to_definite()
1874                .map(|size| Au::zero().max(size - self.pbm_auto_is_zero.cross));
1875            LazySize::new(
1876                &self.content_cross_sizes,
1877                Direction::Block,
1878                Size::FitContent,
1879                Au::zero,
1880                stretch_size,
1881                is_table,
1882            )
1883        };
1884
1885        let layout = independent_formatting_context.layout(
1886            flex_context.layout_context,
1887            &mut positioning_context,
1888            &item_as_containing_block,
1889            containing_block,
1890            self.preferred_aspect_ratio,
1891            &lazy_block_size,
1892        );
1893        let IndependentFormattingContextLayoutResult {
1894            fragments,
1895            content_block_size,
1896            baselines: content_box_baselines,
1897            depends_on_block_constraints,
1898            specific_layout_info,
1899            ..
1900        } = layout;
1901
1902        let hypothetical_cross_size = if cross_axis_is_item_block_axis {
1903            lazy_block_size.resolve(|| content_block_size)
1904        } else {
1905            inline_size
1906        };
1907
1908        let item_inline_axis_is_horizontal = item_style.writing_mode.is_horizontal();
1909        let container_inline_axis_is_horizontal = flex_context.config.writing_mode.is_horizontal();
1910        let container_main_axis_is_horizontal = match flex_axis {
1911            FlexAxis::Row => container_inline_axis_is_horizontal,
1912            FlexAxis::Column => !container_inline_axis_is_horizontal,
1913        };
1914        let item_inline_axis_parallel_to_container_inline_axis =
1915            item_inline_axis_is_horizontal == container_inline_axis_is_horizontal;
1916        let item_inline_axis_parallel_to_container_main_axis =
1917            item_inline_axis_is_horizontal == container_main_axis_is_horizontal;
1918
1919        let content_baselines_relative_to_margin_box = content_box_baselines.offset(
1920            self.margin.cross_start.auto_is(Au::zero) +
1921                self.padding.cross_start +
1922                self.border.cross_start,
1923        );
1924
1925        let content_baselines_for_parent_relative_to_margin_box =
1926            if item_inline_axis_parallel_to_container_inline_axis {
1927                content_baselines_relative_to_margin_box
1928            } else {
1929                Baselines::default()
1930            };
1931
1932        let flex_alignment_baseline_relative_to_margin_box =
1933            if item_inline_axis_parallel_to_container_main_axis {
1934                match self.align_self.0.value() {
1935                    // ‘baseline’ computes to ‘first baseline’.
1936                    AlignFlags::BASELINE => content_baselines_relative_to_margin_box.first,
1937                    AlignFlags::LAST_BASELINE => content_baselines_relative_to_margin_box.last,
1938                    _ => None,
1939                }
1940            } else {
1941                None
1942            };
1943
1944        FlexItemLayoutResult {
1945            hypothetical_cross_size,
1946            fragments,
1947            positioning_context,
1948            content_baselines_for_parent_relative_to_margin_box,
1949            flex_alignment_baseline_relative_to_margin_box,
1950            content_block_size,
1951            containing_block_size: item_as_containing_block.size,
1952            depends_on_block_constraints,
1953            specific_layout_info,
1954        }
1955    }
1956
1957    fn synthesized_baseline_relative_to_margin_box(&self, content_size: Au) -> Au {
1958        // If the item does not have a baseline in the necessary axis,
1959        // then one is synthesized from the flex item’s border box.
1960        // https://drafts.csswg.org/css-flexbox/#valdef-align-items-baseline
1961        content_size +
1962            self.margin.cross_start.auto_is(Au::zero) +
1963            self.padding.cross_start +
1964            self.border.cross_start +
1965            self.border.cross_end +
1966            self.padding.cross_end
1967    }
1968
1969    fn subtree_size(&self) -> usize {
1970        self.box_.independent_formatting_context.subtree_size()
1971    }
1972
1973    /// Return the cross-start, cross-end, main-start, and main-end margins, with `auto` values resolved.
1974    /// See:
1975    ///
1976    /// - <https://drafts.csswg.org/css-flexbox/#algo-cross-margins>
1977    /// - <https://drafts.csswg.org/css-flexbox/#algo-main-align>
1978    fn resolve_auto_margins(
1979        &self,
1980        flex_context: &FlexContext,
1981        line_cross_size: Au,
1982        item_cross_content_size: Au,
1983        space_distributed_to_auto_main_margins: Au,
1984    ) -> FlexRelativeSides<Au> {
1985        let main_start = self
1986            .margin
1987            .main_start
1988            .auto_is(|| space_distributed_to_auto_main_margins);
1989        let main_end = self
1990            .margin
1991            .main_end
1992            .auto_is(|| space_distributed_to_auto_main_margins);
1993
1994        let auto_count = match (self.margin.cross_start, self.margin.cross_end) {
1995            (AuOrAuto::LengthPercentage(cross_start), AuOrAuto::LengthPercentage(cross_end)) => {
1996                return FlexRelativeSides {
1997                    cross_start,
1998                    cross_end,
1999                    main_start,
2000                    main_end,
2001                };
2002            },
2003            (AuOrAuto::Auto, AuOrAuto::Auto) => 2,
2004            _ => 1,
2005        };
2006        let outer_cross_size = self.pbm_auto_is_zero.cross + item_cross_content_size;
2007        let available = line_cross_size - outer_cross_size;
2008        let cross_start;
2009        let cross_end;
2010        if available > Au::zero() {
2011            let each_auto_margin = available / auto_count;
2012            cross_start = self.margin.cross_start.auto_is(|| each_auto_margin);
2013            cross_end = self.margin.cross_end.auto_is(|| each_auto_margin);
2014        } else {
2015            // “the block-start or inline-start margin (whichever is in the cross axis)”
2016            // This margin is the cross-end on iff `flex-wrap` is `wrap-reverse`,
2017            // cross-start otherwise.
2018            // We know this because:
2019            // https://drafts.csswg.org/css-flexbox/#flex-wrap-property
2020            // “For the values that are not wrap-reverse,
2021            //  the cross-start direction is equivalent to
2022            //  either the inline-start or block-start direction of the current writing mode
2023            //  (whichever is in the cross axis)
2024            //  and the cross-end direction is the opposite direction of cross-start.
2025            //  When flex-wrap is wrap-reverse,
2026            //  the cross-start and cross-end directions are swapped.”
2027            let flex_wrap = flex_context.containing_block.style.get_position().flex_wrap;
2028            let flex_wrap_reverse = match flex_wrap {
2029                FlexWrap::Nowrap | FlexWrap::Wrap => false,
2030                FlexWrap::WrapReverse => true,
2031            };
2032            // “if the block-start or inline-start margin (whichever is in the cross axis) is auto,
2033            //  set it to zero. Set the opposite margin so that the outer cross size of the item
2034            //  equals the cross size of its flex line.”
2035            if flex_wrap_reverse {
2036                cross_start = self.margin.cross_start.auto_is(|| available);
2037                cross_end = self.margin.cross_end.auto_is(Au::zero);
2038            } else {
2039                cross_start = self.margin.cross_start.auto_is(Au::zero);
2040                cross_end = self.margin.cross_end.auto_is(|| available);
2041            }
2042        }
2043
2044        FlexRelativeSides {
2045            cross_start,
2046            cross_end,
2047            main_start,
2048            main_end,
2049        }
2050    }
2051
2052    /// Return the coordinate of the cross-start side of the content area
2053    fn align_along_cross_axis(
2054        &self,
2055        margin: &FlexRelativeSides<Au>,
2056        used_cross_size: &Au,
2057        line_cross_size: Au,
2058        propagated_baseline: Au,
2059        max_propagated_baseline: Au,
2060        wrap_reverse: bool,
2061    ) -> Au {
2062        let ending_alignment = line_cross_size - *used_cross_size - self.pbm_auto_is_zero.cross;
2063        let outer_cross_start =
2064            if self.margin.cross_start.is_auto() || self.margin.cross_end.is_auto() {
2065                Au::zero()
2066            } else {
2067                match self.align_self.0.value() {
2068                    AlignFlags::STRETCH => Au::zero(),
2069                    AlignFlags::CENTER => ending_alignment / 2,
2070                    AlignFlags::BASELINE | AlignFlags::LAST_BASELINE => {
2071                        max_propagated_baseline - propagated_baseline
2072                    },
2073                    AlignFlags::START => {
2074                        if !wrap_reverse {
2075                            Au::zero()
2076                        } else {
2077                            ending_alignment
2078                        }
2079                    },
2080                    AlignFlags::END => {
2081                        if !wrap_reverse {
2082                            ending_alignment
2083                        } else {
2084                            Au::zero()
2085                        }
2086                    },
2087                    _ => Au::zero(),
2088                }
2089            };
2090        outer_cross_start + margin.cross_start + self.border.cross_start + self.padding.cross_start
2091    }
2092
2093    #[inline]
2094    fn inline_content_sizes(&self, flex_context: &FlexContext, block_size: Au) -> ContentSizes {
2095        self.box_.inline_content_sizes(
2096            flex_context,
2097            SizeConstraint::Definite(block_size),
2098            self.preferred_aspect_ratio,
2099        )
2100    }
2101}
2102
2103impl FlexItemBox {
2104    fn to_flex_item<'a>(
2105        &self,
2106        layout_context: &LayoutContext,
2107        containing_block: &IndefiniteContainingBlock,
2108        content_box_sizes_and_pbm: &ContentBoxSizesAndPBM,
2109        config: &FlexContainerConfig,
2110        flex_context_getter: &impl Fn() -> &'a FlexContext<'a>,
2111    ) -> FlexItem<'_> {
2112        let flex_axis = config.flex_axis;
2113        let style = self.style();
2114        let cross_axis_is_item_block_axis = cross_axis_is_item_block_axis(
2115            containing_block.style.writing_mode.is_horizontal(),
2116            style.writing_mode.is_horizontal(),
2117            flex_axis,
2118        );
2119        let main_axis = if cross_axis_is_item_block_axis {
2120            Direction::Inline
2121        } else {
2122            Direction::Block
2123        };
2124        let align_self = AlignItems(config.resolve_align_self_for_child(style));
2125
2126        let ContentBoxSizesAndPBM {
2127            content_box_sizes,
2128            pbm,
2129            depends_on_block_constraints,
2130            preferred_size_computes_to_auto,
2131        } = content_box_sizes_and_pbm;
2132
2133        let preferred_aspect_ratio = self
2134            .independent_formatting_context
2135            .preferred_aspect_ratio(&pbm.padding_border_sums);
2136        let padding = config.sides_to_flex_relative(pbm.padding);
2137        let border = config.sides_to_flex_relative(pbm.border);
2138        let margin = config.sides_to_flex_relative(pbm.margin);
2139        let padding_border = padding.sum_by_axis() + border.sum_by_axis();
2140        let margin_auto_is_zero = config.sides_to_flex_relative(pbm.margin.auto_is(Au::zero));
2141        let pbm_auto_is_zero = FlexRelativeVec2 {
2142            main: padding_border.main,
2143            cross: padding_border.cross,
2144        } + margin_auto_is_zero.sum_by_axis();
2145        let (content_main_sizes, content_cross_sizes, cross_size_computes_to_auto) = match flex_axis
2146        {
2147            FlexAxis::Row => (
2148                &content_box_sizes.inline,
2149                &content_box_sizes.block,
2150                preferred_size_computes_to_auto.block,
2151            ),
2152            FlexAxis::Column => (
2153                &content_box_sizes.block,
2154                &content_box_sizes.inline,
2155                preferred_size_computes_to_auto.inline,
2156            ),
2157        };
2158        let automatic_cross_size = if cross_size_computes_to_auto &&
2159            item_with_auto_cross_size_stretches_to_line_size(align_self, &margin)
2160        {
2161            Size::Stretch
2162        } else {
2163            Size::FitContent
2164        };
2165        let automatic_cross_size_for_intrinsic_sizing = if config.container_is_single_line {
2166            automatic_cross_size
2167        } else {
2168            Size::FitContent
2169        };
2170        let containing_block_size = flex_axis.vec2_to_flex_relative(containing_block.size);
2171        let stretch_size = FlexRelativeVec2 {
2172            main: containing_block_size
2173                .main
2174                .map(|v| Au::zero().max(v - pbm_auto_is_zero.main)),
2175            cross: containing_block_size
2176                .cross
2177                .map(|v| Au::zero().max(v - pbm_auto_is_zero.cross)),
2178        };
2179
2180        let is_table = self.independent_formatting_context.is_table();
2181        let tentative_cross_content_size = if cross_axis_is_item_block_axis {
2182            self.independent_formatting_context
2183                .tentative_block_content_size(
2184                    preferred_aspect_ratio,
2185                    stretch_size.main.unwrap_or_default(),
2186                )
2187        } else {
2188            None
2189        };
2190        let (preferred_cross_size, min_cross_size, max_cross_size) =
2191            if let Some(cross_content_size) = tentative_cross_content_size {
2192                let (preferred, min, max) = content_cross_sizes.resolve_each(
2193                    automatic_cross_size_for_intrinsic_sizing,
2194                    Au::zero,
2195                    stretch_size.cross,
2196                    || cross_content_size,
2197                    is_table,
2198                );
2199                (Some(preferred), min, max)
2200            } else {
2201                content_cross_sizes.resolve_each_extrinsic(
2202                    automatic_cross_size_for_intrinsic_sizing,
2203                    Au::zero(),
2204                    stretch_size.cross,
2205                )
2206            };
2207        let cross_size = SizeConstraint::new(preferred_cross_size, min_cross_size, max_cross_size);
2208
2209        // <https://drafts.csswg.org/css-flexbox/#transferred-size-suggestion>
2210        // > If the item has a preferred aspect ratio and its preferred cross size is definite, then the
2211        // > transferred size suggestion is that size (clamped by its minimum and maximum cross sizes if they
2212        // > are definite), converted through the aspect ratio. It is otherwise undefined.
2213        let transferred_size_suggestion =
2214            LazyCell::new(|| match (preferred_aspect_ratio, cross_size) {
2215                (Some(ratio), SizeConstraint::Definite(cross_size)) => {
2216                    Some(ratio.compute_dependent_size(main_axis, cross_size))
2217                },
2218                _ => None,
2219            });
2220
2221        // <https://drafts.csswg.org/css-flexbox/#algo-main-item>
2222        let flex_base_size_is_definite = Cell::new(true);
2223        let main_content_sizes = LazyCell::new(|| {
2224            let flex_item = &self.independent_formatting_context;
2225            // > B: If the flex item has ...
2226            // >   - a preferred aspect ratio,
2227            // >   - a used flex basis of content, and
2228            // >   - a definite cross size,
2229            // > then the flex base size is calculated from its used cross size and the flex item’s aspect ratio.
2230            if let Some(transferred_size_suggestion) = *transferred_size_suggestion {
2231                return transferred_size_suggestion.into();
2232            }
2233
2234            flex_base_size_is_definite.set(false);
2235
2236            // FIXME: implement cases C, D.
2237
2238            // > E. Otherwise, size the item into the available space using its used flex basis in place of
2239            // > its main size, treating a value of content as max-content. If a cross size is needed to
2240            // > determine the main size (e.g. when the flex item’s main size is in its block axis, or when
2241            // > it has a preferred aspect ratio) and the flex item’s cross size is auto and not definite,
2242            // > in this calculation use fit-content as the flex item’s cross size. The flex base size is
2243            // > the item’s resulting main size.
2244            if cross_axis_is_item_block_axis {
2245                // The main axis is the inline axis, so we can get the content size from the normal
2246                // preferred widths calculation.
2247                let constraint_space =
2248                    ConstraintSpace::new(cross_size, style, preferred_aspect_ratio);
2249                let content_sizes = flex_item
2250                    .inline_content_sizes(layout_context, &constraint_space)
2251                    .sizes;
2252                if let Some(ratio) = preferred_aspect_ratio {
2253                    let transferred_min = ratio.compute_dependent_size(main_axis, min_cross_size);
2254                    let transferred_max =
2255                        max_cross_size.map(|v| ratio.compute_dependent_size(main_axis, v));
2256                    content_sizes
2257                        .map(|size| size.clamp_between_extremums(transferred_min, transferred_max))
2258                } else {
2259                    content_sizes
2260                }
2261            } else {
2262                self.layout_for_block_content_size(
2263                    flex_context_getter(),
2264                    &pbm_auto_is_zero,
2265                    content_box_sizes,
2266                    preferred_aspect_ratio,
2267                    automatic_cross_size_for_intrinsic_sizing,
2268                    IntrinsicSizingMode::Size,
2269                )
2270                .into()
2271            }
2272        });
2273
2274        let flex_base_size = self
2275            .flex_basis(
2276                containing_block_size.main,
2277                content_main_sizes.preferred,
2278                padding_border.main,
2279            )
2280            .resolve_for_preferred(Size::MaxContent, stretch_size.main, &main_content_sizes);
2281        let flex_base_size_is_definite = flex_base_size_is_definite.take();
2282
2283        let content_max_main_size = content_main_sizes
2284            .max
2285            .resolve_for_max(stretch_size.main, &main_content_sizes);
2286
2287        let get_automatic_minimum_size = || {
2288            // This is an implementation of <https://drafts.csswg.org/css-flexbox/#min-size-auto>.
2289            if style.establishes_scroll_container(self.base_fragment_info().flags) {
2290                return Au::zero();
2291            }
2292
2293            // > **specified size suggestion**
2294            // > If the item’s preferred main size is definite and not automatic, then the specified
2295            // > size suggestion is that size. It is otherwise undefined.
2296            let specified_size_suggestion = content_main_sizes
2297                .preferred
2298                .maybe_resolve_extrinsic(stretch_size.main);
2299
2300            let is_replaced = self.independent_formatting_context.is_replaced();
2301
2302            // > **content size suggestion**
2303            // > The content size suggestion is the min-content size in the main axis, clamped, if it has a
2304            // > preferred aspect ratio, by any definite minimum and maximum cross sizes converted through the
2305            // > aspect ratio.
2306            let content_size_suggestion = match preferred_aspect_ratio {
2307                Some(ratio) => main_content_sizes.min_content.clamp_between_extremums(
2308                    ratio.compute_dependent_size(main_axis, min_cross_size),
2309                    max_cross_size.map(|l| ratio.compute_dependent_size(main_axis, l)),
2310                ),
2311                None => main_content_sizes.min_content,
2312            };
2313
2314            // > The content-based minimum size of a flex item is the smaller of its specified size
2315            // > suggestion and its content size suggestion if its specified size suggestion exists;
2316            // > otherwise, the smaller of its transferred size suggestion and its content size
2317            // > suggestion if the element is replaced and its transferred size suggestion exists;
2318            // > otherwise its content size suggestion. In all cases, the size is clamped by the maximum
2319            // > main size if it’s definite.
2320            match (specified_size_suggestion, *transferred_size_suggestion) {
2321                (Some(specified), _) => specified.min(content_size_suggestion),
2322                (_, Some(transferred)) if is_replaced => transferred.min(content_size_suggestion),
2323                _ => content_size_suggestion,
2324            }
2325            .clamp_below_max(content_max_main_size)
2326        };
2327        let content_min_main_size = content_main_sizes.min.resolve_for_min(
2328            get_automatic_minimum_size,
2329            stretch_size.main,
2330            &main_content_sizes,
2331            is_table,
2332        );
2333
2334        FlexItem {
2335            box_: self,
2336            content_cross_sizes: content_cross_sizes.clone(),
2337            padding,
2338            border,
2339            margin: config.sides_to_flex_relative(pbm.margin),
2340            pbm_auto_is_zero,
2341            flex_base_size,
2342            flex_base_size_is_definite,
2343            hypothetical_main_size: flex_base_size
2344                .clamp_between_extremums(content_min_main_size, content_max_main_size),
2345            content_min_main_size,
2346            content_max_main_size,
2347            align_self,
2348            depends_on_block_constraints: *depends_on_block_constraints,
2349            preferred_aspect_ratio,
2350            automatic_cross_size,
2351            automatic_cross_size_for_intrinsic_sizing,
2352        }
2353    }
2354
2355    fn main_content_size_info<'a>(
2356        &self,
2357        layout_context: &LayoutContext,
2358        containing_block: &IndefiniteContainingBlock,
2359        config: &FlexContainerConfig,
2360        flex_context_getter: &impl Fn() -> &'a FlexContext<'a>,
2361    ) -> FlexItemBoxInlineContentSizesInfo {
2362        let content_box_sizes_and_pbm = self
2363            .independent_formatting_context
2364            .layout_style()
2365            .content_box_sizes_and_padding_border_margin(containing_block);
2366
2367        // TODO: when laying out a column container with an indefinite main size,
2368        // we compute the base sizes of the items twice. We should consider caching.
2369        let FlexItem {
2370            flex_base_size,
2371            content_min_main_size,
2372            content_max_main_size,
2373            pbm_auto_is_zero,
2374            preferred_aspect_ratio,
2375            automatic_cross_size_for_intrinsic_sizing,
2376            ..
2377        } = self.to_flex_item(
2378            layout_context,
2379            containing_block,
2380            &content_box_sizes_and_pbm,
2381            config,
2382            flex_context_getter,
2383        );
2384
2385        // Compute the min-content and max-content contributions of the item.
2386        // <https://drafts.csswg.org/css-flexbox/#intrinsic-item-contributions>
2387        let (content_contribution_sizes, depends_on_block_constraints) = match config.flex_axis {
2388            FlexAxis::Row => {
2389                let auto_minimum = LogicalVec2 {
2390                    inline: content_min_main_size,
2391                    block: Au::zero(),
2392                };
2393                let InlineContentSizesResult {
2394                    sizes,
2395                    depends_on_block_constraints,
2396                } = self
2397                    .independent_formatting_context
2398                    .outer_inline_content_sizes(
2399                        layout_context,
2400                        containing_block,
2401                        &auto_minimum,
2402                        automatic_cross_size_for_intrinsic_sizing == Size::Stretch,
2403                    );
2404                (sizes, depends_on_block_constraints)
2405            },
2406            FlexAxis::Column => {
2407                let size = self.layout_for_block_content_size(
2408                    flex_context_getter(),
2409                    &pbm_auto_is_zero,
2410                    &content_box_sizes_and_pbm.content_box_sizes,
2411                    preferred_aspect_ratio,
2412                    automatic_cross_size_for_intrinsic_sizing,
2413                    IntrinsicSizingMode::Contribution,
2414                );
2415                (size.into(), true)
2416            },
2417        };
2418
2419        let outer_flex_base_size = flex_base_size + pbm_auto_is_zero.main;
2420        let outer_min_main_size = content_min_main_size + pbm_auto_is_zero.main;
2421        let outer_max_main_size = content_max_main_size.map(|v| v + pbm_auto_is_zero.main);
2422        let max_flex_factors = self.desired_flex_factors_for_preferred_width(
2423            content_contribution_sizes.max_content,
2424            flex_base_size,
2425            outer_flex_base_size,
2426        );
2427
2428        // > The min-content main size of a single-line flex container is calculated
2429        // > identically to the max-content main size, except that the flex items’
2430        // > min-content contributions are used instead of their max-content contributions.
2431        let min_flex_factors = self.desired_flex_factors_for_preferred_width(
2432            content_contribution_sizes.min_content,
2433            flex_base_size,
2434            outer_flex_base_size,
2435        );
2436
2437        // > However, for a multi-line container, the min-content main size is simply the
2438        // > largest min-content contribution of all the non-collapsed flex items in the
2439        // > flex container. For this purpose, each item’s contribution is capped by the
2440        // > item’s flex base size if the item is not growable, floored by the item’s flex
2441        // > base size if the item is not shrinkable, and then further clamped by the item’s
2442        // > min and max main sizes.
2443        let mut min_content_main_size_for_multiline_container =
2444            content_contribution_sizes.min_content;
2445        let style_position = &self.style().get_position();
2446        if style_position.flex_grow.is_zero() {
2447            min_content_main_size_for_multiline_container.min_assign(outer_flex_base_size);
2448        }
2449        if style_position.flex_shrink.is_zero() {
2450            min_content_main_size_for_multiline_container.max_assign(outer_flex_base_size);
2451        }
2452        min_content_main_size_for_multiline_container =
2453            min_content_main_size_for_multiline_container
2454                .clamp_between_extremums(outer_min_main_size, outer_max_main_size);
2455
2456        FlexItemBoxInlineContentSizesInfo {
2457            outer_flex_base_size,
2458            outer_min_main_size,
2459            outer_max_main_size,
2460            min_flex_factors,
2461            max_flex_factors,
2462            min_content_main_size_for_multiline_container,
2463            depends_on_block_constraints,
2464        }
2465    }
2466
2467    fn desired_flex_factors_for_preferred_width(
2468        &self,
2469        preferred_width: Au,
2470        flex_base_size: Au,
2471        outer_flex_base_size: Au,
2472    ) -> DesiredFlexFractionAndGrowOrShrinkFactor {
2473        let difference = (preferred_width - outer_flex_base_size).to_f32_px();
2474        let (flex_grow_or_scaled_flex_shrink_factor, desired_flex_fraction) = if difference > 0.0 {
2475            // > If that result is positive, divide it by the item’s flex
2476            // > grow factor if the flex grow > factor is ≥ 1, or multiply
2477            // > it by the flex grow factor if the flex grow factor is < 1;
2478            let flex_grow_factor = self.style().get_position().flex_grow.0;
2479
2480            (
2481                flex_grow_factor,
2482                if flex_grow_factor >= 1.0 {
2483                    difference / flex_grow_factor
2484                } else {
2485                    difference * flex_grow_factor
2486                },
2487            )
2488        } else if difference < 0.0 {
2489            // > if the result is negative, divide it by the item’s scaled
2490            // > flex shrink factor (if dividing > by zero, treat the result
2491            // > as negative infinity).
2492            let flex_shrink_factor = self.style().get_position().flex_shrink.0;
2493            let scaled_flex_shrink_factor = flex_shrink_factor * flex_base_size.to_f32_px();
2494
2495            (
2496                scaled_flex_shrink_factor,
2497                if scaled_flex_shrink_factor != 0.0 {
2498                    difference / scaled_flex_shrink_factor
2499                } else {
2500                    f32::NEG_INFINITY
2501                },
2502            )
2503        } else {
2504            (0.0, 0.0)
2505        };
2506
2507        DesiredFlexFractionAndGrowOrShrinkFactor {
2508            desired_flex_fraction,
2509            flex_grow_or_shrink_factor: flex_grow_or_scaled_flex_shrink_factor,
2510        }
2511    }
2512
2513    /// <https://drafts.csswg.org/css-flexbox-1/#flex-basis-property>
2514    /// Returns the used value of the `flex-basis` property, after resolving percentages,
2515    /// resolving `auto`, and taking `box-sizing` into account.
2516    /// Note that a return value of `Size::Initial` represents `flex-basis: content`,
2517    /// not `flex-basis: auto`, since the latter always resolves to something else.
2518    fn flex_basis(
2519        &self,
2520        container_definite_main_size: Option<Au>,
2521        main_preferred_size: Size<Au>,
2522        main_padding_border_sum: Au,
2523    ) -> Size<Au> {
2524        let style_position = &self.independent_formatting_context.style().get_position();
2525        match &style_position.flex_basis {
2526            // https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-content
2527            // > Indicates an automatic size based on the flex item’s content.
2528            FlexBasis::Content => Size::Initial,
2529
2530            FlexBasis::Size(size) => match Size::<LengthPercentage>::from(size.clone()) {
2531                // https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
2532                // > When specified on a flex item, the `auto` keyword retrieves
2533                // > the value of the main size property as the used `flex-basis`.
2534                Size::Initial => main_preferred_size,
2535
2536                // https://drafts.csswg.org/css-flexbox-1/#flex-basis-property
2537                // > For all values other than `auto` and `content` (defined above),
2538                // > `flex-basis` is resolved the same way as `width` in horizontal
2539                // > writing modes, except that if a value would resolve to `auto`
2540                // > for `width`, it instead resolves to `content` for `flex-basis`.
2541                // > For example, percentage values of `flex-basis` are resolved
2542                // > against the flex item’s containing block (i.e. its flex container);
2543                // > and if that containing block’s size is indefinite,
2544                // > the used value for `flex-basis` is `content`.
2545                size => {
2546                    let apply_box_sizing = |length: Au| {
2547                        match style_position.box_sizing {
2548                            BoxSizing::ContentBox => length,
2549                            // This may make `length` negative,
2550                            // but it will be clamped in the hypothetical main size
2551                            BoxSizing::BorderBox => length - main_padding_border_sum,
2552                        }
2553                    };
2554                    size.resolve_percentages_for_preferred(container_definite_main_size)
2555                        .map(apply_box_sizing)
2556                },
2557            },
2558        }
2559    }
2560
2561    #[allow(clippy::too_many_arguments)]
2562    #[servo_tracing::instrument(name = "FlexContainer::layout_for_block_content_size", skip_all)]
2563    fn layout_for_block_content_size(
2564        &self,
2565        flex_context: &FlexContext,
2566        pbm_auto_is_zero: &FlexRelativeVec2<Au>,
2567        content_box_sizes: &LogicalVec2<Sizes>,
2568        preferred_aspect_ratio: Option<AspectRatio>,
2569        automatic_inline_size: Size<Au>,
2570        intrinsic_sizing_mode: IntrinsicSizingMode,
2571    ) -> Au {
2572        let content_block_size = || {
2573            let mut positioning_context = PositioningContext::default();
2574            let style = self.independent_formatting_context.style();
2575
2576            // We are computing the intrinsic block size, so the tentative block size that we use
2577            // as an input to the intrinsic inline sizes needs to ignore the values of the sizing
2578            // properties in the block axis.
2579            let tentative_block_size = SizeConstraint::default();
2580
2581            // TODO: This is wrong if the item writing mode is different from the flex
2582            // container's writing mode.
2583            let inline_size = {
2584                let stretch_size =
2585                    flex_context.containing_block.size.inline - pbm_auto_is_zero.cross;
2586                let get_content_size = || {
2587                    self.inline_content_sizes(
2588                        flex_context,
2589                        tentative_block_size,
2590                        preferred_aspect_ratio,
2591                    )
2592                };
2593                content_box_sizes.inline.resolve(
2594                    Direction::Inline,
2595                    automatic_inline_size,
2596                    Au::zero,
2597                    Some(stretch_size),
2598                    get_content_size,
2599                    false,
2600                )
2601            };
2602            let item_as_containing_block = ContainingBlock {
2603                size: ContainingBlockSize {
2604                    inline: inline_size,
2605                    block: tentative_block_size,
2606                },
2607                style,
2608            };
2609            self.independent_formatting_context
2610                .layout(
2611                    flex_context.layout_context,
2612                    &mut positioning_context,
2613                    &item_as_containing_block,
2614                    flex_context.containing_block,
2615                    preferred_aspect_ratio,
2616                    &LazySize::intrinsic(),
2617                )
2618                .content_block_size
2619        };
2620        match intrinsic_sizing_mode {
2621            IntrinsicSizingMode::Contribution => {
2622                let stretch_size = flex_context
2623                    .containing_block
2624                    .size
2625                    .block
2626                    .to_definite()
2627                    .map(|block_size| block_size - pbm_auto_is_zero.main);
2628                let inner_block_size = content_box_sizes.block.resolve(
2629                    Direction::Block,
2630                    Size::FitContent,
2631                    Au::zero,
2632                    stretch_size,
2633                    || ContentSizes::from(content_block_size()),
2634                    // Tables have a special sizing in the block axis that handles collapsed rows
2635                    // by ignoring the sizing properties and instead relying on the content block size,
2636                    // which should indirectly take sizing properties into account.
2637                    // However, above we laid out the table with a SizeConstraint::default() block size,
2638                    // so the content block size doesn't take sizing properties into account.
2639                    // Therefore, pretending that it's never a table tends to provide a better result.
2640                    false, /* is_table */
2641                );
2642                inner_block_size + pbm_auto_is_zero.main
2643            },
2644            IntrinsicSizingMode::Size => content_block_size(),
2645        }
2646    }
2647
2648    fn inline_content_sizes(
2649        &self,
2650        flex_context: &FlexContext,
2651        block_size: SizeConstraint,
2652        preferred_aspect_ratio: Option<AspectRatio>,
2653    ) -> ContentSizes {
2654        let style = self.independent_formatting_context.style();
2655        let constraint_space = ConstraintSpace::new(block_size, style, preferred_aspect_ratio);
2656        self.independent_formatting_context
2657            .inline_content_sizes(flex_context.layout_context, &constraint_space)
2658            .sizes
2659    }
2660}