mirror of
https://github.com/servo/servo.git
synced 2025-06-12 02:14:41 +00:00
The recent changes to containing blocks, exposed an issue in the StyleExt trait: - When deciding whether an element creates a reference frame, whether or not it is a non-replaced inline is taken into account when determining if it has a transform. - When deciding whether an element creates a stacking context for all descendants, whether or not it is a non-replaced inline is *not* taken into account when determining if it has a transform. In both cases, elements that are inline should not be considered to have transforms. This commit fixes that issue as well as making it so that inlines cannot be transformed. Note that is also breaks transforms on replaced elements, but that functionality was fairly half-baked due to the inconsistent determination of transforms.
496 lines
18 KiB
Rust
496 lines
18 KiB
Rust
/* 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 crate::geom::flow_relative;
|
|
use crate::geom::{LengthOrAuto, LengthPercentageOrAuto, PhysicalSides, PhysicalSize};
|
|
use crate::ContainingBlock;
|
|
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::logical_geometry::WritingMode;
|
|
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::ComputedValues;
|
|
use style::values::computed::image::Image as ComputedImageLayer;
|
|
use style::values::computed::{Length, LengthPercentage};
|
|
use style::values::computed::{NonNegativeLengthPercentage, Size};
|
|
use style::values::generics::box_::Perspective;
|
|
use style::values::generics::length::MaxSize;
|
|
use style::values::specified::box_ as stylo;
|
|
use style::Zero;
|
|
use webrender_api as wr;
|
|
|
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
pub(crate) enum Display {
|
|
None,
|
|
Contents,
|
|
GeneratingBox(DisplayGeneratingBox),
|
|
}
|
|
|
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
pub(crate) enum DisplayGeneratingBox {
|
|
OutsideInside {
|
|
outside: DisplayOutside,
|
|
inside: DisplayInside,
|
|
},
|
|
// Layout-internal display types go here:
|
|
// https://drafts.csswg.org/css-display-3/#layout-specific-display
|
|
}
|
|
|
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
pub(crate) enum DisplayOutside {
|
|
Block,
|
|
Inline,
|
|
}
|
|
|
|
#[derive(Clone, Copy, 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,
|
|
}
|
|
|
|
/// Percentages resolved but not `auto` margins
|
|
pub(crate) struct PaddingBorderMargin {
|
|
pub padding: flow_relative::Sides<Length>,
|
|
pub border: flow_relative::Sides<Length>,
|
|
pub margin: flow_relative::Sides<LengthOrAuto>,
|
|
|
|
/// Pre-computed sums in each axis
|
|
pub padding_border_sums: flow_relative::Vec2<Length>,
|
|
}
|
|
|
|
pub(crate) trait ComputedValuesExt {
|
|
fn inline_size_is_length(&self) -> bool;
|
|
fn inline_box_offsets_are_both_non_auto(&self) -> bool;
|
|
fn box_offsets(
|
|
&self,
|
|
containing_block: &ContainingBlock,
|
|
) -> flow_relative::Sides<LengthPercentageOrAuto<'_>>;
|
|
fn box_size(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Vec2<LengthPercentageOrAuto<'_>>;
|
|
fn min_box_size(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Vec2<LengthPercentageOrAuto<'_>>;
|
|
fn max_box_size(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Vec2<Option<&LengthPercentage>>;
|
|
fn content_box_size(
|
|
&self,
|
|
containing_block: &ContainingBlock,
|
|
pbm: &PaddingBorderMargin,
|
|
) -> flow_relative::Vec2<LengthOrAuto>;
|
|
fn content_min_box_size(
|
|
&self,
|
|
containing_block: &ContainingBlock,
|
|
pbm: &PaddingBorderMargin,
|
|
) -> flow_relative::Vec2<LengthOrAuto>;
|
|
fn content_max_box_size(
|
|
&self,
|
|
containing_block: &ContainingBlock,
|
|
pbm: &PaddingBorderMargin,
|
|
) -> flow_relative::Vec2<Option<Length>>;
|
|
fn padding_border_margin(&self, containing_block: &ContainingBlock) -> PaddingBorderMargin;
|
|
fn padding(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Sides<&LengthPercentage>;
|
|
fn border_width(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Sides<Length>;
|
|
fn margin(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Sides<LengthPercentageOrAuto<'_>>;
|
|
fn has_transform_or_perspective(&self) -> bool;
|
|
fn effective_z_index(&self) -> i32;
|
|
fn establishes_stacking_context(&self) -> bool;
|
|
fn establishes_containing_block_for_absolute_descendants(&self) -> bool;
|
|
fn establishes_containing_block_for_all_descendants(&self) -> bool;
|
|
fn background_is_transparent(&self) -> bool;
|
|
fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags;
|
|
}
|
|
|
|
impl ComputedValuesExt for ComputedValues {
|
|
fn inline_size_is_length(&self) -> bool {
|
|
let position = self.get_position();
|
|
// FIXME: this is the wrong writing mode but we plan to remove eager content size computation.
|
|
let size = if self.writing_mode.is_horizontal() {
|
|
&position.width
|
|
} else {
|
|
&position.height
|
|
};
|
|
matches!(size, Size::LengthPercentage(lp) if lp.0.to_length().is_some())
|
|
}
|
|
|
|
fn inline_box_offsets_are_both_non_auto(&self) -> bool {
|
|
let position = self.get_position();
|
|
// FIXME: this is the wrong writing mode but we plan to remove eager content size computation.
|
|
let (a, b) = if self.writing_mode.is_horizontal() {
|
|
(&position.left, &position.right)
|
|
} else {
|
|
(&position.top, &position.bottom)
|
|
};
|
|
!a.is_auto() && !b.is_auto()
|
|
}
|
|
|
|
fn box_offsets(
|
|
&self,
|
|
containing_block: &ContainingBlock,
|
|
) -> flow_relative::Sides<LengthPercentageOrAuto<'_>> {
|
|
let position = self.get_position();
|
|
flow_relative::Sides::from_physical(
|
|
&PhysicalSides::new(
|
|
position.top.as_ref(),
|
|
position.right.as_ref(),
|
|
position.bottom.as_ref(),
|
|
position.left.as_ref(),
|
|
),
|
|
containing_block.style.writing_mode,
|
|
)
|
|
}
|
|
|
|
fn box_size(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Vec2<LengthPercentageOrAuto<'_>> {
|
|
let position = self.get_position();
|
|
flow_relative::Vec2::from_physical_size(
|
|
&PhysicalSize::new(
|
|
size_to_length(&position.width),
|
|
size_to_length(&position.height),
|
|
),
|
|
containing_block_writing_mode,
|
|
)
|
|
}
|
|
|
|
fn min_box_size(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Vec2<LengthPercentageOrAuto<'_>> {
|
|
let position = self.get_position();
|
|
flow_relative::Vec2::from_physical_size(
|
|
&PhysicalSize::new(
|
|
size_to_length(&position.min_width),
|
|
size_to_length(&position.min_height),
|
|
),
|
|
containing_block_writing_mode,
|
|
)
|
|
}
|
|
|
|
fn max_box_size(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Vec2<Option<&LengthPercentage>> {
|
|
fn unwrap(max_size: &MaxSize<NonNegativeLengthPercentage>) -> Option<&LengthPercentage> {
|
|
match max_size {
|
|
MaxSize::LengthPercentage(length) => Some(&length.0),
|
|
MaxSize::None => None,
|
|
}
|
|
}
|
|
let position = self.get_position();
|
|
flow_relative::Vec2::from_physical_size(
|
|
&PhysicalSize::new(unwrap(&position.max_width), unwrap(&position.max_height)),
|
|
containing_block_writing_mode,
|
|
)
|
|
}
|
|
|
|
fn content_box_size(
|
|
&self,
|
|
containing_block: &ContainingBlock,
|
|
pbm: &PaddingBorderMargin,
|
|
) -> flow_relative::Vec2<LengthOrAuto> {
|
|
let box_size = self
|
|
.box_size(containing_block.style.writing_mode)
|
|
.percentages_relative_to(containing_block);
|
|
match self.get_position().box_sizing {
|
|
BoxSizing::ContentBox => box_size,
|
|
BoxSizing::BorderBox => flow_relative::Vec2 {
|
|
// These may be negative, but will later be clamped by `min-width`/`min-height`
|
|
// which is clamped to zero.
|
|
inline: box_size.inline.map(|i| i - pbm.padding_border_sums.inline),
|
|
block: box_size.block.map(|b| b - pbm.padding_border_sums.block),
|
|
},
|
|
}
|
|
}
|
|
|
|
fn content_min_box_size(
|
|
&self,
|
|
containing_block: &ContainingBlock,
|
|
pbm: &PaddingBorderMargin,
|
|
) -> flow_relative::Vec2<LengthOrAuto> {
|
|
let min_box_size = self
|
|
.min_box_size(containing_block.style.writing_mode)
|
|
.percentages_relative_to(containing_block);
|
|
match self.get_position().box_sizing {
|
|
BoxSizing::ContentBox => min_box_size,
|
|
BoxSizing::BorderBox => flow_relative::Vec2 {
|
|
// Clamp to zero to make sure the used size components are non-negative
|
|
inline: min_box_size
|
|
.inline
|
|
.map(|i| (i - pbm.padding_border_sums.inline).max(Length::zero())),
|
|
block: min_box_size
|
|
.block
|
|
.map(|b| (b - pbm.padding_border_sums.block).max(Length::zero())),
|
|
},
|
|
}
|
|
}
|
|
|
|
fn content_max_box_size(
|
|
&self,
|
|
containing_block: &ContainingBlock,
|
|
pbm: &PaddingBorderMargin,
|
|
) -> flow_relative::Vec2<Option<Length>> {
|
|
let max_box_size = self
|
|
.max_box_size(containing_block.style.writing_mode)
|
|
.percentages_relative_to(containing_block);
|
|
match self.get_position().box_sizing {
|
|
BoxSizing::ContentBox => max_box_size,
|
|
BoxSizing::BorderBox => {
|
|
// This may be negative, but will later be clamped by `min-width`
|
|
// which itself is clamped to zero.
|
|
flow_relative::Vec2 {
|
|
inline: max_box_size
|
|
.inline
|
|
.map(|i| i - pbm.padding_border_sums.inline),
|
|
block: max_box_size
|
|
.block
|
|
.map(|b| b - pbm.padding_border_sums.block),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn padding_border_margin(&self, containing_block: &ContainingBlock) -> PaddingBorderMargin {
|
|
let cbis = containing_block.inline_size;
|
|
let padding = self
|
|
.padding(containing_block.style.writing_mode)
|
|
.percentages_relative_to(cbis);
|
|
let border = self.border_width(containing_block.style.writing_mode);
|
|
PaddingBorderMargin {
|
|
padding_border_sums: flow_relative::Vec2 {
|
|
inline: padding.inline_sum() + border.inline_sum(),
|
|
block: padding.block_sum() + border.block_sum(),
|
|
},
|
|
padding,
|
|
border,
|
|
margin: self
|
|
.margin(containing_block.style.writing_mode)
|
|
.percentages_relative_to(cbis),
|
|
}
|
|
}
|
|
|
|
fn padding(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Sides<&LengthPercentage> {
|
|
let padding = self.get_padding();
|
|
flow_relative::Sides::from_physical(
|
|
&PhysicalSides::new(
|
|
&padding.padding_top.0,
|
|
&padding.padding_right.0,
|
|
&padding.padding_bottom.0,
|
|
&padding.padding_left.0,
|
|
),
|
|
containing_block_writing_mode,
|
|
)
|
|
}
|
|
|
|
fn border_width(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Sides<Length> {
|
|
let border = self.get_border();
|
|
flow_relative::Sides::from_physical(
|
|
&PhysicalSides::new(
|
|
border.border_top_width.0,
|
|
border.border_right_width.0,
|
|
border.border_bottom_width.0,
|
|
border.border_left_width.0,
|
|
),
|
|
containing_block_writing_mode,
|
|
)
|
|
}
|
|
|
|
fn margin(
|
|
&self,
|
|
containing_block_writing_mode: WritingMode,
|
|
) -> flow_relative::Sides<LengthPercentageOrAuto<'_>> {
|
|
let margin = self.get_margin();
|
|
flow_relative::Sides::from_physical(
|
|
&PhysicalSides::new(
|
|
margin.margin_top.as_ref(),
|
|
margin.margin_right.as_ref(),
|
|
margin.margin_bottom.as_ref(),
|
|
margin.margin_left.as_ref(),
|
|
),
|
|
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) -> 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
|
|
// FIXME(mrobinson): Properly handle tables and replaced elements here.
|
|
if self.get_box().display.is_inline_flow() {
|
|
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) -> i32 {
|
|
match self.get_box().position {
|
|
ComputedPosition::Static => 0,
|
|
_ => self.get_position().z_index.integer_or(0),
|
|
}
|
|
}
|
|
|
|
/// Returns true if this fragment establishes a new stacking context and false otherwise.
|
|
fn establishes_stacking_context(&self) -> 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() {
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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.
|
|
if self.get_box().position == ComputedPosition::Static {
|
|
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) -> bool {
|
|
if self.establishes_containing_block_for_all_descendants() {
|
|
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) -> bool {
|
|
if self.has_transform_or_perspective() {
|
|
return true;
|
|
}
|
|
|
|
if !self.get_effects().filter.0.is_empty() {
|
|
return true;
|
|
}
|
|
|
|
// TODO: We need to handle CSS Contain here.
|
|
false
|
|
}
|
|
|
|
/// 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 &&
|
|
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(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<stylo::Display> for Display {
|
|
fn from(packed: stylo::Display) -> Self {
|
|
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,
|
|
|
|
// These should not be values of DisplayInside, but oh well
|
|
stylo::DisplayInside::None => return Display::None,
|
|
stylo::DisplayInside::Contents => return Display::Contents,
|
|
};
|
|
let outside = match packed.outside() {
|
|
stylo::DisplayOutside::Block => DisplayOutside::Block,
|
|
stylo::DisplayOutside::Inline => DisplayOutside::Inline,
|
|
|
|
// This should not be a value of DisplayInside, but oh well
|
|
stylo::DisplayOutside::None => return Display::None,
|
|
};
|
|
Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside })
|
|
}
|
|
}
|
|
|
|
fn size_to_length(size: &Size) -> LengthPercentageOrAuto {
|
|
match size {
|
|
Size::LengthPercentage(length) => LengthPercentageOrAuto::LengthPercentage(&length.0),
|
|
Size::Auto => LengthPercentageOrAuto::Auto,
|
|
}
|
|
}
|