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