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