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