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