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