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::{LogicalAxis, 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, TextWrapMode, WhiteSpaceCollapse,
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: LogicalAxis,
222        ratio_determining_size: Au,
223    ) -> Au {
224        match ratio_dependent_axis {
225            // Calculate the inline size from the block size
226            LogicalAxis::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            LogicalAxis::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_or_extends_3d_rendering_context(&self, fragment_flags: FragmentFlags) -> bool;
356    fn establishes_containing_block_for_absolute_descendants(
357        &self,
358        fragment_flags: FragmentFlags,
359    ) -> bool;
360    fn establishes_containing_block_for_all_descendants(
361        &self,
362        fragment_flags: FragmentFlags,
363    ) -> bool;
364    fn preferred_aspect_ratio(
365        &self,
366        natural_aspect_ratio: Option<CSSFloat>,
367        padding_border_sums: &LogicalVec2<Au>,
368    ) -> Option<AspectRatio>;
369    fn background_is_transparent(&self) -> bool;
370    fn webrender_primitive_flags(&self) -> wr::PrimitiveFlags;
371    fn bidi_control_chars(&self) -> (&'static str, &'static str);
372    fn resolve_align_self(
373        &self,
374        resolved_auto_value: ItemPlacement,
375        resolved_normal_value: AlignFlags,
376    ) -> SelfAlignment;
377    fn depends_on_block_constraints_due_to_relative_positioning(
378        &self,
379        writing_mode: WritingMode,
380    ) -> bool;
381    fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool;
382    fn is_atomic_inline_level(&self, fragment_flags: FragmentFlags) -> bool;
383    fn overflow_direction(&self) -> OverflowDirection;
384    fn to_bidi_level(&self) -> Level;
385    /// Whether or not this style causes conditional hanging in inline layout
386    /// i.e. if it is `white-space-collapse: preserve` and `text-wrap-mode: wrap`.
387    fn white_space_may_hang_conditionally(&self) -> bool;
388}
389
390impl ComputedValuesExt for ComputedValues {
391    fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
392        fn convert(inset: &Inset) -> LengthPercentageOrAuto<'_> {
393            match inset {
394                Inset::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
395                Inset::Auto => LengthPercentageOrAuto::Auto,
396                Inset::AnchorFunction(_) => unreachable!("anchor() should be disabled"),
397                Inset::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
398                Inset::AnchorContainingCalcFunction(_) => {
399                    unreachable!("anchor() and anchor-size() should be disabled")
400                },
401            }
402        }
403        let position = self.get_position();
404        PhysicalSides::new(
405            convert(&position.top),
406            convert(&position.right),
407            convert(&position.bottom),
408            convert(&position.left),
409        )
410    }
411
412    fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>> {
413        LogicalSides::from_physical(&self.physical_box_offsets(), writing_mode)
414    }
415
416    fn box_size(
417        &self,
418        containing_block_writing_mode: WritingMode,
419    ) -> LogicalVec2<Size<LengthPercentage>> {
420        let position = self.get_position();
421        LogicalVec2::from_physical_size(
422            &PhysicalSize::new(position.get_width().into(), position.get_height().into()),
423            containing_block_writing_mode,
424        )
425    }
426
427    fn min_box_size(
428        &self,
429        containing_block_writing_mode: WritingMode,
430    ) -> LogicalVec2<Size<LengthPercentage>> {
431        let position = self.get_position();
432        LogicalVec2::from_physical_size(
433            &PhysicalSize::new(
434                position.get_min_width().into(),
435                position.get_min_height().into(),
436            ),
437            containing_block_writing_mode,
438        )
439    }
440
441    fn max_box_size(
442        &self,
443        containing_block_writing_mode: WritingMode,
444    ) -> LogicalVec2<Size<LengthPercentage>> {
445        let position = self.get_position();
446        LogicalVec2::from_physical_size(
447            &PhysicalSize::new(
448                position.get_max_width().into(),
449                position.get_max_height().into(),
450            ),
451            containing_block_writing_mode,
452        )
453    }
454
455    fn content_box_size_for_box_size(
456        &self,
457        box_size: LogicalVec2<Size<Au>>,
458        pbm: &PaddingBorderMargin,
459    ) -> LogicalVec2<Size<Au>> {
460        match self.get_position().box_sizing {
461            BoxSizing::ContentBox => box_size,
462            // These may be negative, but will later be clamped by `min-width`/`min-height`
463            // which is clamped to zero.
464            BoxSizing::BorderBox => box_size.map_inline_and_block_sizes(
465                |value| value - pbm.padding_border_sums.inline,
466                |value| value - pbm.padding_border_sums.block,
467            ),
468        }
469    }
470
471    fn content_min_box_size_for_min_size(
472        &self,
473        min_box_size: LogicalVec2<Size<Au>>,
474        pbm: &PaddingBorderMargin,
475    ) -> LogicalVec2<Size<Au>> {
476        match self.get_position().box_sizing {
477            BoxSizing::ContentBox => min_box_size,
478            // Clamp to zero to make sure the used size components are non-negative
479            BoxSizing::BorderBox => min_box_size.map_inline_and_block_sizes(
480                |value| Au::zero().max(value - pbm.padding_border_sums.inline),
481                |value| Au::zero().max(value - pbm.padding_border_sums.block),
482            ),
483        }
484    }
485
486    fn content_max_box_size_for_max_size(
487        &self,
488        max_box_size: LogicalVec2<Size<Au>>,
489        pbm: &PaddingBorderMargin,
490    ) -> LogicalVec2<Size<Au>> {
491        match self.get_position().box_sizing {
492            BoxSizing::ContentBox => max_box_size,
493            // This may be negative, but will later be clamped by `min-width`
494            // which itself is clamped to zero.
495            BoxSizing::BorderBox => max_box_size.map_inline_and_block_sizes(
496                |value| value - pbm.padding_border_sums.inline,
497                |value| value - pbm.padding_border_sums.block,
498            ),
499        }
500    }
501
502    fn border_style_color(
503        &self,
504        containing_block_writing_mode: WritingMode,
505    ) -> LogicalSides<BorderStyleColor> {
506        let current_color = self.get_color();
507        LogicalSides::from_physical(
508            &BorderStyleColor::from_border(self.get_border(), current_color),
509            containing_block_writing_mode,
510        )
511    }
512
513    fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
514        fn convert(inset: &Margin) -> LengthPercentageOrAuto<'_> {
515            match inset {
516                Margin::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
517                Margin::Auto => LengthPercentageOrAuto::Auto,
518                Margin::AnchorSizeFunction(_) | Margin::AnchorContainingCalcFunction(_) => {
519                    unreachable!("anchor-size() should be disabled")
520                },
521            }
522        }
523        let margin = self.get_margin();
524        PhysicalSides::new(
525            convert(&margin.margin_top),
526            convert(&margin.margin_right),
527            convert(&margin.margin_bottom),
528            convert(&margin.margin_left),
529        )
530    }
531
532    fn margin(
533        &self,
534        containing_block_writing_mode: WritingMode,
535    ) -> LogicalSides<LengthPercentageOrAuto<'_>> {
536        LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode)
537    }
538
539    fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool {
540        (self.get_box().display.is_inline_flow() &&
541            !fragment_flags.intersects(
542                FragmentFlags::IS_REPLACED |
543                    FragmentFlags::IS_WIDGET |
544                    FragmentFlags::IS_FLEX_OR_GRID_ITEM,
545            )) ||
546            matches!(self.pseudo(), Some(PseudoElement::FirstLetter))
547    }
548
549    fn is_atomic_inline_level(&self, fragment_flags: FragmentFlags) -> bool {
550        self.get_box().display.outside() == stylo::DisplayOutside::Inline &&
551            !self.is_inline_box(fragment_flags) &&
552            !fragment_flags.intersects(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
553    }
554
555    /// Returns true if this is a transformable element.
556    fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool {
557        // "A transformable element is an element in one of these categories:
558        //   * all elements whose layout is governed by the CSS box model except for
559        //     non-replaced inline boxes, table-column boxes, and table-column-group
560        //     boxes,
561        //   * all SVG paint server elements, the clipPath element  and SVG renderable
562        //     elements with the exception of any descendant element of text content
563        //     elements."
564        // <https://drafts.csswg.org/css-transforms/#transformable-element>
565        // TODO: check for all cases listed in the above spec.
566        !self.is_inline_box(fragment_flags)
567    }
568
569    /// Returns true if this style has a transform or perspective property set.
570    fn has_transform_or_perspective_style(&self) -> bool {
571        !self.get_box().transform.0.is_empty() ||
572            self.get_box().scale != GenericScale::None ||
573            self.get_box().rotate != GenericRotate::None ||
574            self.get_box().translate != GenericTranslate::None ||
575            self.get_box().perspective != Perspective::None
576    }
577
578    /// Returns true if this style has a transform or perspective property set, and
579    /// it applies to this element.
580    #[inline]
581    fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
582        self.is_transformable(fragment_flags) && self.has_transform_or_perspective_style()
583    }
584
585    /// Whether the `z-index` property applies to this fragment.
586    fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool {
587        // As per CSS 2 § 9.9.1, `z-index` applies to positioned elements.
588        // <http://www.w3.org/TR/CSS2/visuren.html#z-index>
589        if self.get_box().position != ComputedPosition::Static {
590            return true;
591        }
592        // More modern specs also apply it to flex and grid items.
593        // - From <https://www.w3.org/TR/css-flexbox-1/#painting>:
594        //   > Flex items paint exactly the same as inline blocks [CSS2], except that order-modified
595        //   > document order is used in place of raw document order, and z-index values other than auto
596        //   > create a stacking context even if position is static (behaving exactly as if position
597        //   > were relative).
598        // - From <https://drafts.csswg.org/css-flexbox/#painting>:
599        //   > The painting order of grid items is exactly the same as inline blocks [CSS2], except that
600        //   > order-modified document order is used in place of raw document order, and z-index values
601        //   > other than auto create a stacking context even if position is static (behaving exactly
602        //   > as if position were relative).
603        fragment_flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
604    }
605
606    /// Get the effective z-index of this fragment. Z-indices only apply to positioned elements
607    /// per CSS 2 9.9.1 (<http://www.w3.org/TR/CSS2/visuren.html#z-index>), so this value may differ
608    /// from the value specified in the style.
609    fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32 {
610        if self.z_index_applies(fragment_flags) {
611            self.get_position().z_index.integer_or(0)
612        } else {
613            0
614        }
615    }
616
617    /// Get the effective overflow of this box. The property only applies to block containers,
618    /// flex containers, and grid containers. And some box types only accept a few values.
619    /// <https://www.w3.org/TR/css-overflow-3/#overflow-control>
620    fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow {
621        // https://www.w3.org/TR/css-overflow-3/#overflow-propagation
622        // The element from which the value is propagated must then have a used overflow value of visible.
623        if fragment_flags.contains(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT) {
624            return AxesOverflow::default();
625        }
626
627        let mut overflow = AxesOverflow::from(self);
628
629        // From <https://www.w3.org/TR/css-overflow-4/#overflow-control>:
630        // "On replaced elements, the used values of all computed values other than visible is clip."
631        if fragment_flags.contains(FragmentFlags::IS_REPLACED) {
632            if overflow.x != Overflow::Visible {
633                overflow.x = Overflow::Clip;
634            }
635            if overflow.y != Overflow::Visible {
636                overflow.y = Overflow::Clip;
637            }
638            return overflow;
639        }
640
641        let ignores_overflow = match self.get_box().display.inside() {
642            // <https://drafts.csswg.org/css-overflow-3/#overflow-control>
643            // `overflow` doesn't apply to inline boxes.
644            stylo::DisplayInside::Flow => self.is_inline_box(fragment_flags),
645
646            // According to <https://drafts.csswg.org/css-tables/#global-style-overrides>,
647            // - overflow applies to table-wrapper boxes and not to table grid boxes.
648            //   That's what Blink and WebKit do, however Firefox matches a CSSWG resolution that says
649            //   the opposite: <https://lists.w3.org/Archives/Public/www-style/2012Aug/0298.html>
650            //   Due to the way that we implement table-wrapper boxes, it's easier to align with Firefox.
651            // - Tables ignore overflow values different than visible, clip and hidden.
652            //   This affects both axes, to ensure they have the same scrollability.
653            stylo::DisplayInside::Table => {
654                !matches!(self.pseudo(), Some(PseudoElement::ServoTableGrid)) ||
655                    matches!(overflow.x, Overflow::Auto | Overflow::Scroll) ||
656                    matches!(overflow.y, Overflow::Auto | Overflow::Scroll)
657            },
658
659            // <https://drafts.csswg.org/css-tables/#global-style-overrides>
660            // Table-track and table-track-group boxes ignore overflow.
661            stylo::DisplayInside::TableColumn |
662            stylo::DisplayInside::TableColumnGroup |
663            stylo::DisplayInside::TableRow |
664            stylo::DisplayInside::TableRowGroup |
665            stylo::DisplayInside::TableHeaderGroup |
666            stylo::DisplayInside::TableFooterGroup => true,
667
668            _ => false,
669        };
670        if ignores_overflow {
671            return AxesOverflow::default();
672        }
673
674        overflow
675    }
676
677    /// Get the used `transform-style` value according to the the rules in
678    /// <https://drafts.csswg.org/css-transforms/#grouping-property-values>.
679    fn used_transform_style(&self, fragment_flags: FragmentFlags) -> ComputedTransformStyle {
680        // The check for flat here is to avoid having to check all of the properties below when
681        // possible.
682        let box_style = self.get_box();
683        if box_style.transform_style == ComputedTransformStyle::Flat {
684            return ComputedTransformStyle::Flat;
685        }
686
687        // https://drafts.csswg.org/css-transforms-2/#grouping-property-values
688        //  * overflow: any value other than visible or clip.
689        //  * opacity: any value less than 1.
690        //  * filter: any value other than none.
691        //  * clip: any value other than auto.
692        //  * clip-path: any value other than none.
693        //  * isolation: used value of isolate.
694        //  * mask-image: any value other than none.
695        //  * mask-border-source: any value other than none.
696        //  * mix-blend-mode: any value other than normal.
697        //  * contain: paint and any other property/value combination that causes
698        //    paint containment. Note: this includes any property that affect the
699        //    used value of the contain property, such as content-visibility:
700        //    hidden.
701        //
702        // TODO: Support `mask-image`, `mask-border-source`, and `contain`.
703        let effects = self.get_effects();
704        let overflow = self.effective_overflow(fragment_flags);
705        if !matches!(overflow.x, Overflow::Visible | Overflow::Clip) ||
706            !matches!(overflow.y, Overflow::Visible | Overflow::Clip) ||
707            effects.opacity < 1.0 ||
708            !effects.filter.0.is_empty() ||
709            !effects.clip.is_auto() ||
710            self.get_svg().clip_path != ClipPath::None ||
711            self.get_box().isolation == ComputedIsolation::Isolate ||
712            effects.mix_blend_mode != ComputedMixBlendMode::Normal
713        {
714            return ComputedTransformStyle::Flat;
715        }
716
717        // Return the computed value if not overridden by the above exceptions
718        box_style.transform_style
719    }
720
721    /// Return true if this style is a normal block and establishes
722    /// a new block formatting context.
723    ///
724    /// NOTE: This should be kept in sync with the checks in `impl
725    /// TElement::compute_layout_damage` for `ServoLayoutElement` in
726    /// `components/script/layout_dom/element.rs`.
727    fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool {
728        if self.establishes_scroll_container(fragment_flags) {
729            return true;
730        }
731
732        if self.get_column().is_multicol() {
733            return true;
734        }
735
736        if self.get_column().column_span == ColumnSpan::All {
737            return true;
738        }
739
740        // Per <https://drafts.csswg.org/css-align/#distribution-block>:
741        // Block containers with an `align-content` value that is not `normal` should
742        // form an independent block formatting context. This should really only happen
743        // for block containers, but we do not support subgrid containers yet which is the
744        // only other case.
745        if self.get_position().align_content.primary() != AlignFlags::NORMAL {
746            return true;
747        }
748
749        // TODO: We need to handle CSS Contain here.
750        false
751    }
752
753    /// Whether or not the `overflow` value of this style establishes a scroll container.
754    fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool {
755        self.effective_overflow(fragment_flags)
756            .establishes_scroll_container()
757    }
758
759    /// Returns true if this fragment establishes or extends a 3D rendering context.
760    fn establishes_or_extends_3d_rendering_context(&self, fragment_flags: FragmentFlags) -> bool {
761        // > A 3D rendering context is established by a transformable element whose used
762        // > value for transform-style is preserve-3d and which itself is not part of a 3D
763        // > rendering context. An element that establishes a 3D rendering context also
764        // > participates in that context.
765        self.is_transformable(fragment_flags) &&
766            self.used_transform_style(fragment_flags) == ComputedTransformStyle::Preserve3d
767    }
768
769    /// Returns true if this fragment establishes a new stacking context and false otherwise.
770    fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool {
771        // From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
772        // > If any non-initial value of a property would create a stacking context on the element,
773        // > specifying that property in will-change must create a stacking context on the element.
774        let will_change_bits = self.get_will_change().bits;
775        if will_change_bits
776            .intersects(WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::OPACITY)
777        {
778            return true;
779        }
780
781        // From <https://www.w3.org/TR/CSS2/visuren.html#z-index>, values different than `auto`
782        // make the box establish a stacking context.
783        if self.z_index_applies(fragment_flags) &&
784            (!self.get_position().z_index.is_auto() ||
785                will_change_bits.intersects(WillChangeBits::Z_INDEX))
786        {
787            return true;
788        }
789
790        // Fixed position and sticky position always create stacking contexts.
791        // Note `will-change: position` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
792        if matches!(
793            self.get_box().position,
794            ComputedPosition::Fixed | ComputedPosition::Sticky
795        ) {
796            return true;
797        }
798
799        // From <https://www.w3.org/TR/css-transforms-1/#transform-rendering>
800        // > For elements whose layout is governed by the CSS box model, any value other than
801        // > `none` for the `transform` property results in the creation of a stacking context.
802        //
803        // From <https://www.w3.org/TR/css-transforms-2/#individual-transforms>
804        // > all other values […] create a stacking context and containing block for all
805        // > descendants, per usual for transforms.
806        //
807        // From <https://www.w3.org/TR/css-transforms-2/#perspective-property>
808        // > any value other than none establishes a stacking context.
809        //
810        // From <https://www.w3.org/TR/css-transforms-2/#transform-style-property>
811        // > A computed value of `preserve-3d` for `transform-style` on a transformable element
812        // > establishes both a stacking context and a containing block for all descendants.
813        if self.is_transformable(fragment_flags) &&
814            (self.has_transform_or_perspective_style() ||
815                self.used_transform_style(fragment_flags) ==
816                    ComputedTransformStyle::Preserve3d ||
817                will_change_bits
818                    .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
819        {
820            return true;
821        }
822
823        // From <https://www.w3.org/TR/css-color-3/#transparency>
824        // > implementations must create a new stacking context for any element with opacity less than 1.
825        // Note `will-change: opacity` is handled above by `WillChangeBits::OPACITY`.
826        let effects = self.get_effects();
827        if effects.opacity != 1.0 {
828            return true;
829        }
830
831        // From <https://www.w3.org/TR/filter-effects-1/#FilterProperty>
832        // > A computed value of other than `none` results in the creation of a stacking context
833        // Note `will-change: filter` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
834        if !effects.filter.0.is_empty() {
835            return true;
836        }
837
838        // From <https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty>
839        // > A computed value of other than none results in the creation of
840        // > both a stacking context [CSS21] and a Containing Block for
841        // > absolute and fixed position descendants, unless the element it
842        // > applies to is a document root element in the current browsing
843        // > context.
844        // Note `will-change: backdrop-filter` is handled above by
845        // `WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL`.
846        if !effects.backdrop_filter.0.is_empty() &&
847            !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT)
848        {
849            return true;
850        }
851
852        // From <https://www.w3.org/TR/compositing-1/#mix-blend-mode>
853        // > Applying a blendmode other than `normal` to the element must establish a new stacking context
854        // Note `will-change: mix-blend-mode` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
855        if effects.mix_blend_mode != ComputedMixBlendMode::Normal {
856            return true;
857        }
858
859        // From <https://www.w3.org/TR/css-masking-1/#the-clip-path>
860        // > A computed value of other than `none` results in the creation of a stacking context.
861        // Note `will-change: clip-path` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
862        if self.get_svg().clip_path != ClipPath::None {
863            return true;
864        }
865
866        // From <https://www.w3.org/TR/compositing-1/#isolation>
867        // > For CSS, setting `isolation` to `isolate` will turn the element into a stacking context.
868        // Note `will-change: isolation` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`.
869        if self.get_box().isolation == ComputedIsolation::Isolate {
870            return true;
871        }
872
873        // From https://www.w3.org/TR/CSS22/visuren.html#z-index:
874        // > The root element forms the root stacking context.
875        if fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) {
876            return true;
877        }
878
879        // TODO: We need to handle CSS Contain here.
880        false
881    }
882
883    /// Returns true if this style establishes a containing block for absolute
884    /// descendants (`position: absolute`). If this style happens to establish a
885    /// containing block for “all descendants” (ie including `position: fixed`
886    /// descendants) this method will return true, but a true return value does
887    /// not imply that the style establishes a containing block for all descendants.
888    /// Use `establishes_containing_block_for_all_descendants()` instead.
889    fn establishes_containing_block_for_absolute_descendants(
890        &self,
891        fragment_flags: FragmentFlags,
892    ) -> bool {
893        if self.establishes_containing_block_for_all_descendants(fragment_flags) {
894            return true;
895        }
896
897        // From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
898        // > If any non-initial value of a property would cause the element to
899        // > generate a containing block for absolutely positioned elements, specifying that property in
900        // > will-change must cause the element to generate a containing block for absolutely positioned elements.
901        if self
902            .get_will_change()
903            .bits
904            .intersects(WillChangeBits::POSITION)
905        {
906            return true;
907        }
908
909        self.get_box().position != ComputedPosition::Static
910    }
911
912    /// Returns true if this style establishes a containing block for
913    /// all descendants, including fixed descendants (`position: fixed`).
914    /// Note that this also implies that it establishes a containing block
915    /// for absolute descendants (`position: absolute`).
916    fn establishes_containing_block_for_all_descendants(
917        &self,
918        fragment_flags: FragmentFlags,
919    ) -> bool {
920        // From <https://www.w3.org/TR/css-will-change/#valdef-will-change-custom-ident>:
921        // > If any non-initial value of a property would cause the element to generate a
922        // > containing block for fixed positioned elements, specifying that property in will-change
923        // > must cause the element to generate a containing block for fixed positioned elements.
924        let will_change_bits = self.get_will_change().bits;
925
926        // From <https://drafts.csswg.org/css-transforms-1/#transform-rendering>:
927        // > any value other than `none` for the `transform` property also causes the element
928        // > to establish a containing block for all descendants.
929        //
930        // From <https://www.w3.org/TR/css-transforms-2/#individual-transforms>
931        // > all other values […] create a stacking context and containing block for all
932        // > descendants, per usual for transforms.
933        //
934        // From <https://drafts.csswg.org/css-transforms-2/#perspective-property>:
935        // > The use of this property with any value other than `none` […] establishes a
936        // > containing block for all descendants, just like the `transform` property does.
937        //
938        // From <https://drafts.csswg.org/css-transforms-2/#transform-style-property>:
939        // > A computed value of `preserve-3d` for `transform-style` on a transformable element
940        // > establishes both a stacking context and a containing block for all descendants.
941        if self.is_transformable(fragment_flags) &&
942            (self.has_transform_or_perspective_style() ||
943                self.used_transform_style(fragment_flags) ==
944                    ComputedTransformStyle::Preserve3d ||
945                will_change_bits
946                    .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
947        {
948            return true;
949        }
950
951        // From <https://www.w3.org/TR/filter-effects-1/#propdef-filter>:
952        // > A value other than none for the filter property results in the creation of a containing
953        // > block for absolute and fixed positioned descendants unless the element it applies to is
954        // > a document root element in the current browsing context.
955        //
956        // From <https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty>:
957        // > A computed value other than `none` results in the creation of both
958        // > a stacking context [CSS21] and a containing block for absolute and
959        // > fixed positioned descendants, unless the element it applies to is
960        // > a document root element in the current browsing context.
961        if !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) &&
962            (!self.get_effects().filter.0.is_empty() ||
963                !self.get_effects().backdrop_filter.0.is_empty() ||
964                will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG))
965        {
966            return true;
967        }
968
969        // TODO: We need to handle CSS Contain here.
970        false
971    }
972
973    /// Resolve the preferred aspect ratio according to the given natural aspect
974    /// ratio and the `aspect-ratio` property.
975    /// See <https://drafts.csswg.org/css-sizing-4/#aspect-ratio>.
976    fn preferred_aspect_ratio(
977        &self,
978        natural_aspect_ratio: Option<CSSFloat>,
979        padding_border_sums: &LogicalVec2<Au>,
980    ) -> Option<AspectRatio> {
981        let &GenericAspectRatio {
982            ref auto,
983            ratio: mut preferred_ratio,
984        } = self.get_aspect_ratio();
985
986        // For all cases where a ratio is specified:
987        // "If the <ratio> is degenerate, the property instead behaves as auto."
988        if matches!(preferred_ratio, PreferredRatio::Ratio(ratio) if ratio.is_degenerate()) {
989            preferred_ratio = PreferredRatio::None;
990        }
991
992        let to_logical_ratio = |physical_ratio| {
993            if self.writing_mode.is_horizontal() {
994                physical_ratio
995            } else {
996                1.0 / physical_ratio
997            }
998        };
999
1000        match (auto, preferred_ratio) {
1001            // The value `auto`. Either the ratio was not specified, or was
1002            // degenerate and set to PreferredRatio::None above.
1003            //
1004            // "Replaced elements with a natural aspect ratio use that aspect
1005            // ratio; otherwise the box has no preferred aspect ratio. Size
1006            // calculations involving the aspect ratio work with the content box
1007            // dimensions always."
1008            (_, PreferredRatio::None) => natural_aspect_ratio
1009                .map(to_logical_ratio)
1010                .map(AspectRatio::from_logical_content_ratio),
1011            // "If both auto and a <ratio> are specified together, the preferred
1012            // aspect ratio is the specified ratio of width / height unless it
1013            // is a replaced element with a natural aspect ratio, in which case
1014            // that aspect ratio is used instead. In all cases, size
1015            // calculations involving the aspect ratio work with the content box
1016            // dimensions always."
1017            (true, PreferredRatio::Ratio(preferred_ratio)) => Some({
1018                let physical_ratio = natural_aspect_ratio
1019                    .unwrap_or_else(|| (preferred_ratio.0).0 / (preferred_ratio.1).0);
1020                AspectRatio::from_logical_content_ratio(to_logical_ratio(physical_ratio))
1021            }),
1022
1023            // "The box’s preferred aspect ratio is the specified ratio of width
1024            // / height. Size calculations involving the aspect ratio work with
1025            // the dimensions of the box specified by box-sizing."
1026            (false, PreferredRatio::Ratio(preferred_ratio)) => {
1027                // If the `box-sizing` is `border-box`, use the padding and
1028                // border when calculating the aspect ratio.
1029                let box_sizing_adjustment = match self.get_box_sizing() {
1030                    BoxSizing::ContentBox => LogicalVec2::zero(),
1031                    BoxSizing::BorderBox => *padding_border_sums,
1032                };
1033                Some(AspectRatio {
1034                    i_over_b: to_logical_ratio((preferred_ratio.0).0 / (preferred_ratio.1).0),
1035                    box_sizing_adjustment,
1036                })
1037            },
1038        }
1039    }
1040
1041    /// Whether or not this style specifies a non-transparent background.
1042    fn background_is_transparent(&self) -> bool {
1043        let background = self.get_background();
1044        let color = self.resolve_color(&background.background_color);
1045        color.alpha == 0.0 &&
1046            background
1047                .background_image
1048                .0
1049                .iter()
1050                .all(|layer| matches!(layer, ComputedImageLayer::None))
1051    }
1052
1053    /// Generate appropriate WebRender `PrimitiveFlags` that should be used
1054    /// for display items generated by the `Fragment` which owns this style.
1055    fn webrender_primitive_flags(&self) -> wr::PrimitiveFlags {
1056        match self.get_box().backface_visibility {
1057            BackfaceVisiblity::Visible => wr::PrimitiveFlags::default(),
1058            BackfaceVisiblity::Hidden => wr::PrimitiveFlags::empty(),
1059        }
1060    }
1061
1062    /// If the 'unicode-bidi' property has a value other than 'normal', return the bidi control codes
1063    /// to inject before and after the text content of the element.
1064    /// See the table in <http://dev.w3.org/csswg/css-writing-modes/#unicode-bidi>.
1065    fn bidi_control_chars(&self) -> (&'static str, &'static str) {
1066        match (
1067            self.get_text().unicode_bidi,
1068            self.get_inherited_box().direction,
1069        ) {
1070            (UnicodeBidi::Normal, _) => ("", ""),
1071            (UnicodeBidi::Embed, Direction::Ltr) => ("\u{202a}", "\u{202c}"),
1072            (UnicodeBidi::Embed, Direction::Rtl) => ("\u{202b}", "\u{202c}"),
1073            (UnicodeBidi::Isolate, Direction::Ltr) => ("\u{2066}", "\u{2069}"),
1074            (UnicodeBidi::Isolate, Direction::Rtl) => ("\u{2067}", "\u{2069}"),
1075            (UnicodeBidi::BidiOverride, Direction::Ltr) => ("\u{202d}", "\u{202c}"),
1076            (UnicodeBidi::BidiOverride, Direction::Rtl) => ("\u{202e}", "\u{202c}"),
1077            (UnicodeBidi::IsolateOverride, Direction::Ltr) => {
1078                ("\u{2068}\u{202d}", "\u{202c}\u{2069}")
1079            },
1080            (UnicodeBidi::IsolateOverride, Direction::Rtl) => {
1081                ("\u{2068}\u{202e}", "\u{202c}\u{2069}")
1082            },
1083            (UnicodeBidi::Plaintext, _) => ("\u{2068}", "\u{2069}"),
1084        }
1085    }
1086
1087    fn resolve_align_self(
1088        &self,
1089        resolved_auto_value: ItemPlacement,
1090        resolved_normal_value: AlignFlags,
1091    ) -> SelfAlignment {
1092        SelfAlignment(match self.get_align_self().0 {
1093            AlignFlags::AUTO => resolved_auto_value.0,
1094            AlignFlags::NORMAL => resolved_normal_value,
1095            value => value,
1096        })
1097    }
1098
1099    fn depends_on_block_constraints_due_to_relative_positioning(
1100        &self,
1101        writing_mode: WritingMode,
1102    ) -> bool {
1103        if !matches!(
1104            self.get_box().position,
1105            ComputedPosition::Relative | ComputedPosition::Sticky
1106        ) {
1107            return false;
1108        }
1109        let box_offsets = self.box_offsets(writing_mode);
1110        let has_percentage = |offset: LengthPercentageOrAuto<'_>| {
1111            offset
1112                .non_auto()
1113                .is_some_and(LengthPercentage::has_percentage)
1114        };
1115        has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end)
1116    }
1117
1118    // <https://drafts.csswg.org/cssom-view/#overflow-directions>
1119    fn overflow_direction(&self) -> OverflowDirection {
1120        let inline_end_direction = self.writing_mode.inline_end_physical_side();
1121        let block_end_direction = self.writing_mode.block_end_physical_side();
1122
1123        let rightward = inline_end_direction == PhysicalSide::Right ||
1124            block_end_direction == PhysicalSide::Right;
1125        let downward = inline_end_direction == PhysicalSide::Bottom ||
1126            block_end_direction == PhysicalSide::Bottom;
1127
1128        // TODO(stevennovaryo): We should consider the flex-container's CSS (e.g. flow-direction: column-reverse).
1129        OverflowDirection {
1130            rightward,
1131            downward,
1132        }
1133    }
1134
1135    /// The default bidirectional embedding level for the writing mode of this style.
1136    ///
1137    /// Returns bidi level 0 if the mode is LTR, or 1 otherwise.
1138    fn to_bidi_level(&self) -> Level {
1139        if self.writing_mode.is_bidi_ltr() {
1140            Level::ltr()
1141        } else {
1142            Level::rtl()
1143        }
1144    }
1145
1146    fn white_space_may_hang_conditionally(&self) -> bool {
1147        *self.get_white_space_collapse() == WhiteSpaceCollapse::Preserve &&
1148            *self.get_text_wrap_mode() == TextWrapMode::Wrap
1149    }
1150}
1151
1152pub(crate) enum LayoutStyle<'a> {
1153    Default(&'a ComputedValues),
1154    Table(TableLayoutStyle<'a>),
1155}
1156
1157impl LayoutStyle<'_> {
1158    #[inline]
1159    pub(crate) fn style(&self) -> &ComputedValues {
1160        match self {
1161            Self::Default(style) => style,
1162            Self::Table(table) => table.style(),
1163        }
1164    }
1165
1166    #[inline]
1167    pub(crate) fn is_table(&self) -> bool {
1168        matches!(self, Self::Table(_))
1169    }
1170
1171    pub(crate) fn content_box_sizes_and_padding_border_margin(
1172        &self,
1173        containing_block: &IndefiniteContainingBlock,
1174    ) -> ContentBoxSizesAndPBM {
1175        // <https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution>
1176        // If max size properties or preferred size properties are set to a value containing
1177        // indefinite percentages, we treat the entire value as the initial value of the property.
1178        // However, for min size properties, as well as for margins and paddings,
1179        // we instead resolve indefinite percentages against zero.
1180        let containing_block_size_or_zero =
1181            containing_block.size.map(|value| value.unwrap_or_default());
1182        let writing_mode = containing_block.style.writing_mode;
1183        let pbm = self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1184            writing_mode,
1185            containing_block_size_or_zero.inline,
1186        );
1187        let style = self.style();
1188        let box_size = style.box_size(writing_mode);
1189        let min_size = style.min_box_size(writing_mode);
1190        let max_size = style.max_box_size(writing_mode);
1191        let preferred_size_computes_to_auto = box_size.map(|size| size.is_initial());
1192
1193        let depends_on_block_constraints = |size: &Size<LengthPercentage>| {
1194            match size {
1195                // fit-content is like clamp(min-content, stretch, max-content), but currently
1196                // min-content and max-content have the same behavior in the block axis,
1197                // so there is no dependency on block constraints.
1198                // TODO: for flex and grid layout, min-content and max-content should be different.
1199                // TODO: We are assuming that Size::Initial doesn't stretch. However, it may actually
1200                // stretch flex and grid items depending on the CSS Align properties, in that case
1201                // the caller needs to take care of it.
1202                Size::Stretch => true,
1203                Size::Numeric(length_percentage) => length_percentage.has_percentage(),
1204                _ => false,
1205            }
1206        };
1207        let depends_on_block_constraints = depends_on_block_constraints(&box_size.block) ||
1208            depends_on_block_constraints(&min_size.block) ||
1209            depends_on_block_constraints(&max_size.block) ||
1210            style.depends_on_block_constraints_due_to_relative_positioning(writing_mode);
1211
1212        let box_size = box_size.map_with(&containing_block.size, |size, basis| {
1213            size.resolve_percentages_for_preferred(*basis)
1214        });
1215        let content_box_size = style.content_box_size_for_box_size(box_size, &pbm);
1216        let min_size = min_size.percentages_relative_to_basis(&containing_block_size_or_zero);
1217        let content_min_box_size = style.content_min_box_size_for_min_size(min_size, &pbm);
1218        let max_size = max_size.map_with(&containing_block.size, |size, basis| {
1219            size.resolve_percentages_for_max(*basis)
1220        });
1221        let content_max_box_size = style.content_max_box_size_for_max_size(max_size, &pbm);
1222        ContentBoxSizesAndPBM {
1223            content_box_sizes: LogicalVec2 {
1224                block: Sizes::new(
1225                    content_box_size.block,
1226                    content_min_box_size.block,
1227                    content_max_box_size.block,
1228                ),
1229                inline: Sizes::new(
1230                    content_box_size.inline,
1231                    content_min_box_size.inline,
1232                    content_max_box_size.inline,
1233                ),
1234            },
1235            pbm,
1236            depends_on_block_constraints,
1237            preferred_size_computes_to_auto,
1238        }
1239    }
1240
1241    pub(crate) fn padding_border_margin(
1242        &self,
1243        containing_block: &ContainingBlock,
1244    ) -> PaddingBorderMargin {
1245        self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1246            containing_block.style.writing_mode,
1247            containing_block.size.inline,
1248        )
1249    }
1250
1251    pub(crate) fn padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1252        &self,
1253        writing_mode: WritingMode,
1254        containing_block_inline_size: Au,
1255    ) -> PaddingBorderMargin {
1256        let padding = self
1257            .padding(writing_mode)
1258            .percentages_relative_to(containing_block_inline_size);
1259        let style = self.style();
1260        let border = self.border_width(writing_mode);
1261        let margin = style
1262            .margin(writing_mode)
1263            .percentages_relative_to(containing_block_inline_size);
1264        PaddingBorderMargin {
1265            padding_border_sums: LogicalVec2 {
1266                inline: padding.inline_sum() + border.inline_sum(),
1267                block: padding.block_sum() + border.block_sum(),
1268            },
1269            padding,
1270            border,
1271            margin,
1272        }
1273    }
1274
1275    pub(crate) fn padding(
1276        &self,
1277        containing_block_writing_mode: WritingMode,
1278    ) -> LogicalSides<LengthPercentage> {
1279        if matches!(self, Self::Table(table) if table.collapses_borders()) {
1280            // https://drafts.csswg.org/css-tables/#collapsed-style-overrides
1281            // > The padding of the table-root is ignored (as if it was set to 0px).
1282            return LogicalSides::zero();
1283        }
1284        let padding = self.style().get_padding().clone();
1285        LogicalSides::from_physical(
1286            &PhysicalSides::new(
1287                padding.padding_top.0,
1288                padding.padding_right.0,
1289                padding.padding_bottom.0,
1290                padding.padding_left.0,
1291            ),
1292            containing_block_writing_mode,
1293        )
1294    }
1295
1296    pub(crate) fn border_width(
1297        &self,
1298        containing_block_writing_mode: WritingMode,
1299    ) -> LogicalSides<Au> {
1300        let border_width = match self {
1301            // For tables in collapsed-borders mode we halve the border widths, because
1302            // > in this model, the width of the table includes half the table border.
1303            // https://www.w3.org/TR/CSS22/tables.html#collapsing-borders
1304            Self::Table(table) if table.collapses_borders() => table
1305                .halved_collapsed_border_widths()
1306                .to_physical(self.style().writing_mode),
1307            _ => {
1308                let border = self.style().get_border();
1309                let resolve = |width: &BorderSideWidth, style: BorderStyle| {
1310                    if style.none_or_hidden() {
1311                        Au::zero()
1312                    } else {
1313                        width.0
1314                    }
1315                };
1316                PhysicalSides::new(
1317                    resolve(&border.border_top_width, border.border_top_style),
1318                    resolve(&border.border_right_width, border.border_right_style),
1319                    resolve(&border.border_bottom_width, border.border_bottom_style),
1320                    resolve(&border.border_left_width, border.border_left_style),
1321                )
1322            },
1323        };
1324        LogicalSides::from_physical(&border_width, containing_block_writing_mode)
1325    }
1326}
1327
1328impl From<stylo::Display> for Display {
1329    fn from(packed: stylo::Display) -> Self {
1330        let outside = packed.outside();
1331        let inside = packed.inside();
1332
1333        let outside = match outside {
1334            stylo::DisplayOutside::Block => DisplayOutside::Block,
1335            stylo::DisplayOutside::Inline => DisplayOutside::Inline,
1336            stylo::DisplayOutside::TableCaption => {
1337                return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(
1338                    DisplayLayoutInternal::TableCaption,
1339                ));
1340            },
1341            stylo::DisplayOutside::InternalTable => {
1342                let internal = match inside {
1343                    stylo::DisplayInside::TableRowGroup => DisplayLayoutInternal::TableRowGroup,
1344                    stylo::DisplayInside::TableColumn => DisplayLayoutInternal::TableColumn,
1345                    stylo::DisplayInside::TableColumnGroup => {
1346                        DisplayLayoutInternal::TableColumnGroup
1347                    },
1348                    stylo::DisplayInside::TableHeaderGroup => {
1349                        DisplayLayoutInternal::TableHeaderGroup
1350                    },
1351                    stylo::DisplayInside::TableFooterGroup => {
1352                        DisplayLayoutInternal::TableFooterGroup
1353                    },
1354                    stylo::DisplayInside::TableRow => DisplayLayoutInternal::TableRow,
1355                    stylo::DisplayInside::TableCell => DisplayLayoutInternal::TableCell,
1356                    _ => unreachable!("Non-internal DisplayInside found"),
1357                };
1358                return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(internal));
1359            },
1360            // This should not be a value of DisplayInside, but oh well
1361            // special-case display: contents because we still want it to work despite the early return
1362            stylo::DisplayOutside::None if inside == stylo::DisplayInside::Contents => {
1363                return Display::Contents;
1364            },
1365            stylo::DisplayOutside::None => return Display::None,
1366        };
1367
1368        let inside = match inside {
1369            stylo::DisplayInside::Flow => DisplayInside::Flow {
1370                is_list_item: packed.is_list_item(),
1371            },
1372            stylo::DisplayInside::FlowRoot => DisplayInside::FlowRoot {
1373                is_list_item: packed.is_list_item(),
1374            },
1375            stylo::DisplayInside::Flex => DisplayInside::Flex,
1376            stylo::DisplayInside::Grid => DisplayInside::Grid,
1377            stylo::DisplayInside::GridLanes => unreachable!("Should be disabled behind a pref"),
1378            stylo::DisplayInside::Table => DisplayInside::Table,
1379
1380            // These should not be values of DisplayInside, but oh well
1381            stylo::DisplayInside::None => return Display::None,
1382            stylo::DisplayInside::Contents => return Display::Contents,
1383
1384            stylo::DisplayInside::TableRowGroup |
1385            stylo::DisplayInside::TableColumn |
1386            stylo::DisplayInside::TableColumnGroup |
1387            stylo::DisplayInside::TableHeaderGroup |
1388            stylo::DisplayInside::TableFooterGroup |
1389            stylo::DisplayInside::TableRow |
1390            stylo::DisplayInside::TableCell => unreachable!("Internal DisplayInside found"),
1391        };
1392        Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside })
1393    }
1394}
1395
1396pub(crate) trait Clamp: Sized {
1397    fn clamp_below_max(self, max: Option<Self>) -> Self;
1398    fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self;
1399}
1400
1401impl Clamp for Au {
1402    fn clamp_below_max(self, max: Option<Self>) -> Self {
1403        match max {
1404            None => self,
1405            Some(max) => self.min(max),
1406        }
1407    }
1408
1409    fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self {
1410        self.clamp_below_max(max).max(min)
1411    }
1412}
1413
1414pub(crate) trait TransformExt {
1415    fn change_basis(&self, x: f32, y: f32, z: f32) -> Self;
1416}
1417
1418impl TransformExt for LayoutTransform {
1419    /// <https://drafts.csswg.org/css-transforms/#transformation-matrix-computation>
1420    fn change_basis(&self, x: f32, y: f32, z: f32) -> Self {
1421        let pre_translation = Self::translation(x, y, z);
1422        let post_translation = Self::translation(-x, -y, -z);
1423        post_translation.then(self).then(&pre_translation)
1424    }
1425}