Skip to main content

layout/
style_ext.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 app_units::Au;
6use layout_api::{AxesOverflow, LayoutElementType, LayoutNode, LayoutNodeType};
7use malloc_size_of_derive::MallocSizeOf;
8use style::Zero;
9use style::color::AbsoluteColor;
10use style::computed_values::direction::T as Direction;
11use style::computed_values::isolation::T as ComputedIsolation;
12use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
13use style::computed_values::position::T as ComputedPosition;
14use style::computed_values::transform_style::T as ComputedTransformStyle;
15use style::computed_values::unicode_bidi::T as UnicodeBidi;
16use style::logical_geometry::{Direction as AxisDirection, PhysicalSide, WritingMode};
17use style::properties::ComputedValues;
18use style::properties::longhands::backface_visibility::computed_value::T as BackfaceVisiblity;
19use style::properties::longhands::box_sizing::computed_value::T as BoxSizing;
20use style::properties::longhands::column_span::computed_value::T as ColumnSpan;
21use style::properties::style_structs::Border;
22use style::servo::selector_parser::PseudoElement;
23use style::values::CSSFloat;
24use style::values::computed::basic_shape::ClipPath;
25use style::values::computed::image::Image as ComputedImageLayer;
26use style::values::computed::{
27    BorderSideWidth, BorderStyle, Color, Inset, ItemPlacement, LengthPercentage, Margin,
28    SelfAlignment,
29};
30use style::values::generics::box_::Perspective;
31use style::values::generics::position::{GenericAspectRatio, PreferredRatio};
32use style::values::generics::transform::{GenericRotate, GenericScale, GenericTranslate};
33use style::values::specified::align::AlignFlags;
34use style::values::specified::{Overflow, WillChangeBits, box_ as stylo};
35use unicode_bidi::Level;
36use webrender_api as wr;
37use webrender_api::units::LayoutTransform;
38
39use crate::dom_traversal::{Contents, NodeAndStyleInfo};
40use crate::fragment_tree::FragmentFlags;
41use crate::geom::{
42    AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides,
43    PhysicalSize,
44};
45use crate::sizing::{Size, Sizes};
46use crate::table::TableLayoutStyle;
47use crate::{ContainingBlock, IndefiniteContainingBlock};
48
49#[derive(Clone, Copy, Eq, PartialEq)]
50pub(crate) enum Display {
51    None,
52    Contents,
53    GeneratingBox(DisplayGeneratingBox),
54}
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub(crate) enum DisplayGeneratingBox {
58    OutsideInside {
59        outside: DisplayOutside,
60        inside: DisplayInside,
61    },
62    /// <https://drafts.csswg.org/css-display-3/#layout-specific-display>
63    LayoutInternal(DisplayLayoutInternal),
64}
65impl DisplayGeneratingBox {
66    pub(crate) fn display_inside(&self) -> DisplayInside {
67        match *self {
68            DisplayGeneratingBox::OutsideInside { inside, .. } => inside,
69            DisplayGeneratingBox::LayoutInternal(layout_internal) => {
70                layout_internal.display_inside()
71            },
72        }
73    }
74
75    pub(crate) fn used_value_for_contents(
76        &self,
77        contents: &Contents,
78        info: &NodeAndStyleInfo,
79    ) -> Self {
80        // From <https://www.w3.org/TR/css-display-3/#layout-specific-display>:
81        // > When the display property of a replaced element computes to one of
82        // > the layout-internal values, it is handled as having a used value of
83        // > inline.
84        if matches!(self, Self::LayoutInternal(_)) && contents.is_replaced() {
85            Self::OutsideInside {
86                outside: DisplayOutside::Inline,
87                inside: DisplayInside::Flow {
88                    is_list_item: false,
89                },
90            }
91        } else if matches!(contents, Contents::Widget(_)) {
92            // <https://html.spec.whatwg.org/multipage/#form-controls>
93            // Widgets should establish an independent formatting context. Therefore,
94            // replace a `flow` inner display type with `flow-root`, or just use
95            // `flow-root` unconditionally, depending on the element.
96            // Also, prevent it from being a list item, like Blink and WebKit, but
97            // unlike Gecko. See https://github.com/w3c/csswg-drafts/issues/14187
98            if let DisplayGeneratingBox::OutsideInside { outside, inside } = self &&
99                (matches!(
100                    inside,
101                    DisplayInside::Flow { .. } | DisplayInside::FlowRoot { .. }
102                ) || info.node.type_id() !=
103                    Some(LayoutNodeType::Element(
104                        LayoutElementType::HTMLButtonElement,
105                    )))
106            {
107                DisplayGeneratingBox::OutsideInside {
108                    outside: *outside,
109                    inside: DisplayInside::FlowRoot {
110                        is_list_item: false,
111                    },
112                }
113            } else {
114                *self
115            }
116        } else {
117            *self
118        }
119    }
120}
121
122#[derive(Clone, Copy, Debug, Eq, PartialEq)]
123pub(crate) enum DisplayOutside {
124    Block,
125    Inline,
126}
127
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub(crate) enum DisplayInside {
130    // “list-items are limited to the Flow Layout display types”
131    // <https://drafts.csswg.org/css-display/#list-items>
132    Flow { is_list_item: bool },
133    FlowRoot { is_list_item: bool },
134    Flex,
135    Grid,
136    Table,
137}
138
139#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140#[expect(clippy::enum_variant_names)]
141/// <https://drafts.csswg.org/css-display-3/#layout-specific-display>
142pub(crate) enum DisplayLayoutInternal {
143    TableCaption,
144    TableCell,
145    TableColumn,
146    TableColumnGroup,
147    TableFooterGroup,
148    TableHeaderGroup,
149    TableRow,
150    TableRowGroup,
151}
152
153impl DisplayLayoutInternal {
154    /// <https://drafts.csswg.org/css-display-3/#layout-specific-displa>
155    pub(crate) fn display_inside(&self) -> DisplayInside {
156        // When we add ruby, the display_inside of ruby must be Flow.
157        // TODO: this should be unreachable for everything but
158        // table cell and caption, once we have box tree fixups.
159        DisplayInside::FlowRoot {
160            is_list_item: false,
161        }
162    }
163}
164
165/// Percentages resolved but not `auto` margins
166#[derive(Clone, Debug)]
167pub(crate) struct PaddingBorderMargin {
168    pub padding: LogicalSides<Au>,
169    pub border: LogicalSides<Au>,
170    pub margin: LogicalSides<AuOrAuto>,
171
172    /// Pre-computed sums in each axis
173    pub padding_border_sums: LogicalVec2<Au>,
174}
175
176impl PaddingBorderMargin {
177    pub(crate) fn zero() -> Self {
178        Self {
179            padding: LogicalSides::zero(),
180            border: LogicalSides::zero(),
181            margin: LogicalSides::zero(),
182            padding_border_sums: LogicalVec2::zero(),
183        }
184    }
185
186    pub(crate) fn sums_auto_is_zero(
187        &self,
188        ignore_block_margins: LogicalSides1D<bool>,
189    ) -> LogicalVec2<Au> {
190        let margin = self.margin.auto_is(Au::zero);
191        let mut sums = self.padding_border_sums;
192        sums.inline += margin.inline_sum();
193        if !ignore_block_margins.start {
194            sums.block += margin.block_start;
195        }
196        if !ignore_block_margins.end {
197            sums.block += margin.block_end;
198        }
199        sums
200    }
201}
202
203/// Resolved `aspect-ratio` property with respect to a specific element. Depends
204/// on that element's `box-sizing` (and padding and border, if that `box-sizing`
205/// is `border-box`).
206#[derive(Clone, Copy, Debug)]
207pub(crate) struct AspectRatio {
208    /// If the element that this aspect ratio belongs to uses box-sizing:
209    /// border-box, and the aspect-ratio property does not contain "auto", then
210    /// the aspect ratio is in respect to the border box. This will then contain
211    /// the summed sizes of the padding and border. Otherwise, it's 0.
212    box_sizing_adjustment: LogicalVec2<Au>,
213    /// The ratio itself (inline over block).
214    i_over_b: CSSFloat,
215}
216
217impl AspectRatio {
218    /// Given one side length, compute the other one.
219    pub(crate) fn compute_dependent_size(
220        &self,
221        ratio_dependent_axis: AxisDirection,
222        ratio_determining_size: Au,
223    ) -> Au {
224        match ratio_dependent_axis {
225            // Calculate the inline size from the block size
226            AxisDirection::Inline => {
227                (ratio_determining_size + self.box_sizing_adjustment.block).scale_by(self.i_over_b) -
228                    self.box_sizing_adjustment.inline
229            },
230            // Calculate the block size from the inline size
231            AxisDirection::Block => {
232                (ratio_determining_size + self.box_sizing_adjustment.inline)
233                    .scale_by(1.0 / self.i_over_b) -
234                    self.box_sizing_adjustment.block
235            },
236        }
237    }
238
239    pub(crate) fn from_logical_content_ratio(i_over_b: CSSFloat) -> Self {
240        Self {
241            box_sizing_adjustment: LogicalVec2::zero(),
242            i_over_b,
243        }
244    }
245}
246
247#[derive(Clone)]
248pub(crate) struct ContentBoxSizesAndPBM {
249    pub content_box_sizes: LogicalVec2<Sizes>,
250    pub pbm: PaddingBorderMargin,
251    pub depends_on_block_constraints: bool,
252    pub preferred_size_computes_to_auto: LogicalVec2<bool>,
253}
254
255#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
256pub(crate) struct BorderStyleColor {
257    pub style: BorderStyle,
258    pub color: AbsoluteColor,
259}
260
261impl BorderStyleColor {
262    pub(crate) fn new(style: BorderStyle, color: AbsoluteColor) -> Self {
263        Self { style, color }
264    }
265
266    pub(crate) fn from_border(
267        border: &Border,
268        current_color: &AbsoluteColor,
269    ) -> PhysicalSides<Self> {
270        let resolve = |color: &Color| color.resolve_to_absolute(current_color);
271        PhysicalSides::<Self>::new(
272            Self::new(border.border_top_style, resolve(&border.border_top_color)),
273            Self::new(
274                border.border_right_style,
275                resolve(&border.border_right_color),
276            ),
277            Self::new(
278                border.border_bottom_style,
279                resolve(&border.border_bottom_color),
280            ),
281            Self::new(border.border_left_style, resolve(&border.border_left_color)),
282        )
283    }
284
285    pub(crate) fn hidden() -> Self {
286        Self::new(BorderStyle::Hidden, AbsoluteColor::TRANSPARENT_BLACK)
287    }
288}
289
290impl Default for BorderStyleColor {
291    fn default() -> Self {
292        Self::new(BorderStyle::None, AbsoluteColor::TRANSPARENT_BLACK)
293    }
294}
295
296/// <https://drafts.csswg.org/cssom-view/#overflow-directions>
297/// > A scrolling box of a viewport or element has two overflow directions,
298/// > which are the block-end and inline-end directions for that viewport or element.
299pub(crate) struct OverflowDirection {
300    /// Whether block-end or inline-end direction is [PhysicalSide::Right].
301    pub rightward: bool,
302    /// Whether block-end or inline-end direction is [PhysicalSide::Bottom].
303    pub downward: bool,
304}
305
306pub(crate) trait ComputedValuesExt {
307    fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>;
308    fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>>;
309    fn box_size(
310        &self,
311        containing_block_writing_mode: WritingMode,
312    ) -> LogicalVec2<Size<LengthPercentage>>;
313    fn min_box_size(
314        &self,
315        containing_block_writing_mode: WritingMode,
316    ) -> LogicalVec2<Size<LengthPercentage>>;
317    fn max_box_size(
318        &self,
319        containing_block_writing_mode: WritingMode,
320    ) -> LogicalVec2<Size<LengthPercentage>>;
321    fn content_box_size_for_box_size(
322        &self,
323        box_size: LogicalVec2<Size<Au>>,
324        pbm: &PaddingBorderMargin,
325    ) -> LogicalVec2<Size<Au>>;
326    fn content_min_box_size_for_min_size(
327        &self,
328        box_size: LogicalVec2<Size<Au>>,
329        pbm: &PaddingBorderMargin,
330    ) -> LogicalVec2<Size<Au>>;
331    fn content_max_box_size_for_max_size(
332        &self,
333        box_size: LogicalVec2<Size<Au>>,
334        pbm: &PaddingBorderMargin,
335    ) -> LogicalVec2<Size<Au>>;
336    fn border_style_color(
337        &self,
338        containing_block_writing_mode: WritingMode,
339    ) -> LogicalSides<BorderStyleColor>;
340    fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>;
341    fn margin(
342        &self,
343        containing_block_writing_mode: WritingMode,
344    ) -> LogicalSides<LengthPercentageOrAuto<'_>>;
345    fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool;
346    fn has_transform_or_perspective_style(&self) -> bool;
347    fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool;
348    fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool;
349    fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32;
350    fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow;
351    fn used_transform_style(&self, fragment_flags: FragmentFlags) -> ComputedTransformStyle;
352    fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool;
353    fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool;
354    fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool;
355    fn establishes_or_extends_3d_rendering_context(&self, fragment_flags: FragmentFlags) -> bool;
356    fn establishes_containing_block_for_absolute_descendants(
357        &self,
358        fragment_flags: FragmentFlags,
359    ) -> bool;
360    fn establishes_containing_block_for_all_descendants(
361        &self,
362        fragment_flags: FragmentFlags,
363    ) -> bool;
364    fn preferred_aspect_ratio(
365        &self,
366        natural_aspect_ratio: Option<CSSFloat>,
367        padding_border_sums: &LogicalVec2<Au>,
368    ) -> Option<AspectRatio>;
369    fn background_is_transparent(&self) -> bool;
370    fn webrender_primitive_flags(&self) -> wr::PrimitiveFlags;
371    fn bidi_control_chars(&self) -> (&'static str, &'static str);
372    fn resolve_align_self(
373        &self,
374        resolved_auto_value: ItemPlacement,
375        resolved_normal_value: AlignFlags,
376    ) -> SelfAlignment;
377    fn depends_on_block_constraints_due_to_relative_positioning(
378        &self,
379        writing_mode: WritingMode,
380    ) -> bool;
381    fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool;
382    fn is_atomic_inline_level(&self, fragment_flags: FragmentFlags) -> bool;
383    fn overflow_direction(&self) -> OverflowDirection;
384    fn to_bidi_level(&self) -> Level;
385}
386
387impl ComputedValuesExt for ComputedValues {
388    fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
389        fn convert(inset: &Inset) -> LengthPercentageOrAuto<'_> {
390            match inset {
391                Inset::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
392                Inset::Auto => LengthPercentageOrAuto::Auto,
393                Inset::AnchorFunction(_) => unreachable!("anchor() should be disabled"),
394                Inset::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
395                Inset::AnchorContainingCalcFunction(_) => {
396                    unreachable!("anchor() and anchor-size() should be disabled")
397                },
398            }
399        }
400        let position = self.get_position();
401        PhysicalSides::new(
402            convert(&position.top),
403            convert(&position.right),
404            convert(&position.bottom),
405            convert(&position.left),
406        )
407    }
408
409    fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>> {
410        LogicalSides::from_physical(&self.physical_box_offsets(), writing_mode)
411    }
412
413    fn box_size(
414        &self,
415        containing_block_writing_mode: WritingMode,
416    ) -> LogicalVec2<Size<LengthPercentage>> {
417        let position = self.get_position();
418        LogicalVec2::from_physical_size(
419            &PhysicalSize::new(position.get_width().into(), position.get_height().into()),
420            containing_block_writing_mode,
421        )
422    }
423
424    fn min_box_size(
425        &self,
426        containing_block_writing_mode: WritingMode,
427    ) -> LogicalVec2<Size<LengthPercentage>> {
428        let position = self.get_position();
429        LogicalVec2::from_physical_size(
430            &PhysicalSize::new(
431                position.get_min_width().into(),
432                position.get_min_height().into(),
433            ),
434            containing_block_writing_mode,
435        )
436    }
437
438    fn max_box_size(
439        &self,
440        containing_block_writing_mode: WritingMode,
441    ) -> LogicalVec2<Size<LengthPercentage>> {
442        let position = self.get_position();
443        LogicalVec2::from_physical_size(
444            &PhysicalSize::new(
445                position.get_max_width().into(),
446                position.get_max_height().into(),
447            ),
448            containing_block_writing_mode,
449        )
450    }
451
452    fn content_box_size_for_box_size(
453        &self,
454        box_size: LogicalVec2<Size<Au>>,
455        pbm: &PaddingBorderMargin,
456    ) -> LogicalVec2<Size<Au>> {
457        match self.get_position().box_sizing {
458            BoxSizing::ContentBox => box_size,
459            // These may be negative, but will later be clamped by `min-width`/`min-height`
460            // which is clamped to zero.
461            BoxSizing::BorderBox => box_size.map_inline_and_block_sizes(
462                |value| value - pbm.padding_border_sums.inline,
463                |value| value - pbm.padding_border_sums.block,
464            ),
465        }
466    }
467
468    fn content_min_box_size_for_min_size(
469        &self,
470        min_box_size: LogicalVec2<Size<Au>>,
471        pbm: &PaddingBorderMargin,
472    ) -> LogicalVec2<Size<Au>> {
473        match self.get_position().box_sizing {
474            BoxSizing::ContentBox => min_box_size,
475            // Clamp to zero to make sure the used size components are non-negative
476            BoxSizing::BorderBox => min_box_size.map_inline_and_block_sizes(
477                |value| Au::zero().max(value - pbm.padding_border_sums.inline),
478                |value| Au::zero().max(value - pbm.padding_border_sums.block),
479            ),
480        }
481    }
482
483    fn content_max_box_size_for_max_size(
484        &self,
485        max_box_size: LogicalVec2<Size<Au>>,
486        pbm: &PaddingBorderMargin,
487    ) -> LogicalVec2<Size<Au>> {
488        match self.get_position().box_sizing {
489            BoxSizing::ContentBox => max_box_size,
490            // This may be negative, but will later be clamped by `min-width`
491            // which itself is clamped to zero.
492            BoxSizing::BorderBox => max_box_size.map_inline_and_block_sizes(
493                |value| value - pbm.padding_border_sums.inline,
494                |value| value - pbm.padding_border_sums.block,
495            ),
496        }
497    }
498
499    fn border_style_color(
500        &self,
501        containing_block_writing_mode: WritingMode,
502    ) -> LogicalSides<BorderStyleColor> {
503        let current_color = self.get_color();
504        LogicalSides::from_physical(
505            &BorderStyleColor::from_border(self.get_border(), current_color),
506            containing_block_writing_mode,
507        )
508    }
509
510    fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
511        fn convert(inset: &Margin) -> LengthPercentageOrAuto<'_> {
512            match inset {
513                Margin::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
514                Margin::Auto => LengthPercentageOrAuto::Auto,
515                Margin::AnchorSizeFunction(_) | Margin::AnchorContainingCalcFunction(_) => {
516                    unreachable!("anchor-size() should be disabled")
517                },
518            }
519        }
520        let margin = self.get_margin();
521        PhysicalSides::new(
522            convert(&margin.margin_top),
523            convert(&margin.margin_right),
524            convert(&margin.margin_bottom),
525            convert(&margin.margin_left),
526        )
527    }
528
529    fn margin(
530        &self,
531        containing_block_writing_mode: WritingMode,
532    ) -> LogicalSides<LengthPercentageOrAuto<'_>> {
533        LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode)
534    }
535
536    fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool {
537        (self.get_box().display.is_inline_flow() &&
538            !fragment_flags.intersects(
539                FragmentFlags::IS_REPLACED |
540                    FragmentFlags::IS_WIDGET |
541                    FragmentFlags::IS_FLEX_OR_GRID_ITEM,
542            )) ||
543            matches!(self.pseudo(), Some(PseudoElement::FirstLetter))
544    }
545
546    fn is_atomic_inline_level(&self, fragment_flags: FragmentFlags) -> bool {
547        self.get_box().display.outside() == stylo::DisplayOutside::Inline &&
548            !self.is_inline_box(fragment_flags) &&
549            !fragment_flags.intersects(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
550    }
551
552    /// Returns true if this is a transformable element.
553    fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool {
554        // "A transformable element is an element in one of these categories:
555        //   * all elements whose layout is governed by the CSS box model except for
556        //     non-replaced inline boxes, table-column boxes, and table-column-group
557        //     boxes,
558        //   * all SVG paint server elements, the clipPath element  and SVG renderable
559        //     elements with the exception of any descendant element of text content
560        //     elements."
561        // <https://drafts.csswg.org/css-transforms/#transformable-element>
562        // TODO: check for all cases listed in the above spec.
563        !self.is_inline_box(fragment_flags)
564    }
565
566    /// Returns true if this style has a transform or perspective property set.
567    fn has_transform_or_perspective_style(&self) -> bool {
568        !self.get_box().transform.0.is_empty() ||
569            self.get_box().scale != GenericScale::None ||
570            self.get_box().rotate != GenericRotate::None ||
571            self.get_box().translate != GenericTranslate::None ||
572            self.get_box().perspective != Perspective::None
573    }
574
575    /// Returns true if this style has a transform or perspective property set, and
576    /// it applies to this element.
577    #[inline]
578    fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
579        self.is_transformable(fragment_flags) && self.has_transform_or_perspective_style()
580    }
581
582    /// Whether the `z-index` property applies to this fragment.
583    fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool {
584        // As per CSS 2 § 9.9.1, `z-index` applies to positioned elements.
585        // <http://www.w3.org/TR/CSS2/visuren.html#z-index>
586        if self.get_box().position != ComputedPosition::Static {
587            return true;
588        }
589        // More modern specs also apply it to flex and grid items.
590        // - From <https://www.w3.org/TR/css-flexbox-1/#painting>:
591        //   > Flex items paint exactly the same as inline blocks [CSS2], except that order-modified
592        //   > document order is used in place of raw document order, and z-index values other than auto
593        //   > create a stacking context even if position is static (behaving exactly as if position
594        //   > were relative).
595        // - From <https://drafts.csswg.org/css-flexbox/#painting>:
596        //   > The painting order of grid items is exactly the same as inline blocks [CSS2], except that
597        //   > order-modified document order is used in place of raw document order, and z-index values
598        //   > other than auto create a stacking context even if position is static (behaving exactly
599        //   > as if position were relative).
600        fragment_flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
601    }
602
603    /// Get the effective z-index of this fragment. Z-indices only apply to positioned elements
604    /// per CSS 2 9.9.1 (<http://www.w3.org/TR/CSS2/visuren.html#z-index>), so this value may differ
605    /// from the value specified in the style.
606    fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32 {
607        if self.z_index_applies(fragment_flags) {
608            self.get_position().z_index.integer_or(0)
609        } else {
610            0
611        }
612    }
613
614    /// Get the effective overflow of this box. The property only applies to block containers,
615    /// flex containers, and grid containers. And some box types only accept a few values.
616    /// <https://www.w3.org/TR/css-overflow-3/#overflow-control>
617    fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow {
618        // https://www.w3.org/TR/css-overflow-3/#overflow-propagation
619        // The element from which the value is propagated must then have a used overflow value of visible.
620        if fragment_flags.contains(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT) {
621            return AxesOverflow::default();
622        }
623
624        let mut overflow = AxesOverflow::from(self);
625
626        // From <https://www.w3.org/TR/css-overflow-4/#overflow-control>:
627        // "On replaced elements, the used values of all computed values other than visible is clip."
628        if fragment_flags.contains(FragmentFlags::IS_REPLACED) {
629            if overflow.x != Overflow::Visible {
630                overflow.x = Overflow::Clip;
631            }
632            if overflow.y != Overflow::Visible {
633                overflow.y = Overflow::Clip;
634            }
635            return overflow;
636        }
637
638        let ignores_overflow = match self.get_box().display.inside() {
639            // <https://drafts.csswg.org/css-overflow-3/#overflow-control>
640            // `overflow` doesn't apply to inline boxes.
641            stylo::DisplayInside::Flow => self.is_inline_box(fragment_flags),
642
643            // According to <https://drafts.csswg.org/css-tables/#global-style-overrides>,
644            // - overflow applies to table-wrapper boxes and not to table grid boxes.
645            //   That's what Blink and WebKit do, however Firefox matches a CSSWG resolution that says
646            //   the opposite: <https://lists.w3.org/Archives/Public/www-style/2012Aug/0298.html>
647            //   Due to the way that we implement table-wrapper boxes, it's easier to align with Firefox.
648            // - Tables ignore overflow values different than visible, clip and hidden.
649            //   This affects both axes, to ensure they have the same scrollability.
650            stylo::DisplayInside::Table => {
651                !matches!(self.pseudo(), Some(PseudoElement::ServoTableGrid)) ||
652                    matches!(overflow.x, Overflow::Auto | Overflow::Scroll) ||
653                    matches!(overflow.y, Overflow::Auto | Overflow::Scroll)
654            },
655
656            // <https://drafts.csswg.org/css-tables/#global-style-overrides>
657            // Table-track and table-track-group boxes ignore overflow.
658            stylo::DisplayInside::TableColumn |
659            stylo::DisplayInside::TableColumnGroup |
660            stylo::DisplayInside::TableRow |
661            stylo::DisplayInside::TableRowGroup |
662            stylo::DisplayInside::TableHeaderGroup |
663            stylo::DisplayInside::TableFooterGroup => true,
664
665            _ => false,
666        };
667        if ignores_overflow {
668            return AxesOverflow::default();
669        }
670
671        overflow
672    }
673
674    /// Get the used `transform-style` value according to the the rules in
675    /// <https://drafts.csswg.org/css-transforms/#grouping-property-values>.
676    fn used_transform_style(&self, fragment_flags: FragmentFlags) -> ComputedTransformStyle {
677        // The check for flat here is to avoid having to check all of the properties below when
678        // possible.
679        let box_style = self.get_box();
680        if box_style.transform_style == ComputedTransformStyle::Flat {
681            return ComputedTransformStyle::Flat;
682        }
683
684        // https://drafts.csswg.org/css-transforms-2/#grouping-property-values
685        //  * overflow: any value other than visible or clip.
686        //  * opacity: any value less than 1.
687        //  * filter: any value other than none.
688        //  * clip: any value other than auto.
689        //  * clip-path: any value other than none.
690        //  * isolation: used value of isolate.
691        //  * mask-image: any value other than none.
692        //  * mask-border-source: any value other than none.
693        //  * mix-blend-mode: any value other than normal.
694        //  * contain: paint and any other property/value combination that causes
695        //    paint containment. Note: this includes any property that affect the
696        //    used value of the contain property, such as content-visibility:
697        //    hidden.
698        //
699        // TODO: Support `mask-image`, `mask-border-source`, and `contain`.
700        let effects = self.get_effects();
701        let overflow = self.effective_overflow(fragment_flags);
702        if !matches!(overflow.x, Overflow::Visible | Overflow::Clip) ||
703            !matches!(overflow.y, Overflow::Visible | Overflow::Clip) ||
704            effects.opacity < 1.0 ||
705            !effects.filter.0.is_empty() ||
706            !effects.clip.is_auto() ||
707            self.get_svg().clip_path != ClipPath::None ||
708            self.get_box().isolation == ComputedIsolation::Isolate ||
709            effects.mix_blend_mode != ComputedMixBlendMode::Normal
710        {
711            return ComputedTransformStyle::Flat;
712        }
713
714        // Return the computed value if not overridden by the above exceptions
715        box_style.transform_style
716    }
717
718    /// Return true if this style is a normal block and establishes
719    /// a new block formatting context.
720    ///
721    /// NOTE: This should be kept in sync with the checks in `impl
722    /// TElement::compute_layout_damage` for `ServoLayoutElement` in
723    /// `components/script/layout_dom/element.rs`.
724    fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool {
725        if self.establishes_scroll_container(fragment_flags) {
726            return true;
727        }
728
729        if self.get_column().is_multicol() {
730            return true;
731        }
732
733        if self.get_column().column_span == ColumnSpan::All {
734            return true;
735        }
736
737        // Per <https://drafts.csswg.org/css-align/#distribution-block>:
738        // Block containers with an `align-content` value that is not `normal` should
739        // form an independent block formatting context. This should really only happen
740        // for block containers, but we do not support subgrid containers yet which is the
741        // only other case.
742        if self.get_position().align_content.primary() != AlignFlags::NORMAL {
743            return true;
744        }
745
746        // TODO: We need to handle CSS Contain here.
747        false
748    }
749
750    /// Whether or not the `overflow` value of this style establishes a scroll container.
751    fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool {
752        self.effective_overflow(fragment_flags)
753            .establishes_scroll_container()
754    }
755
756    /// Returns true if this fragment establishes or extends a 3D rendering context.
757    fn establishes_or_extends_3d_rendering_context(&self, fragment_flags: FragmentFlags) -> bool {
758        // > A 3D rendering context is established by a transformable element whose used
759        // > value for transform-style is preserve-3d and which itself is not part of a 3D
760        // > rendering context. An element that establishes a 3D rendering context also
761        // > participates in that context.
762        self.is_transformable(fragment_flags) &&
763            self.used_transform_style(fragment_flags) == ComputedTransformStyle::Preserve3d
764    }
765
766    /// Returns true if this fragment establishes a new stacking context and false otherwise.
767    fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool {
768        // From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
769        // > If any non-initial value of a property would create a stacking context on the element,
770        // > specifying that property in will-change must create a stacking context on the element.
771        let will_change_bits = self.get_will_change().bits;
772        if will_change_bits
773            .intersects(WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::OPACITY)
774        {
775            return true;
776        }
777
778        // From <https://www.w3.org/TR/CSS2/visuren.html#z-index>, values different than `auto`
779        // make the box establish a stacking context.
780        if self.z_index_applies(fragment_flags) &&
781            (!self.get_position().z_index.is_auto() ||
782                will_change_bits.intersects(WillChangeBits::Z_INDEX))
783        {
784            return true;
785        }
786
787        // Fixed position and sticky position always create stacking contexts.
788        // Note `will-change: position` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
789        if matches!(
790            self.get_box().position,
791            ComputedPosition::Fixed | ComputedPosition::Sticky
792        ) {
793            return true;
794        }
795
796        // From <https://www.w3.org/TR/css-transforms-1/#transform-rendering>
797        // > For elements whose layout is governed by the CSS box model, any value other than
798        // > `none` for the `transform` property results in the creation of a stacking context.
799        //
800        // From <https://www.w3.org/TR/css-transforms-2/#individual-transforms>
801        // > all other values […] create a stacking context and containing block for all
802        // > descendants, per usual for transforms.
803        //
804        // From <https://www.w3.org/TR/css-transforms-2/#perspective-property>
805        // > any value other than none establishes a stacking context.
806        //
807        // From <https://www.w3.org/TR/css-transforms-2/#transform-style-property>
808        // > A computed value of `preserve-3d` for `transform-style` on a transformable element
809        // > establishes both a stacking context and a containing block for all descendants.
810        if self.is_transformable(fragment_flags) &&
811            (self.has_transform_or_perspective_style() ||
812                self.used_transform_style(fragment_flags) ==
813                    ComputedTransformStyle::Preserve3d ||
814                will_change_bits
815                    .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
816        {
817            return true;
818        }
819
820        // From <https://www.w3.org/TR/css-color-3/#transparency>
821        // > implementations must create a new stacking context for any element with opacity less than 1.
822        // Note `will-change: opacity` is handled above by `WillChangeBits::OPACITY`.
823        let effects = self.get_effects();
824        if effects.opacity != 1.0 {
825            return true;
826        }
827
828        // From <https://www.w3.org/TR/filter-effects-1/#FilterProperty>
829        // > A computed value of other than `none` results in the creation of a stacking context
830        // Note `will-change: filter` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
831        if !effects.filter.0.is_empty() {
832            return true;
833        }
834
835        // From <https://www.w3.org/TR/compositing-1/#mix-blend-mode>
836        // > Applying a blendmode other than `normal` to the element must establish a new stacking context
837        // Note `will-change: mix-blend-mode` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
838        if effects.mix_blend_mode != ComputedMixBlendMode::Normal {
839            return true;
840        }
841
842        // From <https://www.w3.org/TR/css-masking-1/#the-clip-path>
843        // > A computed value of other than `none` results in the creation of a stacking context.
844        // Note `will-change: clip-path` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
845        if self.get_svg().clip_path != ClipPath::None {
846            return true;
847        }
848
849        // From <https://www.w3.org/TR/compositing-1/#isolation>
850        // > For CSS, setting `isolation` to `isolate` will turn the element into a stacking context.
851        // Note `will-change: isolation` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
852        if self.get_box().isolation == ComputedIsolation::Isolate {
853            return true;
854        }
855
856        // From https://www.w3.org/TR/CSS22/visuren.html#z-index:
857        // > The root element forms the root stacking context.
858        if fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) {
859            return true;
860        }
861
862        // TODO: We need to handle CSS Contain here.
863        false
864    }
865
866    /// Returns true if this style establishes a containing block for absolute
867    /// descendants (`position: absolute`). If this style happens to establish a
868    /// containing block for “all descendants” (ie including `position: fixed`
869    /// descendants) this method will return true, but a true return value does
870    /// not imply that the style establishes a containing block for all descendants.
871    /// Use `establishes_containing_block_for_all_descendants()` instead.
872    fn establishes_containing_block_for_absolute_descendants(
873        &self,
874        fragment_flags: FragmentFlags,
875    ) -> bool {
876        if self.establishes_containing_block_for_all_descendants(fragment_flags) {
877            return true;
878        }
879
880        // From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
881        // > If any non-initial value of a property would cause the element to
882        // > generate a containing block for absolutely positioned elements, specifying that property in
883        // > will-change must cause the element to generate a containing block for absolutely positioned elements.
884        if self
885            .get_will_change()
886            .bits
887            .intersects(WillChangeBits::POSITION)
888        {
889            return true;
890        }
891
892        self.get_box().position != ComputedPosition::Static
893    }
894
895    /// Returns true if this style establishes a containing block for
896    /// all descendants, including fixed descendants (`position: fixed`).
897    /// Note that this also implies that it establishes a containing block
898    /// for absolute descendants (`position: absolute`).
899    fn establishes_containing_block_for_all_descendants(
900        &self,
901        fragment_flags: FragmentFlags,
902    ) -> bool {
903        // From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
904        // > If any non-initial value of a property would cause the element to generate a
905        // > containing block for fixed positioned elements, specifying that property in will-change
906        // > must cause the element to generate a containing block for fixed positioned elements.
907        let will_change_bits = self.get_will_change().bits;
908
909        // From <https://drafts.csswg.org/css-transforms-1/#transform-rendering>:
910        // > any value other than `none` for the `transform` property also causes the element
911        // > to establish a containing block for all descendants.
912        //
913        // From <https://www.w3.org/TR/css-transforms-2/#individual-transforms>
914        // > all other values […] create a stacking context and containing block for all
915        // > descendants, per usual for transforms.
916        //
917        // From <https://drafts.csswg.org/css-transforms-2/#perspective-property>:
918        // > The use of this property with any value other than `none` […] establishes a
919        // > containing block for all descendants, just like the `transform` property does.
920        //
921        // From <https://drafts.csswg.org/css-transforms-2/#transform-style-property>:
922        // > A computed value of `preserve-3d` for `transform-style` on a transformable element
923        // > establishes both a stacking context and a containing block for all descendants.
924        if self.is_transformable(fragment_flags) &&
925            (self.has_transform_or_perspective_style() ||
926                self.used_transform_style(fragment_flags) ==
927                    ComputedTransformStyle::Preserve3d ||
928                will_change_bits
929                    .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
930        {
931            return true;
932        }
933
934        // From <https://www.w3.org/TR/filter-effects-1/#propdef-filter>:
935        // > A value other than none for the filter property results in the creation of a containing
936        // > block for absolute and fixed positioned descendants unless the element it applies to is
937        // > a document root element in the current browsing context.
938        if !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) &&
939            (!self.get_effects().filter.0.is_empty() ||
940                will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG))
941        {
942            return true;
943        }
944
945        // TODO: We need to handle CSS Contain here.
946        false
947    }
948
949    /// Resolve the preferred aspect ratio according to the given natural aspect
950    /// ratio and the `aspect-ratio` property.
951    /// See <https://drafts.csswg.org/css-sizing-4/#aspect-ratio>.
952    fn preferred_aspect_ratio(
953        &self,
954        natural_aspect_ratio: Option<CSSFloat>,
955        padding_border_sums: &LogicalVec2<Au>,
956    ) -> Option<AspectRatio> {
957        let &GenericAspectRatio {
958            ref auto,
959            ratio: mut preferred_ratio,
960        } = self.get_aspect_ratio();
961
962        // For all cases where a ratio is specified:
963        // "If the <ratio> is degenerate, the property instead behaves as auto."
964        if matches!(preferred_ratio, PreferredRatio::Ratio(ratio) if ratio.is_degenerate()) {
965            preferred_ratio = PreferredRatio::None;
966        }
967
968        let to_logical_ratio = |physical_ratio| {
969            if self.writing_mode.is_horizontal() {
970                physical_ratio
971            } else {
972                1.0 / physical_ratio
973            }
974        };
975
976        match (auto, preferred_ratio) {
977            // The value `auto`. Either the ratio was not specified, or was
978            // degenerate and set to PreferredRatio::None above.
979            //
980            // "Replaced elements with a natural aspect ratio use that aspect
981            // ratio; otherwise the box has no preferred aspect ratio. Size
982            // calculations involving the aspect ratio work with the content box
983            // dimensions always."
984            (_, PreferredRatio::None) => natural_aspect_ratio
985                .map(to_logical_ratio)
986                .map(AspectRatio::from_logical_content_ratio),
987            // "If both auto and a <ratio> are specified together, the preferred
988            // aspect ratio is the specified ratio of width / height unless it
989            // is a replaced element with a natural aspect ratio, in which case
990            // that aspect ratio is used instead. In all cases, size
991            // calculations involving the aspect ratio work with the content box
992            // dimensions always."
993            (true, PreferredRatio::Ratio(preferred_ratio)) => Some({
994                let physical_ratio = natural_aspect_ratio
995                    .unwrap_or_else(|| (preferred_ratio.0).0 / (preferred_ratio.1).0);
996                AspectRatio::from_logical_content_ratio(to_logical_ratio(physical_ratio))
997            }),
998
999            // "The box’s preferred aspect ratio is the specified ratio of width
1000            // / height. Size calculations involving the aspect ratio work with
1001            // the dimensions of the box specified by box-sizing."
1002            (false, PreferredRatio::Ratio(preferred_ratio)) => {
1003                // If the `box-sizing` is `border-box`, use the padding and
1004                // border when calculating the aspect ratio.
1005                let box_sizing_adjustment = match self.get_box_sizing() {
1006                    BoxSizing::ContentBox => LogicalVec2::zero(),
1007                    BoxSizing::BorderBox => *padding_border_sums,
1008                };
1009                Some(AspectRatio {
1010                    i_over_b: to_logical_ratio((preferred_ratio.0).0 / (preferred_ratio.1).0),
1011                    box_sizing_adjustment,
1012                })
1013            },
1014        }
1015    }
1016
1017    /// Whether or not this style specifies a non-transparent background.
1018    fn background_is_transparent(&self) -> bool {
1019        let background = self.get_background();
1020        let color = self.resolve_color(&background.background_color);
1021        color.alpha == 0.0 &&
1022            background
1023                .background_image
1024                .0
1025                .iter()
1026                .all(|layer| matches!(layer, ComputedImageLayer::None))
1027    }
1028
1029    /// Generate appropriate WebRender `PrimitiveFlags` that should be used
1030    /// for display items generated by the `Fragment` which owns this style.
1031    fn webrender_primitive_flags(&self) -> wr::PrimitiveFlags {
1032        match self.get_box().backface_visibility {
1033            BackfaceVisiblity::Visible => wr::PrimitiveFlags::default(),
1034            BackfaceVisiblity::Hidden => wr::PrimitiveFlags::empty(),
1035        }
1036    }
1037
1038    /// If the 'unicode-bidi' property has a value other than 'normal', return the bidi control codes
1039    /// to inject before and after the text content of the element.
1040    /// See the table in <http://dev.w3.org/csswg/css-writing-modes/#unicode-bidi>.
1041    fn bidi_control_chars(&self) -> (&'static str, &'static str) {
1042        match (
1043            self.get_text().unicode_bidi,
1044            self.get_inherited_box().direction,
1045        ) {
1046            (UnicodeBidi::Normal, _) => ("", ""),
1047            (UnicodeBidi::Embed, Direction::Ltr) => ("\u{202a}", "\u{202c}"),
1048            (UnicodeBidi::Embed, Direction::Rtl) => ("\u{202b}", "\u{202c}"),
1049            (UnicodeBidi::Isolate, Direction::Ltr) => ("\u{2066}", "\u{2069}"),
1050            (UnicodeBidi::Isolate, Direction::Rtl) => ("\u{2067}", "\u{2069}"),
1051            (UnicodeBidi::BidiOverride, Direction::Ltr) => ("\u{202d}", "\u{202c}"),
1052            (UnicodeBidi::BidiOverride, Direction::Rtl) => ("\u{202e}", "\u{202c}"),
1053            (UnicodeBidi::IsolateOverride, Direction::Ltr) => {
1054                ("\u{2068}\u{202d}", "\u{202c}\u{2069}")
1055            },
1056            (UnicodeBidi::IsolateOverride, Direction::Rtl) => {
1057                ("\u{2068}\u{202e}", "\u{202c}\u{2069}")
1058            },
1059            (UnicodeBidi::Plaintext, _) => ("\u{2068}", "\u{2069}"),
1060        }
1061    }
1062
1063    fn resolve_align_self(
1064        &self,
1065        resolved_auto_value: ItemPlacement,
1066        resolved_normal_value: AlignFlags,
1067    ) -> SelfAlignment {
1068        SelfAlignment(match self.get_align_self().0 {
1069            AlignFlags::AUTO => resolved_auto_value.0,
1070            AlignFlags::NORMAL => resolved_normal_value,
1071            value => value,
1072        })
1073    }
1074
1075    fn depends_on_block_constraints_due_to_relative_positioning(
1076        &self,
1077        writing_mode: WritingMode,
1078    ) -> bool {
1079        if !matches!(
1080            self.get_box().position,
1081            ComputedPosition::Relative | ComputedPosition::Sticky
1082        ) {
1083            return false;
1084        }
1085        let box_offsets = self.box_offsets(writing_mode);
1086        let has_percentage = |offset: LengthPercentageOrAuto<'_>| {
1087            offset
1088                .non_auto()
1089                .is_some_and(LengthPercentage::has_percentage)
1090        };
1091        has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end)
1092    }
1093
1094    // <https://drafts.csswg.org/cssom-view/#overflow-directions>
1095    fn overflow_direction(&self) -> OverflowDirection {
1096        let inline_end_direction = self.writing_mode.inline_end_physical_side();
1097        let block_end_direction = self.writing_mode.block_end_physical_side();
1098
1099        let rightward = inline_end_direction == PhysicalSide::Right ||
1100            block_end_direction == PhysicalSide::Right;
1101        let downward = inline_end_direction == PhysicalSide::Bottom ||
1102            block_end_direction == PhysicalSide::Bottom;
1103
1104        // TODO(stevennovaryo): We should consider the flex-container's CSS (e.g. flow-direction: column-reverse).
1105        OverflowDirection {
1106            rightward,
1107            downward,
1108        }
1109    }
1110
1111    /// The default bidirectional embedding level for the writing mode of this style.
1112    ///
1113    /// Returns bidi level 0 if the mode is LTR, or 1 otherwise.
1114    fn to_bidi_level(&self) -> Level {
1115        if self.writing_mode.is_bidi_ltr() {
1116            Level::ltr()
1117        } else {
1118            Level::rtl()
1119        }
1120    }
1121}
1122
1123pub(crate) enum LayoutStyle<'a> {
1124    Default(&'a ComputedValues),
1125    Table(TableLayoutStyle<'a>),
1126}
1127
1128impl LayoutStyle<'_> {
1129    #[inline]
1130    pub(crate) fn style(&self) -> &ComputedValues {
1131        match self {
1132            Self::Default(style) => style,
1133            Self::Table(table) => table.style(),
1134        }
1135    }
1136
1137    #[inline]
1138    pub(crate) fn is_table(&self) -> bool {
1139        matches!(self, Self::Table(_))
1140    }
1141
1142    pub(crate) fn content_box_sizes_and_padding_border_margin(
1143        &self,
1144        containing_block: &IndefiniteContainingBlock,
1145    ) -> ContentBoxSizesAndPBM {
1146        // <https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution>
1147        // If max size properties or preferred size properties are set to a value containing
1148        // indefinite percentages, we treat the entire value as the initial value of the property.
1149        // However, for min size properties, as well as for margins and paddings,
1150        // we instead resolve indefinite percentages against zero.
1151        let containing_block_size_or_zero =
1152            containing_block.size.map(|value| value.unwrap_or_default());
1153        let writing_mode = containing_block.style.writing_mode;
1154        let pbm = self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1155            writing_mode,
1156            containing_block_size_or_zero.inline,
1157        );
1158        let style = self.style();
1159        let box_size = style.box_size(writing_mode);
1160        let min_size = style.min_box_size(writing_mode);
1161        let max_size = style.max_box_size(writing_mode);
1162        let preferred_size_computes_to_auto = box_size.map(|size| size.is_initial());
1163
1164        let depends_on_block_constraints = |size: &Size<LengthPercentage>| {
1165            match size {
1166                // fit-content is like clamp(min-content, stretch, max-content), but currently
1167                // min-content and max-content have the same behavior in the block axis,
1168                // so there is no dependency on block constraints.
1169                // TODO: for flex and grid layout, min-content and max-content should be different.
1170                // TODO: We are assuming that Size::Initial doesn't stretch. However, it may actually
1171                // stretch flex and grid items depending on the CSS Align properties, in that case
1172                // the caller needs to take care of it.
1173                Size::Stretch => true,
1174                Size::Numeric(length_percentage) => length_percentage.has_percentage(),
1175                _ => false,
1176            }
1177        };
1178        let depends_on_block_constraints = depends_on_block_constraints(&box_size.block) ||
1179            depends_on_block_constraints(&min_size.block) ||
1180            depends_on_block_constraints(&max_size.block) ||
1181            style.depends_on_block_constraints_due_to_relative_positioning(writing_mode);
1182
1183        let box_size = box_size.map_with(&containing_block.size, |size, basis| {
1184            size.resolve_percentages_for_preferred(*basis)
1185        });
1186        let content_box_size = style.content_box_size_for_box_size(box_size, &pbm);
1187        let min_size = min_size.percentages_relative_to_basis(&containing_block_size_or_zero);
1188        let content_min_box_size = style.content_min_box_size_for_min_size(min_size, &pbm);
1189        let max_size = max_size.map_with(&containing_block.size, |size, basis| {
1190            size.resolve_percentages_for_max(*basis)
1191        });
1192        let content_max_box_size = style.content_max_box_size_for_max_size(max_size, &pbm);
1193        ContentBoxSizesAndPBM {
1194            content_box_sizes: LogicalVec2 {
1195                block: Sizes::new(
1196                    content_box_size.block,
1197                    content_min_box_size.block,
1198                    content_max_box_size.block,
1199                ),
1200                inline: Sizes::new(
1201                    content_box_size.inline,
1202                    content_min_box_size.inline,
1203                    content_max_box_size.inline,
1204                ),
1205            },
1206            pbm,
1207            depends_on_block_constraints,
1208            preferred_size_computes_to_auto,
1209        }
1210    }
1211
1212    pub(crate) fn padding_border_margin(
1213        &self,
1214        containing_block: &ContainingBlock,
1215    ) -> PaddingBorderMargin {
1216        self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1217            containing_block.style.writing_mode,
1218            containing_block.size.inline,
1219        )
1220    }
1221
1222    pub(crate) fn padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1223        &self,
1224        writing_mode: WritingMode,
1225        containing_block_inline_size: Au,
1226    ) -> PaddingBorderMargin {
1227        let padding = self
1228            .padding(writing_mode)
1229            .percentages_relative_to(containing_block_inline_size);
1230        let style = self.style();
1231        let border = self.border_width(writing_mode);
1232        let margin = style
1233            .margin(writing_mode)
1234            .percentages_relative_to(containing_block_inline_size);
1235        PaddingBorderMargin {
1236            padding_border_sums: LogicalVec2 {
1237                inline: padding.inline_sum() + border.inline_sum(),
1238                block: padding.block_sum() + border.block_sum(),
1239            },
1240            padding,
1241            border,
1242            margin,
1243        }
1244    }
1245
1246    pub(crate) fn padding(
1247        &self,
1248        containing_block_writing_mode: WritingMode,
1249    ) -> LogicalSides<LengthPercentage> {
1250        if matches!(self, Self::Table(table) if table.collapses_borders()) {
1251            // https://drafts.csswg.org/css-tables/#collapsed-style-overrides
1252            // > The padding of the table-root is ignored (as if it was set to 0px).
1253            return LogicalSides::zero();
1254        }
1255        let padding = self.style().get_padding().clone();
1256        LogicalSides::from_physical(
1257            &PhysicalSides::new(
1258                padding.padding_top.0,
1259                padding.padding_right.0,
1260                padding.padding_bottom.0,
1261                padding.padding_left.0,
1262            ),
1263            containing_block_writing_mode,
1264        )
1265    }
1266
1267    pub(crate) fn border_width(
1268        &self,
1269        containing_block_writing_mode: WritingMode,
1270    ) -> LogicalSides<Au> {
1271        let border_width = match self {
1272            // For tables in collapsed-borders mode we halve the border widths, because
1273            // > in this model, the width of the table includes half the table border.
1274            // https://www.w3.org/TR/CSS22/tables.html#collapsing-borders
1275            Self::Table(table) if table.collapses_borders() => table
1276                .halved_collapsed_border_widths()
1277                .to_physical(self.style().writing_mode),
1278            _ => {
1279                let border = self.style().get_border();
1280                let resolve = |width: &BorderSideWidth, style: BorderStyle| {
1281                    if style.none_or_hidden() {
1282                        Au::zero()
1283                    } else {
1284                        width.0
1285                    }
1286                };
1287                PhysicalSides::new(
1288                    resolve(&border.border_top_width, border.border_top_style),
1289                    resolve(&border.border_right_width, border.border_right_style),
1290                    resolve(&border.border_bottom_width, border.border_bottom_style),
1291                    resolve(&border.border_left_width, border.border_left_style),
1292                )
1293            },
1294        };
1295        LogicalSides::from_physical(&border_width, containing_block_writing_mode)
1296    }
1297}
1298
1299impl From<stylo::Display> for Display {
1300    fn from(packed: stylo::Display) -> Self {
1301        let outside = packed.outside();
1302        let inside = packed.inside();
1303
1304        let outside = match outside {
1305            stylo::DisplayOutside::Block => DisplayOutside::Block,
1306            stylo::DisplayOutside::Inline => DisplayOutside::Inline,
1307            stylo::DisplayOutside::TableCaption => {
1308                return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(
1309                    DisplayLayoutInternal::TableCaption,
1310                ));
1311            },
1312            stylo::DisplayOutside::InternalTable => {
1313                let internal = match inside {
1314                    stylo::DisplayInside::TableRowGroup => DisplayLayoutInternal::TableRowGroup,
1315                    stylo::DisplayInside::TableColumn => DisplayLayoutInternal::TableColumn,
1316                    stylo::DisplayInside::TableColumnGroup => {
1317                        DisplayLayoutInternal::TableColumnGroup
1318                    },
1319                    stylo::DisplayInside::TableHeaderGroup => {
1320                        DisplayLayoutInternal::TableHeaderGroup
1321                    },
1322                    stylo::DisplayInside::TableFooterGroup => {
1323                        DisplayLayoutInternal::TableFooterGroup
1324                    },
1325                    stylo::DisplayInside::TableRow => DisplayLayoutInternal::TableRow,
1326                    stylo::DisplayInside::TableCell => DisplayLayoutInternal::TableCell,
1327                    _ => unreachable!("Non-internal DisplayInside found"),
1328                };
1329                return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(internal));
1330            },
1331            // This should not be a value of DisplayInside, but oh well
1332            // special-case display: contents because we still want it to work despite the early return
1333            stylo::DisplayOutside::None if inside == stylo::DisplayInside::Contents => {
1334                return Display::Contents;
1335            },
1336            stylo::DisplayOutside::None => return Display::None,
1337        };
1338
1339        let inside = match inside {
1340            stylo::DisplayInside::Flow => DisplayInside::Flow {
1341                is_list_item: packed.is_list_item(),
1342            },
1343            stylo::DisplayInside::FlowRoot => DisplayInside::FlowRoot {
1344                is_list_item: packed.is_list_item(),
1345            },
1346            stylo::DisplayInside::Flex => DisplayInside::Flex,
1347            stylo::DisplayInside::Grid => DisplayInside::Grid,
1348            stylo::DisplayInside::GridLanes => unreachable!("Should be disabled behind a pref"),
1349            stylo::DisplayInside::Table => DisplayInside::Table,
1350
1351            // These should not be values of DisplayInside, but oh well
1352            stylo::DisplayInside::None => return Display::None,
1353            stylo::DisplayInside::Contents => return Display::Contents,
1354
1355            stylo::DisplayInside::TableRowGroup |
1356            stylo::DisplayInside::TableColumn |
1357            stylo::DisplayInside::TableColumnGroup |
1358            stylo::DisplayInside::TableHeaderGroup |
1359            stylo::DisplayInside::TableFooterGroup |
1360            stylo::DisplayInside::TableRow |
1361            stylo::DisplayInside::TableCell => unreachable!("Internal DisplayInside found"),
1362        };
1363        Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside })
1364    }
1365}
1366
1367pub(crate) trait Clamp: Sized {
1368    fn clamp_below_max(self, max: Option<Self>) -> Self;
1369    fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self;
1370}
1371
1372impl Clamp for Au {
1373    fn clamp_below_max(self, max: Option<Self>) -> Self {
1374        match max {
1375            None => self,
1376            Some(max) => self.min(max),
1377        }
1378    }
1379
1380    fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self {
1381        self.clamp_below_max(max).max(min)
1382    }
1383}
1384
1385pub(crate) trait TransformExt {
1386    fn change_basis(&self, x: f32, y: f32, z: f32) -> Self;
1387}
1388
1389impl TransformExt for LayoutTransform {
1390    /// <https://drafts.csswg.org/css-transforms/#transformation-matrix-computation>
1391    fn change_basis(&self, x: f32, y: f32, z: f32) -> Self {
1392        let pre_translation = Self::translation(x, y, z);
1393        let post_translation = Self::translation(-x, -y, -z);
1394        post_translation.then(self).then(&pre_translation)
1395    }
1396}