layout/
style_ext.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use app_units::Au;
6use layout_api::AxesOverflow;
7use malloc_size_of_derive::MallocSizeOf;
8use style::Zero;
9use style::color::AbsoluteColor;
10use style::computed_values::direction::T as Direction;
11use style::computed_values::isolation::T as ComputedIsolation;
12use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
13use style::computed_values::position::T as ComputedPosition;
14use style::computed_values::transform_style::T as ComputedTransformStyle;
15use style::computed_values::unicode_bidi::T as UnicodeBidi;
16use style::logical_geometry::{Direction as AxisDirection, PhysicalSide, WritingMode};
17use style::properties::ComputedValues;
18use style::properties::longhands::backface_visibility::computed_value::T as BackfaceVisiblity;
19use style::properties::longhands::box_sizing::computed_value::T as BoxSizing;
20use style::properties::longhands::column_span::computed_value::T as ColumnSpan;
21use style::properties::style_structs::Border;
22use style::servo::selector_parser::PseudoElement;
23use style::values::CSSFloat;
24use style::values::computed::basic_shape::ClipPath;
25use style::values::computed::image::Image as ComputedImageLayer;
26use style::values::computed::{
27    BorderStyle, Color, Inset, ItemPlacement, LengthPercentage, Margin, SelfAlignment,
28};
29use style::values::generics::box_::Perspective;
30use style::values::generics::position::{GenericAspectRatio, PreferredRatio};
31use style::values::generics::transform::{GenericRotate, GenericScale, GenericTranslate};
32use style::values::specified::align::AlignFlags;
33use style::values::specified::{Overflow, WillChangeBits, box_ as stylo};
34use unicode_bidi::Level;
35use webrender_api as wr;
36use webrender_api::units::LayoutTransform;
37
38use crate::dom_traversal::Contents;
39use crate::fragment_tree::FragmentFlags;
40use crate::geom::{
41    AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides,
42    PhysicalSize,
43};
44use crate::sizing::{Size, Sizes};
45use crate::table::TableLayoutStyle;
46use crate::{ContainingBlock, IndefiniteContainingBlock};
47
48#[derive(Clone, Copy, Eq, PartialEq)]
49pub(crate) enum Display {
50    None,
51    Contents,
52    GeneratingBox(DisplayGeneratingBox),
53}
54
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub(crate) enum DisplayGeneratingBox {
57    OutsideInside {
58        outside: DisplayOutside,
59        inside: DisplayInside,
60    },
61    /// <https://drafts.csswg.org/css-display-3/#layout-specific-display>
62    LayoutInternal(DisplayLayoutInternal),
63}
64impl DisplayGeneratingBox {
65    pub(crate) fn display_inside(&self) -> DisplayInside {
66        match *self {
67            DisplayGeneratingBox::OutsideInside { inside, .. } => inside,
68            DisplayGeneratingBox::LayoutInternal(layout_internal) => {
69                layout_internal.display_inside()
70            },
71        }
72    }
73
74    pub(crate) fn used_value_for_contents(&self, contents: &Contents) -> Self {
75        // From <https://www.w3.org/TR/css-display-3/#layout-specific-display>:
76        // > When the display property of a replaced element computes to one of
77        // > the layout-internal values, it is handled as having a used value of
78        // > inline.
79        if matches!(self, Self::LayoutInternal(_)) && contents.is_replaced() {
80            Self::OutsideInside {
81                outside: DisplayOutside::Inline,
82                inside: DisplayInside::Flow {
83                    is_list_item: false,
84                },
85            }
86        } else if matches!(contents, Contents::Widget(_)) {
87            // If it's a widget, make sure the display-inside is flow-root.
88            // <https://html.spec.whatwg.org/multipage/#form-controls>
89            // TODO: Do we want flow-root, or just an independent formatting context?
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: ItemPlacement,
357        resolved_normal_value: AlignFlags,
358    ) -> SelfAlignment;
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.intersects(FragmentFlags::IS_REPLACED | FragmentFlags::IS_WIDGET)
523    }
524
525    /// Returns true if this is a transformable element.
526    fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool {
527        // "A transformable element is an element in one of these categories:
528        //   * all elements whose layout is governed by the CSS box model except for
529        //     non-replaced inline boxes, table-column boxes, and table-column-group
530        //     boxes,
531        //   * all SVG paint server elements, the clipPath element  and SVG renderable
532        //     elements with the exception of any descendant element of text content
533        //     elements."
534        // <https://drafts.csswg.org/css-transforms/#transformable-element>
535        // TODO: check for all cases listed in the above spec.
536        !self.is_inline_box(fragment_flags)
537    }
538
539    /// Returns true if this style has a transform or perspective property set.
540    fn has_transform_or_perspective_style(&self) -> bool {
541        !self.get_box().transform.0.is_empty() ||
542            self.get_box().scale != GenericScale::None ||
543            self.get_box().rotate != GenericRotate::None ||
544            self.get_box().translate != GenericTranslate::None ||
545            self.get_box().perspective != Perspective::None
546    }
547
548    /// Returns true if this style has a transform or perspective property set, and
549    /// it applies to this element.
550    #[inline]
551    fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
552        self.is_transformable(fragment_flags) && self.has_transform_or_perspective_style()
553    }
554
555    /// Whether the `z-index` property applies to this fragment.
556    fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool {
557        // As per CSS 2 § 9.9.1, `z-index` applies to positioned elements.
558        // <http://www.w3.org/TR/CSS2/visuren.html#z-index>
559        if self.get_box().position != ComputedPosition::Static {
560            return true;
561        }
562        // More modern specs also apply it to flex and grid items.
563        // - From <https://www.w3.org/TR/css-flexbox-1/#painting>:
564        //   > Flex items paint exactly the same as inline blocks [CSS2], except that order-modified
565        //   > document order is used in place of raw document order, and z-index values other than auto
566        //   > create a stacking context even if position is static (behaving exactly as if position
567        //   > were relative).
568        // - From <https://drafts.csswg.org/css-flexbox/#painting>:
569        //   > The painting order of grid items is exactly the same as inline blocks [CSS2], except that
570        //   > order-modified document order is used in place of raw document order, and z-index values
571        //   > other than auto create a stacking context even if position is static (behaving exactly
572        //   > as if position were relative).
573        fragment_flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
574    }
575
576    /// Get the effective z-index of this fragment. Z-indices only apply to positioned elements
577    /// per CSS 2 9.9.1 (<http://www.w3.org/TR/CSS2/visuren.html#z-index>), so this value may differ
578    /// from the value specified in the style.
579    fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32 {
580        if self.z_index_applies(fragment_flags) {
581            self.get_position().z_index.integer_or(0)
582        } else {
583            0
584        }
585    }
586
587    /// Get the effective overflow of this box. The property only applies to block containers,
588    /// flex containers, and grid containers. And some box types only accept a few values.
589    /// <https://www.w3.org/TR/css-overflow-3/#overflow-control>
590    fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow {
591        // https://www.w3.org/TR/css-overflow-3/#overflow-propagation
592        // The element from which the value is propagated must then have a used overflow value of visible.
593        if fragment_flags.contains(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT) {
594            return AxesOverflow::default();
595        }
596
597        let mut overflow = AxesOverflow::from(self);
598
599        // From <https://www.w3.org/TR/css-overflow-4/#overflow-control>:
600        // "On replaced elements, the used values of all computed values other than visible is clip."
601        if fragment_flags.contains(FragmentFlags::IS_REPLACED) {
602            if overflow.x != Overflow::Visible {
603                overflow.x = Overflow::Clip;
604            }
605            if overflow.y != Overflow::Visible {
606                overflow.y = Overflow::Clip;
607            }
608            return overflow;
609        }
610
611        let ignores_overflow = match self.get_box().display.inside() {
612            // <https://drafts.csswg.org/css-overflow-3/#overflow-control>
613            // `overflow` doesn't apply to inline boxes.
614            stylo::DisplayInside::Flow => self.is_inline_box(fragment_flags),
615
616            // According to <https://drafts.csswg.org/css-tables/#global-style-overrides>,
617            // - overflow applies to table-wrapper boxes and not to table grid boxes.
618            //   That's what Blink and WebKit do, however Firefox matches a CSSWG resolution that says
619            //   the opposite: <https://lists.w3.org/Archives/Public/www-style/2012Aug/0298.html>
620            //   Due to the way that we implement table-wrapper boxes, it's easier to align with Firefox.
621            // - Tables ignore overflow values different than visible, clip and hidden.
622            //   This affects both axes, to ensure they have the same scrollability.
623            stylo::DisplayInside::Table => {
624                !matches!(self.pseudo(), Some(PseudoElement::ServoTableGrid)) ||
625                    matches!(overflow.x, Overflow::Auto | Overflow::Scroll) ||
626                    matches!(overflow.y, Overflow::Auto | Overflow::Scroll)
627            },
628
629            // <https://drafts.csswg.org/css-tables/#global-style-overrides>
630            // Table-track and table-track-group boxes ignore overflow.
631            stylo::DisplayInside::TableColumn |
632            stylo::DisplayInside::TableColumnGroup |
633            stylo::DisplayInside::TableRow |
634            stylo::DisplayInside::TableRowGroup |
635            stylo::DisplayInside::TableHeaderGroup |
636            stylo::DisplayInside::TableFooterGroup => true,
637
638            _ => false,
639        };
640        if ignores_overflow {
641            return AxesOverflow::default();
642        }
643
644        overflow
645    }
646
647    /// Return true if this style is a normal block and establishes
648    /// a new block formatting context.
649    ///
650    /// NOTE: This should be kept in sync with the checks in `impl
651    /// TElement::compute_layout_damage` for `ServoLayoutElement` in
652    /// `components/script/layout_dom/element.rs`.
653    fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool {
654        if self.establishes_scroll_container(fragment_flags) {
655            return true;
656        }
657
658        if self.get_column().is_multicol() {
659            return true;
660        }
661
662        if self.get_column().column_span == ColumnSpan::All {
663            return true;
664        }
665
666        // Per <https://drafts.csswg.org/css-align/#distribution-block>:
667        // Block containers with an `align-content` value that is not `normal` should
668        // form an independent block formatting context. This should really only happen
669        // for block containers, but we do not support subgrid containers yet which is the
670        // only other case.
671        if self.get_position().align_content.primary() != AlignFlags::NORMAL {
672            return true;
673        }
674
675        // TODO: We need to handle CSS Contain here.
676        false
677    }
678
679    /// Whether or not the `overflow` value of this style establishes a scroll container.
680    fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool {
681        // Checking one axis suffices, because the computed value ensures that
682        // either both axes are scrollable, or none is scrollable.
683        self.effective_overflow(fragment_flags).x.is_scrollable()
684    }
685
686    /// Returns true if this fragment establishes a new stacking context and false otherwise.
687    fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool {
688        // From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
689        // > If any non-initial value of a property would create a stacking context on the element,
690        // > specifying that property in will-change must create a stacking context on the element.
691        let will_change_bits = self.clone_will_change().bits;
692        if will_change_bits
693            .intersects(WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::OPACITY)
694        {
695            return true;
696        }
697
698        // From <https://www.w3.org/TR/CSS2/visuren.html#z-index>, values different than `auto`
699        // make the box establish a stacking context.
700        if self.z_index_applies(fragment_flags) &&
701            (!self.get_position().z_index.is_auto() ||
702                will_change_bits.intersects(WillChangeBits::Z_INDEX))
703        {
704            return true;
705        }
706
707        // Fixed position and sticky position always create stacking contexts.
708        // Note `will-change: position` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
709        if matches!(
710            self.get_box().position,
711            ComputedPosition::Fixed | ComputedPosition::Sticky
712        ) {
713            return true;
714        }
715
716        // From <https://www.w3.org/TR/css-transforms-1/#transform-rendering>
717        // > For elements whose layout is governed by the CSS box model, any value other than
718        // > `none` for the `transform` property results in the creation of a stacking context.
719        //
720        // From <https://www.w3.org/TR/css-transforms-2/#individual-transforms>
721        // > all other values […] create a stacking context and containing block for all
722        // > descendants, per usual for transforms.
723        //
724        // From <https://www.w3.org/TR/css-transforms-2/#perspective-property>
725        // > any value other than none establishes a stacking context.
726        //
727        // From <https://www.w3.org/TR/css-transforms-2/#transform-style-property>
728        // > A computed value of `preserve-3d` for `transform-style` on a transformable element
729        // > establishes both a stacking context and a containing block for all descendants.
730        if self.is_transformable(fragment_flags) &&
731            (self.has_transform_or_perspective_style() ||
732                self.get_box().transform_style == ComputedTransformStyle::Preserve3d ||
733                will_change_bits
734                    .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
735        {
736            return true;
737        }
738
739        // From <https://www.w3.org/TR/css-color-3/#transparency>
740        // > implementations must create a new stacking context for any element with opacity less than 1.
741        // Note `will-change: opacity` is handled above by `WillChangeBits::OPACITY`.
742        let effects = self.get_effects();
743        if effects.opacity != 1.0 {
744            return true;
745        }
746
747        // From <https://www.w3.org/TR/filter-effects-1/#FilterProperty>
748        // > A computed value of other than `none` results in the creation of a stacking context
749        // Note `will-change: filter` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
750        if !effects.filter.0.is_empty() {
751            return true;
752        }
753
754        // From <https://www.w3.org/TR/compositing-1/#mix-blend-mode>
755        // > Applying a blendmode other than `normal` to the element must establish a new stacking context
756        // Note `will-change: mix-blend-mode` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
757        if effects.mix_blend_mode != ComputedMixBlendMode::Normal {
758            return true;
759        }
760
761        // From <https://www.w3.org/TR/css-masking-1/#the-clip-path>
762        // > A computed value of other than `none` results in the creation of a stacking context.
763        // Note `will-change: clip-path` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
764        if self.get_svg().clip_path != ClipPath::None {
765            return true;
766        }
767
768        // From <https://www.w3.org/TR/compositing-1/#isolation>
769        // > For CSS, setting `isolation` to `isolate` will turn the element into a stacking context.
770        // Note `will-change: isolation` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
771        if self.get_box().isolation == ComputedIsolation::Isolate {
772            return true;
773        }
774
775        // From https://www.w3.org/TR/CSS22/visuren.html#z-index:
776        // > The root element forms the root stacking context.
777        if fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) {
778            return true;
779        }
780
781        // TODO: We need to handle CSS Contain here.
782        false
783    }
784
785    /// Returns true if this style establishes a containing block for absolute
786    /// descendants (`position: absolute`). If this style happens to establish a
787    /// containing block for “all descendants” (ie including `position: fixed`
788    /// descendants) this method will return true, but a true return value does
789    /// not imply that the style establishes a containing block for all descendants.
790    /// Use `establishes_containing_block_for_all_descendants()` instead.
791    fn establishes_containing_block_for_absolute_descendants(
792        &self,
793        fragment_flags: FragmentFlags,
794    ) -> bool {
795        if self.establishes_containing_block_for_all_descendants(fragment_flags) {
796            return true;
797        }
798
799        // From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
800        // > If any non-initial value of a property would cause the element to
801        // > generate a containing block for absolutely positioned elements, specifying that property in
802        // > will-change must cause the element to generate a containing block for absolutely positioned elements.
803        if self
804            .clone_will_change()
805            .bits
806            .intersects(WillChangeBits::POSITION)
807        {
808            return true;
809        }
810
811        self.clone_position() != ComputedPosition::Static
812    }
813
814    /// Returns true if this style establishes a containing block for
815    /// all descendants, including fixed descendants (`position: fixed`).
816    /// Note that this also implies that it establishes a containing block
817    /// for absolute descendants (`position: absolute`).
818    fn establishes_containing_block_for_all_descendants(
819        &self,
820        fragment_flags: FragmentFlags,
821    ) -> bool {
822        // From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
823        // > If any non-initial value of a property would cause the element to generate a
824        // > containing block for fixed positioned elements, specifying that property in will-change
825        // > must cause the element to generate a containing block for fixed positioned elements.
826        let will_change_bits = self.clone_will_change().bits;
827
828        // From <https://drafts.csswg.org/css-transforms-1/#transform-rendering>:
829        // > any value other than `none` for the `transform` property also causes the element
830        // > to establish a containing block for all descendants.
831        //
832        // From <https://www.w3.org/TR/css-transforms-2/#individual-transforms>
833        // > all other values […] create a stacking context and containing block for all
834        // > descendants, per usual for transforms.
835        //
836        // From <https://drafts.csswg.org/css-transforms-2/#perspective-property>:
837        // > The use of this property with any value other than `none` […] establishes a
838        // > containing block for all descendants, just like the `transform` property does.
839        //
840        // From <https://drafts.csswg.org/css-transforms-2/#transform-style-property>:
841        // > A computed value of `preserve-3d` for `transform-style` on a transformable element
842        // > establishes both a stacking context and a containing block for all descendants.
843        if self.is_transformable(fragment_flags) &&
844            (self.has_transform_or_perspective_style() ||
845                self.get_box().transform_style == ComputedTransformStyle::Preserve3d ||
846                will_change_bits
847                    .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
848        {
849            return true;
850        }
851
852        // From <https://www.w3.org/TR/filter-effects-1/#propdef-filter>:
853        // > A value other than none for the filter property results in the creation of a containing
854        // > block for absolute and fixed positioned descendants unless the element it applies to is
855        // > a document root element in the current browsing context.
856        if !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) &&
857            (!self.get_effects().filter.0.is_empty() ||
858                will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG))
859        {
860            return true;
861        }
862
863        // TODO: We need to handle CSS Contain here.
864        false
865    }
866
867    /// Resolve the preferred aspect ratio according to the given natural aspect
868    /// ratio and the `aspect-ratio` property.
869    /// See <https://drafts.csswg.org/css-sizing-4/#aspect-ratio>.
870    fn preferred_aspect_ratio(
871        &self,
872        natural_aspect_ratio: Option<CSSFloat>,
873        padding_border_sums: &LogicalVec2<Au>,
874    ) -> Option<AspectRatio> {
875        let GenericAspectRatio {
876            auto,
877            ratio: mut preferred_ratio,
878        } = self.clone_aspect_ratio();
879
880        // For all cases where a ratio is specified:
881        // "If the <ratio> is degenerate, the property instead behaves as auto."
882        if matches!(preferred_ratio, PreferredRatio::Ratio(ratio) if ratio.is_degenerate()) {
883            preferred_ratio = PreferredRatio::None;
884        }
885
886        let to_logical_ratio = |physical_ratio| {
887            if self.writing_mode.is_horizontal() {
888                physical_ratio
889            } else {
890                1.0 / physical_ratio
891            }
892        };
893
894        match (auto, preferred_ratio) {
895            // The value `auto`. Either the ratio was not specified, or was
896            // degenerate and set to PreferredRatio::None above.
897            //
898            // "Replaced elements with a natural aspect ratio use that aspect
899            // ratio; otherwise the box has no preferred aspect ratio. Size
900            // calculations involving the aspect ratio work with the content box
901            // dimensions always."
902            (_, PreferredRatio::None) => natural_aspect_ratio
903                .map(to_logical_ratio)
904                .map(AspectRatio::from_logical_content_ratio),
905            // "If both auto and a <ratio> are specified together, the preferred
906            // aspect ratio is the specified ratio of width / height unless it
907            // is a replaced element with a natural aspect ratio, in which case
908            // that aspect ratio is used instead. In all cases, size
909            // calculations involving the aspect ratio work with the content box
910            // dimensions always."
911            (true, PreferredRatio::Ratio(preferred_ratio)) => Some({
912                let physical_ratio = natural_aspect_ratio
913                    .unwrap_or_else(|| (preferred_ratio.0).0 / (preferred_ratio.1).0);
914                AspectRatio::from_logical_content_ratio(to_logical_ratio(physical_ratio))
915            }),
916
917            // "The box’s preferred aspect ratio is the specified ratio of width
918            // / height. Size calculations involving the aspect ratio work with
919            // the dimensions of the box specified by box-sizing."
920            (false, PreferredRatio::Ratio(preferred_ratio)) => {
921                // If the `box-sizing` is `border-box`, use the padding and
922                // border when calculating the aspect ratio.
923                let box_sizing_adjustment = match self.clone_box_sizing() {
924                    BoxSizing::ContentBox => LogicalVec2::zero(),
925                    BoxSizing::BorderBox => *padding_border_sums,
926                };
927                Some(AspectRatio {
928                    i_over_b: to_logical_ratio((preferred_ratio.0).0 / (preferred_ratio.1).0),
929                    box_sizing_adjustment,
930                })
931            },
932        }
933    }
934
935    /// Whether or not this style specifies a non-transparent background.
936    fn background_is_transparent(&self) -> bool {
937        let background = self.get_background();
938        let color = self.resolve_color(&background.background_color);
939        color.alpha == 0.0 &&
940            background
941                .background_image
942                .0
943                .iter()
944                .all(|layer| matches!(layer, ComputedImageLayer::None))
945    }
946
947    /// Generate appropriate WebRender `PrimitiveFlags` that should be used
948    /// for display items generated by the `Fragment` which owns this style.
949    fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags {
950        match self.get_box().backface_visibility {
951            BackfaceVisiblity::Visible => wr::PrimitiveFlags::default(),
952            BackfaceVisiblity::Hidden => wr::PrimitiveFlags::empty(),
953        }
954    }
955
956    /// If the 'unicode-bidi' property has a value other than 'normal', return the bidi control codes
957    /// to inject before and after the text content of the element.
958    /// See the table in <http://dev.w3.org/csswg/css-writing-modes/#unicode-bidi>.
959    fn bidi_control_chars(&self) -> (&'static str, &'static str) {
960        match (
961            self.get_text().unicode_bidi,
962            self.get_inherited_box().direction,
963        ) {
964            (UnicodeBidi::Normal, _) => ("", ""),
965            (UnicodeBidi::Embed, Direction::Ltr) => ("\u{202a}", "\u{202c}"),
966            (UnicodeBidi::Embed, Direction::Rtl) => ("\u{202b}", "\u{202c}"),
967            (UnicodeBidi::Isolate, Direction::Ltr) => ("\u{2066}", "\u{2069}"),
968            (UnicodeBidi::Isolate, Direction::Rtl) => ("\u{2067}", "\u{2069}"),
969            (UnicodeBidi::BidiOverride, Direction::Ltr) => ("\u{202d}", "\u{202c}"),
970            (UnicodeBidi::BidiOverride, Direction::Rtl) => ("\u{202e}", "\u{202c}"),
971            (UnicodeBidi::IsolateOverride, Direction::Ltr) => {
972                ("\u{2068}\u{202d}", "\u{202c}\u{2069}")
973            },
974            (UnicodeBidi::IsolateOverride, Direction::Rtl) => {
975                ("\u{2068}\u{202e}", "\u{202c}\u{2069}")
976            },
977            (UnicodeBidi::Plaintext, _) => ("\u{2068}", "\u{2069}"),
978        }
979    }
980
981    fn resolve_align_self(
982        &self,
983        resolved_auto_value: ItemPlacement,
984        resolved_normal_value: AlignFlags,
985    ) -> SelfAlignment {
986        SelfAlignment(match self.clone_align_self().0 {
987            AlignFlags::AUTO => resolved_auto_value.0,
988            AlignFlags::NORMAL => resolved_normal_value,
989            value => value,
990        })
991    }
992
993    fn depends_on_block_constraints_due_to_relative_positioning(
994        &self,
995        writing_mode: WritingMode,
996    ) -> bool {
997        if !matches!(
998            self.get_box().position,
999            ComputedPosition::Relative | ComputedPosition::Sticky
1000        ) {
1001            return false;
1002        }
1003        let box_offsets = self.box_offsets(writing_mode);
1004        let has_percentage = |offset: LengthPercentageOrAuto<'_>| {
1005            offset
1006                .non_auto()
1007                .is_some_and(LengthPercentage::has_percentage)
1008        };
1009        has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end)
1010    }
1011
1012    // <https://drafts.csswg.org/cssom-view/#overflow-directions>
1013    fn overflow_direction(&self) -> OverflowDirection {
1014        let inline_end_direction = self.writing_mode.inline_end_physical_side();
1015        let block_end_direction = self.writing_mode.block_end_physical_side();
1016
1017        let rightward = inline_end_direction == PhysicalSide::Right ||
1018            block_end_direction == PhysicalSide::Right;
1019        let downward = inline_end_direction == PhysicalSide::Bottom ||
1020            block_end_direction == PhysicalSide::Bottom;
1021
1022        // TODO(stevennovaryo): We should consider the flex-container's CSS (e.g. flow-direction: column-reverse).
1023        OverflowDirection {
1024            rightward,
1025            downward,
1026        }
1027    }
1028
1029    /// The default bidirectional embedding level for the writing mode of this style.
1030    ///
1031    /// Returns bidi level 0 if the mode is LTR, or 1 otherwise.
1032    fn to_bidi_level(&self) -> Level {
1033        if self.writing_mode.is_bidi_ltr() {
1034            Level::ltr()
1035        } else {
1036            Level::rtl()
1037        }
1038    }
1039}
1040
1041pub(crate) enum LayoutStyle<'a> {
1042    Default(&'a ComputedValues),
1043    Table(TableLayoutStyle<'a>),
1044}
1045
1046impl LayoutStyle<'_> {
1047    #[inline]
1048    pub(crate) fn style(&self) -> &ComputedValues {
1049        match self {
1050            Self::Default(style) => style,
1051            Self::Table(table) => table.style(),
1052        }
1053    }
1054
1055    #[inline]
1056    pub(crate) fn is_table(&self) -> bool {
1057        matches!(self, Self::Table(_))
1058    }
1059
1060    pub(crate) fn content_box_sizes_and_padding_border_margin(
1061        &self,
1062        containing_block: &IndefiniteContainingBlock,
1063    ) -> ContentBoxSizesAndPBM {
1064        // <https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution>
1065        // If max size properties or preferred size properties are set to a value containing
1066        // indefinite percentages, we treat the entire value as the initial value of the property.
1067        // However, for min size properties, as well as for margins and paddings,
1068        // we instead resolve indefinite percentages against zero.
1069        let containing_block_size_or_zero =
1070            containing_block.size.map(|value| value.unwrap_or_default());
1071        let writing_mode = containing_block.style.writing_mode;
1072        let pbm = self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1073            writing_mode,
1074            containing_block_size_or_zero.inline,
1075        );
1076        let style = self.style();
1077        let box_size = style.box_size(writing_mode);
1078        let min_size = style.min_box_size(writing_mode);
1079        let max_size = style.max_box_size(writing_mode);
1080        let preferred_size_computes_to_auto = box_size.map(|size| size.is_initial());
1081
1082        let depends_on_block_constraints = |size: &Size<LengthPercentage>| {
1083            match size {
1084                // fit-content is like clamp(min-content, stretch, max-content), but currently
1085                // min-content and max-content have the same behavior in the block axis,
1086                // so there is no dependency on block constraints.
1087                // TODO: for flex and grid layout, min-content and max-content should be different.
1088                // TODO: We are assuming that Size::Initial doesn't stretch. However, it may actually
1089                // stretch flex and grid items depending on the CSS Align properties, in that case
1090                // the caller needs to take care of it.
1091                Size::Stretch => true,
1092                Size::Numeric(length_percentage) => length_percentage.has_percentage(),
1093                _ => false,
1094            }
1095        };
1096        let depends_on_block_constraints = depends_on_block_constraints(&box_size.block) ||
1097            depends_on_block_constraints(&min_size.block) ||
1098            depends_on_block_constraints(&max_size.block) ||
1099            style.depends_on_block_constraints_due_to_relative_positioning(writing_mode);
1100
1101        let box_size = box_size.map_with(&containing_block.size, |size, basis| {
1102            size.resolve_percentages_for_preferred(*basis)
1103        });
1104        let content_box_size = style.content_box_size_for_box_size(box_size, &pbm);
1105        let min_size = min_size.percentages_relative_to_basis(&containing_block_size_or_zero);
1106        let content_min_box_size = style.content_min_box_size_for_min_size(min_size, &pbm);
1107        let max_size = max_size.map_with(&containing_block.size, |size, basis| {
1108            size.resolve_percentages_for_max(*basis)
1109        });
1110        let content_max_box_size = style.content_max_box_size_for_max_size(max_size, &pbm);
1111        ContentBoxSizesAndPBM {
1112            content_box_sizes: LogicalVec2 {
1113                block: Sizes::new(
1114                    content_box_size.block,
1115                    content_min_box_size.block,
1116                    content_max_box_size.block,
1117                ),
1118                inline: Sizes::new(
1119                    content_box_size.inline,
1120                    content_min_box_size.inline,
1121                    content_max_box_size.inline,
1122                ),
1123            },
1124            pbm,
1125            depends_on_block_constraints,
1126            preferred_size_computes_to_auto,
1127        }
1128    }
1129
1130    pub(crate) fn padding_border_margin(
1131        &self,
1132        containing_block: &ContainingBlock,
1133    ) -> PaddingBorderMargin {
1134        self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1135            containing_block.style.writing_mode,
1136            containing_block.size.inline,
1137        )
1138    }
1139
1140    pub(crate) fn padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1141        &self,
1142        writing_mode: WritingMode,
1143        containing_block_inline_size: Au,
1144    ) -> PaddingBorderMargin {
1145        let padding = self
1146            .padding(writing_mode)
1147            .percentages_relative_to(containing_block_inline_size);
1148        let style = self.style();
1149        let border = self.border_width(writing_mode);
1150        let margin = style
1151            .margin(writing_mode)
1152            .percentages_relative_to(containing_block_inline_size);
1153        PaddingBorderMargin {
1154            padding_border_sums: LogicalVec2 {
1155                inline: padding.inline_sum() + border.inline_sum(),
1156                block: padding.block_sum() + border.block_sum(),
1157            },
1158            padding,
1159            border,
1160            margin,
1161        }
1162    }
1163
1164    pub(crate) fn padding(
1165        &self,
1166        containing_block_writing_mode: WritingMode,
1167    ) -> LogicalSides<LengthPercentage> {
1168        if matches!(self, Self::Table(table) if table.collapses_borders()) {
1169            // https://drafts.csswg.org/css-tables/#collapsed-style-overrides
1170            // > The padding of the table-root is ignored (as if it was set to 0px).
1171            return LogicalSides::zero();
1172        }
1173        let padding = self.style().get_padding().clone();
1174        LogicalSides::from_physical(
1175            &PhysicalSides::new(
1176                padding.padding_top.0,
1177                padding.padding_right.0,
1178                padding.padding_bottom.0,
1179                padding.padding_left.0,
1180            ),
1181            containing_block_writing_mode,
1182        )
1183    }
1184
1185    pub(crate) fn border_width(
1186        &self,
1187        containing_block_writing_mode: WritingMode,
1188    ) -> LogicalSides<Au> {
1189        let border_width = match self {
1190            // For tables in collapsed-borders mode we halve the border widths, because
1191            // > in this model, the width of the table includes half the table border.
1192            // https://www.w3.org/TR/CSS22/tables.html#collapsing-borders
1193            Self::Table(table) if table.collapses_borders() => table
1194                .halved_collapsed_border_widths()
1195                .to_physical(self.style().writing_mode),
1196            _ => {
1197                let border = self.style().get_border();
1198                PhysicalSides::new(
1199                    border.border_top_width,
1200                    border.border_right_width,
1201                    border.border_bottom_width,
1202                    border.border_left_width,
1203                )
1204            },
1205        };
1206        LogicalSides::from_physical(&border_width, containing_block_writing_mode)
1207    }
1208}
1209
1210impl From<stylo::Display> for Display {
1211    fn from(packed: stylo::Display) -> Self {
1212        let outside = packed.outside();
1213        let inside = packed.inside();
1214
1215        let outside = match outside {
1216            stylo::DisplayOutside::Block => DisplayOutside::Block,
1217            stylo::DisplayOutside::Inline => DisplayOutside::Inline,
1218            stylo::DisplayOutside::TableCaption => {
1219                return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(
1220                    DisplayLayoutInternal::TableCaption,
1221                ));
1222            },
1223            stylo::DisplayOutside::InternalTable => {
1224                let internal = match inside {
1225                    stylo::DisplayInside::TableRowGroup => DisplayLayoutInternal::TableRowGroup,
1226                    stylo::DisplayInside::TableColumn => DisplayLayoutInternal::TableColumn,
1227                    stylo::DisplayInside::TableColumnGroup => {
1228                        DisplayLayoutInternal::TableColumnGroup
1229                    },
1230                    stylo::DisplayInside::TableHeaderGroup => {
1231                        DisplayLayoutInternal::TableHeaderGroup
1232                    },
1233                    stylo::DisplayInside::TableFooterGroup => {
1234                        DisplayLayoutInternal::TableFooterGroup
1235                    },
1236                    stylo::DisplayInside::TableRow => DisplayLayoutInternal::TableRow,
1237                    stylo::DisplayInside::TableCell => DisplayLayoutInternal::TableCell,
1238                    _ => unreachable!("Non-internal DisplayInside found"),
1239                };
1240                return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(internal));
1241            },
1242            // This should not be a value of DisplayInside, but oh well
1243            // special-case display: contents because we still want it to work despite the early return
1244            stylo::DisplayOutside::None if inside == stylo::DisplayInside::Contents => {
1245                return Display::Contents;
1246            },
1247            stylo::DisplayOutside::None => return Display::None,
1248        };
1249
1250        let inside = match packed.inside() {
1251            stylo::DisplayInside::Flow => DisplayInside::Flow {
1252                is_list_item: packed.is_list_item(),
1253            },
1254            stylo::DisplayInside::FlowRoot => DisplayInside::FlowRoot {
1255                is_list_item: packed.is_list_item(),
1256            },
1257            stylo::DisplayInside::Flex => DisplayInside::Flex,
1258            stylo::DisplayInside::Grid => DisplayInside::Grid,
1259
1260            // These should not be values of DisplayInside, but oh well
1261            stylo::DisplayInside::None => return Display::None,
1262            stylo::DisplayInside::Contents => return Display::Contents,
1263
1264            stylo::DisplayInside::Table => DisplayInside::Table,
1265            stylo::DisplayInside::TableRowGroup |
1266            stylo::DisplayInside::TableColumn |
1267            stylo::DisplayInside::TableColumnGroup |
1268            stylo::DisplayInside::TableHeaderGroup |
1269            stylo::DisplayInside::TableFooterGroup |
1270            stylo::DisplayInside::TableRow |
1271            stylo::DisplayInside::TableCell => unreachable!("Internal DisplayInside found"),
1272        };
1273        Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside })
1274    }
1275}
1276
1277pub(crate) trait Clamp: Sized {
1278    fn clamp_below_max(self, max: Option<Self>) -> Self;
1279    fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self;
1280}
1281
1282impl Clamp for Au {
1283    fn clamp_below_max(self, max: Option<Self>) -> Self {
1284        match max {
1285            None => self,
1286            Some(max) => self.min(max),
1287        }
1288    }
1289
1290    fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self {
1291        self.clamp_below_max(max).max(min)
1292    }
1293}
1294
1295pub(crate) trait TransformExt {
1296    fn change_basis(&self, x: f32, y: f32, z: f32) -> Self;
1297}
1298
1299impl TransformExt for LayoutTransform {
1300    /// <https://drafts.csswg.org/css-transforms/#transformation-matrix-computation>
1301    fn change_basis(&self, x: f32, y: f32, z: f32) -> Self {
1302        let pre_translation = Self::translation(x, y, z);
1303        let post_translation = Self::translation(-x, -y, -z);
1304        post_translation.then(self).then(&pre_translation)
1305    }
1306}