servo/components/layout_2020/style_ext.rs
Simon Wülker 3d320fa96a
Update rustfmt to the 2024 style edition (#35764)
* Use 2024 style edition

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Reformat all code

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
2025-03-03 11:26:53 +00:00

1084 lines
43 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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, box_ as stylo};
use webrender_api as wr;
use crate::dom_traversal::Contents;
use crate::fragment_tree::FragmentFlags;
use crate::geom::{
AuOrAuto, LengthPercentageOrAuto, LogicalSides, 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,
},
/// <https://drafts.csswg.org/css-display-3/#layout-specific-display>
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 <https://www.w3.org/TR/css-display-3/#layout-specific-display>:
// > 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”
// <https://drafts.csswg.org/css-display/#list-items>
Flow { is_list_item: bool },
FlowRoot { is_list_item: bool },
Flex,
Grid,
Table,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(clippy::enum_variant_names)]
/// <https://drafts.csswg.org/css-display-3/#layout-specific-display>
pub(crate) enum DisplayLayoutInternal {
TableCaption,
TableCell,
TableColumn,
TableColumnGroup,
TableFooterGroup,
TableHeaderGroup,
TableRow,
TableRowGroup,
}
impl DisplayLayoutInternal {
/// <https://drafts.csswg.org/css-display-3/#layout-specific-displa>
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<Au>,
pub border: LogicalSides<Au>,
pub margin: LogicalSides<AuOrAuto>,
/// Pre-computed sums in each axis
pub padding_border_sums: LogicalVec2<Au>,
}
impl PaddingBorderMargin {
pub(crate) fn zero() -> Self {
Self {
padding: LogicalSides::zero(),
border: LogicalSides::zero(),
margin: LogicalSides::zero(),
padding_border_sums: LogicalVec2::zero(),
}
}
}
/// 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<Au>,
/// 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<Sizes>,
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<Self> {
let resolve = |color: &Color| color.resolve_to_absolute(current_color);
PhysicalSides::<Self>::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<LengthPercentageOrAuto<'_>>;
fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>>;
fn box_size(
&self,
containing_block_writing_mode: WritingMode,
) -> LogicalVec2<Size<LengthPercentage>>;
fn min_box_size(
&self,
containing_block_writing_mode: WritingMode,
) -> LogicalVec2<Size<LengthPercentage>>;
fn max_box_size(
&self,
containing_block_writing_mode: WritingMode,
) -> LogicalVec2<Size<LengthPercentage>>;
fn content_box_size_for_box_size(
&self,
box_size: LogicalVec2<Size<Au>>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<Size<Au>>;
fn content_min_box_size_for_min_size(
&self,
box_size: LogicalVec2<Size<Au>>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<Size<Au>>;
fn content_max_box_size_for_max_size(
&self,
box_size: LogicalVec2<Size<Au>>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<Size<Au>>;
fn border_style_color(
&self,
containing_block_writing_mode: WritingMode,
) -> LogicalSides<BorderStyleColor>;
fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>;
fn margin(
&self,
containing_block_writing_mode: WritingMode,
) -> LogicalSides<LengthPercentageOrAuto<'_>>;
fn has_transform_or_perspective(&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<CSSFloat>,
padding_border_sums: &LogicalVec2<Au>,
) -> Option<AspectRatio>;
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<LengthPercentageOrAuto<'_>> {
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<LengthPercentageOrAuto<'_>> {
LogicalSides::from_physical(&self.physical_box_offsets(), writing_mode)
}
fn box_size(
&self,
containing_block_writing_mode: WritingMode,
) -> LogicalVec2<Size<LengthPercentage>> {
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<Size<LengthPercentage>> {
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<Size<LengthPercentage>> {
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<Size<Au>>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<Size<Au>> {
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<Size<Au>>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<Size<Au>> {
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<Size<Au>>,
pbm: &PaddingBorderMargin,
) -> LogicalVec2<Size<Au>> {
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<BorderStyleColor> {
let current_color = self.get_inherited_text().clone_color();
LogicalSides::from_physical(
&BorderStyleColor::from_border(self.get_border(), &current_color),
containing_block_writing_mode,
)
}
fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
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<LengthPercentageOrAuto<'_>> {
LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode)
}
/// 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 {
// "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."
// https://drafts.csswg.org/css-transforms/#transformable-element
if self.get_box().display.is_inline_flow() &&
!fragment_flags.contains(FragmentFlags::IS_REPLACED)
{
return false;
}
!self.get_box().transform.0.is_empty() || self.get_box().perspective != Perspective::None
}
/// Get the effective z-index of this fragment. Z-indices only apply to positioned elements
/// per CSS 2 9.9.1 (<http://www.w3.org/TR/CSS2/visuren.html#z-index>), so this value may differ
/// from the value specified in the style.
fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32 {
// From <https://drafts.csswg.org/css-flexbox/#painting>:
// > 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).
match self.get_box().position {
ComputedPosition::Static if !fragment_flags.contains(FragmentFlags::IS_FLEX_ITEM) => 0,
_ => self.get_position().z_index.integer_or(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.
/// <https://www.w3.org/TR/css-overflow-3/#overflow-control>
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 <https://www.w3.org/TR/css-overflow-4/#overflow-control>:
// "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 <https://drafts.csswg.org/css-tables/#global-style-overrides>,
// - 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: <https://lists.w3.org/Archives/Public/www-style/2012Aug/0298.html>
// 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 => {
// <https://drafts.csswg.org/css-tables/#global-style-overrides>
// 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 <https://drafts.csswg.org/css-align/#distribution-block>:
// 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 {
let effects = self.get_effects();
if effects.opacity != 1.0 {
return true;
}
if effects.mix_blend_mode != ComputedMixBlendMode::Normal {
return true;
}
if self.has_transform_or_perspective(fragment_flags) {
return true;
}
if !self.get_effects().filter.0.is_empty() {
return true;
}
if self.get_box().transform_style == ComputedTransformStyle::Preserve3d ||
self.overrides_transform_style()
{
return true;
}
if self.get_box().isolation == ComputedIsolation::Isolate {
return true;
}
// Fixed position and sticky position always create stacking contexts.
// TODO(mrobinson): We need to handle sticky positioning here when we support it.
if self.get_box().position == ComputedPosition::Fixed {
return true;
}
if self.get_svg().clip_path != ClipPath::None {
return true;
}
// Statically positioned fragments don't establish stacking contexts if the previous
// conditions are not fulfilled. Furthermore, z-index doesn't apply to statically
// positioned fragments (except for flex items, see below).
//
// From <https://drafts.csswg.org/css-flexbox/#painting>:
// > 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).
if self.get_box().position == ComputedPosition::Static &&
!fragment_flags.contains(FragmentFlags::IS_FLEX_ITEM)
{
return false;
}
// For absolutely and relatively positioned fragments we only establish a stacking
// context if there is a z-index set.
// See https://www.w3.org/TR/CSS2/visuren.html#z-index
!self.get_position().z_index.is_auto()
}
/// 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;
}
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;
}
// 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 <https://drafts.csswg.org/css-sizing-4/#aspect-ratio>.
fn preferred_aspect_ratio(
&self,
natural_aspect_ratio: Option<CSSFloat>,
padding_border_sums: &LogicalVec2<Au>,
) -> Option<AspectRatio> {
let GenericAspectRatio {
auto,
ratio: mut preferred_ratio,
} = self.clone_aspect_ratio();
// For all cases where a ratio is specified:
// "If the <ratio> 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 <ratio> 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 boxs 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 <http://dev.w3.org/csswg/css-writing-modes/#unicode-bidi>.
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 {
// <https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution>
// 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<LengthPercentage>| {
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<LengthPercentage> {
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<Au> {
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<stylo::Display> 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>) -> Self;
fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self;
}
impl Clamp for Au {
fn clamp_below_max(self, max: Option<Self>) -> Self {
match max {
None => self,
Some(max) => self.min(max),
}
}
fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self {
self.clamp_below_max(max).max(min)
}
}