Skip to main content

layout/
style_ext.rs

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