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