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    #[allow(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.clone_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.clone_column_gap(),
540            FlexAxis::Column => self.style.clone_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.clone_row_gap();
686        let column_gap = self.style.clone_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 still needs to happen after this is produced to
1206/// handle stretching.
1207struct InitialFlexLineLayout<'a> {
1208    /// The items that are placed in this line.
1209    items: Vec<FlexLineItem<'a>>,
1210
1211    /// The initial size of this flex line, not taking into account `align-content: stretch`.
1212    line_size: FlexRelativeVec2<Au>,
1213
1214    /// The free space available to this line after the initial layout.
1215    free_space_in_main_axis: Au,
1216}
1217
1218impl InitialFlexLineLayout<'_> {
1219    fn new<'items>(
1220        flex_context: &FlexContext,
1221        items: Vec<FlexItem<'items>>,
1222        outer_hypothetical_main_sizes_sum: Au,
1223        container_main_size: Au,
1224        main_gap: Au,
1225    ) -> InitialFlexLineLayout<'items> {
1226        let item_count = items.len();
1227        let (item_used_main_sizes, free_space_in_main_axis) = Self::resolve_flexible_lengths(
1228            &items,
1229            outer_hypothetical_main_sizes_sum,
1230            container_main_size - main_gap * (item_count as i32 - 1),
1231        );
1232
1233        // https://drafts.csswg.org/css-flexbox/#algo-cross-item
1234        let items: Vec<_> = if flex_context
1235            .layout_context
1236            .should_parallelize_layout(items.iter().map(FlexItem::subtree_size))
1237        {
1238            items
1239                .into_par_iter()
1240                .zip(item_used_main_sizes.into_par_iter())
1241                .map(|(item, used_main_size)| {
1242                    let layout_result = item.layout(used_main_size, flex_context, None);
1243                    FlexLineItem {
1244                        item,
1245                        layout_result,
1246                        used_main_size,
1247                    }
1248                })
1249                .collect()
1250        } else {
1251            items
1252                .into_iter()
1253                .zip(item_used_main_sizes)
1254                .map(|(item, used_main_size)| {
1255                    let layout_result = item.layout(used_main_size, flex_context, None);
1256                    FlexLineItem {
1257                        item,
1258                        layout_result,
1259                        used_main_size,
1260                    }
1261                })
1262                .collect()
1263        };
1264
1265        // https://drafts.csswg.org/css-flexbox/#algo-cross-line
1266        let line_cross_size = Self::cross_size(&items, flex_context);
1267        let line_size = FlexRelativeVec2 {
1268            main: container_main_size,
1269            cross: line_cross_size,
1270        };
1271
1272        InitialFlexLineLayout {
1273            items,
1274            line_size,
1275            free_space_in_main_axis,
1276        }
1277    }
1278
1279    /// Return the *main size* of each item, and the line’s remainaing free space
1280    /// <https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths>
1281    fn resolve_flexible_lengths<'items>(
1282        items: &'items [FlexItem<'items>],
1283        outer_hypothetical_main_sizes_sum: Au,
1284        container_main_size: Au,
1285    ) -> (Vec<Au>, Au) {
1286        struct FlexibleLengthResolutionItem<'items> {
1287            item: &'items FlexItem<'items>,
1288            frozen: Cell<bool>,
1289            target_main_size: Cell<Au>,
1290            flex_factor: f32,
1291            min_max_violation_kind: Cell<Ordering>,
1292        }
1293
1294        // > 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
1295        // > items on the line. If the sum is less than the flex container’s inner main
1296        // > size, use the flex grow factor for the rest of this algorithm; otherwise, use
1297        // > the flex shrink factor.
1298        let grow = outer_hypothetical_main_sizes_sum < container_main_size;
1299
1300        let mut frozen_count = 0;
1301        let items: Vec<_> = items
1302            .iter()
1303            .map(|item| {
1304                // > 2. Each item in the flex line has a target main size, initially set to its
1305                // > flex base size. Each item is initially unfrozen and may become frozen.
1306                let target_main_size = Cell::new(item.flex_base_size);
1307
1308                // > 3. Size inflexible items. Freeze, setting its target main size to its hypothetical main size…
1309                // > - any item that has a flex factor of zero
1310                // > - if using the flex grow factor: any item that has a flex base size
1311                // >   greater than its hypothetical main size
1312                // > - if using the flex shrink factor: any item that has a flex base size
1313                // >   smaller than its hypothetical main size
1314                let flex_factor = if grow {
1315                    item.box_.style().get_position().flex_grow.0
1316                } else {
1317                    item.box_.style().get_position().flex_shrink.0
1318                };
1319
1320                let is_inflexible = flex_factor == 0. ||
1321                    if grow {
1322                        item.flex_base_size > item.hypothetical_main_size
1323                    } else {
1324                        item.flex_base_size < item.hypothetical_main_size
1325                    };
1326
1327                let frozen = Cell::new(false);
1328                if is_inflexible {
1329                    frozen_count += 1;
1330                    frozen.set(true);
1331                    target_main_size.set(item.hypothetical_main_size);
1332                }
1333
1334                FlexibleLengthResolutionItem {
1335                    item,
1336                    frozen,
1337                    target_main_size,
1338                    flex_factor,
1339                    // The actual violation will be computed later.
1340                    min_max_violation_kind: Cell::new(Ordering::Equal),
1341                }
1342            })
1343            .collect();
1344
1345        let unfrozen_items = || items.iter().filter(|item| !item.frozen.get());
1346        let main_sizes = |items: Vec<FlexibleLengthResolutionItem>| {
1347            items
1348                .into_iter()
1349                .map(|item| item.target_main_size.get())
1350                .collect()
1351        };
1352
1353        // https://drafts.csswg.org/css-flexbox/#initial-free-space
1354        // > 4. Calculate initial free space. Sum the outer sizes of all items on the line, and
1355        // > subtract this from the flex container’s inner main size. For frozen items, use
1356        // > their outer target main size; for other items, use their outer flex base size.
1357        let free_space = |all_items_frozen| {
1358            let items_size = items
1359                .iter()
1360                .map(|item| {
1361                    item.item.pbm_auto_is_zero.main +
1362                        if all_items_frozen || item.frozen.get() {
1363                            item.target_main_size.get()
1364                        } else {
1365                            item.item.flex_base_size
1366                        }
1367                })
1368                .sum();
1369            container_main_size - items_size
1370        };
1371
1372        let initial_free_space = free_space(false);
1373        loop {
1374            // https://drafts.csswg.org/css-flexbox/#remaining-free-space
1375            let mut remaining_free_space = free_space(false);
1376            // > 5. a. Check for flexible items. If all the flex items on the line are
1377            // >       frozen, free space has been distributed; exit this loop.
1378            if frozen_count >= items.len() {
1379                return (main_sizes(items), remaining_free_space);
1380            }
1381
1382            // > 5. b. Calculate the remaining free space as for initial free space, above. If the
1383            // > sum of the unfrozen flex items’ flex factors is less than one, multiply the
1384            // > initial free space by this sum. If the magnitude of this value is less than
1385            // > the magnitude of the remaining free space, use this as the remaining free
1386            // > space.
1387            let unfrozen_items_flex_factor_sum =
1388                unfrozen_items().map(|item| item.flex_factor).sum();
1389            if unfrozen_items_flex_factor_sum < 1. {
1390                let multiplied = initial_free_space.scale_by(unfrozen_items_flex_factor_sum);
1391                if multiplied.abs() < remaining_free_space.abs() {
1392                    remaining_free_space = multiplied
1393                }
1394            }
1395
1396            // > 5. c. If the remaining free space is non-zero, distribute it proportional
1397            // to the flex factors:
1398            //
1399            // FIXME: is it a problem if floating point precision errors accumulate
1400            // and we get not-quite-zero remaining free space when we should get zero here?
1401            if !remaining_free_space.is_zero() {
1402                // > If using the flex grow factor:
1403                // > For every unfrozen item on the line, find the ratio of the item’s flex grow factor to
1404                // > the sum of the flex grow factors of all unfrozen items on the line. Set the item’s
1405                // > target main size to its flex base size plus a fraction of the remaining free space
1406                // > proportional to the ratio.
1407                if grow {
1408                    for item in unfrozen_items() {
1409                        let ratio = item.flex_factor / unfrozen_items_flex_factor_sum;
1410                        item.target_main_size
1411                            .set(item.item.flex_base_size + remaining_free_space.scale_by(ratio));
1412                    }
1413                // > If using the flex shrink factor
1414                // > For every unfrozen item on the line, multiply its flex shrink factor by its inner flex
1415                // > base size, and note this as its scaled flex shrink factor. Find the ratio of the
1416                // > item’s scaled flex shrink factor to the sum of the scaled flex shrink factors of all
1417                // > unfrozen items on the line. Set the item’s target main size to its flex base size
1418                // > minus a fraction of the absolute value of the remaining free space proportional to the
1419                // > ratio. Note this may result in a negative inner main size; it will be corrected in the
1420                // > next step.
1421                } else {
1422                    // https://drafts.csswg.org/css-flexbox/#scaled-flex-shrink-factor
1423                    let scaled_shrink_factor = |item: &FlexibleLengthResolutionItem| {
1424                        item.item.flex_base_size.scale_by(item.flex_factor)
1425                    };
1426                    let scaled_shrink_factors_sum: Au =
1427                        unfrozen_items().map(scaled_shrink_factor).sum();
1428                    if scaled_shrink_factors_sum > Au::zero() {
1429                        for item in unfrozen_items() {
1430                            let ratio = scaled_shrink_factor(item).0 as f32 /
1431                                scaled_shrink_factors_sum.0 as f32;
1432                            item.target_main_size.set(
1433                                item.item.flex_base_size -
1434                                    remaining_free_space.abs().scale_by(ratio),
1435                            );
1436                        }
1437                    }
1438                }
1439            }
1440
1441            // > 5. d. Fix min/max violations. Clamp each non-frozen item’s target main size
1442            // > by its used min and max main sizes and floor its content-box size at zero.
1443            // > If the item’s target main size was made smaller by this, it’s a max
1444            // > violation. If the item’s target main size was made larger by this, it’s a
1445            // > min violation.
1446            let mut total_violation = Au::zero();
1447            for item in unfrozen_items() {
1448                let unclamped = item.target_main_size.get();
1449                let clamped = unclamped.clamp_between_extremums(
1450                    item.item.content_min_main_size,
1451                    item.item.content_max_main_size,
1452                );
1453                item.target_main_size.set(clamped);
1454                // We represent min violations with Ordering::Greater, and max violations
1455                // with Ordering::Less.
1456                item.min_max_violation_kind.set(clamped.cmp(&unclamped));
1457                total_violation += clamped - unclamped;
1458            }
1459
1460            // > 5. e. Freeze over-flexed items. The total violation is the sum of the
1461            // > adjustments from the previous step ∑(clamped size - unclamped size). If the
1462            // > total violation is:
1463            // > - Zero:  Freeze all items.
1464            // > - Positive: Freeze all the items with min violations.
1465            // > - Negative:  Freeze all the items with max violations.
1466            match total_violation.cmp(&Au::zero()) {
1467                Ordering::Equal => {
1468                    // “Freeze all items.”
1469                    // Return instead, as that’s what the next loop iteration would do.
1470                    let remaining_free_space = free_space(true);
1471                    return (main_sizes(items), remaining_free_space);
1472                },
1473                total_violation_kind => {
1474                    for item in unfrozen_items() {
1475                        if item.min_max_violation_kind.get() == total_violation_kind {
1476                            item.frozen.set(true);
1477                            frozen_count += 1;
1478                        }
1479                    }
1480                },
1481            }
1482        }
1483    }
1484
1485    /// <https://drafts.csswg.org/css-flexbox/#algo-cross-line>
1486    fn cross_size<'items>(items: &'items [FlexLineItem<'items>], flex_context: &FlexContext) -> Au {
1487        if flex_context.config.container_is_single_line &&
1488            let SizeConstraint::Definite(size) =
1489                flex_context.container_inner_size_constraint.cross
1490        {
1491            return size;
1492        }
1493
1494        let mut max_ascent = Au::zero();
1495        let mut max_descent = Au::zero();
1496        let mut max_outer_hypothetical_cross_size = Au::zero();
1497        for item in items.iter() {
1498            // TODO: check inline-axis is parallel to main axis, check no auto cross margins
1499            if matches!(
1500                item.item.align_self.0.value(),
1501                AlignFlags::BASELINE | AlignFlags::LAST_BASELINE
1502            ) {
1503                let baseline = item.get_or_synthesize_baseline_with_cross_size(
1504                    item.layout_result.hypothetical_cross_size,
1505                    &flex_context.config,
1506                );
1507                let hypothetical_margin_box_cross_size =
1508                    item.layout_result.hypothetical_cross_size + item.item.pbm_auto_is_zero.cross;
1509                max_ascent = max_ascent.max(baseline);
1510                max_descent = max_descent.max(hypothetical_margin_box_cross_size - baseline);
1511            } else {
1512                max_outer_hypothetical_cross_size = max_outer_hypothetical_cross_size.max(
1513                    item.layout_result.hypothetical_cross_size + item.item.pbm_auto_is_zero.cross,
1514                );
1515            }
1516        }
1517
1518        // https://drafts.csswg.org/css-flexbox/#baseline-participation
1519        let largest = max_outer_hypothetical_cross_size.max(max_ascent + max_descent);
1520        match flex_context.container_inner_size_constraint.cross {
1521            SizeConstraint::MinMax(min, max) if flex_context.config.container_is_single_line => {
1522                largest.clamp_between_extremums(min, max)
1523            },
1524            _ => largest,
1525        }
1526    }
1527
1528    fn finish_with_final_cross_size(
1529        mut self,
1530        flex_context: &mut FlexContext,
1531        main_gap: Au,
1532        final_line_cross_size: Au,
1533    ) -> FinalFlexLineLayout {
1534        // FIXME: Collapse `visibility: collapse` items
1535        // This involves “restart layout from the beginning” with a modified second round,
1536        // which will make structuring the code… interesting.
1537        // https://drafts.csswg.org/css-flexbox/#algo-visibility
1538
1539        // Distribute any remaining main free space to auto margins according to
1540        // https://drafts.csswg.org/css-flexbox/#algo-main-align.
1541        let auto_margins_count = self
1542            .items
1543            .iter()
1544            .map(|item| {
1545                item.item.margin.main_start.is_auto() as u32 +
1546                    item.item.margin.main_end.is_auto() as u32
1547            })
1548            .sum::<u32>();
1549        let (space_distributed_to_auto_main_margins, free_space_in_main_axis) =
1550            if self.free_space_in_main_axis > Au::zero() && auto_margins_count > 0 {
1551                (
1552                    self.free_space_in_main_axis / auto_margins_count as i32,
1553                    Au::zero(),
1554                )
1555            } else {
1556                (Au::zero(), self.free_space_in_main_axis)
1557            };
1558
1559        // Determine the used cross size of each flex item
1560        // https://drafts.csswg.org/css-flexbox/#algo-stretch
1561        let item_count = self.items.len();
1562        let mut shared_alignment_baseline = None;
1563        let mut item_used_cross_sizes = Vec::with_capacity(item_count);
1564        let mut item_margins = Vec::with_capacity(item_count);
1565        for item in self.items.iter_mut() {
1566            let cross_axis = match flex_context.config.flex_axis {
1567                FlexAxis::Row => Direction::Block,
1568                FlexAxis::Column => Direction::Inline,
1569            };
1570            let layout = &mut item.layout_result;
1571            let get_content_size = || match cross_axis {
1572                Direction::Block => layout.content_block_size.into(),
1573                Direction::Inline => item
1574                    .item
1575                    .inline_content_sizes(flex_context, item.used_main_size),
1576            };
1577            let used_cross_size = item.item.content_cross_sizes.resolve(
1578                cross_axis,
1579                item.item.automatic_cross_size,
1580                Au::zero,
1581                Some(Au::zero().max(final_line_cross_size - item.item.pbm_auto_is_zero.cross)),
1582                get_content_size,
1583                // Tables have a special sizing in the block axis in that handles collapsed rows,
1584                // but it would prevent stretching. So we only recognize tables in the inline axis.
1585                // The interaction of collapsed table tracks and the flexbox algorithms is unclear,
1586                // see https://github.com/w3c/csswg-drafts/issues/11408.
1587                item.item.box_.independent_formatting_context.is_table() &&
1588                    cross_axis == Direction::Inline,
1589            );
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    #[allow(clippy::too_many_arguments)]
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::WrapReverse => true,
2096            };
2097            // “if the block-start or inline-start margin (whichever is in the cross axis) is auto,
2098            //  set it to zero. Set the opposite margin so that the outer cross size of the item
2099            //  equals the cross size of its flex line.”
2100            if flex_wrap_reverse {
2101                cross_start = self.margin.cross_start.auto_is(|| available);
2102                cross_end = self.margin.cross_end.auto_is(Au::zero);
2103            } else {
2104                cross_start = self.margin.cross_start.auto_is(Au::zero);
2105                cross_end = self.margin.cross_end.auto_is(|| available);
2106            }
2107        }
2108
2109        FlexRelativeSides {
2110            cross_start,
2111            cross_end,
2112            main_start,
2113            main_end,
2114        }
2115    }
2116
2117    /// Return the coordinate of the cross-start side of the content area
2118    fn align_along_cross_axis(
2119        &self,
2120        margin: &FlexRelativeSides<Au>,
2121        used_cross_size: &Au,
2122        line_cross_size: Au,
2123        propagated_baseline: Au,
2124        max_propagated_baseline: Au,
2125        wrap_reverse: bool,
2126    ) -> Au {
2127        let ending_alignment = line_cross_size - *used_cross_size - self.pbm_auto_is_zero.cross;
2128        let outer_cross_start =
2129            if self.margin.cross_start.is_auto() || self.margin.cross_end.is_auto() {
2130                Au::zero()
2131            } else {
2132                match self.align_self.0.value() {
2133                    AlignFlags::STRETCH => Au::zero(),
2134                    AlignFlags::CENTER => ending_alignment / 2,
2135                    AlignFlags::BASELINE | AlignFlags::LAST_BASELINE => {
2136                        max_propagated_baseline - propagated_baseline
2137                    },
2138                    AlignFlags::START => {
2139                        if !wrap_reverse {
2140                            Au::zero()
2141                        } else {
2142                            ending_alignment
2143                        }
2144                    },
2145                    AlignFlags::END => {
2146                        if !wrap_reverse {
2147                            ending_alignment
2148                        } else {
2149                            Au::zero()
2150                        }
2151                    },
2152                    _ => Au::zero(),
2153                }
2154            };
2155        outer_cross_start + margin.cross_start + self.border.cross_start + self.padding.cross_start
2156    }
2157
2158    #[inline]
2159    fn inline_content_sizes(&self, flex_context: &FlexContext, block_size: Au) -> ContentSizes {
2160        self.box_.inline_content_sizes(
2161            flex_context,
2162            SizeConstraint::Definite(block_size),
2163            self.preferred_aspect_ratio,
2164        )
2165    }
2166}
2167
2168impl FlexItemBox {
2169    fn to_flex_item<'a>(
2170        &self,
2171        layout_context: &LayoutContext,
2172        containing_block: &IndefiniteContainingBlock,
2173        content_box_sizes_and_pbm: &ContentBoxSizesAndPBM,
2174        config: &FlexContainerConfig,
2175        flex_context_getter: &impl Fn() -> &'a FlexContext<'a>,
2176    ) -> FlexItem<'_> {
2177        let flex_axis = config.flex_axis;
2178        let style = self.style();
2179        let cross_axis_is_item_block_axis = cross_axis_is_item_block_axis(
2180            containing_block.style.writing_mode.is_horizontal(),
2181            style.writing_mode.is_horizontal(),
2182            flex_axis,
2183        );
2184        let main_axis = if cross_axis_is_item_block_axis {
2185            Direction::Inline
2186        } else {
2187            Direction::Block
2188        };
2189        let align_self = AlignItems(config.resolve_align_self_for_child(style));
2190
2191        let ContentBoxSizesAndPBM {
2192            content_box_sizes,
2193            pbm,
2194            depends_on_block_constraints,
2195            preferred_size_computes_to_auto,
2196        } = content_box_sizes_and_pbm;
2197
2198        let preferred_aspect_ratio = self
2199            .independent_formatting_context
2200            .preferred_aspect_ratio(&pbm.padding_border_sums);
2201        let padding = config.sides_to_flex_relative(pbm.padding);
2202        let border = config.sides_to_flex_relative(pbm.border);
2203        let margin = config.sides_to_flex_relative(pbm.margin);
2204        let padding_border = padding.sum_by_axis() + border.sum_by_axis();
2205        let margin_auto_is_zero = config.sides_to_flex_relative(pbm.margin.auto_is(Au::zero));
2206        let pbm_auto_is_zero = FlexRelativeVec2 {
2207            main: padding_border.main,
2208            cross: padding_border.cross,
2209        } + margin_auto_is_zero.sum_by_axis();
2210        let (content_main_sizes, content_cross_sizes, cross_size_computes_to_auto) = match flex_axis
2211        {
2212            FlexAxis::Row => (
2213                &content_box_sizes.inline,
2214                &content_box_sizes.block,
2215                preferred_size_computes_to_auto.block,
2216            ),
2217            FlexAxis::Column => (
2218                &content_box_sizes.block,
2219                &content_box_sizes.inline,
2220                preferred_size_computes_to_auto.inline,
2221            ),
2222        };
2223        let automatic_cross_size = if cross_size_computes_to_auto &&
2224            item_with_auto_cross_size_stretches_to_line_size(align_self, &margin)
2225        {
2226            Size::Stretch
2227        } else {
2228            Size::FitContent
2229        };
2230        let automatic_cross_size_for_intrinsic_sizing = if config.container_is_single_line {
2231            automatic_cross_size
2232        } else {
2233            Size::FitContent
2234        };
2235        let containing_block_size = flex_axis.vec2_to_flex_relative(containing_block.size);
2236        let stretch_size = FlexRelativeVec2 {
2237            main: containing_block_size
2238                .main
2239                .map(|v| Au::zero().max(v - pbm_auto_is_zero.main)),
2240            cross: containing_block_size
2241                .cross
2242                .map(|v| Au::zero().max(v - pbm_auto_is_zero.cross)),
2243        };
2244
2245        let is_table = self.independent_formatting_context.is_table();
2246        let tentative_cross_content_size = if cross_axis_is_item_block_axis {
2247            self.independent_formatting_context
2248                .tentative_block_content_size(
2249                    preferred_aspect_ratio,
2250                    stretch_size.main.unwrap_or_default(),
2251                )
2252        } else {
2253            None
2254        };
2255        let (preferred_cross_size, min_cross_size, max_cross_size) =
2256            if let Some(cross_content_size) = tentative_cross_content_size {
2257                let (preferred, min, max) = content_cross_sizes.resolve_each(
2258                    automatic_cross_size_for_intrinsic_sizing,
2259                    Au::zero,
2260                    stretch_size.cross,
2261                    || cross_content_size,
2262                    is_table,
2263                );
2264                (Some(preferred), min, max)
2265            } else {
2266                content_cross_sizes.resolve_each_extrinsic(
2267                    automatic_cross_size_for_intrinsic_sizing,
2268                    Au::zero(),
2269                    stretch_size.cross,
2270                )
2271            };
2272        let cross_size = SizeConstraint::new(preferred_cross_size, min_cross_size, max_cross_size);
2273
2274        // <https://drafts.csswg.org/css-flexbox/#transferred-size-suggestion>
2275        // > If the item has a preferred aspect ratio and its preferred cross size is definite, then the
2276        // > transferred size suggestion is that size (clamped by its minimum and maximum cross sizes if they
2277        // > are definite), converted through the aspect ratio. It is otherwise undefined.
2278        let transferred_size_suggestion =
2279            LazyCell::new(|| match (preferred_aspect_ratio, cross_size) {
2280                (Some(ratio), SizeConstraint::Definite(cross_size)) => {
2281                    Some(ratio.compute_dependent_size(main_axis, cross_size))
2282                },
2283                _ => None,
2284            });
2285
2286        // <https://drafts.csswg.org/css-flexbox/#algo-main-item>
2287        let flex_base_size_is_definite = Cell::new(true);
2288        let main_content_sizes = LazyCell::new(|| {
2289            let flex_item = &self.independent_formatting_context;
2290            // > B: If the flex item has ...
2291            // >   - a preferred aspect ratio,
2292            // >   - a used flex basis of content, and
2293            // >   - a definite cross size,
2294            // > then the flex base size is calculated from its used cross size and the flex item’s aspect ratio.
2295            if let Some(transferred_size_suggestion) = *transferred_size_suggestion {
2296                return transferred_size_suggestion.into();
2297            }
2298
2299            flex_base_size_is_definite.set(false);
2300
2301            // FIXME: implement cases C, D.
2302
2303            // > E. Otherwise, size the item into the available space using its used flex basis in place of
2304            // > its main size, treating a value of content as max-content. If a cross size is needed to
2305            // > determine the main size (e.g. when the flex item’s main size is in its block axis, or when
2306            // > it has a preferred aspect ratio) and the flex item’s cross size is auto and not definite,
2307            // > in this calculation use fit-content as the flex item’s cross size. The flex base size is
2308            // > the item’s resulting main size.
2309            if cross_axis_is_item_block_axis {
2310                // The main axis is the inline axis, so we can get the content size from the normal
2311                // preferred widths calculation.
2312                let constraint_space =
2313                    ConstraintSpace::new(cross_size, style, preferred_aspect_ratio);
2314                let content_sizes = flex_item
2315                    .inline_content_sizes(layout_context, &constraint_space)
2316                    .sizes;
2317                if let Some(ratio) = preferred_aspect_ratio {
2318                    let transferred_min = ratio.compute_dependent_size(main_axis, min_cross_size);
2319                    let transferred_max =
2320                        max_cross_size.map(|v| ratio.compute_dependent_size(main_axis, v));
2321                    content_sizes
2322                        .map(|size| size.clamp_between_extremums(transferred_min, transferred_max))
2323                } else {
2324                    content_sizes
2325                }
2326            } else {
2327                self.layout_for_block_content_size(
2328                    flex_context_getter(),
2329                    &pbm_auto_is_zero,
2330                    content_box_sizes,
2331                    preferred_aspect_ratio,
2332                    automatic_cross_size_for_intrinsic_sizing,
2333                    IntrinsicSizingMode::Size,
2334                )
2335                .into()
2336            }
2337        });
2338
2339        let flex_base_size = self
2340            .flex_basis(
2341                containing_block_size.main,
2342                content_main_sizes.preferred,
2343                padding_border.main,
2344            )
2345            .resolve_for_preferred(Size::MaxContent, stretch_size.main, &main_content_sizes);
2346        let flex_base_size_is_definite = flex_base_size_is_definite.take();
2347
2348        let content_max_main_size = content_main_sizes
2349            .max
2350            .resolve_for_max(stretch_size.main, &main_content_sizes);
2351
2352        let get_automatic_minimum_size = || {
2353            // This is an implementation of <https://drafts.csswg.org/css-flexbox/#min-size-auto>.
2354            if style.establishes_scroll_container(self.base_fragment_info().flags) {
2355                return Au::zero();
2356            }
2357
2358            // > **specified size suggestion**
2359            // > If the item’s preferred main size is definite and not automatic, then the specified
2360            // > size suggestion is that size. It is otherwise undefined.
2361            let specified_size_suggestion = content_main_sizes
2362                .preferred
2363                .maybe_resolve_extrinsic(stretch_size.main);
2364
2365            let is_replaced = self.independent_formatting_context.is_replaced();
2366
2367            // > **content size suggestion**
2368            // > The content size suggestion is the min-content size in the main axis, clamped, if it has a
2369            // > preferred aspect ratio, by any definite minimum and maximum cross sizes converted through the
2370            // > aspect ratio.
2371            let content_size_suggestion = match preferred_aspect_ratio {
2372                Some(ratio) => main_content_sizes.min_content.clamp_between_extremums(
2373                    ratio.compute_dependent_size(main_axis, min_cross_size),
2374                    max_cross_size.map(|l| ratio.compute_dependent_size(main_axis, l)),
2375                ),
2376                None => main_content_sizes.min_content,
2377            };
2378
2379            // > The content-based minimum size of a flex item is the smaller of its specified size
2380            // > suggestion and its content size suggestion if its specified size suggestion exists;
2381            // > otherwise, the smaller of its transferred size suggestion and its content size
2382            // > suggestion if the element is replaced and its transferred size suggestion exists;
2383            // > otherwise its content size suggestion. In all cases, the size is clamped by the maximum
2384            // > main size if it’s definite.
2385            match (specified_size_suggestion, *transferred_size_suggestion) {
2386                (Some(specified), _) => specified.min(content_size_suggestion),
2387                (_, Some(transferred)) if is_replaced => transferred.min(content_size_suggestion),
2388                _ => content_size_suggestion,
2389            }
2390            .clamp_below_max(content_max_main_size)
2391        };
2392        let content_min_main_size = content_main_sizes.min.resolve_for_min(
2393            get_automatic_minimum_size,
2394            stretch_size.main,
2395            &main_content_sizes,
2396            is_table,
2397        );
2398
2399        FlexItem {
2400            box_: self,
2401            content_cross_sizes: content_cross_sizes.clone(),
2402            padding,
2403            border,
2404            margin: config.sides_to_flex_relative(pbm.margin),
2405            pbm_auto_is_zero,
2406            flex_base_size,
2407            flex_base_size_is_definite,
2408            hypothetical_main_size: flex_base_size
2409                .clamp_between_extremums(content_min_main_size, content_max_main_size),
2410            content_min_main_size,
2411            content_max_main_size,
2412            align_self,
2413            depends_on_block_constraints: *depends_on_block_constraints,
2414            preferred_aspect_ratio,
2415            automatic_cross_size,
2416            automatic_cross_size_for_intrinsic_sizing,
2417        }
2418    }
2419
2420    fn main_content_size_info<'a>(
2421        &self,
2422        layout_context: &LayoutContext,
2423        containing_block: &IndefiniteContainingBlock,
2424        config: &FlexContainerConfig,
2425        flex_context_getter: &impl Fn() -> &'a FlexContext<'a>,
2426    ) -> FlexItemBoxInlineContentSizesInfo {
2427        let content_box_sizes_and_pbm = self
2428            .independent_formatting_context
2429            .layout_style()
2430            .content_box_sizes_and_padding_border_margin(containing_block);
2431
2432        // TODO: when laying out a column container with an indefinite main size,
2433        // we compute the base sizes of the items twice. We should consider caching.
2434        let FlexItem {
2435            flex_base_size,
2436            content_min_main_size,
2437            content_max_main_size,
2438            pbm_auto_is_zero,
2439            preferred_aspect_ratio,
2440            automatic_cross_size_for_intrinsic_sizing,
2441            ..
2442        } = self.to_flex_item(
2443            layout_context,
2444            containing_block,
2445            &content_box_sizes_and_pbm,
2446            config,
2447            flex_context_getter,
2448        );
2449
2450        // Compute the min-content and max-content contributions of the item.
2451        // <https://drafts.csswg.org/css-flexbox/#intrinsic-item-contributions>
2452        let (content_contribution_sizes, depends_on_block_constraints) = match config.flex_axis {
2453            FlexAxis::Row => {
2454                let auto_minimum = LogicalVec2 {
2455                    inline: content_min_main_size,
2456                    block: Au::zero(),
2457                };
2458                let InlineContentSizesResult {
2459                    sizes,
2460                    depends_on_block_constraints,
2461                } = self
2462                    .independent_formatting_context
2463                    .outer_inline_content_sizes(
2464                        layout_context,
2465                        containing_block,
2466                        &auto_minimum,
2467                        automatic_cross_size_for_intrinsic_sizing == Size::Stretch,
2468                    );
2469                (sizes, depends_on_block_constraints)
2470            },
2471            FlexAxis::Column => {
2472                let size = self.layout_for_block_content_size(
2473                    flex_context_getter(),
2474                    &pbm_auto_is_zero,
2475                    &content_box_sizes_and_pbm.content_box_sizes,
2476                    preferred_aspect_ratio,
2477                    automatic_cross_size_for_intrinsic_sizing,
2478                    IntrinsicSizingMode::Contribution,
2479                );
2480                (size.into(), true)
2481            },
2482        };
2483
2484        let outer_flex_base_size = flex_base_size + pbm_auto_is_zero.main;
2485        let outer_min_main_size = content_min_main_size + pbm_auto_is_zero.main;
2486        let outer_max_main_size = content_max_main_size.map(|v| v + pbm_auto_is_zero.main);
2487        let max_flex_factors = self.desired_flex_factors_for_preferred_width(
2488            content_contribution_sizes.max_content,
2489            flex_base_size,
2490            outer_flex_base_size,
2491        );
2492
2493        // > The min-content main size of a single-line flex container is calculated
2494        // > identically to the max-content main size, except that the flex items’
2495        // > min-content contributions are used instead of their max-content contributions.
2496        let min_flex_factors = self.desired_flex_factors_for_preferred_width(
2497            content_contribution_sizes.min_content,
2498            flex_base_size,
2499            outer_flex_base_size,
2500        );
2501
2502        // > However, for a multi-line container, the min-content main size is simply the
2503        // > largest min-content contribution of all the non-collapsed flex items in the
2504        // > flex container. For this purpose, each item’s contribution is capped by the
2505        // > item’s flex base size if the item is not growable, floored by the item’s flex
2506        // > base size if the item is not shrinkable, and then further clamped by the item’s
2507        // > min and max main sizes.
2508        let mut min_content_main_size_for_multiline_container =
2509            content_contribution_sizes.min_content;
2510        let style_position = &self.style().get_position();
2511        if style_position.flex_grow.is_zero() {
2512            min_content_main_size_for_multiline_container.min_assign(outer_flex_base_size);
2513        }
2514        if style_position.flex_shrink.is_zero() {
2515            min_content_main_size_for_multiline_container.max_assign(outer_flex_base_size);
2516        }
2517        min_content_main_size_for_multiline_container =
2518            min_content_main_size_for_multiline_container
2519                .clamp_between_extremums(outer_min_main_size, outer_max_main_size);
2520
2521        FlexItemBoxInlineContentSizesInfo {
2522            outer_flex_base_size,
2523            outer_min_main_size,
2524            outer_max_main_size,
2525            min_flex_factors,
2526            max_flex_factors,
2527            min_content_main_size_for_multiline_container,
2528            depends_on_block_constraints,
2529        }
2530    }
2531
2532    fn desired_flex_factors_for_preferred_width(
2533        &self,
2534        preferred_width: Au,
2535        flex_base_size: Au,
2536        outer_flex_base_size: Au,
2537    ) -> DesiredFlexFractionAndGrowOrShrinkFactor {
2538        let difference = (preferred_width - outer_flex_base_size).to_f32_px();
2539        let (flex_grow_or_scaled_flex_shrink_factor, desired_flex_fraction) = if difference > 0.0 {
2540            // > If that result is positive, divide it by the item’s flex
2541            // > grow factor if the flex grow > factor is ≥ 1, or multiply
2542            // > it by the flex grow factor if the flex grow factor is < 1;
2543            let flex_grow_factor = self.style().get_position().flex_grow.0;
2544
2545            (
2546                flex_grow_factor,
2547                if flex_grow_factor >= 1.0 {
2548                    difference / flex_grow_factor
2549                } else {
2550                    difference * flex_grow_factor
2551                },
2552            )
2553        } else if difference < 0.0 {
2554            // > if the result is negative, divide it by the item’s scaled
2555            // > flex shrink factor (if dividing > by zero, treat the result
2556            // > as negative infinity).
2557            let flex_shrink_factor = self.style().get_position().flex_shrink.0;
2558            let scaled_flex_shrink_factor = flex_shrink_factor * flex_base_size.to_f32_px();
2559
2560            (
2561                scaled_flex_shrink_factor,
2562                if scaled_flex_shrink_factor != 0.0 {
2563                    difference / scaled_flex_shrink_factor
2564                } else {
2565                    f32::NEG_INFINITY
2566                },
2567            )
2568        } else {
2569            (0.0, 0.0)
2570        };
2571
2572        DesiredFlexFractionAndGrowOrShrinkFactor {
2573            desired_flex_fraction,
2574            flex_grow_or_shrink_factor: flex_grow_or_scaled_flex_shrink_factor,
2575        }
2576    }
2577
2578    /// <https://drafts.csswg.org/css-flexbox-1/#flex-basis-property>
2579    /// Returns the used value of the `flex-basis` property, after resolving percentages,
2580    /// resolving `auto`, and taking `box-sizing` into account.
2581    /// Note that a return value of `Size::Initial` represents `flex-basis: content`,
2582    /// not `flex-basis: auto`, since the latter always resolves to something else.
2583    fn flex_basis(
2584        &self,
2585        container_definite_main_size: Option<Au>,
2586        main_preferred_size: Size<Au>,
2587        main_padding_border_sum: Au,
2588    ) -> Size<Au> {
2589        let style_position = &self.independent_formatting_context.style().get_position();
2590        match &style_position.flex_basis {
2591            // https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-content
2592            // > Indicates an automatic size based on the flex item’s content.
2593            FlexBasis::Content => Size::Initial,
2594
2595            FlexBasis::Size(size) => match Size::<LengthPercentage>::from(size.clone()) {
2596                // https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto
2597                // > When specified on a flex item, the `auto` keyword retrieves
2598                // > the value of the main size property as the used `flex-basis`.
2599                Size::Initial => main_preferred_size,
2600
2601                // https://drafts.csswg.org/css-flexbox-1/#flex-basis-property
2602                // > For all values other than `auto` and `content` (defined above),
2603                // > `flex-basis` is resolved the same way as `width` in horizontal
2604                // > writing modes, except that if a value would resolve to `auto`
2605                // > for `width`, it instead resolves to `content` for `flex-basis`.
2606                // > For example, percentage values of `flex-basis` are resolved
2607                // > against the flex item’s containing block (i.e. its flex container);
2608                // > and if that containing block’s size is indefinite,
2609                // > the used value for `flex-basis` is `content`.
2610                size => {
2611                    let apply_box_sizing = |length: Au| {
2612                        match style_position.box_sizing {
2613                            BoxSizing::ContentBox => length,
2614                            // This may make `length` negative,
2615                            // but it will be clamped in the hypothetical main size
2616                            BoxSizing::BorderBox => length - main_padding_border_sum,
2617                        }
2618                    };
2619                    size.resolve_percentages_for_preferred(container_definite_main_size)
2620                        .map(apply_box_sizing)
2621                },
2622            },
2623        }
2624    }
2625
2626    #[allow(clippy::too_many_arguments)]
2627    #[servo_tracing::instrument(name = "FlexContainer::layout_for_block_content_size", skip_all)]
2628    fn layout_for_block_content_size(
2629        &self,
2630        flex_context: &FlexContext,
2631        pbm_auto_is_zero: &FlexRelativeVec2<Au>,
2632        content_box_sizes: &LogicalVec2<Sizes>,
2633        preferred_aspect_ratio: Option<AspectRatio>,
2634        automatic_inline_size: Size<Au>,
2635        intrinsic_sizing_mode: IntrinsicSizingMode,
2636    ) -> Au {
2637        let content_block_size = || {
2638            let mut positioning_context = PositioningContext::default();
2639            let style = self.independent_formatting_context.style();
2640
2641            // We are computing the intrinsic block size, so the tentative block size that we use
2642            // as an input to the intrinsic inline sizes needs to ignore the values of the sizing
2643            // properties in the block axis.
2644            let tentative_block_size = SizeConstraint::default();
2645
2646            // TODO: This is wrong if the item writing mode is different from the flex
2647            // container's writing mode.
2648            let inline_size = {
2649                let stretch_size =
2650                    flex_context.containing_block.size.inline - pbm_auto_is_zero.cross;
2651                let get_content_size = || {
2652                    self.inline_content_sizes(
2653                        flex_context,
2654                        tentative_block_size,
2655                        preferred_aspect_ratio,
2656                    )
2657                };
2658                content_box_sizes.inline.resolve(
2659                    Direction::Inline,
2660                    automatic_inline_size,
2661                    Au::zero,
2662                    Some(stretch_size),
2663                    get_content_size,
2664                    false,
2665                )
2666            };
2667            let item_as_containing_block = ContainingBlock {
2668                size: ContainingBlockSize {
2669                    inline: inline_size,
2670                    block: tentative_block_size,
2671                },
2672                style,
2673            };
2674            self.independent_formatting_context
2675                .layout(
2676                    flex_context.layout_context,
2677                    &mut positioning_context,
2678                    &item_as_containing_block,
2679                    flex_context.containing_block,
2680                    preferred_aspect_ratio,
2681                    &LazySize::intrinsic(),
2682                )
2683                .content_block_size
2684        };
2685        match intrinsic_sizing_mode {
2686            IntrinsicSizingMode::Contribution => {
2687                let stretch_size = flex_context
2688                    .containing_block
2689                    .size
2690                    .block
2691                    .to_definite()
2692                    .map(|block_size| block_size - pbm_auto_is_zero.main);
2693                let inner_block_size = content_box_sizes.block.resolve(
2694                    Direction::Block,
2695                    Size::FitContent,
2696                    Au::zero,
2697                    stretch_size,
2698                    || ContentSizes::from(content_block_size()),
2699                    // Tables have a special sizing in the block axis that handles collapsed rows
2700                    // by ignoring the sizing properties and instead relying on the content block size,
2701                    // which should indirectly take sizing properties into account.
2702                    // However, above we laid out the table with a SizeConstraint::default() block size,
2703                    // so the content block size doesn't take sizing properties into account.
2704                    // Therefore, pretending that it's never a table tends to provide a better result.
2705                    false, /* is_table */
2706                );
2707                inner_block_size + pbm_auto_is_zero.main
2708            },
2709            IntrinsicSizingMode::Size => content_block_size(),
2710        }
2711    }
2712
2713    fn inline_content_sizes(
2714        &self,
2715        flex_context: &FlexContext,
2716        block_size: SizeConstraint,
2717        preferred_aspect_ratio: Option<AspectRatio>,
2718    ) -> ContentSizes {
2719        let style = self.independent_formatting_context.style();
2720        let constraint_space = ConstraintSpace::new(block_size, style, preferred_aspect_ratio);
2721        self.independent_formatting_context
2722            .inline_content_sizes(flex_context.layout_context, &constraint_space)
2723            .sizes
2724    }
2725}