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