/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use app_units::Au; use style::Zero; use style::color::AbsoluteColor; use style::computed_values::direction::T as Direction; use style::computed_values::isolation::T as ComputedIsolation; use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode; use style::computed_values::position::T as ComputedPosition; use style::computed_values::transform_style::T as ComputedTransformStyle; use style::computed_values::unicode_bidi::T as UnicodeBidi; use style::logical_geometry::{Direction as AxisDirection, WritingMode}; use style::properties::ComputedValues; use style::properties::longhands::backface_visibility::computed_value::T as BackfaceVisiblity; use style::properties::longhands::box_sizing::computed_value::T as BoxSizing; use style::properties::longhands::column_span::computed_value::T as ColumnSpan; use style::properties::style_structs::Border; use style::servo::selector_parser::PseudoElement; use style::values::CSSFloat; use style::values::computed::basic_shape::ClipPath; use style::values::computed::image::Image as ComputedImageLayer; use style::values::computed::{AlignItems, BorderStyle, Color, Inset, LengthPercentage, Margin}; use style::values::generics::box_::Perspective; use style::values::generics::position::{GenericAspectRatio, PreferredRatio}; use style::values::specified::align::AlignFlags; use style::values::specified::{Overflow, WillChangeBits, box_ as stylo}; use webrender_api as wr; use crate::dom_traversal::Contents; use crate::fragment_tree::FragmentFlags; use crate::geom::{ AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides, PhysicalSize, Size, Sizes, }; use crate::table::TableLayoutStyle; use crate::{ContainingBlock, IndefiniteContainingBlock}; #[derive(Clone, Copy, Eq, PartialEq)] pub(crate) enum Display { None, Contents, GeneratingBox(DisplayGeneratingBox), } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum DisplayGeneratingBox { OutsideInside { outside: DisplayOutside, inside: DisplayInside, }, /// LayoutInternal(DisplayLayoutInternal), } #[derive(Clone, Copy, Debug)] pub struct AxesOverflow { pub x: Overflow, pub y: Overflow, } impl DisplayGeneratingBox { pub(crate) fn display_inside(&self) -> DisplayInside { match *self { DisplayGeneratingBox::OutsideInside { inside, .. } => inside, DisplayGeneratingBox::LayoutInternal(layout_internal) => { layout_internal.display_inside() }, } } pub(crate) fn used_value_for_contents(&self, contents: &Contents) -> Self { // From : // > When the display property of a replaced element computes to one of // > the layout-internal values, it is handled as having a used value of // > inline. if matches!(self, Self::LayoutInternal(_)) && contents.is_replaced() { Self::OutsideInside { outside: DisplayOutside::Inline, inside: DisplayInside::Flow { is_list_item: false, }, } } else { *self } } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum DisplayOutside { Block, Inline, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(crate) enum DisplayInside { // “list-items are limited to the Flow Layout display types” // Flow { is_list_item: bool }, FlowRoot { is_list_item: bool }, Flex, Grid, Table, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[allow(clippy::enum_variant_names)] /// pub(crate) enum DisplayLayoutInternal { TableCaption, TableCell, TableColumn, TableColumnGroup, TableFooterGroup, TableHeaderGroup, TableRow, TableRowGroup, } impl DisplayLayoutInternal { /// pub(crate) fn display_inside(&self) -> DisplayInside { // When we add ruby, the display_inside of ruby must be Flow. // TODO: this should be unreachable for everything but // table cell and caption, once we have box tree fixups. DisplayInside::FlowRoot { is_list_item: false, } } } /// Percentages resolved but not `auto` margins #[derive(Clone, Debug)] pub(crate) struct PaddingBorderMargin { pub padding: LogicalSides, pub border: LogicalSides, pub margin: LogicalSides, /// Pre-computed sums in each axis pub padding_border_sums: LogicalVec2, } impl PaddingBorderMargin { pub(crate) fn zero() -> Self { Self { padding: LogicalSides::zero(), border: LogicalSides::zero(), margin: LogicalSides::zero(), padding_border_sums: LogicalVec2::zero(), } } pub(crate) fn sums_auto_is_zero( &self, ignore_block_margins: LogicalSides1D, ) -> LogicalVec2 { let margin = self.margin.auto_is(Au::zero); let mut sums = self.padding_border_sums; sums.inline += margin.inline_sum(); if !ignore_block_margins.start { sums.block += margin.block_start; } if !ignore_block_margins.end { sums.block += margin.block_end; } sums } } /// Resolved `aspect-ratio` property with respect to a specific element. Depends /// on that element's `box-sizing` (and padding and border, if that `box-sizing` /// is `border-box`). #[derive(Clone, Copy, Debug)] pub(crate) struct AspectRatio { /// If the element that this aspect ratio belongs to uses box-sizing: /// border-box, and the aspect-ratio property does not contain "auto", then /// the aspect ratio is in respect to the border box. This will then contain /// the summed sizes of the padding and border. Otherwise, it's 0. box_sizing_adjustment: LogicalVec2, /// The ratio itself (inline over block). i_over_b: CSSFloat, } impl AspectRatio { /// Given one side length, compute the other one. pub(crate) fn compute_dependent_size( &self, ratio_dependent_axis: AxisDirection, ratio_determining_size: Au, ) -> Au { match ratio_dependent_axis { // Calculate the inline size from the block size AxisDirection::Inline => { (ratio_determining_size + self.box_sizing_adjustment.block).scale_by(self.i_over_b) - self.box_sizing_adjustment.inline }, // Calculate the block size from the inline size AxisDirection::Block => { (ratio_determining_size + self.box_sizing_adjustment.inline) .scale_by(1.0 / self.i_over_b) - self.box_sizing_adjustment.block }, } } pub(crate) fn from_content_ratio(i_over_b: CSSFloat) -> Self { Self { box_sizing_adjustment: LogicalVec2::zero(), i_over_b, } } } #[derive(Clone)] pub(crate) struct ContentBoxSizesAndPBM { pub content_box_sizes: LogicalVec2, pub pbm: PaddingBorderMargin, pub depends_on_block_constraints: bool, } #[derive(Clone, Debug, PartialEq)] pub(crate) struct BorderStyleColor { pub style: BorderStyle, pub color: AbsoluteColor, } impl BorderStyleColor { pub(crate) fn new(style: BorderStyle, color: AbsoluteColor) -> Self { Self { style, color } } pub(crate) fn from_border( border: &Border, current_color: &AbsoluteColor, ) -> PhysicalSides { let resolve = |color: &Color| color.resolve_to_absolute(current_color); PhysicalSides::::new( Self::new(border.border_top_style, resolve(&border.border_top_color)), Self::new( border.border_right_style, resolve(&border.border_right_color), ), Self::new( border.border_bottom_style, resolve(&border.border_bottom_color), ), Self::new(border.border_left_style, resolve(&border.border_left_color)), ) } pub(crate) fn hidden() -> Self { Self::new(BorderStyle::Hidden, AbsoluteColor::TRANSPARENT_BLACK) } } impl Default for BorderStyleColor { fn default() -> Self { Self::new(BorderStyle::None, AbsoluteColor::TRANSPARENT_BLACK) } } pub(crate) trait ComputedValuesExt { fn physical_box_offsets(&self) -> PhysicalSides>; fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides>; fn box_size( &self, containing_block_writing_mode: WritingMode, ) -> LogicalVec2>; fn min_box_size( &self, containing_block_writing_mode: WritingMode, ) -> LogicalVec2>; fn max_box_size( &self, containing_block_writing_mode: WritingMode, ) -> LogicalVec2>; fn content_box_size_for_box_size( &self, box_size: LogicalVec2>, pbm: &PaddingBorderMargin, ) -> LogicalVec2>; fn content_min_box_size_for_min_size( &self, box_size: LogicalVec2>, pbm: &PaddingBorderMargin, ) -> LogicalVec2>; fn content_max_box_size_for_max_size( &self, box_size: LogicalVec2>, pbm: &PaddingBorderMargin, ) -> LogicalVec2>; fn border_style_color( &self, containing_block_writing_mode: WritingMode, ) -> LogicalSides; fn physical_margin(&self) -> PhysicalSides>; fn margin( &self, containing_block_writing_mode: WritingMode, ) -> LogicalSides>; fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool; fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool; fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool; fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32; fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow; fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool; fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool; fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool; fn establishes_containing_block_for_absolute_descendants( &self, fragment_flags: FragmentFlags, ) -> bool; fn establishes_containing_block_for_all_descendants( &self, fragment_flags: FragmentFlags, ) -> bool; fn preferred_aspect_ratio( &self, natural_aspect_ratio: Option, padding_border_sums: &LogicalVec2, ) -> Option; fn background_is_transparent(&self) -> bool; fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags; fn bidi_control_chars(&self) -> (&'static str, &'static str); fn resolve_align_self( &self, resolved_auto_value: AlignItems, resolved_normal_value: AlignItems, ) -> AlignItems; fn depends_on_block_constraints_due_to_relative_positioning( &self, writing_mode: WritingMode, ) -> bool; } impl ComputedValuesExt for ComputedValues { fn physical_box_offsets(&self) -> PhysicalSides> { fn convert(inset: &Inset) -> LengthPercentageOrAuto<'_> { match inset { Inset::LengthPercentage(ref v) => LengthPercentageOrAuto::LengthPercentage(v), Inset::Auto => LengthPercentageOrAuto::Auto, Inset::AnchorFunction(_) => unreachable!("anchor() should be disabled"), Inset::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), } } let position = self.get_position(); PhysicalSides::new( convert(&position.top), convert(&position.right), convert(&position.bottom), convert(&position.left), ) } fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides> { LogicalSides::from_physical(&self.physical_box_offsets(), writing_mode) } fn box_size( &self, containing_block_writing_mode: WritingMode, ) -> LogicalVec2> { let position = self.get_position(); LogicalVec2::from_physical_size( &PhysicalSize::new( position.clone_width().into(), position.clone_height().into(), ), containing_block_writing_mode, ) } fn min_box_size( &self, containing_block_writing_mode: WritingMode, ) -> LogicalVec2> { let position = self.get_position(); LogicalVec2::from_physical_size( &PhysicalSize::new( position.clone_min_width().into(), position.clone_min_height().into(), ), containing_block_writing_mode, ) } fn max_box_size( &self, containing_block_writing_mode: WritingMode, ) -> LogicalVec2> { let position = self.get_position(); LogicalVec2::from_physical_size( &PhysicalSize::new( position.clone_max_width().into(), position.clone_max_height().into(), ), containing_block_writing_mode, ) } fn content_box_size_for_box_size( &self, box_size: LogicalVec2>, pbm: &PaddingBorderMargin, ) -> LogicalVec2> { match self.get_position().box_sizing { BoxSizing::ContentBox => box_size, // These may be negative, but will later be clamped by `min-width`/`min-height` // which is clamped to zero. BoxSizing::BorderBox => box_size.map_inline_and_block_sizes( |value| value - pbm.padding_border_sums.inline, |value| value - pbm.padding_border_sums.block, ), } } fn content_min_box_size_for_min_size( &self, min_box_size: LogicalVec2>, pbm: &PaddingBorderMargin, ) -> LogicalVec2> { match self.get_position().box_sizing { BoxSizing::ContentBox => min_box_size, // Clamp to zero to make sure the used size components are non-negative BoxSizing::BorderBox => min_box_size.map_inline_and_block_sizes( |value| Au::zero().max(value - pbm.padding_border_sums.inline), |value| Au::zero().max(value - pbm.padding_border_sums.block), ), } } fn content_max_box_size_for_max_size( &self, max_box_size: LogicalVec2>, pbm: &PaddingBorderMargin, ) -> LogicalVec2> { match self.get_position().box_sizing { BoxSizing::ContentBox => max_box_size, // This may be negative, but will later be clamped by `min-width` // which itself is clamped to zero. BoxSizing::BorderBox => max_box_size.map_inline_and_block_sizes( |value| value - pbm.padding_border_sums.inline, |value| value - pbm.padding_border_sums.block, ), } } fn border_style_color( &self, containing_block_writing_mode: WritingMode, ) -> LogicalSides { let current_color = self.get_inherited_text().clone_color(); LogicalSides::from_physical( &BorderStyleColor::from_border(self.get_border(), ¤t_color), containing_block_writing_mode, ) } fn physical_margin(&self) -> PhysicalSides> { fn convert(inset: &Margin) -> LengthPercentageOrAuto<'_> { match inset { Margin::LengthPercentage(ref v) => LengthPercentageOrAuto::LengthPercentage(v), Margin::Auto => LengthPercentageOrAuto::Auto, Margin::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"), } } let margin = self.get_margin(); PhysicalSides::new( convert(&margin.margin_top), convert(&margin.margin_right), convert(&margin.margin_bottom), convert(&margin.margin_left), ) } fn margin( &self, containing_block_writing_mode: WritingMode, ) -> LogicalSides> { LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode) } /// Returns true if this is a transformable element. fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool { // "A transformable element is an element in one of these categories: // * all elements whose layout is governed by the CSS box model except for // non-replaced inline boxes, table-column boxes, and table-column-group // boxes, // * all SVG paint server elements, the clipPath element and SVG renderable // elements with the exception of any descendant element of text content // elements." // // TODO: check for all cases listed in the above spec. !self.get_box().display.is_inline_flow() || fragment_flags.contains(FragmentFlags::IS_REPLACED) } /// Returns true if this style has a transform, or perspective property set and /// it applies to this element. fn has_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool { self.is_transformable(fragment_flags) && (!self.get_box().transform.0.is_empty() || self.get_box().perspective != Perspective::None) } /// Whether the `z-index` property applies to this fragment. fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool { // As per CSS 2 § 9.9.1, `z-index` applies to positioned elements. // if self.get_box().position != ComputedPosition::Static { return true; } // More modern specs also apply it to flex and grid items. // - From : // > Flex items paint exactly the same as inline blocks [CSS2], except that order-modified // > document order is used in place of raw document order, and z-index values other than auto // > create a stacking context even if position is static (behaving exactly as if position // > were relative). // - From : // > The painting order of grid items is exactly the same as inline blocks [CSS2], except that // > order-modified document order is used in place of raw document order, and z-index values // > other than auto create a stacking context even if position is static (behaving exactly // > as if position were relative). fragment_flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM) } /// Get the effective z-index of this fragment. Z-indices only apply to positioned elements /// per CSS 2 9.9.1 (), so this value may differ /// from the value specified in the style. fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32 { if self.z_index_applies(fragment_flags) { self.get_position().z_index.integer_or(0) } else { 0 } } /// Get the effective overflow of this box. The property only applies to block containers, /// flex containers, and grid containers. And some box types only accept a few values. /// fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow { let style_box = self.get_box(); let mut overflow_x = style_box.overflow_x; let mut overflow_y = style_box.overflow_y; // From : // "On replaced elements, the used values of all computed values other than visible is clip." if fragment_flags.contains(FragmentFlags::IS_REPLACED) { if overflow_x != Overflow::Visible { overflow_x = Overflow::Clip; } if overflow_y != Overflow::Visible { overflow_y = Overflow::Clip; } return AxesOverflow { x: overflow_x, y: overflow_y, }; } let ignores_overflow = match style_box.display.inside() { stylo::DisplayInside::Table => { // According to , // - overflow applies to table-wrapper boxes and not to table grid boxes. // That's what Blink and WebKit do, however Firefox matches a CSSWG resolution that says // the opposite: // Due to the way that we implement table-wrapper boxes, it's easier to align with Firefox. // - Tables ignore overflow values different than visible, clip and hidden. // This affects both axes, to ensure they have the same scrollability. !matches!(self.pseudo(), Some(PseudoElement::ServoTableGrid)) || matches!(overflow_x, Overflow::Auto | Overflow::Scroll) || matches!(overflow_y, Overflow::Auto | Overflow::Scroll) }, stylo::DisplayInside::TableColumn | stylo::DisplayInside::TableColumnGroup | stylo::DisplayInside::TableRow | stylo::DisplayInside::TableRowGroup | stylo::DisplayInside::TableHeaderGroup | stylo::DisplayInside::TableFooterGroup => { // // Table-track and table-track-group boxes ignore overflow. true }, _ => false, }; if ignores_overflow { AxesOverflow { x: Overflow::Visible, y: Overflow::Visible, } } else { AxesOverflow { x: overflow_x, y: overflow_y, } } } /// Return true if this style is a normal block and establishes /// a new block formatting context. fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool { if self.establishes_scroll_container(fragment_flags) { return true; } if self.get_column().is_multicol() { return true; } if self.get_column().column_span == ColumnSpan::All { return true; } // Per : // Block containers with an `align-content` value that is not `normal` should // form an independent block formatting context. This should really only happen // for block containers, but we do not support subgrid containers yet which is the // only other case. if self.get_position().align_content.0.primary() != AlignFlags::NORMAL { return true; } // TODO: We need to handle CSS Contain here. false } /// Whether or not the `overflow` value of this style establishes a scroll container. fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool { // Checking one axis suffices, because the computed value ensures that // either both axes are scrollable, or none is scrollable. self.effective_overflow(fragment_flags).x.is_scrollable() } /// Returns true if this fragment establishes a new stacking context and false otherwise. fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool { // From : // > If any non-initial value of a property would create a stacking context on the element, // > specifying that property in will-change must create a stacking context on the element. let will_change_bits = self.clone_will_change().bits; if will_change_bits .intersects(WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::OPACITY) { return true; } // From , values different than `auto` // make the box establish a stacking context. if self.z_index_applies(fragment_flags) && (!self.get_position().z_index.is_auto() || will_change_bits.intersects(WillChangeBits::Z_INDEX)) { return true; } // Fixed position and sticky position always create stacking contexts. // Note `will-change: position` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`. if matches!( self.get_box().position, ComputedPosition::Fixed | ComputedPosition::Sticky ) { return true; } // From // > For elements whose layout is governed by the CSS box model, any value other than // > `none` for the `transform` property results in the creation of a stacking context. // From // > A computed value of `preserve-3d` for `transform-style` on a transformable element // > establishes both a stacking context and a containing block for all descendants. // From // > any value other than none establishes a stacking context. // TODO: handle individual transform properties (`translate`, `scale` and `rotate`). // if self.is_transformable(fragment_flags) && (!self.get_box().transform.0.is_empty() || self.get_box().transform_style == ComputedTransformStyle::Preserve3d || self.get_box().perspective != Perspective::None || will_change_bits .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE)) { return true; } // From // > implementations must create a new stacking context for any element with opacity less than 1. // Note `will-change: opacity` is handled above by `WillChangeBits::OPACITY`. let effects = self.get_effects(); if effects.opacity != 1.0 { return true; } // From // > A computed value of other than `none` results in the creation of a stacking context // Note `will-change: filter` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`. if !effects.filter.0.is_empty() { return true; } // From // > Applying a blendmode other than `normal` to the element must establish a new stacking context // Note `will-change: mix-blend-mode` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`. if effects.mix_blend_mode != ComputedMixBlendMode::Normal { return true; } // From // > A computed value of other than `none` results in the creation of a stacking context. // Note `will-change: clip-path` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`. if self.get_svg().clip_path != ClipPath::None { return true; } // From // > For CSS, setting `isolation` to `isolate` will turn the element into a stacking context. // Note `will-change: isolation` is handled above by `STACKING_CONTEXT_UNCONDITIONAL`. if self.get_box().isolation == ComputedIsolation::Isolate { return true; } // TODO: We need to handle CSS Contain here. false } /// Returns true if this style establishes a containing block for absolute /// descendants (`position: absolute`). If this style happens to establish a /// containing block for “all descendants” (ie including `position: fixed` /// descendants) this method will return true, but a true return value does /// not imply that the style establishes a containing block for all descendants. /// Use `establishes_containing_block_for_all_descendants()` instead. fn establishes_containing_block_for_absolute_descendants( &self, fragment_flags: FragmentFlags, ) -> bool { if self.establishes_containing_block_for_all_descendants(fragment_flags) { return true; } // From : // > If any non-initial value of a property would cause the element to // > generate a containing block for absolutely positioned elements, specifying that property in // > will-change must cause the element to generate a containing block for absolutely positioned elements. if self .clone_will_change() .bits .intersects(WillChangeBits::POSITION) { return true; } self.clone_position() != ComputedPosition::Static } /// Returns true if this style establishes a containing block for /// all descendants, including fixed descendants (`position: fixed`). /// Note that this also implies that it establishes a containing block /// for absolute descendants (`position: absolute`). fn establishes_containing_block_for_all_descendants( &self, fragment_flags: FragmentFlags, ) -> bool { if self.has_transform_or_perspective(fragment_flags) { return true; } if !self.get_effects().filter.0.is_empty() { return true; } // See . if self.is_transformable(fragment_flags) && self.get_box().transform_style == ComputedTransformStyle::Preserve3d { return true; } // From : // > If any non-initial value of a property would cause the element to generate a // > containing block for fixed positioned elements, specifying that property in will-change // > must cause the element to generate a containing block for fixed positioned elements. let will_change_bits = self.clone_will_change().bits; if will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG) || (will_change_bits .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE) && self.is_transformable(fragment_flags)) { return true; } // TODO: We need to handle CSS Contain here. false } /// Resolve the preferred aspect ratio according to the given natural aspect /// ratio and the `aspect-ratio` property. /// See . fn preferred_aspect_ratio( &self, natural_aspect_ratio: Option, padding_border_sums: &LogicalVec2, ) -> Option { let GenericAspectRatio { auto, ratio: mut preferred_ratio, } = self.clone_aspect_ratio(); // For all cases where a ratio is specified: // "If the is degenerate, the property instead behaves as auto." if matches!(preferred_ratio, PreferredRatio::Ratio(ratio) if ratio.is_degenerate()) { preferred_ratio = PreferredRatio::None; } match (auto, preferred_ratio) { // The value `auto`. Either the ratio was not specified, or was // degenerate and set to PreferredRatio::None above. // // "Replaced elements with a natural aspect ratio use that aspect // ratio; otherwise the box has no preferred aspect ratio. Size // calculations involving the aspect ratio work with the content box // dimensions always." (_, PreferredRatio::None) => natural_aspect_ratio.map(AspectRatio::from_content_ratio), // "If both auto and a are specified together, the preferred // aspect ratio is the specified ratio of width / height unless it // is a replaced element with a natural aspect ratio, in which case // that aspect ratio is used instead. In all cases, size // calculations involving the aspect ratio work with the content box // dimensions always." (true, PreferredRatio::Ratio(preferred_ratio)) => { Some(AspectRatio::from_content_ratio( natural_aspect_ratio .unwrap_or_else(|| (preferred_ratio.0).0 / (preferred_ratio.1).0), )) }, // "The box’s preferred aspect ratio is the specified ratio of width // / height. Size calculations involving the aspect ratio work with // the dimensions of the box specified by box-sizing." (false, PreferredRatio::Ratio(preferred_ratio)) => { // If the `box-sizing` is `border-box`, use the padding and // border when calculating the aspect ratio. let box_sizing_adjustment = match self.clone_box_sizing() { BoxSizing::ContentBox => LogicalVec2::zero(), BoxSizing::BorderBox => *padding_border_sums, }; Some(AspectRatio { i_over_b: (preferred_ratio.0).0 / (preferred_ratio.1).0, box_sizing_adjustment, }) }, } } /// Whether or not this style specifies a non-transparent background. fn background_is_transparent(&self) -> bool { let background = self.get_background(); let color = self.resolve_color(&background.background_color); color.alpha == 0.0 && background .background_image .0 .iter() .all(|layer| matches!(layer, ComputedImageLayer::None)) } /// Generate appropriate WebRender `PrimitiveFlags` that should be used /// for display items generated by the `Fragment` which owns this style. fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags { match self.get_box().backface_visibility { BackfaceVisiblity::Visible => wr::PrimitiveFlags::default(), BackfaceVisiblity::Hidden => wr::PrimitiveFlags::empty(), } } /// If the 'unicode-bidi' property has a value other than 'normal', return the bidi control codes /// to inject before and after the text content of the element. /// See the table in . fn bidi_control_chars(&self) -> (&'static str, &'static str) { match ( self.get_text().unicode_bidi, self.get_inherited_box().direction, ) { (UnicodeBidi::Normal, _) => ("", ""), (UnicodeBidi::Embed, Direction::Ltr) => ("\u{202a}", "\u{202c}"), (UnicodeBidi::Embed, Direction::Rtl) => ("\u{202b}", "\u{202c}"), (UnicodeBidi::Isolate, Direction::Ltr) => ("\u{2066}", "\u{2069}"), (UnicodeBidi::Isolate, Direction::Rtl) => ("\u{2067}", "\u{2069}"), (UnicodeBidi::BidiOverride, Direction::Ltr) => ("\u{202d}", "\u{202c}"), (UnicodeBidi::BidiOverride, Direction::Rtl) => ("\u{202e}", "\u{202c}"), (UnicodeBidi::IsolateOverride, Direction::Ltr) => { ("\u{2068}\u{202d}", "\u{202c}\u{2069}") }, (UnicodeBidi::IsolateOverride, Direction::Rtl) => { ("\u{2068}\u{202e}", "\u{202c}\u{2069}") }, (UnicodeBidi::Plaintext, _) => ("\u{2068}", "\u{2069}"), } } fn resolve_align_self( &self, resolved_auto_value: AlignItems, resolved_normal_value: AlignItems, ) -> AlignItems { match self.clone_align_self().0.0 { AlignFlags::AUTO => resolved_auto_value, AlignFlags::NORMAL => resolved_normal_value, value => AlignItems(value), } } fn depends_on_block_constraints_due_to_relative_positioning( &self, writing_mode: WritingMode, ) -> bool { if !matches!( self.get_box().position, ComputedPosition::Relative | ComputedPosition::Sticky ) { return false; } let box_offsets = self.box_offsets(writing_mode); let has_percentage = |offset: LengthPercentageOrAuto<'_>| { offset .non_auto() .is_some_and(LengthPercentage::has_percentage) }; has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end) } } pub(crate) enum LayoutStyle<'a> { Default(&'a ComputedValues), Table(TableLayoutStyle<'a>), } impl LayoutStyle<'_> { #[inline] pub(crate) fn style(&self) -> &ComputedValues { match self { Self::Default(style) => style, Self::Table(table) => table.style(), } } #[inline] pub(crate) fn is_table(&self) -> bool { matches!(self, Self::Table(_)) } pub(crate) fn content_box_sizes_and_padding_border_margin( &self, containing_block: &IndefiniteContainingBlock, ) -> ContentBoxSizesAndPBM { // // If max size properties or preferred size properties are set to a value containing // indefinite percentages, we treat the entire value as the initial value of the property. // However, for min size properties, as well as for margins and paddings, // we instead resolve indefinite percentages against zero. let containing_block_size_or_zero = containing_block.size.map(|value| value.unwrap_or_default()); let writing_mode = containing_block.writing_mode; let pbm = self.padding_border_margin_with_writing_mode_and_containing_block_inline_size( writing_mode, containing_block_size_or_zero.inline, ); let style = self.style(); let box_size = style.box_size(writing_mode); let min_size = style.min_box_size(writing_mode); let max_size = style.max_box_size(writing_mode); let depends_on_block_constraints = |size: &Size| { match size { // fit-content is like clamp(min-content, stretch, max-content), but currently // min-content and max-content have the same behavior in the block axis, // so there is no dependency on block constraints. // TODO: for flex and grid layout, min-content and max-content should be different. // TODO: We are assuming that Size::Initial doesn't stretch. However, it may actually // stretch flex and grid items depending on the CSS Align properties, in that case // the caller needs to take care of it. Size::Stretch => true, Size::Numeric(length_percentage) => length_percentage.has_percentage(), _ => false, } }; let depends_on_block_constraints = depends_on_block_constraints(&box_size.block) || depends_on_block_constraints(&min_size.block) || depends_on_block_constraints(&max_size.block) || style.depends_on_block_constraints_due_to_relative_positioning(writing_mode); let box_size = box_size.maybe_percentages_relative_to_basis(&containing_block.size); let content_box_size = style .content_box_size_for_box_size(box_size, &pbm) .map(|v| v.map(Au::from)); let min_size = min_size.percentages_relative_to_basis(&containing_block_size_or_zero); let content_min_box_size = style .content_min_box_size_for_min_size(min_size, &pbm) .map(|v| v.map(Au::from)); let max_size = max_size.maybe_percentages_relative_to_basis(&containing_block.size); let content_max_box_size = style .content_max_box_size_for_max_size(max_size, &pbm) .map(|v| v.map(Au::from)); ContentBoxSizesAndPBM { content_box_sizes: LogicalVec2 { block: Sizes::new( content_box_size.block, content_min_box_size.block, content_max_box_size.block, ), inline: Sizes::new( content_box_size.inline, content_min_box_size.inline, content_max_box_size.inline, ), }, pbm, depends_on_block_constraints, } } pub(crate) fn padding_border_margin( &self, containing_block: &ContainingBlock, ) -> PaddingBorderMargin { self.padding_border_margin_with_writing_mode_and_containing_block_inline_size( containing_block.style.writing_mode, containing_block.size.inline, ) } pub(crate) fn padding_border_margin_with_writing_mode_and_containing_block_inline_size( &self, writing_mode: WritingMode, containing_block_inline_size: Au, ) -> PaddingBorderMargin { let padding = self .padding(writing_mode) .percentages_relative_to(containing_block_inline_size); let style = self.style(); let border = self.border_width(writing_mode); let margin = style .margin(writing_mode) .percentages_relative_to(containing_block_inline_size); PaddingBorderMargin { padding_border_sums: LogicalVec2 { inline: padding.inline_sum() + border.inline_sum(), block: padding.block_sum() + border.block_sum(), }, padding, border, margin, } } pub(crate) fn padding( &self, containing_block_writing_mode: WritingMode, ) -> LogicalSides { if matches!(self, Self::Table(table) if table.collapses_borders()) { // https://drafts.csswg.org/css-tables/#collapsed-style-overrides // > The padding of the table-root is ignored (as if it was set to 0px). return LogicalSides::zero(); } let padding = self.style().get_padding().clone(); LogicalSides::from_physical( &PhysicalSides::new( padding.padding_top.0, padding.padding_right.0, padding.padding_bottom.0, padding.padding_left.0, ), containing_block_writing_mode, ) } pub(crate) fn border_width( &self, containing_block_writing_mode: WritingMode, ) -> LogicalSides { let border_width = match self { // For tables in collapsed-borders mode we halve the border widths, because // > in this model, the width of the table includes half the table border. // https://www.w3.org/TR/CSS22/tables.html#collapsing-borders Self::Table(table) if table.collapses_borders() => table .halved_collapsed_border_widths() .to_physical(self.style().writing_mode), _ => { let border = self.style().get_border(); PhysicalSides::new( border.border_top_width, border.border_right_width, border.border_bottom_width, border.border_left_width, ) }, }; LogicalSides::from_physical(&border_width, containing_block_writing_mode) } } impl From for Display { fn from(packed: stylo::Display) -> Self { let outside = packed.outside(); let inside = packed.inside(); let outside = match outside { stylo::DisplayOutside::Block => DisplayOutside::Block, stylo::DisplayOutside::Inline => DisplayOutside::Inline, stylo::DisplayOutside::TableCaption => { return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal( DisplayLayoutInternal::TableCaption, )); }, stylo::DisplayOutside::InternalTable => { let internal = match inside { stylo::DisplayInside::TableRowGroup => DisplayLayoutInternal::TableRowGroup, stylo::DisplayInside::TableColumn => DisplayLayoutInternal::TableColumn, stylo::DisplayInside::TableColumnGroup => { DisplayLayoutInternal::TableColumnGroup }, stylo::DisplayInside::TableHeaderGroup => { DisplayLayoutInternal::TableHeaderGroup }, stylo::DisplayInside::TableFooterGroup => { DisplayLayoutInternal::TableFooterGroup }, stylo::DisplayInside::TableRow => DisplayLayoutInternal::TableRow, stylo::DisplayInside::TableCell => DisplayLayoutInternal::TableCell, _ => unreachable!("Non-internal DisplayInside found"), }; return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(internal)); }, // This should not be a value of DisplayInside, but oh well // special-case display: contents because we still want it to work despite the early return stylo::DisplayOutside::None if inside == stylo::DisplayInside::Contents => { return Display::Contents; }, stylo::DisplayOutside::None => return Display::None, }; let inside = match packed.inside() { stylo::DisplayInside::Flow => DisplayInside::Flow { is_list_item: packed.is_list_item(), }, stylo::DisplayInside::FlowRoot => DisplayInside::FlowRoot { is_list_item: packed.is_list_item(), }, stylo::DisplayInside::Flex => DisplayInside::Flex, stylo::DisplayInside::Grid => DisplayInside::Grid, // These should not be values of DisplayInside, but oh well stylo::DisplayInside::None => return Display::None, stylo::DisplayInside::Contents => return Display::Contents, stylo::DisplayInside::Table => DisplayInside::Table, stylo::DisplayInside::TableRowGroup | stylo::DisplayInside::TableColumn | stylo::DisplayInside::TableColumnGroup | stylo::DisplayInside::TableHeaderGroup | stylo::DisplayInside::TableFooterGroup | stylo::DisplayInside::TableRow | stylo::DisplayInside::TableCell => unreachable!("Internal DisplayInside found"), }; Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside }) } } pub(crate) trait Clamp: Sized { fn clamp_below_max(self, max: Option) -> Self; fn clamp_between_extremums(self, min: Self, max: Option) -> Self; } impl Clamp for Au { fn clamp_below_max(self, max: Option) -> Self { match max { None => self, Some(max) => self.min(max), } } fn clamp_between_extremums(self, min: Self, max: Option) -> Self { self.clamp_below_max(max).max(min) } }