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