summaryrefslogtreecommitdiffstats
path: root/servo/components/style
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:37 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:37 +0000
commita90a5cba08fdf6c0ceb95101c275108a152a3aed (patch)
tree532507288f3defd7f4dcf1af49698bcb76034855 /servo/components/style
parentAdding debian version 126.0.1-1. (diff)
downloadfirefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.tar.xz
firefox-a90a5cba08fdf6c0ceb95101c275108a152a3aed.zip
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo/components/style')
-rw-r--r--servo/components/style/Cargo.toml5
-rw-r--r--servo/components/style/bloom.rs17
-rw-r--r--servo/components/style/color/color_function.rs28
-rw-r--r--servo/components/style/color/component.rs52
-rw-r--r--servo/components/style/color/mod.rs71
-rw-r--r--servo/components/style/color/parsing.rs516
-rw-r--r--servo/components/style/color/to_css.rs8
-rw-r--r--servo/components/style/custom_properties.rs9
-rw-r--r--servo/components/style/data.rs30
-rw-r--r--servo/components/style/gecko/arc_types.rs9
-rw-r--r--servo/components/style/gecko/wrapper.rs12
-rw-r--r--servo/components/style/gecko_bindings/sugar/mod.rs1
-rw-r--r--servo/components/style/gecko_bindings/sugar/ns_t_array.rs144
-rw-r--r--servo/components/style/invalidation/element/document_state.rs5
-rw-r--r--servo/components/style/invalidation/element/relative_selector.rs6
-rw-r--r--servo/components/style/invalidation/element/state_and_attributes.rs4
-rw-r--r--servo/components/style/lib.rs2
-rw-r--r--servo/components/style/matching.rs220
-rw-r--r--servo/components/style/properties/gecko.mako.rs16
-rw-r--r--servo/components/style/properties/mod.rs187
-rw-r--r--servo/components/style/selector_map.rs15
-rw-r--r--servo/components/style/sharing/mod.rs15
-rw-r--r--servo/components/style/style_resolver.rs64
-rw-r--r--servo/components/style/stylesheets/font_feature_values_rule.rs34
-rw-r--r--servo/components/style/stylesheets/margin_rule.rs42
-rw-r--r--servo/components/style/stylist.rs17
-rw-r--r--servo/components/style/traversal.rs16
-rw-r--r--servo/components/style/values/animated/mod.rs27
-rw-r--r--servo/components/style/values/computed/length_percentage.rs41
-rw-r--r--servo/components/style/values/computed/mod.rs19
-rw-r--r--servo/components/style/values/generics/calc.rs243
-rw-r--r--servo/components/style/values/generics/counters.rs37
-rw-r--r--servo/components/style/values/resolved/mod.rs19
-rw-r--r--servo/components/style/values/specified/box.rs24
-rw-r--r--servo/components/style/values/specified/calc.rs82
-rw-r--r--servo/components/style/values/specified/color.rs216
-rw-r--r--servo/components/style/values/specified/counters.rs52
37 files changed, 1283 insertions, 1022 deletions
diff --git a/servo/components/style/Cargo.toml b/servo/components/style/Cargo.toml
index d6e37d4903..7aa9637f70 100644
--- a/servo/components/style/Cargo.toml
+++ b/servo/components/style/Cargo.toml
@@ -30,7 +30,7 @@ arrayvec = "0.7"
atomic_refcell = "0.1"
bitflags = "2"
byteorder = "1.0"
-cssparser = "0.33"
+cssparser = "0.34"
derive_more = { version = "0.99", default-features = false, features = ["add", "add_assign", "deref", "deref_mut", "from"] }
dom = { path = "../../../dom/base/rust" }
new_debug_unreachable = "1.0"
@@ -39,7 +39,7 @@ euclid = "0.22"
fxhash = "0.2"
html5ever = {version = "0.24", optional = true}
icu_segmenter = { version = "1.4", default-features = false, features = ["auto", "compiled_data"] }
-indexmap = {version = "1.0", features = ["std"]}
+indexmap = {version = "2", features = ["std"]}
itertools = "0.10"
itoa = "1.0"
lazy_static = "1"
@@ -52,7 +52,6 @@ num_cpus = {version = "1.1.0"}
num-integer = "0.1"
num-traits = "0.2"
num-derive = "0.4"
-owning_ref = "0.4"
parking_lot = "0.12"
precomputed-hash = "0.1.1"
rayon = "1"
diff --git a/servo/components/style/bloom.rs b/servo/components/style/bloom.rs
index 63be881505..a5f47d8b41 100644
--- a/servo/components/style/bloom.rs
+++ b/servo/components/style/bloom.rs
@@ -10,11 +10,8 @@
use crate::dom::{SendElement, TElement};
use crate::LocalName;
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
-use owning_ref::OwningHandle;
use selectors::bloom::BloomFilter;
-use servo_arc::Arc;
use smallvec::SmallVec;
-use std::mem::ManuallyDrop;
thread_local! {
/// Bloom filters are large allocations, so we store them in thread-local storage
@@ -24,10 +21,12 @@ thread_local! {
/// We intentionally leak this from TLS because we don't have the guarantee
/// of TLS destructors to run in worker threads.
///
+ /// Also, leaking it guarantees that we can borrow it indefinitely.
+ ///
/// We could change this once https://github.com/rayon-rs/rayon/issues/688
- /// is fixed, hopefully.
- static BLOOM_KEY: ManuallyDrop<Arc<AtomicRefCell<BloomFilter>>> =
- ManuallyDrop::new(Arc::new_leaked(Default::default()));
+ /// is fixed, hopefully, which point we'd need to change the filter member below to be an
+ /// arc and carry an owning reference around or so.
+ static BLOOM_KEY: &'static AtomicRefCell<BloomFilter> = Box::leak(Default::default());
}
/// A struct that allows us to fast-reject deep descendant selectors avoiding
@@ -66,7 +65,7 @@ pub struct StyleBloom<E: TElement> {
/// was created. We use AtomicRefCell so that this is all |Send|, which allows
/// StyleBloom to live in ThreadLocalStyleContext, which is dropped from the
/// parent thread.
- filter: OwningHandle<Arc<AtomicRefCell<BloomFilter>>, AtomicRefMut<'static, BloomFilter>>,
+ filter: AtomicRefMut<'static, BloomFilter>,
/// The stack of elements that this bloom filter contains, along with the
/// number of hashes pushed for each element.
@@ -152,9 +151,7 @@ impl<E: TElement> StyleBloom<E> {
// See https://github.com/servo/servo/pull/18420#issuecomment-328769322
#[inline(never)]
pub fn new() -> Self {
- let bloom_arc = BLOOM_KEY.with(|b| Arc::clone(&*b));
- let filter =
- OwningHandle::new_with_fn(bloom_arc, |x| unsafe { x.as_ref() }.unwrap().borrow_mut());
+ let filter = BLOOM_KEY.with(|b| b.borrow_mut());
debug_assert!(
filter.is_zeroed(),
"Forgot to zero the bloom filter last time"
diff --git a/servo/components/style/color/color_function.rs b/servo/components/style/color/color_function.rs
index 2edb4fff3b..426256a2e9 100644
--- a/servo/components/style/color/color_function.rs
+++ b/servo/components/style/color/color_function.rs
@@ -4,7 +4,7 @@
//! Output of parsing a color function, e.g. rgb(..), hsl(..), color(..)
-use crate::values::normalize;
+use crate::{color::ColorFlags, values::normalize};
use cssparser::color::{PredefinedColorSpace, OPAQUE};
use super::{
@@ -30,6 +30,7 @@ pub enum ColorFunction {
ColorComponent<NumberOrPercentage>, // saturation
ColorComponent<NumberOrPercentage>, // lightness
ColorComponent<NumberOrPercentage>, // alpha
+ bool, // is_legacy_syntax
),
/// <https://drafts.csswg.org/css-color-4/#the-hwb-notation>
Hwb(
@@ -37,6 +38,7 @@ pub enum ColorFunction {
ColorComponent<NumberOrPercentage>, // whiteness
ColorComponent<NumberOrPercentage>, // blackness
ColorComponent<NumberOrPercentage>, // alpha
+ bool, // is_legacy_syntax
),
/// <https://drafts.csswg.org/css-color-4/#specifying-lab-lch>
Lab(
@@ -104,31 +106,43 @@ impl ColorFunction {
AbsoluteColor::srgb_legacy(r, g, b, alpha!(alpha).unwrap_or(0.0))
},
- ColorFunction::Hsl(h, s, l, alpha) => {
+ ColorFunction::Hsl(h, s, l, alpha, is_legacy_syntax) => {
// Percent reference range for S and L: 0% = 0.0, 100% = 100.0
const LIGHTNESS_RANGE: f32 = 100.0;
const SATURATION_RANGE: f32 = 100.0;
- AbsoluteColor::new(
+ let mut result = AbsoluteColor::new(
ColorSpace::Hsl,
value!(h).map(|angle| normalize_hue(angle.degrees())),
value!(s).map(|s| s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE)),
value!(l).map(|l| l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE)),
alpha!(alpha),
- )
+ );
+
+ if *is_legacy_syntax {
+ result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
+ }
+
+ result
},
- ColorFunction::Hwb(h, w, b, alpha) => {
+ ColorFunction::Hwb(h, w, b, alpha, is_legacy_syntax) => {
// Percent reference range for W and B: 0% = 0.0, 100% = 100.0
const WHITENESS_RANGE: f32 = 100.0;
const BLACKNESS_RANGE: f32 = 100.0;
- AbsoluteColor::new(
+ let mut result = AbsoluteColor::new(
ColorSpace::Hwb,
value!(h).map(|angle| normalize_hue(angle.degrees())),
value!(w).map(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE)),
value!(b).map(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE)),
alpha!(alpha),
- )
+ );
+
+ if *is_legacy_syntax {
+ result.flags.insert(ColorFlags::IS_LEGACY_SRGB);
+ }
+
+ result
},
ColorFunction::Lab(l, a, b, alpha) => {
// for L: 0% = 0.0, 100% = 100.0
diff --git a/servo/components/style/color/component.rs b/servo/components/style/color/component.rs
index 5f9d8a137e..7edebb6267 100644
--- a/servo/components/style/color/component.rs
+++ b/servo/components/style/color/component.rs
@@ -4,6 +4,10 @@
//! Parse/serialize and resolve a single color component.
+use super::{
+ parsing::{rcs_enabled, ChannelKeyword},
+ AbsoluteColor,
+};
use crate::{
parser::ParserContext,
values::{
@@ -60,6 +64,12 @@ impl<ValueType> ColorComponent<ValueType> {
/// An utility trait that allows the construction of [ColorComponent]
/// `ValueType`'s after parsing a color component.
pub trait ColorComponentType: Sized {
+ // TODO(tlouw): This function should be named according to the rules in the spec
+ // stating that all the values coming from color components are
+ // numbers and that each has their own rules dependeing on types.
+ /// Construct a new component from a single value.
+ fn from_value(value: f32) -> Self;
+
/// Return the [CalcUnits] flags that the impl can handle.
fn units() -> CalcUnits;
@@ -77,6 +87,7 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
context: &ParserContext,
input: &mut Parser<'i, 't>,
allow_none: bool,
+ origin_color: Option<&AbsoluteColor>,
) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location();
@@ -84,11 +95,48 @@ impl<ValueType: ColorComponentType> ColorComponent<ValueType> {
Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
Ok(ColorComponent::None)
},
+ ref t @ Token::Ident(ref ident) if origin_color.is_some() => {
+ if let Ok(channel_keyword) = ChannelKeyword::from_ident(ident) {
+ if let Ok(value) = origin_color
+ .unwrap()
+ .get_component_by_channel_keyword(channel_keyword)
+ {
+ Ok(Self::Value(ValueType::from_value(value.unwrap_or(0.0))))
+ } else {
+ Err(location.new_unexpected_token_error(t.clone()))
+ }
+ } else {
+ Err(location.new_unexpected_token_error(t.clone()))
+ }
+ },
Token::Function(ref name) => {
let function = SpecifiedCalcNode::math_function(context, name, location)?;
- let node = SpecifiedCalcNode::parse(context, input, function, ValueType::units())?;
+ let units = if rcs_enabled() {
+ ValueType::units() | CalcUnits::COLOR_COMPONENT
+ } else {
+ ValueType::units()
+ };
+ let node = SpecifiedCalcNode::parse(context, input, function, units)?;
- let Ok(resolved_leaf) = node.resolve() else {
+ let Ok(resolved_leaf) = node.resolve_map(|leaf| {
+ //
+ Ok(match leaf {
+ SpecifiedLeaf::ColorComponent(channel_keyword) => {
+ if let Some(origin_color) = origin_color {
+ if let Ok(value) =
+ origin_color.get_component_by_channel_keyword(*channel_keyword)
+ {
+ SpecifiedLeaf::Number(value.unwrap_or(0.0))
+ } else {
+ return Err(());
+ }
+ } else {
+ return Err(());
+ }
+ },
+ l => l.clone(),
+ })
+ }) else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
};
diff --git a/servo/components/style/color/mod.rs b/servo/components/style/color/mod.rs
index f7e661cddf..7e427faa84 100644
--- a/servo/components/style/color/mod.rs
+++ b/servo/components/style/color/mod.rs
@@ -15,6 +15,7 @@ pub mod mix;
pub mod parsing;
mod to_css;
+use self::parsing::ChannelKeyword;
use component::ColorComponent;
use cssparser::color::PredefinedColorSpace;
@@ -449,6 +450,76 @@ impl AbsoluteColor {
}
}
+ /// Return the value of a component by its channel keyword.
+ pub fn get_component_by_channel_keyword(
+ &self,
+ channel_keyword: ChannelKeyword,
+ ) -> Result<Option<f32>, ()> {
+ if channel_keyword == ChannelKeyword::Alpha {
+ return Ok(self.alpha());
+ }
+
+ Ok(match self.color_space {
+ ColorSpace::Srgb => {
+ if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
+ match channel_keyword {
+ ChannelKeyword::R => self.c0().map(|v| v * 255.0),
+ ChannelKeyword::G => self.c1().map(|v| v * 255.0),
+ ChannelKeyword::B => self.c2().map(|v| v * 255.0),
+ _ => return Err(()),
+ }
+ } else {
+ match channel_keyword {
+ ChannelKeyword::R => self.c0(),
+ ChannelKeyword::G => self.c1(),
+ ChannelKeyword::B => self.c2(),
+ _ => return Err(()),
+ }
+ }
+ },
+ ColorSpace::Hsl => match channel_keyword {
+ ChannelKeyword::H => self.c0(),
+ ChannelKeyword::S => self.c1(),
+ ChannelKeyword::L => self.c2(),
+ _ => return Err(()),
+ },
+ ColorSpace::Hwb => match channel_keyword {
+ ChannelKeyword::H => self.c0(),
+ ChannelKeyword::W => self.c1(),
+ ChannelKeyword::B => self.c2(),
+ _ => return Err(()),
+ },
+ ColorSpace::Lab | ColorSpace::Oklab => match channel_keyword {
+ ChannelKeyword::L => self.c0(),
+ ChannelKeyword::A => self.c1(),
+ ChannelKeyword::B => self.c2(),
+ _ => return Err(()),
+ },
+ ColorSpace::Lch | ColorSpace::Oklch => match channel_keyword {
+ ChannelKeyword::L => self.c0(),
+ ChannelKeyword::C => self.c1(),
+ ChannelKeyword::H => self.c2(),
+ _ => return Err(()),
+ },
+ ColorSpace::SrgbLinear |
+ ColorSpace::DisplayP3 |
+ ColorSpace::A98Rgb |
+ ColorSpace::ProphotoRgb |
+ ColorSpace::Rec2020 => match channel_keyword {
+ ChannelKeyword::R => self.c0(),
+ ChannelKeyword::G => self.c1(),
+ ChannelKeyword::B => self.c2(),
+ _ => return Err(()),
+ },
+ ColorSpace::XyzD50 | ColorSpace::XyzD65 => match channel_keyword {
+ ChannelKeyword::X => self.c0(),
+ ChannelKeyword::Y => self.c1(),
+ ChannelKeyword::Z => self.c2(),
+ _ => return Err(()),
+ },
+ })
+ }
+
/// Convert this color to the specified color space.
pub fn to_color_space(&self, color_space: ColorSpace) -> Self {
use ColorSpace::*;
diff --git a/servo/components/style/color/parsing.rs b/servo/components/style/color/parsing.rs
index 68bcee6c56..0c8fb2dbaf 100644
--- a/servo/components/style/color/parsing.rs
+++ b/servo/components/style/color/parsing.rs
@@ -4,17 +4,15 @@
#![deny(missing_docs)]
-//! Fairly complete css-color implementation.
-//! Relative colors, color-mix, system colors, and other such things require better calc() support
-//! and integration.
+//! Parsing for CSS colors.
use super::{
color_function::ColorFunction,
component::{ColorComponent, ColorComponentType},
- AbsoluteColor,
+ AbsoluteColor, ColorFlags, ColorSpace,
};
use crate::{
- parser::ParserContext,
+ parser::{Parse, ParserContext},
values::{
generics::calc::CalcUnits,
specified::{
@@ -27,7 +25,6 @@ use cssparser::{
color::{clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE},
match_ignore_ascii_case, CowRcStr, Parser, Token,
};
-use std::str::FromStr;
use style_traits::{ParseError, StyleParseErrorKind};
/// Returns true if the relative color syntax pref is enabled.
@@ -36,11 +33,35 @@ pub fn rcs_enabled() -> bool {
static_prefs::pref!("layout.css.relative-color-syntax.enabled")
}
-impl From<u8> for ColorComponent<u8> {
- #[inline]
- fn from(value: u8) -> Self {
- ColorComponent::Value(value)
- }
+/// Represents a channel keyword inside a color.
+#[derive(Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, PartialOrd, ToCss, ToShmem)]
+pub enum ChannelKeyword {
+ /// alpha
+ Alpha,
+ /// a
+ A,
+ /// b, blackness, blue
+ B,
+ /// chroma
+ C,
+ /// green
+ G,
+ /// hue
+ H,
+ /// lightness
+ L,
+ /// red
+ R,
+ /// saturation
+ S,
+ /// whiteness
+ W,
+ /// x
+ X,
+ /// y
+ Y,
+ /// z
+ Z,
}
/// Return the named color with the given name.
@@ -65,7 +86,7 @@ pub fn parse_color_keyword(ident: &str) -> Result<SpecifiedColor, ()> {
/// Parse a CSS color using the specified [`ColorParser`] and return a new color
/// value on success.
pub fn parse_color_with<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
+ context: &ParserContext,
input: &mut Parser<'i, 't>,
) -> Result<SpecifiedColor, ParseError<'i>> {
let location = input.current_source_location();
@@ -80,7 +101,7 @@ pub fn parse_color_with<'i, 't>(
let name = name.clone();
return input.parse_nested_block(|arguments| {
Ok(SpecifiedColor::from_absolute_color(
- parse_color_function(color_parser, name, arguments)?.resolve_to_absolute(),
+ parse_color_function(context, name, arguments)?.resolve_to_absolute(),
))
});
},
@@ -92,19 +113,26 @@ pub fn parse_color_with<'i, 't>(
/// Parse one of the color functions: rgba(), lab(), color(), etc.
#[inline]
fn parse_color_function<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
+ context: &ParserContext,
name: CowRcStr<'i>,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> {
+ let origin_color = parse_origin_color(context, arguments)?;
+
+ let component_parser = ComponentParser {
+ context,
+ origin_color,
+ };
+
let color = match_ignore_ascii_case! { &name,
- "rgb" | "rgba" => parse_rgb(color_parser, arguments),
- "hsl" | "hsla" => parse_hsl(color_parser, arguments),
- "hwb" => parse_hwb(color_parser, arguments),
- "lab" => parse_lab_like(color_parser, arguments, ColorFunction::Lab),
- "lch" => parse_lch_like(color_parser, arguments, ColorFunction::Lch),
- "oklab" => parse_lab_like(color_parser, arguments, ColorFunction::Oklab),
- "oklch" => parse_lch_like(color_parser, arguments, ColorFunction::Oklch),
- "color" => parse_color_with_color_space(color_parser, arguments),
+ "rgb" | "rgba" => parse_rgb(&component_parser, arguments),
+ "hsl" | "hsla" => parse_hsl(&component_parser, arguments),
+ "hwb" => parse_hwb(&component_parser, arguments),
+ "lab" => parse_lab_like(&component_parser, arguments, ColorSpace::Lab, ColorFunction::Lab),
+ "lch" => parse_lch_like(&component_parser, arguments, ColorSpace::Lch, ColorFunction::Lch),
+ "oklab" => parse_lab_like(&component_parser, arguments, ColorSpace::Oklab, ColorFunction::Oklab),
+ "oklch" => parse_lch_like(&component_parser, arguments, ColorSpace::Oklch, ColorFunction::Oklch),
+ "color" =>parse_color_with_color_space(&component_parser, arguments),
_ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))),
}?;
@@ -113,34 +141,6 @@ fn parse_color_function<'i, 't>(
Ok(color)
}
-fn parse_legacy_alpha<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
- arguments: &mut Parser<'i, 't>,
-) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
- if !arguments.is_exhausted() {
- arguments.expect_comma()?;
- color_parser.parse_number_or_percentage(arguments, false)
- } else {
- Ok(ColorComponent::Value(NumberOrPercentage::Number {
- value: OPAQUE,
- }))
- }
-}
-
-fn parse_modern_alpha<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
- arguments: &mut Parser<'i, 't>,
-) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
- if !arguments.is_exhausted() {
- arguments.expect_delim('/')?;
- color_parser.parse_number_or_percentage(arguments, true)
- } else {
- Ok(ColorComponent::Value(NumberOrPercentage::Number {
- value: OPAQUE,
- }))
- }
-}
-
impl ColorComponent<NumberOrPercentage> {
/// Return true if the component contains a percentage.
pub fn is_percentage(&self) -> Result<bool, ()> {
@@ -153,9 +153,9 @@ impl ColorComponent<NumberOrPercentage> {
/// Parse the relative color syntax "from" syntax `from <color>`.
fn parse_origin_color<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
+ context: &ParserContext,
arguments: &mut Parser<'i, 't>,
-) -> Result<Option<SpecifiedColor>, ParseError<'i>> {
+) -> Result<Option<AbsoluteColor>, ParseError<'i>> {
if !rcs_enabled() {
return Ok(None);
}
@@ -169,72 +169,112 @@ fn parse_origin_color<'i, 't>(
return Ok(None);
}
+ let location = arguments.current_source_location();
+
// We still fail if we can't parse the origin color.
- parse_color_with(color_parser, arguments).map(|color| Some(color))
+ let origin_color = SpecifiedColor::parse(context, arguments)?;
+
+ // Right now we only handle absolute colors.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1890972
+ let Some(computed) = origin_color.to_computed_color(None) else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ };
+
+ Ok(Some(computed.resolve_to_absolute(&AbsoluteColor::BLACK)))
}
#[inline]
fn parse_rgb<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
+ component_parser: &ComponentParser<'_, '_>,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> {
- let origin_color = parse_origin_color(color_parser, arguments)?;
+ let component_parser = ComponentParser {
+ context: component_parser.context,
+ origin_color: component_parser.origin_color.map(|c| {
+ let mut c = c.to_color_space(ColorSpace::Srgb);
+ c.flags.insert(ColorFlags::IS_LEGACY_SRGB);
+ c
+ }),
+ };
let location = arguments.current_source_location();
- let maybe_red = color_parser.parse_number_or_percentage(arguments, true)?;
+ let maybe_red = component_parser.parse_number_or_percentage(arguments, true)?;
// If the first component is not "none" and is followed by a comma, then we
// are parsing the legacy syntax. Legacy syntax also doesn't support an
// origin color.
- let is_legacy_syntax = origin_color.is_none() &&
+ let is_legacy_syntax = component_parser.origin_color.is_none() &&
!maybe_red.is_none() &&
arguments.try_parse(|p| p.expect_comma()).is_ok();
- let (red, green, blue, alpha) = if is_legacy_syntax {
+ Ok(if is_legacy_syntax {
let Ok(is_percentage) = maybe_red.is_percentage() else {
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
};
let (red, green, blue) = if is_percentage {
let red = maybe_red.map_value(|v| clamp_unit_f32(v.to_number(1.0)));
- let green = color_parser
+ let green = component_parser
.parse_percentage(arguments, false)?
.map_value(clamp_unit_f32);
arguments.expect_comma()?;
- let blue = color_parser
+ let blue = component_parser
.parse_percentage(arguments, false)?
.map_value(clamp_unit_f32);
(red, green, blue)
} else {
let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
- let green = color_parser
+ let green = component_parser
.parse_number(arguments, false)?
.map_value(clamp_floor_256_f32);
arguments.expect_comma()?;
- let blue = color_parser
+ let blue = component_parser
.parse_number(arguments, false)?
.map_value(clamp_floor_256_f32);
(red, green, blue)
};
- let alpha = parse_legacy_alpha(color_parser, arguments)?;
+ let alpha = component_parser.parse_legacy_alpha(arguments)?;
- (red, green, blue, alpha)
+ ColorFunction::Rgb(red, green, blue, alpha)
} else {
- let red = maybe_red.map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
- let green = color_parser
- .parse_number_or_percentage(arguments, true)?
- .map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
- let blue = color_parser
- .parse_number_or_percentage(arguments, true)?
- .map_value(|v| clamp_floor_256_f32(v.to_number(255.0)));
-
- let alpha = parse_modern_alpha(color_parser, arguments)?;
+ let green = component_parser.parse_number_or_percentage(arguments, true)?;
+ let blue = component_parser.parse_number_or_percentage(arguments, true)?;
+
+ let alpha = component_parser.parse_modern_alpha(arguments)?;
+
+ // When using the relative color syntax (having an origin color), the
+ // resulting color is always in the modern syntax.
+ if component_parser.origin_color.is_some() {
+ fn adjust(v: NumberOrPercentage) -> NumberOrPercentage {
+ if let NumberOrPercentage::Number { value } = v {
+ NumberOrPercentage::Number {
+ value: value / 255.0,
+ }
+ } else {
+ v
+ }
+ }
+
+ ColorFunction::Color(
+ PredefinedColorSpace::Srgb,
+ maybe_red.map_value(adjust),
+ green.map_value(adjust),
+ blue.map_value(adjust),
+ alpha,
+ )
+ } else {
+ fn clamp(v: NumberOrPercentage) -> u8 {
+ clamp_floor_256_f32(v.to_number(255.0))
+ }
- (red, green, blue, alpha)
- };
+ let red = maybe_red.map_value(clamp);
+ let green = green.map_value(clamp);
+ let blue = blue.map_value(clamp);
- Ok(ColorFunction::Rgb(red, green, blue, alpha))
+ ColorFunction::Rgb(red, green, blue, alpha)
+ }
+ })
}
/// Parses hsl syntax.
@@ -242,43 +282,48 @@ fn parse_rgb<'i, 't>(
/// <https://drafts.csswg.org/css-color/#the-hsl-notation>
#[inline]
fn parse_hsl<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
+ component_parser: &ComponentParser<'_, '_>,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> {
- let origin_color = parse_origin_color(color_parser, arguments)?;
+ let component_parser = ComponentParser {
+ context: component_parser.context,
+ origin_color: component_parser
+ .origin_color
+ .map(|c| c.to_color_space(ColorSpace::Hsl)),
+ };
- let hue = color_parser.parse_number_or_angle(arguments, true)?;
+ let hue = component_parser.parse_number_or_angle(arguments, true)?;
// If the hue is not "none" and is followed by a comma, then we are parsing
// the legacy syntax. Legacy syntax also doesn't support an origin color.
- let is_legacy_syntax = origin_color.is_none() &&
+ let is_legacy_syntax = component_parser.origin_color.is_none() &&
!hue.is_none() &&
arguments.try_parse(|p| p.expect_comma()).is_ok();
let (saturation, lightness, alpha) = if is_legacy_syntax {
- let saturation = color_parser
+ let saturation = component_parser
.parse_percentage(arguments, false)?
.map_value(|unit_value| NumberOrPercentage::Percentage { unit_value });
arguments.expect_comma()?;
- let lightness = color_parser
+ let lightness = component_parser
.parse_percentage(arguments, false)?
.map_value(|unit_value| NumberOrPercentage::Percentage { unit_value });
- (
- saturation,
- lightness,
- parse_legacy_alpha(color_parser, arguments)?,
- )
+ let alpha = component_parser.parse_legacy_alpha(arguments)?;
+ (saturation, lightness, alpha)
} else {
- let saturation = color_parser.parse_number_or_percentage(arguments, true)?;
- let lightness = color_parser.parse_number_or_percentage(arguments, true)?;
- (
- saturation,
- lightness,
- parse_modern_alpha(color_parser, arguments)?,
- )
+ let saturation = component_parser.parse_number_or_percentage(arguments, true)?;
+ let lightness = component_parser.parse_number_or_percentage(arguments, true)?;
+ let alpha = component_parser.parse_modern_alpha(arguments)?;
+ (saturation, lightness, alpha)
};
- Ok(ColorFunction::Hsl(hue, saturation, lightness, alpha))
+ Ok(ColorFunction::Hsl(
+ hue,
+ saturation,
+ lightness,
+ alpha,
+ component_parser.origin_color.is_none(),
+ ))
}
/// Parses hwb syntax.
@@ -286,20 +331,29 @@ fn parse_hsl<'i, 't>(
/// <https://drafts.csswg.org/css-color/#the-hbw-notation>
#[inline]
fn parse_hwb<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
+ component_parser: &ComponentParser<'_, '_>,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> {
- let _origin_color = parse_origin_color(color_parser, arguments)?;
+ let component_parser = ComponentParser {
+ context: component_parser.context,
+ origin_color: component_parser
+ .origin_color
+ .map(|c| c.to_color_space(ColorSpace::Hwb)),
+ };
- let (hue, whiteness, blackness, alpha) = parse_components(
- color_parser,
- arguments,
- ColorParser::parse_number_or_angle,
- ColorParser::parse_number_or_percentage,
- ColorParser::parse_number_or_percentage,
- )?;
+ let hue = component_parser.parse_number_or_angle(arguments, true)?;
+ let whiteness = component_parser.parse_number_or_percentage(arguments, true)?;
+ let blackness = component_parser.parse_number_or_percentage(arguments, true)?;
- Ok(ColorFunction::Hwb(hue, whiteness, blackness, alpha))
+ let alpha = component_parser.parse_modern_alpha(arguments)?;
+
+ Ok(ColorFunction::Hwb(
+ hue,
+ whiteness,
+ blackness,
+ alpha,
+ component_parser.origin_color.is_none(),
+ ))
}
type IntoLabFn<Output> = fn(
@@ -311,19 +365,23 @@ type IntoLabFn<Output> = fn(
#[inline]
fn parse_lab_like<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
+ component_parser: &ComponentParser<'_, '_>,
arguments: &mut Parser<'i, 't>,
+ color_space: ColorSpace,
into_color: IntoLabFn<ColorFunction>,
) -> Result<ColorFunction, ParseError<'i>> {
- let _origin_color = parse_origin_color(color_parser, arguments)?;
+ let component_parser = ComponentParser {
+ context: component_parser.context,
+ origin_color: component_parser
+ .origin_color
+ .map(|c| c.to_color_space(color_space)),
+ };
- let (lightness, a, b, alpha) = parse_components(
- color_parser,
- arguments,
- ColorParser::parse_number_or_percentage,
- ColorParser::parse_number_or_percentage,
- ColorParser::parse_number_or_percentage,
- )?;
+ let lightness = component_parser.parse_number_or_percentage(arguments, true)?;
+ let a = component_parser.parse_number_or_percentage(arguments, true)?;
+ let b = component_parser.parse_number_or_percentage(arguments, true)?;
+
+ let alpha = component_parser.parse_modern_alpha(arguments)?;
Ok(into_color(lightness, a, b, alpha))
}
@@ -337,19 +395,23 @@ type IntoLchFn<Output> = fn(
#[inline]
fn parse_lch_like<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
+ component_parser: &ComponentParser<'_, '_>,
arguments: &mut Parser<'i, 't>,
+ color_space: ColorSpace,
into_color: IntoLchFn<ColorFunction>,
) -> Result<ColorFunction, ParseError<'i>> {
- let _origin_color = parse_origin_color(color_parser, arguments)?;
+ let component_parser = ComponentParser {
+ context: component_parser.context,
+ origin_color: component_parser
+ .origin_color
+ .map(|c| c.to_color_space(color_space)),
+ };
- let (lightness, chroma, hue, alpha) = parse_components(
- color_parser,
- arguments,
- ColorParser::parse_number_or_percentage,
- ColorParser::parse_number_or_percentage,
- ColorParser::parse_number_or_angle,
- )?;
+ let lightness = component_parser.parse_number_or_percentage(arguments, true)?;
+ let chroma = component_parser.parse_number_or_percentage(arguments, true)?;
+ let hue = component_parser.parse_number_or_angle(arguments, true)?;
+
+ let alpha = component_parser.parse_modern_alpha(arguments)?;
Ok(into_color(lightness, chroma, hue, alpha))
}
@@ -357,72 +419,24 @@ fn parse_lch_like<'i, 't>(
/// Parse the color() function.
#[inline]
fn parse_color_with_color_space<'i, 't>(
- color_parser: &ColorParser<'_, '_>,
+ component_parser: &ComponentParser<'_, '_>,
arguments: &mut Parser<'i, 't>,
) -> Result<ColorFunction, ParseError<'i>> {
- let _origin_color = parse_origin_color(color_parser, arguments)?;
-
- let color_space = {
- let location = arguments.current_source_location();
-
- let ident = arguments.expect_ident()?;
- PredefinedColorSpace::from_str(ident)
- .map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone())))?
+ let color_space = PredefinedColorSpace::parse(arguments)?;
+ let component_parser = ComponentParser {
+ context: component_parser.context,
+ origin_color: component_parser
+ .origin_color
+ .map(|c| c.to_color_space(ColorSpace::from(color_space))),
};
- let (c1, c2, c3, alpha) = parse_components(
- color_parser,
- arguments,
- ColorParser::parse_number_or_percentage,
- ColorParser::parse_number_or_percentage,
- ColorParser::parse_number_or_percentage,
- )?;
+ let c1 = component_parser.parse_number_or_percentage(arguments, true)?;
+ let c2 = component_parser.parse_number_or_percentage(arguments, true)?;
+ let c3 = component_parser.parse_number_or_percentage(arguments, true)?;
- Ok(ColorFunction::Color(color_space, c1, c2, c3, alpha))
-}
+ let alpha = component_parser.parse_modern_alpha(arguments)?;
-type ComponentParseResult<'i, R1, R2, R3> = Result<
- (
- ColorComponent<R1>,
- ColorComponent<R2>,
- ColorComponent<R3>,
- ColorComponent<NumberOrPercentage>,
- ),
- ParseError<'i>,
->;
-
-/// Parse the color components and alpha with the modern [color-4] syntax.
-pub fn parse_components<'a, 'b: 'a, 'i, 't, F1, F2, F3, R1, R2, R3>(
- color_parser: &ColorParser<'a, 'b>,
- input: &mut Parser<'i, 't>,
- f1: F1,
- f2: F2,
- f3: F3,
-) -> ComponentParseResult<'i, R1, R2, R3>
-where
- F1: FnOnce(
- &ColorParser<'a, 'b>,
- &mut Parser<'i, 't>,
- bool,
- ) -> Result<ColorComponent<R1>, ParseError<'i>>,
- F2: FnOnce(
- &ColorParser<'a, 'b>,
- &mut Parser<'i, 't>,
- bool,
- ) -> Result<ColorComponent<R2>, ParseError<'i>>,
- F3: FnOnce(
- &ColorParser<'a, 'b>,
- &mut Parser<'i, 't>,
- bool,
- ) -> Result<ColorComponent<R3>, ParseError<'i>>,
-{
- let r1 = f1(color_parser, input, true)?;
- let r2 = f2(color_parser, input, true)?;
- let r3 = f3(color_parser, input, true)?;
-
- let alpha = parse_modern_alpha(color_parser, input)?;
-
- Ok((r1, r2, r3, alpha))
+ Ok(ColorFunction::Color(color_space, c1, c2, c3, alpha))
}
/// Either a number or a percentage.
@@ -453,6 +467,10 @@ impl NumberOrPercentage {
}
impl ColorComponentType for NumberOrPercentage {
+ fn from_value(value: f32) -> Self {
+ Self::Number { value }
+ }
+
fn units() -> CalcUnits {
CalcUnits::PERCENTAGE
}
@@ -503,6 +521,10 @@ impl NumberOrAngle {
}
impl ColorComponentType for NumberOrAngle {
+ fn from_value(value: f32) -> Self {
+ Self::Number { value }
+ }
+
fn units() -> CalcUnits {
CalcUnits::ANGLE
}
@@ -538,6 +560,10 @@ impl ColorComponentType for NumberOrAngle {
/// The raw f32 here is for <number>.
impl ColorComponentType for f32 {
+ fn from_value(value: f32) -> Self {
+ value
+ }
+
fn units() -> CalcUnits {
CalcUnits::empty()
}
@@ -560,19 +586,29 @@ impl ColorComponentType for f32 {
}
/// Used to parse the components of a color.
-pub struct ColorParser<'a, 'b: 'a> {
+pub struct ComponentParser<'a, 'b: 'a> {
/// Parser context used for parsing the colors.
pub context: &'a ParserContext<'b>,
+ /// The origin color that will be used to resolve relative components.
+ pub origin_color: Option<AbsoluteColor>,
}
-impl<'a, 'b: 'a> ColorParser<'a, 'b> {
+impl<'a, 'b: 'a> ComponentParser<'a, 'b> {
+ /// Create a new [ColorParser] with the given context.
+ pub fn new(context: &'a ParserContext<'b>) -> Self {
+ Self {
+ context,
+ origin_color: None,
+ }
+ }
+
/// Parse an `<number>` or `<angle>` value.
fn parse_number_or_angle<'i, 't>(
&self,
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> {
- ColorComponent::parse(self.context, input, allow_none)
+ ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref())
}
/// Parse a `<percentage>` value.
@@ -587,7 +623,12 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> {
// doesn't have any more overhead than just parsing a percentage on its
// own.
Ok(
- match ColorComponent::<NumberOrPercentage>::parse(self.context, input, allow_none)? {
+ match ColorComponent::<NumberOrPercentage>::parse(
+ self.context,
+ input,
+ allow_none,
+ self.origin_color.as_ref(),
+ )? {
ColorComponent::None => ColorComponent::None,
ColorComponent::Value(NumberOrPercentage::Percentage { unit_value }) => {
ColorComponent::Value(unit_value)
@@ -603,7 +644,7 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> {
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<f32>, ParseError<'i>> {
- ColorComponent::parse(self.context, input, allow_none)
+ ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref())
}
/// Parse a `<number>` or `<percentage>` value.
@@ -612,77 +653,34 @@ impl<'a, 'b: 'a> ColorParser<'a, 'b> {
input: &mut Parser<'i, 't>,
allow_none: bool,
) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
- ColorComponent::parse(self.context, input, allow_none)
+ ColorComponent::parse(self.context, input, allow_none, self.origin_color.as_ref())
}
-}
-/// This trait is used by the [`ColorParser`] to construct colors of any type.
-pub trait FromParsedColor {
- /// Construct a new color from the CSS `currentcolor` keyword.
- fn from_current_color() -> Self;
-
- /// Construct a new color from red, green, blue and alpha components.
- fn from_rgba(
- red: ColorComponent<u8>,
- green: ColorComponent<u8>,
- blue: ColorComponent<u8>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self;
-
- /// Construct a new color from hue, saturation, lightness and alpha components.
- fn from_hsl(
- hue: ColorComponent<NumberOrAngle>,
- saturation: ColorComponent<NumberOrPercentage>,
- lightness: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self;
-
- /// Construct a new color from hue, blackness, whiteness and alpha components.
- fn from_hwb(
- hue: ColorComponent<NumberOrAngle>,
- whiteness: ColorComponent<NumberOrPercentage>,
- blackness: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self;
-
- /// Construct a new color from the `lab` notation.
- fn from_lab(
- lightness: ColorComponent<NumberOrPercentage>,
- a: ColorComponent<NumberOrPercentage>,
- b: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self;
-
- /// Construct a new color from the `lch` notation.
- fn from_lch(
- lightness: ColorComponent<NumberOrPercentage>,
- chroma: ColorComponent<NumberOrPercentage>,
- hue: ColorComponent<NumberOrAngle>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self;
-
- /// Construct a new color from the `oklab` notation.
- fn from_oklab(
- lightness: ColorComponent<NumberOrPercentage>,
- a: ColorComponent<NumberOrPercentage>,
- b: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self;
-
- /// Construct a new color from the `oklch` notation.
- fn from_oklch(
- lightness: ColorComponent<NumberOrPercentage>,
- chroma: ColorComponent<NumberOrPercentage>,
- hue: ColorComponent<NumberOrAngle>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self;
-
- /// Construct a new color with a predefined color space.
- fn from_color_function(
- color_space: PredefinedColorSpace,
- c1: ColorComponent<NumberOrPercentage>,
- c2: ColorComponent<NumberOrPercentage>,
- c3: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self;
+ fn parse_legacy_alpha<'i, 't>(
+ &self,
+ arguments: &mut Parser<'i, 't>,
+ ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
+ if !arguments.is_exhausted() {
+ arguments.expect_comma()?;
+ self.parse_number_or_percentage(arguments, false)
+ } else {
+ Ok(ColorComponent::Value(NumberOrPercentage::Number {
+ value: OPAQUE,
+ }))
+ }
+ }
+
+ fn parse_modern_alpha<'i, 't>(
+ &self,
+ arguments: &mut Parser<'i, 't>,
+ ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
+ if !arguments.is_exhausted() {
+ arguments.expect_delim('/')?;
+ self.parse_number_or_percentage(arguments, true)
+ } else {
+ Ok(ColorComponent::Value(NumberOrPercentage::Number {
+ value: self.origin_color.map(|c| c.alpha).unwrap_or(OPAQUE),
+ }))
+ }
+ }
}
diff --git a/servo/components/style/color/to_css.rs b/servo/components/style/color/to_css.rs
index 350d8386f1..aba809759a 100644
--- a/servo/components/style/color/to_css.rs
+++ b/servo/components/style/color/to_css.rs
@@ -60,7 +60,13 @@ impl ToCss for AbsoluteColor {
dest.write_char(')')
},
- ColorSpace::Hsl | ColorSpace::Hwb => self.into_srgb_legacy().to_css(dest),
+ ColorSpace::Hsl | ColorSpace::Hwb => {
+ if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) {
+ self.into_srgb_legacy().to_css(dest)
+ } else {
+ self.to_color_space(ColorSpace::Srgb).to_css(dest)
+ }
+ },
ColorSpace::Oklab | ColorSpace::Lab | ColorSpace::Oklch | ColorSpace::Lch => {
if let ColorSpace::Oklab | ColorSpace::Oklch = self.color_space {
dest.write_str("ok")?;
diff --git a/servo/components/style/custom_properties.rs b/servo/components/style/custom_properties.rs
index 766fe530d9..3a7beff90a 100644
--- a/servo/components/style/custom_properties.rs
+++ b/servo/components/style/custom_properties.rs
@@ -274,6 +274,11 @@ impl ComputedCustomProperties {
name: &Name,
value: ComputedRegisteredValue,
) {
+ // Broadening the assert to
+ // registration.syntax.is_universal() ^ value.as_universal().is_none() would require
+ // rewriting the cascade to not temporarily store unparsed custom properties with references
+ // as universal in the custom properties map.
+ debug_assert!(!registration.syntax.is_universal() || value.as_universal().is_some());
self.map_mut(registration).insert(name, value)
}
@@ -1925,13 +1930,13 @@ fn do_substitute_chunk<'a>(
computed_context,
references,
)?;
+ let substitution = substitution.into_universal();
// Optimize the property: var(--...) case to avoid allocating at all.
if reference.start == start && reference.end == end && registration.syntax.is_universal() {
- return Ok(substitution);
+ return Ok(Substitution::Universal(substitution));
}
- let substitution = substitution.into_universal();
substituted.push(
&substitution.css,
substitution.first_token_type,
diff --git a/servo/components/style/data.rs b/servo/components/style/data.rs
index ceddc5bd20..366318b658 100644
--- a/servo/components/style/data.rs
+++ b/servo/components/style/data.rs
@@ -45,6 +45,9 @@ bitflags! {
/// The former gives us stronger transitive guarantees that allows us to
/// apply the style sharing cache to cousins.
const PRIMARY_STYLE_REUSED_VIA_RULE_NODE = 1 << 2;
+
+ /// Whether this element may have matched rules inside @starting-style.
+ const MAY_HAVE_STARTING_STYLE = 1 << 3;
}
}
@@ -344,22 +347,28 @@ impl ElementData {
let reused_via_rule_node = self
.flags
.contains(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
+ let may_have_starting_style = self
+ .flags
+ .contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE);
PrimaryStyle {
style: ResolvedStyle(self.styles.primary().clone()),
reused_via_rule_node,
+ may_have_starting_style,
}
}
/// Sets a new set of styles, returning the old ones.
pub fn set_styles(&mut self, new_styles: ResolvedElementStyles) -> ElementStyles {
- if new_styles.primary.reused_via_rule_node {
- self.flags
- .insert(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
- } else {
- self.flags
- .remove(ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE);
- }
+ self.flags.set(
+ ElementDataFlags::PRIMARY_STYLE_REUSED_VIA_RULE_NODE,
+ new_styles.primary.reused_via_rule_node,
+ );
+ self.flags.set(
+ ElementDataFlags::MAY_HAVE_STARTING_STYLE,
+ new_styles.primary.may_have_starting_style,
+ );
+
mem::replace(&mut self.styles, new_styles.into())
}
@@ -542,4 +551,11 @@ impl ElementData {
n
}
+
+ /// Returns true if this element data may need to compute the starting style for CSS
+ /// transitions.
+ #[inline]
+ pub fn may_have_starting_style(&self) -> bool {
+ self.flags.contains(ElementDataFlags::MAY_HAVE_STARTING_STYLE)
+ }
}
diff --git a/servo/components/style/gecko/arc_types.rs b/servo/components/style/gecko/arc_types.rs
index 420b86d332..259d9f1570 100644
--- a/servo/components/style/gecko/arc_types.rs
+++ b/servo/components/style/gecko/arc_types.rs
@@ -16,8 +16,8 @@ use crate::stylesheets::keyframes_rule::Keyframe;
use crate::stylesheets::{
ContainerRule, CounterStyleRule, CssRules, DocumentRule, FontFaceRule, FontFeatureValuesRule,
FontPaletteValuesRule, ImportRule, KeyframesRule, LayerBlockRule, LayerStatementRule,
- MediaRule, NamespaceRule, PageRule, PropertyRule, ScopeRule, StartingStyleRule, StyleRule,
- StylesheetContents, SupportsRule,
+ MarginRule, MediaRule, NamespaceRule, PageRule, PropertyRule, ScopeRule, StartingStyleRule,
+ StyleRule, StylesheetContents, SupportsRule,
};
use servo_arc::Arc;
@@ -101,6 +101,11 @@ impl_simple_arc_ffi!(
Servo_NamespaceRule_AddRef,
Servo_NamespaceRule_Release
);
+impl_simple_arc_ffi!(
+ MarginRule,
+ Servo_MarginRule_AddRef,
+ Servo_MarginRule_Release
+);
impl_locked_arc_ffi!(
PageRule,
LockedPageRule,
diff --git a/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs
index eab968149c..5e2d893b15 100644
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -577,14 +577,14 @@ pub enum GeckoChildrenIterator<'a> {
/// replaces it with the next sibling when requested.
Current(Option<GeckoNode<'a>>),
/// A Gecko-implemented iterator we need to drop appropriately.
- GeckoIterator(structs::StyleChildrenIterator),
+ GeckoIterator(std::mem::ManuallyDrop<structs::StyleChildrenIterator>),
}
impl<'a> Drop for GeckoChildrenIterator<'a> {
fn drop(&mut self) {
if let GeckoChildrenIterator::GeckoIterator(ref mut it) = *self {
unsafe {
- bindings::Gecko_DestroyStyleChildrenIterator(it);
+ bindings::Gecko_DestroyStyleChildrenIterator(&mut **it);
}
}
}
@@ -605,7 +605,7 @@ impl<'a> Iterator for GeckoChildrenIterator<'a> {
// however we can't express this easily with bindgen, and it would
// introduce functions with two input lifetimes into bindgen,
// which would be out of scope for elision.
- bindings::Gecko_GetNextStyleChild(&mut *(it as *mut _))
+ bindings::Gecko_GetNextStyleChild(&mut **it)
.as_ref()
.map(GeckoNode)
},
@@ -1015,9 +1015,9 @@ impl<'le> TElement for GeckoElement<'le> {
self.may_have_anonymous_children()
{
unsafe {
- let mut iter: structs::StyleChildrenIterator = ::std::mem::zeroed();
- bindings::Gecko_ConstructStyleChildrenIterator(self.0, &mut iter);
- return LayoutIterator(GeckoChildrenIterator::GeckoIterator(iter));
+ let mut iter = std::mem::MaybeUninit::<structs::StyleChildrenIterator>::uninit();
+ bindings::Gecko_ConstructStyleChildrenIterator(self.0, iter.as_mut_ptr());
+ return LayoutIterator(GeckoChildrenIterator::GeckoIterator(std::mem::ManuallyDrop::new(iter.assume_init())));
}
}
diff --git a/servo/components/style/gecko_bindings/sugar/mod.rs b/servo/components/style/gecko_bindings/sugar/mod.rs
index 00faf63ba6..e639abeda0 100644
--- a/servo/components/style/gecko_bindings/sugar/mod.rs
+++ b/servo/components/style/gecko_bindings/sugar/mod.rs
@@ -7,7 +7,6 @@
mod ns_com_ptr;
mod ns_compatibility;
mod ns_style_auto_array;
-mod ns_t_array;
pub mod origin_flags;
pub mod ownership;
pub mod refptr;
diff --git a/servo/components/style/gecko_bindings/sugar/ns_t_array.rs b/servo/components/style/gecko_bindings/sugar/ns_t_array.rs
deleted file mode 100644
index d10ed420dd..0000000000
--- a/servo/components/style/gecko_bindings/sugar/ns_t_array.rs
+++ /dev/null
@@ -1,144 +0,0 @@
-/* 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/. */
-
-//! Rust helpers for Gecko's nsTArray.
-
-use crate::gecko_bindings::bindings;
-use crate::gecko_bindings::structs::{nsTArray, nsTArrayHeader, CopyableTArray};
-use std::mem;
-use std::ops::{Deref, DerefMut};
-use std::slice;
-
-impl<T> Deref for nsTArray<T> {
- type Target = [T];
-
- #[inline]
- fn deref<'a>(&'a self) -> &'a [T] {
- unsafe { slice::from_raw_parts(self.slice_begin(), self.header().mLength as usize) }
- }
-}
-
-impl<T> DerefMut for nsTArray<T> {
- fn deref_mut<'a>(&'a mut self) -> &'a mut [T] {
- unsafe { slice::from_raw_parts_mut(self.slice_begin(), self.header().mLength as usize) }
- }
-}
-
-impl<T> nsTArray<T> {
- #[inline]
- fn header<'a>(&'a self) -> &'a nsTArrayHeader {
- debug_assert!(!self.mBuffer.is_null());
- unsafe { mem::transmute(self.mBuffer) }
- }
-
- // unsafe, since header may be in shared static or something
- unsafe fn header_mut<'a>(&'a mut self) -> &'a mut nsTArrayHeader {
- debug_assert!(!self.mBuffer.is_null());
-
- mem::transmute(self.mBuffer)
- }
-
- #[inline]
- unsafe fn slice_begin(&self) -> *mut T {
- debug_assert!(!self.mBuffer.is_null());
- (self.mBuffer as *const nsTArrayHeader).offset(1) as *mut _
- }
-
- /// Ensures the array has enough capacity at least to hold `cap` elements.
- ///
- /// NOTE: This doesn't call the constructor on the values!
- pub fn ensure_capacity(&mut self, cap: usize) {
- if cap >= self.len() {
- unsafe {
- bindings::Gecko_EnsureTArrayCapacity(
- self as *mut nsTArray<T> as *mut _,
- cap,
- mem::size_of::<T>(),
- )
- }
- }
- }
-
- /// Clears the array storage without calling the destructor on the values.
- #[inline]
- pub unsafe fn clear(&mut self) {
- if self.len() != 0 {
- bindings::Gecko_ClearPODTArray(
- self as *mut nsTArray<T> as *mut _,
- mem::size_of::<T>(),
- mem::align_of::<T>(),
- );
- }
- }
-
- /// Clears a POD array. This is safe since copy types are memcopyable.
- #[inline]
- pub fn clear_pod(&mut self)
- where
- T: Copy,
- {
- unsafe { self.clear() }
- }
-
- /// Resize and set the length of the array to `len`.
- ///
- /// unsafe because this may leave the array with uninitialized elements.
- ///
- /// This will not call constructors. If you need that, either manually add
- /// bindings or run the typed `EnsureCapacity` call on the gecko side.
- pub unsafe fn set_len(&mut self, len: u32) {
- // this can leak
- debug_assert!(len >= self.len() as u32);
- if self.len() == len as usize {
- return;
- }
- self.ensure_capacity(len as usize);
- self.header_mut().mLength = len;
- }
-
- /// Resizes an array containing only POD elements
- ///
- /// unsafe because this may leave the array with uninitialized elements.
- ///
- /// This will not leak since it only works on POD types (and thus doesn't assert)
- pub unsafe fn set_len_pod(&mut self, len: u32)
- where
- T: Copy,
- {
- if self.len() == len as usize {
- return;
- }
- self.ensure_capacity(len as usize);
- let header = self.header_mut();
- header.mLength = len;
- }
-
- /// Collects the given iterator into this array.
- ///
- /// Not unsafe because we won't leave uninitialized elements in the array.
- pub fn assign_from_iter_pod<I>(&mut self, iter: I)
- where
- T: Copy,
- I: ExactSizeIterator + Iterator<Item = T>,
- {
- debug_assert!(iter.len() <= 0xFFFFFFFF);
- unsafe {
- self.set_len_pod(iter.len() as u32);
- }
- self.iter_mut().zip(iter).for_each(|(r, v)| *r = v);
- }
-}
-
-impl<T> Deref for CopyableTArray<T> {
- type Target = nsTArray<T>;
- fn deref(&self) -> &Self::Target {
- &self._base
- }
-}
-
-impl<T> DerefMut for CopyableTArray<T> {
- fn deref_mut(&mut self) -> &mut nsTArray<T> {
- &mut self._base
- }
-}
diff --git a/servo/components/style/invalidation/element/document_state.rs b/servo/components/style/invalidation/element/document_state.rs
index 0b846510d8..5437943066 100644
--- a/servo/components/style/invalidation/element/document_state.rs
+++ b/servo/components/style/invalidation/element/document_state.rs
@@ -14,8 +14,8 @@ use crate::invalidation::element::state_and_attributes;
use crate::stylist::CascadeData;
use dom::DocumentState;
use selectors::matching::{
- MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, QuirksMode,
- SelectorCaches, VisitedHandlingMode,
+ IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode,
+ NeedsSelectorFlags, QuirksMode, SelectorCaches, VisitedHandlingMode,
};
/// A struct holding the members necessary to invalidate document state
@@ -59,6 +59,7 @@ impl<'a, 'b, E: TElement, I> DocumentStateInvalidationProcessor<'a, 'b, E, I> {
None,
selector_caches,
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
+ IncludeStartingStyle::No,
quirks_mode,
NeedsSelectorFlags::No,
MatchingForInvalidation::No,
diff --git a/servo/components/style/invalidation/element/relative_selector.rs b/servo/components/style/invalidation/element/relative_selector.rs
index 41222304be..d57676905b 100644
--- a/servo/components/style/invalidation/element/relative_selector.rs
+++ b/servo/components/style/invalidation/element/relative_selector.rs
@@ -26,8 +26,8 @@ use dom::ElementState;
use fxhash::FxHashMap;
use selectors::matching::{
matches_compound_selector_from, matches_selector, CompoundSelectorMatchingResult,
- ElementSelectorFlags, MatchingContext, MatchingForInvalidation, MatchingMode,
- NeedsSelectorFlags, QuirksMode, SelectorCaches, VisitedHandlingMode,
+ ElementSelectorFlags, IncludeStartingStyle, MatchingContext, MatchingForInvalidation,
+ MatchingMode, NeedsSelectorFlags, QuirksMode, SelectorCaches, VisitedHandlingMode,
};
use selectors::parser::{Combinator, SelectorKey};
use selectors::OpaqueElement;
@@ -822,6 +822,7 @@ where
None,
&mut selector_caches,
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
+ IncludeStartingStyle::No,
self.quirks_mode,
NeedsSelectorFlags::No,
MatchingForInvalidation::Yes,
@@ -1032,6 +1033,7 @@ where
None,
selector_caches,
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
+ IncludeStartingStyle::No,
quirks_mode,
NeedsSelectorFlags::No,
MatchingForInvalidation::Yes,
diff --git a/servo/components/style/invalidation/element/state_and_attributes.rs b/servo/components/style/invalidation/element/state_and_attributes.rs
index d5f0723d66..ffb2302b55 100644
--- a/servo/components/style/invalidation/element/state_and_attributes.rs
+++ b/servo/components/style/invalidation/element/state_and_attributes.rs
@@ -24,7 +24,8 @@ use dom::ElementState;
use selectors::attr::CaseSensitivity;
use selectors::kleene_value::KleeneValue;
use selectors::matching::{
- matches_selector_kleene, MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode
+ matches_selector_kleene, IncludeStartingStyle, MatchingContext, MatchingForInvalidation,
+ MatchingMode, NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode
};
use smallvec::SmallVec;
@@ -73,6 +74,7 @@ impl<'a, 'b: 'a, E: TElement + 'b> StateAndAttrInvalidationProcessor<'a, 'b, E>
None,
selector_caches,
VisitedHandlingMode::AllLinksVisitedAndUnvisited,
+ IncludeStartingStyle::No,
shared_context.quirks_mode(),
NeedsSelectorFlags::No,
MatchingForInvalidation::Yes,
diff --git a/servo/components/style/lib.rs b/servo/components/style/lib.rs
index b2a1e20ce8..d390ee371b 100644
--- a/servo/components/style/lib.rs
+++ b/servo/components/style/lib.rs
@@ -69,6 +69,8 @@ extern crate static_assertions;
#[macro_use]
extern crate style_derive;
#[macro_use]
+extern crate thin_vec;
+#[macro_use]
extern crate to_shmem_derive;
#[macro_use]
diff --git a/servo/components/style/matching.rs b/servo/components/style/matching.rs
index e9d754aa10..739504d260 100644
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -21,8 +21,10 @@ use crate::properties::PropertyDeclarationBlock;
use crate::rule_tree::{CascadeLevel, StrongRuleNode};
use crate::selector_parser::{PseudoElement, RestyleDamage};
use crate::shared_lock::Locked;
-use crate::style_resolver::ResolvedElementStyles;
-use crate::style_resolver::{PseudoElementResolution, StyleResolverForElement};
+use crate::style_resolver::{PseudoElementResolution, ResolvedElementStyles};
+#[cfg(feature = "gecko")]
+use crate::style_resolver::ResolvedStyle;
+use crate::style_resolver::StyleResolverForElement;
use crate::stylesheets::layer_rule::LayerOrder;
use crate::stylist::RuleInclusion;
use crate::traversal_flags::TraversalFlags;
@@ -385,6 +387,158 @@ trait PrivateMatchMethods: TElement {
}
#[cfg(feature = "gecko")]
+ fn resolve_starting_style(
+ &self,
+ context: &mut StyleContext<Self>,
+ ) -> ResolvedStyle {
+ use selectors::matching::IncludeStartingStyle;
+
+ // Compute after-change style for the parent and the layout parent.
+ // Per spec, starting style inherits from the parent’s after-change style just like
+ // after-change style does.
+ let parent_el = self.inheritance_parent();
+ let parent_data = parent_el.as_ref().and_then(|e| e.borrow_data());
+ let parent_style = parent_data.as_ref().map(|d| d.styles.primary());
+ let parent_after_change_style =
+ parent_style.and_then(|s| self.after_change_style(context, s));
+ let parent_values = parent_after_change_style
+ .as_ref()
+ .or(parent_style)
+ .map(|x| &**x);
+
+ let mut layout_parent_el = parent_el.clone();
+ let layout_parent_data;
+ let layout_parent_after_change_style;
+ let layout_parent_values = if parent_style.map_or(false, |s| s.is_display_contents()) {
+ layout_parent_el = Some(layout_parent_el.unwrap().layout_parent());
+ layout_parent_data = layout_parent_el.as_ref().unwrap().borrow_data().unwrap();
+ let layout_parent_style = Some(layout_parent_data.styles.primary());
+ layout_parent_after_change_style =
+ layout_parent_style.and_then(|s| self.after_change_style(context, s));
+ layout_parent_after_change_style
+ .as_ref()
+ .or(layout_parent_style)
+ .map(|x| &**x)
+ } else {
+ parent_values
+ };
+
+ // Note: Basically, we have to remove transition rules because the starting style for an
+ // element is the after-change style with @starting-style rules applied in addition.
+ // However, we expect there is no transition rules for this element when calling this
+ // function because we do this only when we don't have before-change style or we change
+ // from display:none. In these cases, it's unlikely to have running transitions on this
+ // element.
+ let mut resolver = StyleResolverForElement::new(
+ *self,
+ context,
+ RuleInclusion::All,
+ PseudoElementResolution::IfApplicable,
+ );
+ resolver
+ .resolve_primary_style(
+ parent_values,
+ layout_parent_values,
+ IncludeStartingStyle::Yes,
+ )
+ .style
+ }
+
+ #[cfg(feature = "gecko")]
+ fn maybe_resolve_starting_style(
+ &self,
+ context: &mut StyleContext<Self>,
+ old_values: Option<&Arc<ComputedValues>>,
+ new_styles: &ResolvedElementStyles,
+ ) -> Option<Arc<ComputedValues>> {
+ // For both cases:
+ // 1. If we didn't see any starting-style rules for this given element during full matching.
+ // 2. If there is no transitions specified.
+ // We don't have to resolve starting style.
+ if !new_styles.may_have_starting_style()
+ || !new_styles.primary_style().get_ui().specifies_transitions()
+ {
+ return None;
+ }
+
+ // We resolve starting style only if we don't have before-change-style, or we change from
+ // display:none.
+ if old_values.is_some()
+ && !new_styles
+ .primary_style()
+ .is_display_property_changed_from_none(old_values.map(|s| &**s))
+ {
+ return None;
+ }
+
+ let starting_style = self.resolve_starting_style(context);
+ if starting_style.style().clone_display().is_none() {
+ return None;
+ }
+
+ Some(starting_style.0)
+ }
+
+ /// Handle CSS Transitions. Returns None if we don't need to update transitions. And it returns
+ /// the before-change style per CSS Transitions spec.
+ ///
+ /// Note: The before-change style could be the computed values of all properties on the element
+ /// as of the previous style change event, or the starting style if we don't have the valid
+ /// before-change style there.
+ #[cfg(feature = "gecko")]
+ fn process_transitions(
+ &self,
+ context: &mut StyleContext<Self>,
+ old_values: Option<&Arc<ComputedValues>>,
+ new_styles: &mut ResolvedElementStyles,
+ ) -> Option<Arc<ComputedValues>> {
+ let starting_values = self.maybe_resolve_starting_style(context, old_values, new_styles);
+ let before_change_or_starting = if starting_values.is_some() {
+ starting_values.as_ref()
+ } else {
+ old_values
+ };
+ let new_values = new_styles.primary_style_mut();
+
+ if !self.might_need_transitions_update(
+ context,
+ before_change_or_starting.map(|s| &**s),
+ new_values,
+ /* pseudo_element = */ None,
+ ) {
+ return None;
+ }
+
+ let after_change_style =
+ if self.has_css_transitions(context.shared, /* pseudo_element = */ None) {
+ self.after_change_style(context, new_values)
+ } else {
+ None
+ };
+
+ // In order to avoid creating a SequentialTask for transitions which
+ // may not be updated, we check it per property to make sure Gecko
+ // side will really update transition.
+ if !self.needs_transitions_update(
+ before_change_or_starting.unwrap(),
+ after_change_style.as_ref().unwrap_or(&new_values),
+ ) {
+ return None;
+ }
+
+ if let Some(values_without_transitions) = after_change_style {
+ *new_values = values_without_transitions;
+ }
+
+ // Move the new-created starting style, or clone the old values.
+ if starting_values.is_some() {
+ starting_values
+ } else {
+ old_values.cloned()
+ }
+ }
+
+ #[cfg(feature = "gecko")]
fn process_animations(
&self,
context: &mut StyleContext<Self>,
@@ -395,13 +549,12 @@ trait PrivateMatchMethods: TElement {
) {
use crate::context::UpdateAnimationsTasks;
- let new_values = new_styles.primary_style_mut();
let old_values = &old_styles.primary;
if context.shared.traversal_flags.for_animation_only() {
self.handle_display_change_for_smil_if_needed(
context,
old_values.as_deref(),
- new_values,
+ new_styles.primary_style(),
restyle_hint,
);
return;
@@ -413,15 +566,15 @@ trait PrivateMatchMethods: TElement {
let mut tasks = UpdateAnimationsTasks::empty();
if old_values.as_deref().map_or_else(
- || new_values.get_ui().specifies_scroll_timelines(),
- |old| !old.get_ui().scroll_timelines_equals(new_values.get_ui()),
+ || new_styles.primary_style().get_ui().specifies_scroll_timelines(),
+ |old| !old.get_ui().scroll_timelines_equals(new_styles.primary_style().get_ui()),
) {
tasks.insert(UpdateAnimationsTasks::SCROLL_TIMELINES);
}
if old_values.as_deref().map_or_else(
- || new_values.get_ui().specifies_view_timelines(),
- |old| !old.get_ui().view_timelines_equals(new_values.get_ui()),
+ || new_styles.primary_style().get_ui().specifies_view_timelines(),
+ |old| !old.get_ui().view_timelines_equals(new_styles.primary_style().get_ui()),
) {
tasks.insert(UpdateAnimationsTasks::VIEW_TIMELINES);
}
@@ -429,58 +582,27 @@ trait PrivateMatchMethods: TElement {
if self.needs_animations_update(
context,
old_values.as_deref(),
- new_values,
+ new_styles.primary_style(),
/* pseudo_element = */ None,
) {
tasks.insert(UpdateAnimationsTasks::CSS_ANIMATIONS);
}
- let before_change_style = if self.might_need_transitions_update(
- context,
- old_values.as_deref(),
- new_values,
- /* pseudo_element = */ None,
- ) {
- let after_change_style =
- if self.has_css_transitions(context.shared, /* pseudo_element = */ None) {
- self.after_change_style(context, new_values)
- } else {
- None
- };
-
- // In order to avoid creating a SequentialTask for transitions which
- // may not be updated, we check it per property to make sure Gecko
- // side will really update transition.
- let needs_transitions_update = {
- // We borrow new_values here, so need to add a scope to make
- // sure we release it before assigning a new value to it.
- let after_change_style_ref = after_change_style.as_ref().unwrap_or(&new_values);
-
- self.needs_transitions_update(old_values.as_ref().unwrap(), after_change_style_ref)
- };
-
- if needs_transitions_update {
- if let Some(values_without_transitions) = after_change_style {
- *new_values = values_without_transitions;
- }
- tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS);
-
- // We need to clone old_values into SequentialTask, so we can
- // use it later.
- old_values.clone()
- } else {
- None
- }
- } else {
- None
- };
+ let before_change_style =
+ self.process_transitions(context, old_values.as_ref(), new_styles);
+ if before_change_style.is_some() {
+ tasks.insert(UpdateAnimationsTasks::CSS_TRANSITIONS);
+ }
if self.has_animations(&context.shared) {
tasks.insert(UpdateAnimationsTasks::EFFECT_PROPERTIES);
if important_rules_changed {
tasks.insert(UpdateAnimationsTasks::CASCADE_RESULTS);
}
- if new_values.is_display_property_changed_from_none(old_values.as_deref()) {
+ if new_styles
+ .primary_style()
+ .is_display_property_changed_from_none(old_values.as_deref())
+ {
tasks.insert(UpdateAnimationsTasks::DISPLAY_CHANGED_FROM_NONE);
}
}
diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs
index 8d3f90f8f5..c9f4e60832 100644
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -580,26 +580,18 @@ impl Clone for ${style_struct.gecko_struct_name} {
</%def>
<%def name="impl_font_settings(ident, gecko_type, tag_type, value_type, gecko_value_type)">
- <%
- gecko_ffi_name = to_camel_case_lower(ident)
- %>
+ <% gecko_ffi_name = to_camel_case_lower(ident) %>
pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
let iter = v.0.iter().map(|other| structs::${gecko_type} {
mTag: other.tag.0,
mValue: other.value as ${gecko_value_type},
});
- self.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter);
- }
-
- pub fn copy_${ident}_from(&mut self, other: &Self) {
- let iter = other.mFont.${gecko_ffi_name}.iter().map(|s| *s);
- self.mFont.${gecko_ffi_name}.assign_from_iter_pod(iter);
+ self.mFont.${gecko_ffi_name}.clear();
+ self.mFont.${gecko_ffi_name}.extend(iter);
}
- pub fn reset_${ident}(&mut self, other: &Self) {
- self.copy_${ident}_from(other)
- }
+ <% impl_simple_copy(ident, "mFont." + gecko_ffi_name) %>
pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
use crate::values::generics::font::{FontSettings, FontTag, ${tag_type}};
diff --git a/servo/components/style/properties/mod.rs b/servo/components/style/properties/mod.rs
index 4a08d67f77..1b378a38bf 100644
--- a/servo/components/style/properties/mod.rs
+++ b/servo/components/style/properties/mod.rs
@@ -690,6 +690,68 @@ impl ShorthandId {
}
}
+fn parse_non_custom_property_declaration_value_into<'i>(
+ declarations: &mut SourcePropertyDeclaration,
+ context: &ParserContext,
+ input: &mut Parser<'i, '_>,
+ start: &cssparser::ParserState,
+ parse_entirely_into: impl FnOnce(&mut SourcePropertyDeclaration, &mut Parser<'i, '_>) -> Result<(), ParseError<'i>>,
+ parsed_wide_keyword: impl FnOnce(&mut SourcePropertyDeclaration, CSSWideKeyword),
+ parsed_custom: impl FnOnce(&mut SourcePropertyDeclaration, custom_properties::VariableValue),
+) -> Result<(), ParseError<'i>> {
+ let mut starts_with_curly_block = false;
+ if let Ok(token) = input.next() {
+ match token {
+ cssparser::Token::Ident(ref ident) => match CSSWideKeyword::from_ident(ident) {
+ Ok(wk) => {
+ if input.expect_exhausted().is_ok() {
+ return Ok(parsed_wide_keyword(declarations, wk));
+ }
+ },
+ Err(()) => {},
+ },
+ cssparser::Token::CurlyBracketBlock => {
+ starts_with_curly_block = true;
+ },
+ _ => {},
+ }
+ };
+
+ input.reset(&start);
+ input.look_for_var_or_env_functions();
+ let err = match parse_entirely_into(declarations, input) {
+ Ok(()) => {
+ input.seen_var_or_env_functions();
+ return Ok(());
+ },
+ Err(e) => e,
+ };
+
+ // Look for var(), env() and top-level curly blocks after the error.
+ let start_pos = start.position();
+ let mut at_start = start_pos == input.position();
+ let mut invalid = false;
+ while let Ok(token) = input.next() {
+ if matches!(token, cssparser::Token::CurlyBracketBlock) {
+ if !starts_with_curly_block || !at_start {
+ invalid = true;
+ break;
+ }
+ } else if starts_with_curly_block {
+ invalid = true;
+ break;
+ }
+ at_start = false;
+ }
+ if !input.seen_var_or_env_functions() || invalid {
+ return Err(err);
+ }
+ input.reset(start);
+ let value = custom_properties::VariableValue::parse(input, &context.url_data)?;
+ parsed_custom(declarations, value);
+ Ok(())
+}
+
impl PropertyDeclaration {
fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option<&str> {
match *self {
@@ -793,76 +855,67 @@ impl PropertyDeclaration {
};
match non_custom_id.longhand_or_shorthand() {
Ok(longhand_id) => {
- let declaration = input
- .try_parse(CSSWideKeyword::parse)
- .map(|keyword| PropertyDeclaration::css_wide_keyword(longhand_id, keyword))
- .or_else(|()| {
- input.look_for_var_or_env_functions();
- input.parse_entirely(|input| longhand_id.parse_value(context, input))
- })
- .or_else(|err| {
- while let Ok(_) = input.next() {} // Look for var() after the error.
- if !input.seen_var_or_env_functions() {
- return Err(err);
- }
- input.reset(&start);
- let variable_value =
- custom_properties::VariableValue::parse(input, &context.url_data)?;
- Ok(PropertyDeclaration::WithVariables(VariableDeclaration {
+ parse_non_custom_property_declaration_value_into(
+ declarations,
+ context,
+ input,
+ &start,
+ |declarations, input| {
+ let decl = input.parse_entirely(|input| longhand_id.parse_value(context, input))?;
+ declarations.push(decl);
+ Ok(())
+ },
+ |declarations, wk| {
+ declarations.push(PropertyDeclaration::css_wide_keyword(longhand_id, wk));
+ },
+ |declarations, variable_value| {
+ declarations.push(PropertyDeclaration::WithVariables(VariableDeclaration {
id: longhand_id,
value: Arc::new(UnparsedValue {
variable_value,
from_shorthand: None,
}),
}))
- })?;
- declarations.push(declaration)
+ }
+ )?;
},
Err(shorthand_id) => {
- if let Ok(keyword) = input.try_parse(CSSWideKeyword::parse) {
- if shorthand_id == ShorthandId::All {
- declarations.all_shorthand = AllShorthand::CSSWideKeyword(keyword)
- } else {
- for longhand in shorthand_id.longhands() {
- declarations
- .push(PropertyDeclaration::css_wide_keyword(longhand, keyword));
- }
- }
- } else {
- input.look_for_var_or_env_functions();
- // Not using parse_entirely here: each
- // ${shorthand.ident}::parse_into function needs to do so
- // *before* pushing to `declarations`.
- shorthand_id
- .parse_into(declarations, context, input)
- .or_else(|err| {
- while let Ok(_) = input.next() {} // Look for var() after the error.
- if !input.seen_var_or_env_functions() {
- return Err(err);
+ parse_non_custom_property_declaration_value_into(
+ declarations,
+ context,
+ input,
+ &start,
+ // Not using parse_entirely here: each ShorthandId::parse_into function needs
+ // to do so *before* pushing to `declarations`.
+ |declarations, input| shorthand_id.parse_into(declarations, context, input),
+ |declarations, wk| {
+ if shorthand_id == ShorthandId::All {
+ declarations.all_shorthand = AllShorthand::CSSWideKeyword(wk)
+ } else {
+ for longhand in shorthand_id.longhands() {
+ declarations.push(PropertyDeclaration::css_wide_keyword(longhand, wk));
}
-
- input.reset(&start);
- let variable_value =
- custom_properties::VariableValue::parse(input, &context.url_data)?;
- let unparsed = Arc::new(UnparsedValue {
- variable_value,
- from_shorthand: Some(shorthand_id),
- });
- if shorthand_id == ShorthandId::All {
- declarations.all_shorthand = AllShorthand::WithVariables(unparsed)
- } else {
- for id in shorthand_id.longhands() {
- declarations.push(PropertyDeclaration::WithVariables(
- VariableDeclaration {
- id,
- value: unparsed.clone(),
- },
- ))
- }
+ }
+ },
+ |declarations, variable_value| {
+ let unparsed = Arc::new(UnparsedValue {
+ variable_value,
+ from_shorthand: Some(shorthand_id),
+ });
+ if shorthand_id == ShorthandId::All {
+ declarations.all_shorthand = AllShorthand::WithVariables(unparsed)
+ } else {
+ for id in shorthand_id.longhands() {
+ declarations.push(PropertyDeclaration::WithVariables(
+ VariableDeclaration {
+ id,
+ value: unparsed.clone(),
+ },
+ ))
}
- Ok(())
- })?;
- }
+ }
+ }
+ )?;
},
}
if let Some(use_counters) = context.use_counters {
@@ -1051,14 +1104,12 @@ impl<'a> PropertyDeclarationId<'a> {
}
/// Convert a `PropertyDeclarationId` into an `AnimatedPropertyID`
- /// Note that the rust AnimatedPropertyID doesn't implement Drop, so owned controls whether the
- /// custom name should be addrefed or not.
///
- /// FIXME(emilio, bug 1870107): This is a bit error-prone. We should consider using cbindgen to
- /// generate the property id representation or so.
+ /// FIXME(emilio, bug 1870107): We should consider using cbindgen to generate the property id
+ /// representation or so.
#[cfg(feature = "gecko")]
#[inline]
- pub fn to_gecko_animated_property_id(&self, owned: bool) -> AnimatedPropertyID {
+ pub fn to_gecko_animated_property_id(&self) -> AnimatedPropertyID {
match self {
Self::Longhand(id) => AnimatedPropertyID {
mID: id.to_nscsspropertyid(),
@@ -1069,11 +1120,7 @@ impl<'a> PropertyDeclarationId<'a> {
mID: nsCSSPropertyID::eCSSPropertyExtra_variable,
mCustomName: RefPtr::null(),
};
- property_id.mCustomName.mRawPtr = if owned {
- (*name).clone().into_addrefed()
- } else {
- name.as_ptr()
- };
+ property_id.mCustomName.mRawPtr = (*name).clone().into_addrefed();
property_id
},
}
diff --git a/servo/components/style/selector_map.rs b/servo/components/style/selector_map.rs
index 2b8d6add55..439fd414a6 100644
--- a/servo/components/style/selector_map.rs
+++ b/servo/components/style/selector_map.rs
@@ -330,6 +330,10 @@ impl SelectorMap<Rule> {
) where
E: TElement,
{
+ use selectors::matching::IncludeStartingStyle;
+
+ let include_starting_style =
+ matches!(matching_context.include_starting_style, IncludeStartingStyle::Yes);
for rule in rules {
if !matches_selector(
&rule.selector,
@@ -352,6 +356,17 @@ impl SelectorMap<Rule> {
}
}
+ if rule.is_starting_style {
+ // Set this flag if there are any rules inside @starting-style. This flag is for
+ // optimization to avoid any redundant resolution of starting style if the author
+ // doesn't specify for this element.
+ matching_context.has_starting_style = true;
+
+ if !include_starting_style {
+ continue;
+ }
+ }
+
matching_rules.push(rule.to_applicable_declaration_block(cascade_level, cascade_data));
}
}
diff --git a/servo/components/style/sharing/mod.rs b/servo/components/style/sharing/mod.rs
index 28592a02c5..2b66db5530 100644
--- a/servo/components/style/sharing/mod.rs
+++ b/servo/components/style/sharing/mod.rs
@@ -76,13 +76,11 @@ use crate::style_resolver::{PrimaryStyle, ResolvedElementStyles};
use crate::stylist::Stylist;
use crate::values::AtomIdent;
use atomic_refcell::{AtomicRefCell, AtomicRefMut};
-use owning_ref::OwningHandle;
use selectors::matching::{NeedsSelectorFlags, SelectorCaches, VisitedHandlingMode};
-use servo_arc::Arc;
use smallbitvec::SmallBitVec;
use smallvec::SmallVec;
use std::marker::PhantomData;
-use std::mem::{self, ManuallyDrop};
+use std::mem;
use std::ops::Deref;
use std::ptr::NonNull;
use uluru::LRUCache;
@@ -535,12 +533,11 @@ impl<E: TElement> SharingCache<E> {
/// [2] https://github.com/rust-lang/rust/issues/13707
type SharingCache<E> = SharingCacheBase<StyleSharingCandidate<E>>;
type TypelessSharingCache = SharingCacheBase<FakeCandidate>;
-type StoredSharingCache = Arc<AtomicRefCell<TypelessSharingCache>>;
thread_local! {
// See the comment on bloom.rs about why do we leak this.
- static SHARING_CACHE_KEY: ManuallyDrop<StoredSharingCache> =
- ManuallyDrop::new(Arc::new_leaked(Default::default()));
+ static SHARING_CACHE_KEY: &'static AtomicRefCell<TypelessSharingCache> =
+ Box::leak(Default::default());
}
/// An LRU cache of the last few nodes seen, so that we can aggressively try to
@@ -550,7 +547,7 @@ thread_local! {
/// storing nodes here temporarily is safe.
pub struct StyleSharingCache<E: TElement> {
/// The LRU cache, with the type cast away to allow persisting the allocation.
- cache_typeless: OwningHandle<StoredSharingCache, AtomicRefMut<'static, TypelessSharingCache>>,
+ cache_typeless: AtomicRefMut<'static, TypelessSharingCache>,
/// Bind this structure to the lifetime of E, since that's what we effectively store.
marker: PhantomData<SendElement<E>>,
/// The DOM depth we're currently at. This is used as an optimization to
@@ -593,9 +590,7 @@ impl<E: TElement> StyleSharingCache<E> {
mem::align_of::<SharingCache<E>>(),
mem::align_of::<TypelessSharingCache>()
);
- let cache_arc = SHARING_CACHE_KEY.with(|c| Arc::clone(&*c));
- let cache =
- OwningHandle::new_with_fn(cache_arc, |x| unsafe { x.as_ref() }.unwrap().borrow_mut());
+ let cache = SHARING_CACHE_KEY.with(|c| c.borrow_mut());
debug_assert!(cache.is_empty());
StyleSharingCache {
diff --git a/servo/components/style/style_resolver.rs b/servo/components/style/style_resolver.rs
index 5c940ad2be..70afb92eb0 100644
--- a/servo/components/style/style_resolver.rs
+++ b/servo/components/style/style_resolver.rs
@@ -17,7 +17,8 @@ use crate::selector_parser::{PseudoElement, SelectorImpl};
use crate::stylist::RuleInclusion;
use log::Level::Trace;
use selectors::matching::{
- MatchingContext, MatchingForInvalidation, MatchingMode, NeedsSelectorFlags, VisitedHandlingMode,
+ IncludeStartingStyle, MatchingContext, MatchingForInvalidation, MatchingMode,
+ NeedsSelectorFlags, VisitedHandlingMode,
};
use servo_arc::Arc;
@@ -47,11 +48,20 @@ where
struct MatchingResults {
rule_node: StrongRuleNode,
flags: ComputedValueFlags,
+ has_starting_style: bool,
}
/// A style returned from the resolver machinery.
pub struct ResolvedStyle(pub Arc<ComputedValues>);
+impl ResolvedStyle {
+ /// Convenience accessor for the style.
+ #[inline]
+ pub fn style(&self) -> &ComputedValues {
+ &*self.0
+ }
+}
+
/// The primary style of an element or an element-backed pseudo-element.
pub struct PrimaryStyle {
/// The style itself.
@@ -59,6 +69,11 @@ pub struct PrimaryStyle {
/// Whether the style was reused from another element via the rule node (see
/// `StyleSharingCache::lookup_by_rules`).
pub reused_via_rule_node: bool,
+ /// The element may have matched rules inside @starting-style.
+ /// Basically, we don't apply @starting-style rules to |style|. This is a sugar to let us know
+ /// if we should resolve the element again for starting style, which is the after-change style
+ /// with @starting-style rules applied in addition.
+ pub may_have_starting_style: bool,
}
/// A set of style returned from the resolver machinery.
@@ -79,6 +94,12 @@ impl ResolvedElementStyles {
pub fn primary_style_mut(&mut self) -> &mut Arc<ComputedValues> {
&mut self.primary.style.0
}
+
+ /// Returns true if this element may have starting style rules.
+ #[inline]
+ pub fn may_have_starting_style(&self) -> bool {
+ self.primary.may_have_starting_style
+ }
}
impl PrimaryStyle {
@@ -186,16 +207,22 @@ where
&mut self,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
+ include_starting_style: IncludeStartingStyle,
) -> PrimaryStyle {
- let primary_results = self.match_primary(VisitedHandlingMode::AllLinksUnvisited);
+ let primary_results = self.match_primary(
+ VisitedHandlingMode::AllLinksUnvisited,
+ include_starting_style,
+ );
let inside_link = parent_style.map_or(false, |s| s.visited_style().is_some());
let visited_rules = if self.context.shared.visited_styles_enabled &&
(inside_link || self.element.is_link())
{
- let visited_matching_results =
- self.match_primary(VisitedHandlingMode::RelevantLinkVisited);
+ let visited_matching_results = self.match_primary(
+ VisitedHandlingMode::RelevantLinkVisited,
+ IncludeStartingStyle::No,
+ );
Some(visited_matching_results.rule_node)
} else {
None
@@ -209,6 +236,7 @@ where
},
parent_style,
layout_parent_style,
+ primary_results.has_starting_style,
)
}
@@ -217,6 +245,7 @@ where
inputs: CascadeInputs,
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
+ may_have_starting_style: bool,
) -> PrimaryStyle {
// Before doing the cascade, check the sharing cache and see if we can
// reuse the style via rule node identity.
@@ -253,6 +282,7 @@ where
/* pseudo = */ None,
),
reused_via_rule_node: false,
+ may_have_starting_style,
}
}
@@ -262,7 +292,11 @@ where
parent_style: Option<&ComputedValues>,
layout_parent_style: Option<&ComputedValues>,
) -> ResolvedElementStyles {
- let primary_style = self.resolve_primary_style(parent_style, layout_parent_style);
+ let primary_style = self.resolve_primary_style(
+ parent_style,
+ layout_parent_style,
+ IncludeStartingStyle::No,
+ );
let mut pseudo_styles = EagerPseudoStyles::default();
@@ -375,10 +409,15 @@ where
pub fn cascade_styles_with_default_parents(
&mut self,
inputs: ElementCascadeInputs,
+ may_have_starting_style: bool,
) -> ResolvedElementStyles {
with_default_parent_styles(self.element, move |parent_style, layout_parent_style| {
- let primary_style =
- self.cascade_primary_style(inputs.primary, parent_style, layout_parent_style);
+ let primary_style = self.cascade_primary_style(
+ inputs.primary,
+ parent_style,
+ layout_parent_style,
+ may_have_starting_style,
+ );
let mut pseudo_styles = EagerPseudoStyles::default();
if let Some(mut pseudo_array) = inputs.pseudos.into_array() {
@@ -427,6 +466,7 @@ where
let MatchingResults {
rule_node,
mut flags,
+ has_starting_style: _,
} = self.match_pseudo(
&originating_element_style.style.0,
pseudo,
@@ -459,7 +499,11 @@ where
))
}
- fn match_primary(&mut self, visited_handling: VisitedHandlingMode) -> MatchingResults {
+ fn match_primary(
+ &mut self,
+ visited_handling: VisitedHandlingMode,
+ include_starting_style: IncludeStartingStyle,
+ ) -> MatchingResults {
debug!(
"Match primary for {:?}, visited: {:?}",
self.element, visited_handling
@@ -473,6 +517,7 @@ where
Some(bloom_filter),
selector_caches,
visited_handling,
+ include_starting_style,
self.context.shared.quirks_mode(),
NeedsSelectorFlags::Yes,
MatchingForInvalidation::No,
@@ -512,6 +557,7 @@ where
MatchingResults {
rule_node,
flags: matching_context.extra_data.cascade_input_flags,
+ has_starting_style: matching_context.has_starting_style,
}
}
@@ -550,6 +596,7 @@ where
Some(bloom_filter),
selector_caches,
visited_handling,
+ IncludeStartingStyle::No,
self.context.shared.quirks_mode(),
NeedsSelectorFlags::Yes,
MatchingForInvalidation::No,
@@ -580,6 +627,7 @@ where
Some(MatchingResults {
rule_node,
flags: matching_context.extra_data.cascade_input_flags,
+ has_starting_style: false, // We don't care.
})
}
}
diff --git a/servo/components/style/stylesheets/font_feature_values_rule.rs b/servo/components/style/stylesheets/font_feature_values_rule.rs
index 06016ec2bd..73d499c041 100644
--- a/servo/components/style/stylesheets/font_feature_values_rule.rs
+++ b/servo/components/style/stylesheets/font_feature_values_rule.rs
@@ -10,7 +10,7 @@ use crate::error_reporting::ContextualParseError;
#[cfg(feature = "gecko")]
use crate::gecko_bindings::bindings::Gecko_AppendFeatureValueHashEntry;
#[cfg(feature = "gecko")]
-use crate::gecko_bindings::structs::{self, gfxFontFeatureValueSet, nsTArray};
+use crate::gecko_bindings::structs::{self, gfxFontFeatureValueSet};
use crate::parser::{Parse, ParserContext};
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
@@ -24,6 +24,7 @@ use cssparser::{
};
use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+use thin_vec::ThinVec;
/// A @font-feature-values block declaration.
/// It is `<ident>: <integer>+`.
@@ -54,8 +55,8 @@ impl<T: ToCss> ToCss for FFVDeclaration<T> {
/// A trait for @font-feature-values rule to gecko values conversion.
#[cfg(feature = "gecko")]
pub trait ToGeckoFontFeatureValues {
- /// Sets the equivalent of declaration to gecko `nsTArray<u32>` array.
- fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>);
+ /// Sets the equivalent of declaration to gecko `ThinVec<u32>` array.
+ fn to_gecko_font_feature_values(&self) -> ThinVec<u32>;
}
/// A @font-feature-values block declaration value that keeps one value.
@@ -79,11 +80,8 @@ impl Parse for SingleValue {
#[cfg(feature = "gecko")]
impl ToGeckoFontFeatureValues for SingleValue {
- fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) {
- unsafe {
- array.set_len_pod(1);
- }
- array[0] = self.0 as u32;
+ fn to_gecko_font_feature_values(&self) -> ThinVec<u32> {
+ thin_vec::thin_vec![self.0 as u32]
}
}
@@ -118,16 +116,12 @@ impl Parse for PairValues {
#[cfg(feature = "gecko")]
impl ToGeckoFontFeatureValues for PairValues {
- fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) {
- let len = if self.1.is_some() { 2 } else { 1 };
-
- unsafe {
- array.set_len_pod(len);
- }
- array[0] = self.0 as u32;
+ fn to_gecko_font_feature_values(&self) -> ThinVec<u32> {
+ let mut result = thin_vec::thin_vec![self.0 as u32];
if let Some(second) = self.1 {
- array[1] = second as u32;
- };
+ result.push(second as u32);
+ }
+ result
}
}
@@ -165,8 +159,8 @@ impl Parse for VectorValues {
#[cfg(feature = "gecko")]
impl ToGeckoFontFeatureValues for VectorValues {
- fn to_gecko_font_feature_values(&self, array: &mut nsTArray<u32>) {
- array.assign_from_iter_pod(self.0.iter().map(|v| *v));
+ fn to_gecko_font_feature_values(&self) -> ThinVec<u32> {
+ self.0.iter().copied().collect()
}
}
@@ -338,7 +332,7 @@ macro_rules! font_feature_values_blocks {
)
};
unsafe {
- val.value.to_gecko_font_feature_values(&mut *array);
+ *array = val.value.to_gecko_font_feature_values();
}
}
}
diff --git a/servo/components/style/stylesheets/margin_rule.rs b/servo/components/style/stylesheets/margin_rule.rs
index ab46283151..19928c04ba 100644
--- a/servo/components/style/stylesheets/margin_rule.rs
+++ b/servo/components/style/stylesheets/margin_rule.rs
@@ -21,19 +21,21 @@ macro_rules! margin_rule_types {
/// [`@margin`][margin] rule names.
///
/// https://drafts.csswg.org/css-page-3/#margin-at-rules
- #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToShmem)]
+ #[derive(Clone, Copy, Eq, MallocSizeOf, PartialEq, ToShmem)]
#[repr(u8)]
pub enum MarginRuleType {
$($(#[$($meta)+])* $id,)+
}
+ /// All [`@margin`][margin] rule names, with a preceding '@'.
+ ///
+ /// This array lets us have just one single memory region used for
+ /// to_str, name, and the Debug implementation.
+ const MARGIN_RULE_AT_NAMES:&[&'static str] = &[
+ $( concat!('@', $val), )+
+ ];
+
impl MarginRuleType {
- #[inline]
- fn to_str(&self) -> &'static str {
- match *self {
- $(MarginRuleType::$id => concat!('@', $val),)+
- }
- }
/// Matches the rule type for this name. This does not expect a
/// leading '@'.
pub fn match_name(name: &str) -> Option<Self> {
@@ -113,6 +115,27 @@ margin_rule_types! {
RightBottom => "right-bottom",
}
+impl MarginRuleType {
+ #[inline]
+ fn to_str(&self) -> &'static str {
+ &MARGIN_RULE_AT_NAMES[*self as usize]
+ }
+ #[inline]
+ fn name(&self) -> &'static str {
+ // Use the at-name array, skipping the first character to get
+ // the name without the @ sign.
+ &MARGIN_RULE_AT_NAMES[*self as usize][1..]
+ }
+}
+
+// Implement Debug manually so that it will share the same string memory as
+// MarginRuleType::name and MarginRuleType::to_str.
+impl fmt::Debug for MarginRuleType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ f.write_str(self.name())
+ }
+}
+
/// A [`@margin`][margin] rule.
///
/// [margin]: https://drafts.csswg.org/css-page-3/#margin-at-rules
@@ -134,6 +157,11 @@ impl MarginRule {
self.block.unconditional_shallow_size_of(ops) +
self.block.read_with(guard).size_of(ops)
}
+ /// Gets the name for this margin rule.
+ #[inline]
+ pub fn name(&self) -> &'static str {
+ self.rule_type.name()
+ }
}
impl ToCssWithGuard for MarginRule {
diff --git a/servo/components/style/stylist.rs b/servo/components/style/stylist.rs
index 06cbe6276b..bef4e569cd 100644
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -577,11 +577,12 @@ impl From<StyleRuleInclusion> for RuleInclusion {
}
/// A struct containing state from ancestor rules like @layer / @import /
-/// @container / nesting.
+/// @container / @starting-style / nesting.
struct ContainingRuleState {
layer_name: LayerName,
layer_id: LayerId,
container_condition_id: ContainerConditionId,
+ in_starting_style: bool,
ancestor_selector_lists: SmallVec<[SelectorList<SelectorImpl>; 2]>,
}
@@ -591,6 +592,7 @@ impl Default for ContainingRuleState {
layer_name: LayerName::new_empty(),
layer_id: LayerId::root(),
container_condition_id: ContainerConditionId::none(),
+ in_starting_style: false,
ancestor_selector_lists: Default::default(),
}
}
@@ -601,6 +603,7 @@ struct SavedContainingRuleState {
layer_name_len: usize,
layer_id: LayerId,
container_condition_id: ContainerConditionId,
+ in_starting_style: bool,
}
impl ContainingRuleState {
@@ -610,6 +613,7 @@ impl ContainingRuleState {
layer_name_len: self.layer_name.0.len(),
layer_id: self.layer_id,
container_condition_id: self.container_condition_id,
+ in_starting_style: self.in_starting_style,
}
}
@@ -621,6 +625,7 @@ impl ContainingRuleState {
self.layer_name.0.truncate(saved.layer_name_len);
self.layer_id = saved.layer_id;
self.container_condition_id = saved.container_condition_id;
+ self.in_starting_style = saved.in_starting_style;
}
}
@@ -1258,6 +1263,7 @@ impl Stylist {
None,
&mut selector_caches,
VisitedHandlingMode::RelevantLinkVisited,
+ selectors::matching::IncludeStartingStyle::No,
self.quirks_mode,
needs_selector_flags,
MatchingForInvalidation::No,
@@ -2858,6 +2864,7 @@ impl CascadeData {
self.rules_source_order,
containing_rule_state.layer_id,
containing_rule_state.container_condition_id,
+ containing_rule_state.in_starting_style,
);
if collect_replaced_selectors {
@@ -3146,6 +3153,9 @@ impl CascadeData {
});
containing_rule_state.container_condition_id = id;
},
+ CssRule::StartingStyle(..) => {
+ containing_rule_state.in_starting_style = true;
+ },
// We don't care about any other rule.
_ => {},
}
@@ -3434,6 +3444,9 @@ pub struct Rule {
/// The current @container rule id.
pub container_condition_id: ContainerConditionId,
+ /// True if this rule is inside @starting-style.
+ pub is_starting_style: bool,
+
/// The actual style rule.
#[cfg_attr(
feature = "gecko",
@@ -3480,6 +3493,7 @@ impl Rule {
source_order: u32,
layer_id: LayerId,
container_condition_id: ContainerConditionId,
+ is_starting_style: bool,
) -> Self {
Rule {
selector,
@@ -3488,6 +3502,7 @@ impl Rule {
source_order,
layer_id,
container_condition_id,
+ is_starting_style,
}
}
}
diff --git a/servo/components/style/traversal.rs b/servo/components/style/traversal.rs
index fa256d0f60..651b5cb073 100644
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -350,7 +350,11 @@ where
rule_inclusion,
PseudoElementResolution::IfApplicable,
)
- .resolve_primary_style(style.as_deref(), layout_parent_style.as_deref());
+ .resolve_primary_style(
+ style.as_deref(),
+ layout_parent_style.as_deref(),
+ selectors::matching::IncludeStartingStyle::No,
+ );
let is_display_contents = primary_style.style().is_display_contents();
@@ -639,7 +643,10 @@ where
PseudoElementResolution::IfApplicable,
);
- resolver.cascade_styles_with_default_parents(cascade_inputs)
+ resolver.cascade_styles_with_default_parents(
+ cascade_inputs,
+ data.may_have_starting_style(),
+ )
},
CascadeOnly => {
// Skipping full matching, load cascade inputs from previous values.
@@ -653,7 +660,10 @@ where
PseudoElementResolution::IfApplicable,
);
- resolver.cascade_styles_with_default_parents(cascade_inputs)
+ resolver.cascade_styles_with_default_parents(
+ cascade_inputs,
+ data.may_have_starting_style(),
+ )
};
// Insert into the cache, but only if this style isn't reused from a
diff --git a/servo/components/style/values/animated/mod.rs b/servo/components/style/values/animated/mod.rs
index 31ea206fc0..235ddcba5a 100644
--- a/servo/components/style/values/animated/mod.rs
+++ b/servo/components/style/values/animated/mod.rs
@@ -273,6 +273,23 @@ where
}
}
+impl<T> ToAnimatedValue for thin_vec::ThinVec<T>
+where
+ T: ToAnimatedValue,
+{
+ type AnimatedValue = thin_vec::ThinVec<<T as ToAnimatedValue>::AnimatedValue>;
+
+ #[inline]
+ fn to_animated_value(self) -> Self::AnimatedValue {
+ self.into_iter().map(T::to_animated_value).collect()
+ }
+
+ #[inline]
+ fn from_animated_value(animated: Self::AnimatedValue) -> Self {
+ animated.into_iter().map(T::from_animated_value).collect()
+ }
+}
+
impl<T> ToAnimatedValue for Box<T>
where
T: ToAnimatedValue,
@@ -452,6 +469,16 @@ where
}
}
+impl<T> ToAnimatedZero for thin_vec::ThinVec<T>
+where
+ T: ToAnimatedZero,
+{
+ #[inline]
+ fn to_animated_zero(&self) -> Result<Self, ()> {
+ self.iter().map(|v| v.to_animated_zero()).collect()
+ }
+}
+
impl<T> ToAnimatedZero for Box<[T]>
where
T: ToAnimatedZero,
diff --git a/servo/components/style/values/computed/length_percentage.rs b/servo/components/style/values/computed/length_percentage.rs
index c448025dd1..f5f3303141 100644
--- a/servo/components/style/values/computed/length_percentage.rs
+++ b/servo/components/style/values/computed/length_percentage.rs
@@ -680,12 +680,12 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
}
}
- fn unitless_value(&self) -> f32 {
- match *self {
+ fn unitless_value(&self) -> Option<f32> {
+ Some(match *self {
Self::Length(ref l) => l.px(),
Self::Percentage(ref p) => p.0,
Self::Number(n) => n,
- }
+ })
}
fn new_number(value: f32) -> Self {
@@ -709,9 +709,18 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
return None;
}
- let self_negative = self.is_negative();
- if self_negative != other.is_negative() {
- return Some(if self_negative { std::cmp::Ordering::Less } else { std::cmp::Ordering::Greater });
+ let Ok(self_negative) = self.is_negative() else {
+ return None;
+ };
+ let Ok(other_negative) = other.is_negative() else {
+ return None;
+ };
+ if self_negative != other_negative {
+ return Some(if self_negative {
+ std::cmp::Ordering::Less
+ } else {
+ std::cmp::Ordering::Greater
+ });
}
match (self, other) {
@@ -774,15 +783,17 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
} else {
// The right side is not a number, so the result should be in the units of the right
// side.
- other.map(|v| v * *left);
- std::mem::swap(self, other);
- true
+ if other.map(|v| v * *left).is_ok() {
+ std::mem::swap(self, other);
+ true
+ } else {
+ false
+ }
}
} else if let Self::Number(ref right) = *other {
// The left side is not a number, but the right side is, so the result is the left
// side unit.
- self.map(|v| v * *right);
- true
+ self.map(|v| v * *right).is_ok()
} else {
// Neither side is a number, so a product is not possible.
false
@@ -814,8 +825,8 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
})
}
- fn map(&mut self, mut op: impl FnMut(f32) -> f32) {
- match self {
+ fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
+ Ok(match self {
Self::Length(value) => {
*value = Length::new(op(value.px()));
},
@@ -825,7 +836,7 @@ impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
Self::Number(value) => {
*value = op(*value);
},
- }
+ })
}
fn simplify(&mut self) {}
@@ -921,7 +932,7 @@ impl specified::CalcLengthPercentage {
}
}),
Leaf::Number(n) => CalcLengthPercentageLeaf::Number(n),
- Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) => {
+ Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) | Leaf::ColorComponent(..) => {
unreachable!("Shouldn't have parsed")
},
});
diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs
index 85aadb401f..ca32408a79 100644
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -649,6 +649,25 @@ where
}
}
+impl<T> ToComputedValue for thin_vec::ThinVec<T>
+where
+ T: ToComputedValue,
+{
+ type ComputedValue = thin_vec::ThinVec<<T as ToComputedValue>::ComputedValue>;
+
+ #[inline]
+ fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+ self.iter()
+ .map(|item| item.to_computed_value(context))
+ .collect()
+ }
+
+ #[inline]
+ fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+ computed.iter().map(T::from_computed_value).collect()
+ }
+}
+
// NOTE(emilio): This is implementable more generically, but it's unlikely
// what you want there, as it forces you to have an extra allocation.
//
diff --git a/servo/components/style/values/generics/calc.rs b/servo/components/style/values/generics/calc.rs
index abcb5fe6eb..215db1932b 100644
--- a/servo/components/style/values/generics/calc.rs
+++ b/servo/components/style/values/generics/calc.rs
@@ -255,12 +255,15 @@ bitflags! {
const TIME = 1 << 3;
/// <resolution>
const RESOLUTION = 1 << 4;
+ /// A component of a color (r, g, b, h, s, l, alpha, etc.)
+ const COLOR_COMPONENT = 1 << 5;
/// <length-percentage>
const LENGTH_PERCENTAGE = Self::LENGTH.bits() | Self::PERCENTAGE.bits();
// NOTE: When you add to this, make sure to make Atan2 deal with these.
/// Allow all units.
- const ALL = Self::LENGTH.bits() | Self::PERCENTAGE.bits() | Self::ANGLE.bits() | Self::TIME.bits() | Self::RESOLUTION.bits();
+ const ALL = Self::LENGTH.bits() | Self::PERCENTAGE.bits() | Self::ANGLE.bits() |
+ Self::TIME.bits() | Self::RESOLUTION.bits() | Self::COLOR_COMPONENT.bits();
}
}
@@ -323,8 +326,8 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss {
/// Returns the unit of the leaf.
fn unit(&self) -> CalcUnits;
- /// Returns the unitless value of this leaf.
- fn unitless_value(&self) -> f32;
+ /// Returns the unitless value of this leaf if one is available.
+ fn unitless_value(&self) -> Option<f32>;
/// Return true if the units of both leaves are equal. (NOTE: Does not take
/// the values into account)
@@ -347,23 +350,31 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss {
fn as_number(&self) -> Option<f32>;
/// Whether this value is known-negative.
- fn is_negative(&self) -> bool {
- self.unitless_value().is_sign_negative()
+ fn is_negative(&self) -> Result<bool, ()> {
+ self.unitless_value()
+ .map(|v| Ok(v.is_sign_negative()))
+ .unwrap_or_else(|| Err(()))
}
/// Whether this value is infinite.
- fn is_infinite(&self) -> bool {
- self.unitless_value().is_infinite()
+ fn is_infinite(&self) -> Result<bool, ()> {
+ self.unitless_value()
+ .map(|v| Ok(v.is_infinite()))
+ .unwrap_or_else(|| Err(()))
}
/// Whether this value is zero.
- fn is_zero(&self) -> bool {
- self.unitless_value().is_zero()
+ fn is_zero(&self) -> Result<bool, ()> {
+ self.unitless_value()
+ .map(|v| Ok(v.is_zero()))
+ .unwrap_or_else(|| Err(()))
}
/// Whether this value is NaN.
- fn is_nan(&self) -> bool {
- self.unitless_value().is_nan()
+ fn is_nan(&self) -> Result<bool, ()> {
+ self.unitless_value()
+ .map(|v| Ok(v.is_nan()))
+ .unwrap_or_else(|| Err(()))
}
/// Tries to merge one leaf into another using the sum, that is, perform `x` + `y`.
@@ -379,12 +390,7 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss {
O: Fn(f32, f32) -> f32;
/// Map the value of this node with the given operation.
- fn map(&mut self, op: impl FnMut(f32) -> f32);
-
- /// Negates the leaf.
- fn negate(&mut self) {
- self.map(std::ops::Neg::neg);
- }
+ fn map(&mut self, op: impl FnMut(f32) -> f32) -> Result<(), ()>;
/// Canonicalizes the expression if necessary.
fn simplify(&mut self);
@@ -393,16 +399,20 @@ pub trait CalcNodeLeaf: Clone + Sized + PartialEq + ToCss {
fn sort_key(&self) -> SortKey;
/// Create a new leaf containing the sign() result of the given leaf.
- fn sign_from(leaf: &impl CalcNodeLeaf) -> Self {
- Self::new_number(if leaf.is_nan() {
+ fn sign_from(leaf: &impl CalcNodeLeaf) -> Result<Self, ()> {
+ let Some(value) = leaf.unitless_value() else {
+ return Err(());
+ };
+
+ Ok(Self::new_number(if value.is_nan() {
f32::NAN
- } else if leaf.is_zero() {
- leaf.unitless_value()
- } else if leaf.is_negative() {
+ } else if value.is_zero() {
+ value
+ } else if value.is_sign_negative() {
-1.0
} else {
1.0
- })
+ }))
}
}
@@ -426,8 +436,8 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
/// Change all the leaf nodes to have the given value. This is useful when
/// you have `calc(1px * nan)` and you want to replace the product node with
/// `calc(nan)`, in which case the unit will be retained.
- fn coerce_to_value(&mut self, value: f32) {
- self.map(|_| value);
+ fn coerce_to_value(&mut self, value: f32) -> Result<(), ()> {
+ self.map(|_| value)
}
/// Return true if a product is distributive over this node.
@@ -436,7 +446,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
#[inline]
pub fn is_product_distributive(&self) -> bool {
match self {
- Self::Leaf(_) => true,
+ Self::Leaf(l) => l.unit() != CalcUnits::COLOR_COMPONENT,
Self::Sum(children) => children.iter().all(|c| c.is_product_distributive()),
_ => false,
}
@@ -546,7 +556,11 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
}
match *self {
- CalcNode::Leaf(ref mut leaf) => leaf.negate(),
+ CalcNode::Leaf(ref mut leaf) => {
+ if leaf.map(std::ops::Neg::neg).is_err() {
+ wrap_self_in_negate(self)
+ }
+ },
CalcNode::Negate(ref mut value) => {
// Don't negate the value here. Replace `self` with it's child.
let result = mem::replace(value.as_mut(), Self::dummy());
@@ -668,7 +682,9 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
}
if self.is_product_distributive() {
- self.map(|v| v * number);
+ if self.map(|v| v * number).is_err() {
+ return false;
+ }
return true;
}
}
@@ -682,7 +698,9 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
}
if other.is_product_distributive() {
- other.map(|v| v * number);
+ if other.map(|v| v * number).is_err() {
+ return false;
+ }
std::mem::swap(self, other);
return true;
}
@@ -706,48 +724,52 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
}
/// Map the value of this node with the given operation.
- pub fn map(&mut self, mut op: impl FnMut(f32) -> f32) {
- fn map_internal<L: CalcNodeLeaf>(node: &mut CalcNode<L>, op: &mut impl FnMut(f32) -> f32) {
+ pub fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
+ fn map_internal<L: CalcNodeLeaf>(
+ node: &mut CalcNode<L>,
+ op: &mut impl FnMut(f32) -> f32,
+ ) -> Result<(), ()> {
match node {
CalcNode::Leaf(l) => l.map(op),
CalcNode::Negate(v) | CalcNode::Invert(v) => map_internal(v, op),
CalcNode::Sum(children) | CalcNode::Product(children) => {
for node in &mut **children {
- map_internal(node, op);
+ map_internal(node, op)?;
}
+ Ok(())
},
CalcNode::MinMax(children, _) => {
for node in &mut **children {
- map_internal(node, op);
+ map_internal(node, op)?;
}
+ Ok(())
},
CalcNode::Clamp { min, center, max } => {
- map_internal(min, op);
- map_internal(center, op);
- map_internal(max, op);
+ map_internal(min, op)?;
+ map_internal(center, op)?;
+ map_internal(max, op)
},
CalcNode::Round { value, step, .. } => {
- map_internal(value, op);
- map_internal(step, op);
+ map_internal(value, op)?;
+ map_internal(step, op)
},
CalcNode::ModRem {
dividend, divisor, ..
} => {
- map_internal(dividend, op);
- map_internal(divisor, op);
+ map_internal(dividend, op)?;
+ map_internal(divisor, op)
},
CalcNode::Hypot(children) => {
for node in &mut **children {
- map_internal(node, op);
+ map_internal(node, op)?;
}
+ Ok(())
},
- CalcNode::Abs(child) | CalcNode::Sign(child) => {
- map_internal(child, op);
- },
+ CalcNode::Abs(child) | CalcNode::Sign(child) => map_internal(child, op),
}
}
- map_internal(self, &mut op);
+ map_internal(self, &mut op)
}
/// Convert this `CalcNode` into a `CalcNode` with a different leaf kind.
@@ -849,12 +871,12 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
Self::Leaf(l) => leaf_to_output_fn(l),
Self::Negate(child) => {
let mut result = child.resolve_internal(leaf_to_output_fn)?;
- result.map(|v| v.neg());
+ result.map(|v| v.neg())?;
Ok(result)
},
Self::Invert(child) => {
let mut result = child.resolve_internal(leaf_to_output_fn)?;
- result.map(|v| 1.0 / v);
+ result.map(|v| 1.0 / v)?;
Ok(result)
},
Self::Sum(children) => {
@@ -878,13 +900,13 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
Some(left) => {
// Left side is a number, so we use the right node as the result.
result = right;
- result.map(|v| v * left);
+ result.map(|v| v * left)?;
},
None => {
// Left side is not a number, so check if the right side is.
match right.as_number() {
Some(right) => {
- result.map(|v| v * right);
+ result.map(|v| v * right)?;
},
None => {
// Multiplying with both sides having units.
@@ -900,7 +922,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
Self::MinMax(children, op) => {
let mut result = children[0].resolve_internal(leaf_to_output_fn)?;
- if result.is_nan() {
+ if result.is_nan()? {
return Ok(result);
}
@@ -912,7 +934,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
return Err(());
}
- if candidate.is_nan() {
+ if candidate.is_nan()? {
result = candidate;
break;
}
@@ -938,15 +960,15 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
return Err(());
}
- if min.is_nan() {
+ if min.is_nan()? {
return Ok(min);
}
- if center.is_nan() {
+ if center.is_nan()? {
return Ok(center);
}
- if max.is_nan() {
+ if max.is_nan()? {
return Ok(max);
}
@@ -972,7 +994,10 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
return Err(());
}
- let step = step.unitless_value().abs();
+ let Some(step) = step.unitless_value() else {
+ return Err(());
+ };
+ let step = step.abs();
value.map(|value| {
// TODO(emilio): Seems like at least a few of these
@@ -1039,7 +1064,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
}
},
}
- });
+ })?;
Ok(value)
},
@@ -1055,13 +1080,15 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
return Err(());
}
- let divisor = divisor.unitless_value();
- dividend.map(|dividend| op.apply(dividend, divisor));
+ let Some(divisor) = divisor.unitless_value() else {
+ return Err(());
+ };
+ dividend.map(|dividend| op.apply(dividend, divisor))?;
Ok(dividend)
},
Self::Hypot(children) => {
let mut result = children[0].resolve_internal(leaf_to_output_fn)?;
- result.map(|v| v.powi(2));
+ result.map(|v| v.powi(2))?;
for child in children.iter().skip(1) {
let child_value = child.resolve_internal(leaf_to_output_fn)?;
@@ -1070,45 +1097,48 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
return Err(());
}
- result.map(|v| v + child_value.unitless_value().powi(2));
+ let Some(child_value) = child_value.unitless_value() else {
+ return Err(());
+ };
+ result.map(|v| v + child_value.powi(2))?;
}
- result.map(|v| v.sqrt());
+ result.map(|v| v.sqrt())?;
Ok(result)
},
Self::Abs(ref c) => {
let mut result = c.resolve_internal(leaf_to_output_fn)?;
- result.map(|v| v.abs());
+ result.map(|v| v.abs())?;
Ok(result)
},
Self::Sign(ref c) => {
let result = c.resolve_internal(leaf_to_output_fn)?;
- Ok(L::sign_from(&result))
+ Ok(L::sign_from(&result)?)
},
}
}
- fn is_negative_leaf(&self) -> bool {
- match *self {
- Self::Leaf(ref l) => l.is_negative(),
+ fn is_negative_leaf(&self) -> Result<bool, ()> {
+ Ok(match *self {
+ Self::Leaf(ref l) => l.is_negative()?,
_ => false,
- }
+ })
}
- fn is_zero_leaf(&self) -> bool {
- match *self {
- Self::Leaf(ref l) => l.is_zero(),
+ fn is_zero_leaf(&self) -> Result<bool, ()> {
+ Ok(match *self {
+ Self::Leaf(ref l) => l.is_zero()?,
_ => false,
- }
+ })
}
- fn is_infinite_leaf(&self) -> bool {
- match *self {
- Self::Leaf(ref l) => l.is_infinite(),
+ fn is_infinite_leaf(&self) -> Result<bool, ()> {
+ Ok(match *self {
+ Self::Leaf(ref l) => l.is_infinite()?,
_ => false,
- }
+ })
}
/// Visits all the nodes in this calculation tree recursively, starting by
@@ -1250,54 +1280,64 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
ref mut value,
ref mut step,
} => {
- if step.is_zero_leaf() {
- value.coerce_to_value(f32::NAN);
+ if value_or_stop!(step.is_zero_leaf()) {
+ value_or_stop!(value.coerce_to_value(f32::NAN));
replace_self_with!(&mut **value);
return;
}
- if value.is_infinite_leaf() && step.is_infinite_leaf() {
- value.coerce_to_value(f32::NAN);
+ if value_or_stop!(value.is_infinite_leaf()) &&
+ value_or_stop!(step.is_infinite_leaf())
+ {
+ value_or_stop!(value.coerce_to_value(f32::NAN));
replace_self_with!(&mut **value);
return;
}
- if value.is_infinite_leaf() {
+ if value_or_stop!(value.is_infinite_leaf()) {
replace_self_with!(&mut **value);
return;
}
- if step.is_infinite_leaf() {
+ if value_or_stop!(step.is_infinite_leaf()) {
match strategy {
RoundingStrategy::Nearest | RoundingStrategy::ToZero => {
- value.coerce_to_value(0.0);
+ value_or_stop!(value.coerce_to_value(0.0));
replace_self_with!(&mut **value);
return;
},
RoundingStrategy::Up => {
- if !value.is_negative_leaf() && !value.is_zero_leaf() {
- value.coerce_to_value(f32::INFINITY);
+ if !value_or_stop!(value.is_negative_leaf()) &&
+ !value_or_stop!(value.is_zero_leaf())
+ {
+ value_or_stop!(value.coerce_to_value(f32::INFINITY));
replace_self_with!(&mut **value);
return;
- } else if !value.is_negative_leaf() && value.is_zero_leaf() {
+ } else if !value_or_stop!(value.is_negative_leaf()) &&
+ value_or_stop!(value.is_zero_leaf())
+ {
replace_self_with!(&mut **value);
return;
} else {
- value.coerce_to_value(0.0);
+ value_or_stop!(value.coerce_to_value(0.0));
replace_self_with!(&mut **value);
return;
}
},
RoundingStrategy::Down => {
- if value.is_negative_leaf() && !value.is_zero_leaf() {
- value.coerce_to_value(f32::INFINITY);
+ if value_or_stop!(value.is_negative_leaf()) &&
+ !value_or_stop!(value.is_zero_leaf())
+ {
+ value_or_stop!(value.coerce_to_value(f32::INFINITY));
replace_self_with!(&mut **value);
return;
- } else if value.is_negative_leaf() && value.is_zero_leaf() {
+ } else if value_or_stop!(value.is_negative_leaf()) &&
+ value_or_stop!(value.is_zero_leaf())
+ {
replace_self_with!(&mut **value);
return;
} else {
- value.coerce_to_value(0.0);
+ value_or_stop!(value.coerce_to_value(0.0));
replace_self_with!(&mut **value);
return;
}
@@ -1305,17 +1345,18 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
}
}
- if step.is_negative_leaf() {
+ if value_or_stop!(step.is_negative_leaf()) {
step.negate();
}
let remainder = value_or_stop!(value.try_op(step, Rem::rem));
- if remainder.is_zero_leaf() {
+ if value_or_stop!(remainder.is_zero_leaf()) {
replace_self_with!(&mut **value);
return;
}
- let (mut lower_bound, mut upper_bound) = if value.is_negative_leaf() {
+ let (mut lower_bound, mut upper_bound) = if value_or_stop!(value.is_negative_leaf())
+ {
let upper_bound = value_or_stop!(value.try_op(&remainder, Sub::sub));
let lower_bound = value_or_stop!(upper_bound.try_op(&step, Sub::sub));
@@ -1348,11 +1389,11 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
let mut lower_diff = lower_bound.clone();
let mut upper_diff = upper_bound.clone();
- if lower_diff.is_negative_leaf() {
+ if value_or_stop!(lower_diff.is_negative_leaf()) {
lower_diff.negate();
}
- if upper_diff.is_negative_leaf() {
+ if value_or_stop!(upper_diff.is_negative_leaf()) {
upper_diff.negate();
}
@@ -1518,13 +1559,13 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
},
Self::Abs(ref mut child) => {
if let CalcNode::Leaf(leaf) = child.as_mut() {
- leaf.map(|v| v.abs());
+ value_or_stop!(leaf.map(|v| v.abs()));
replace_self_with!(&mut **child);
}
},
Self::Sign(ref mut child) => {
if let CalcNode::Leaf(leaf) = child.as_mut() {
- let mut result = Self::Leaf(L::sign_from(leaf));
+ let mut result = Self::Leaf(value_or_stop!(L::sign_from(leaf)));
replace_self_with!(&mut result);
}
},
@@ -1553,7 +1594,7 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
// 1. If root’s child is a number (not a percentage or dimension) return the
// reciprocal of the child’s value.
if leaf.unit().is_empty() {
- child.map(|v| 1.0 / v);
+ value_or_stop!(child.map(|v| 1.0 / v));
replace_self_with!(&mut **child);
}
},
@@ -1678,10 +1719,12 @@ impl<L: CalcNodeLeaf> CalcNode<L> {
if !first {
match child {
Self::Leaf(l) => {
- if l.is_negative() {
+ if let Ok(true) = l.is_negative() {
dest.write_str(" - ")?;
let mut negated = l.clone();
- negated.negate();
+ // We can unwrap here, because we already
+ // checked if the value inside is negative.
+ negated.map(std::ops::Neg::neg).unwrap();
negated.to_css(dest)?;
} else {
dest.write_str(" + ")?;
diff --git a/servo/components/style/values/generics/counters.rs b/servo/components/style/values/generics/counters.rs
index 3f23c74b33..d6db48f9c4 100644
--- a/servo/components/style/values/generics/counters.rs
+++ b/servo/components/style/values/generics/counters.rs
@@ -203,6 +203,41 @@ fn is_decimal(counter_type: &CounterStyleType) -> bool {
*counter_type == CounterStyle::decimal()
}
+/// The non-normal, non-none values of the content property.
+#[derive(
+ Clone, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToShmem,
+)]
+#[repr(C)]
+pub struct GenericContentItems<Image> {
+ /// The actual content items. Note that, past the alt marker, only some subset (strings,
+ /// attr(), counter())
+ pub items: thin_vec::ThinVec<GenericContentItem<Image>>,
+ /// The index at which alt text starts, always non-zero. If equal to items.len(), no alt text
+ /// exists.
+ pub alt_start: usize,
+}
+
+impl<Image> ToCss for GenericContentItems<Image>
+where
+ Image: ToCss,
+{
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ for (i, item) in self.items.iter().enumerate() {
+ if i == self.alt_start {
+ dest.write_str(" /")?;
+ }
+ if i != 0 {
+ dest.write_str(" ")?;
+ }
+ item.to_css(dest)?;
+ }
+ Ok(())
+ }
+}
+
/// The specified value for the `content` property.
///
/// https://drafts.csswg.org/css-content/#propdef-content
@@ -216,7 +251,7 @@ pub enum GenericContent<Image> {
/// `none` reserved keyword.
None,
/// Content items.
- Items(#[css(iterable)] crate::OwnedSlice<GenericContentItem<Image>>),
+ Items(GenericContentItems<Image>),
}
pub use self::GenericContent as Content;
diff --git a/servo/components/style/values/resolved/mod.rs b/servo/components/style/values/resolved/mod.rs
index d830474fe6..fc59f8f36d 100644
--- a/servo/components/style/values/resolved/mod.rs
+++ b/servo/components/style/values/resolved/mod.rs
@@ -180,6 +180,25 @@ where
}
}
+impl<T> ToResolvedValue for thin_vec::ThinVec<T>
+where
+ T: ToResolvedValue,
+{
+ type ResolvedValue = thin_vec::ThinVec<<T as ToResolvedValue>::ResolvedValue>;
+
+ #[inline]
+ fn to_resolved_value(self, context: &Context) -> Self::ResolvedValue {
+ self.into_iter()
+ .map(|item| item.to_resolved_value(context))
+ .collect()
+ }
+
+ #[inline]
+ fn from_resolved_value(resolved: Self::ResolvedValue) -> Self {
+ resolved.into_iter().map(T::from_resolved_value).collect()
+ }
+}
+
impl<T> ToResolvedValue for Box<T>
where
T: ToResolvedValue,
diff --git a/servo/components/style/values/specified/box.rs b/servo/components/style/values/specified/box.rs
index ee50227504..9b87103048 100644
--- a/servo/components/style/values/specified/box.rs
+++ b/servo/components/style/values/specified/box.rs
@@ -1495,6 +1495,9 @@ pub enum Appearance {
/// For HTML's <input type=number>
#[parse(condition = "ParserContext::chrome_rules_enabled")]
NumberInput,
+ /// For HTML's <input type=password>
+ #[parse(condition = "ParserContext::chrome_rules_enabled")]
+ PasswordInput,
/// The progress bar's progress indicator
#[parse(condition = "ParserContext::chrome_rules_enabled")]
Progresschunk,
@@ -1576,27 +1579,6 @@ pub enum Appearance {
/// A tooltip.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
Tooltip,
- /// A listbox or tree widget header
- #[parse(condition = "ParserContext::chrome_rules_enabled")]
- Treeheader,
- /// An individual header cell
- #[parse(condition = "ParserContext::chrome_rules_enabled")]
- Treeheadercell,
- /// A tree item.
- #[parse(condition = "ParserContext::chrome_rules_enabled")]
- Treeitem,
- /// A tree widget branch line
- #[parse(condition = "ParserContext::chrome_rules_enabled")]
- Treeline,
- /// A tree widget twisty.
- #[parse(condition = "ParserContext::chrome_rules_enabled")]
- Treetwisty,
- /// Open tree widget twisty.
- #[parse(condition = "ParserContext::chrome_rules_enabled")]
- Treetwistyopen,
- /// A tree widget.
- #[parse(condition = "ParserContext::chrome_rules_enabled")]
- Treeview,
/// Mac help button.
#[parse(condition = "ParserContext::chrome_rules_enabled")]
diff --git a/servo/components/style/values/specified/calc.rs b/servo/components/style/values/specified/calc.rs
index 17f043ac58..a354963ce1 100644
--- a/servo/components/style/values/specified/calc.rs
+++ b/servo/components/style/values/specified/calc.rs
@@ -6,6 +6,7 @@
//!
//! [calc]: https://drafts.csswg.org/css-values/#calc-notation
+use crate::color::parsing::ChannelKeyword;
use crate::parser::ParserContext;
use crate::values::generics::calc::{
self as generic, CalcNodeLeaf, CalcUnits, MinMaxOp, ModRemOp, PositivePercentageBasis,
@@ -80,6 +81,8 @@ pub enum Leaf {
Time(Time),
/// `<resolution>`
Resolution(Resolution),
+ /// A component of a color.
+ ColorComponent(ChannelKeyword),
/// `<percentage>`
Percentage(CSSFloat),
/// `<number>`
@@ -107,6 +110,7 @@ impl ToCss for Leaf {
Self::Percentage(p) => serialize_percentage(p, dest),
Self::Angle(ref a) => a.to_css(dest),
Self::Time(ref t) => t.to_css(dest),
+ Self::ColorComponent(ref s) => s.to_css(dest),
}
}
}
@@ -152,19 +156,21 @@ impl generic::CalcNodeLeaf for Leaf {
Leaf::Angle(_) => CalcUnits::ANGLE,
Leaf::Time(_) => CalcUnits::TIME,
Leaf::Resolution(_) => CalcUnits::RESOLUTION,
+ Leaf::ColorComponent(_) => CalcUnits::COLOR_COMPONENT,
Leaf::Percentage(_) => CalcUnits::PERCENTAGE,
Leaf::Number(_) => CalcUnits::empty(),
}
}
- fn unitless_value(&self) -> f32 {
- match *self {
+ fn unitless_value(&self) -> Option<f32> {
+ Some(match *self {
Self::Length(ref l) => l.unitless_value(),
Self::Percentage(n) | Self::Number(n) => n,
Self::Resolution(ref r) => r.dppx(),
Self::Angle(ref a) => a.degrees(),
Self::Time(ref t) => t.seconds(),
- }
+ Self::ColorComponent(_) => return None,
+ })
}
fn new_number(value: f32) -> Self {
@@ -182,8 +188,8 @@ impl generic::CalcNodeLeaf for Leaf {
return None;
}
- let self_negative = self.is_negative();
- if self_negative != other.is_negative() {
+ let self_negative = self.is_negative().unwrap_or(false);
+ if self_negative != other.is_negative().unwrap_or(false) {
return Some(if self_negative {
cmp::Ordering::Less
} else {
@@ -198,10 +204,11 @@ impl generic::CalcNodeLeaf for Leaf {
(&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()),
(&Resolution(ref one), &Resolution(ref other)) => one.dppx().partial_cmp(&other.dppx()),
(&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
+ (&ColorComponent(ref one), &ColorComponent(ref other)) => one.partial_cmp(other),
_ => {
match *self {
Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..) |
- Resolution(..) => {},
+ Resolution(..) | ColorComponent(..) => {},
}
unsafe {
debug_unreachable!("Forgot a branch?");
@@ -216,14 +223,15 @@ impl generic::CalcNodeLeaf for Leaf {
Leaf::Angle(_) |
Leaf::Time(_) |
Leaf::Resolution(_) |
- Leaf::Percentage(_) => None,
+ Leaf::Percentage(_) |
+ Leaf::ColorComponent(_) => None,
Leaf::Number(value) => Some(value),
}
}
fn sort_key(&self) -> SortKey {
match *self {
- Self::Number(..) => SortKey::Number,
+ Self::Number(..) | Self::ColorComponent(..) => SortKey::Number,
Self::Percentage(..) => SortKey::Percentage,
Self::Time(..) => SortKey::Sec,
Self::Resolution(..) => SortKey::Dppx,
@@ -316,7 +324,7 @@ impl generic::CalcNodeLeaf for Leaf {
_ => {
match *other {
Number(..) | Percentage(..) | Angle(..) | Time(..) | Resolution(..) |
- Length(..) => {},
+ Length(..) | ColorComponent(..) => {},
}
unsafe {
debug_unreachable!();
@@ -336,15 +344,17 @@ impl generic::CalcNodeLeaf for Leaf {
} else {
// The right side is not a number, so the result should be in the units of the right
// side.
- other.map(|v| v * *left);
- std::mem::swap(self, other);
- true
+ if other.map(|v| v * *left).is_ok() {
+ std::mem::swap(self, other);
+ true
+ } else {
+ false
+ }
}
} else if let Self::Number(ref right) = *other {
// The left side is not a number, but the right side is, so the result is the left
// side unit.
- self.map(|v| v * *right);
- true
+ self.map(|v| v * *right).is_ok()
} else {
// Neither side is a number, so a product is not possible.
false
@@ -392,7 +402,7 @@ impl generic::CalcNodeLeaf for Leaf {
_ => {
match *other {
Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) |
- Resolution(..) => {},
+ Resolution(..) | ColorComponent(..) => {},
}
unsafe {
debug_unreachable!();
@@ -401,15 +411,16 @@ impl generic::CalcNodeLeaf for Leaf {
}
}
- fn map(&mut self, mut op: impl FnMut(f32) -> f32) {
- match self {
+ fn map(&mut self, mut op: impl FnMut(f32) -> f32) -> Result<(), ()> {
+ Ok(match self {
Leaf::Length(one) => *one = one.map(op),
Leaf::Angle(one) => *one = specified::Angle::from_calc(op(one.degrees())),
Leaf::Time(one) => *one = specified::Time::from_seconds(op(one.seconds())),
Leaf::Resolution(one) => *one = specified::Resolution::from_dppx(op(one.dppx())),
Leaf::Percentage(one) => *one = op(*one),
Leaf::Number(one) => *one = op(*one),
- }
+ Leaf::ColorComponent(..) => return Err(()),
+ })
}
}
@@ -468,15 +479,30 @@ impl CalcNode {
CalcNode::parse(context, input, function, allowed_units)
},
&Token::Ident(ref ident) => {
- let number = match_ignore_ascii_case! { &**ident,
- "e" => std::f32::consts::E,
- "pi" => std::f32::consts::PI,
- "infinity" => f32::INFINITY,
- "-infinity" => f32::NEG_INFINITY,
- "nan" => f32::NAN,
- _ => return Err(location.new_unexpected_token_error(Token::Ident(ident.clone()))),
+ let leaf = match_ignore_ascii_case! { &**ident,
+ "e" => Leaf::Number(std::f32::consts::E),
+ "pi" => Leaf::Number(std::f32::consts::PI),
+ "infinity" => Leaf::Number(f32::INFINITY),
+ "-infinity" => Leaf::Number(f32::NEG_INFINITY),
+ "nan" => Leaf::Number(f32::NAN),
+ _ => {
+ if crate::color::parsing::rcs_enabled() &&
+ allowed_units.intersects(CalcUnits::COLOR_COMPONENT)
+ {
+ if let Ok(channel_keyword) = ChannelKeyword::from_ident(&ident) {
+ Leaf::ColorComponent(channel_keyword)
+ } else {
+ return Err(location
+ .new_unexpected_token_error(Token::Ident(ident.clone())));
+ }
+ } else {
+ return Err(
+ location.new_unexpected_token_error(Token::Ident(ident.clone()))
+ );
+ }
+ },
};
- Ok(CalcNode::Leaf(Leaf::Number(number)))
+ Ok(CalcNode::Leaf(leaf))
},
t => Err(location.new_unexpected_token_error(t.clone())),
}
@@ -817,7 +843,9 @@ impl CalcNode {
if let Ok(resolved) = right.resolve() {
if let Some(number) = resolved.as_number() {
if number != 1.0 && left.is_product_distributive() {
- left.map(|l| l / number);
+ if left.map(|l| l / number).is_err() {
+ return InPlaceDivisionResult::Invalid;
+ }
return InPlaceDivisionResult::Merged;
}
} else {
diff --git a/servo/components/style/values/specified/color.rs b/servo/components/style/values/specified/color.rs
index f823ba7d30..289d89e44e 100644
--- a/servo/components/style/values/specified/color.rs
+++ b/servo/components/style/values/specified/color.rs
@@ -5,12 +5,8 @@
//! Specified color values.
use super::AllowQuirks;
-use crate::color::component::ColorComponent;
-use crate::color::convert::normalize_hue;
-use crate::color::parsing::{
- self, ColorParser, FromParsedColor, NumberOrAngle, NumberOrPercentage,
-};
-use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorSpace};
+use crate::color::mix::ColorInterpolationMethod;
+use crate::color::{parsing, AbsoluteColor, ColorSpace};
use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
@@ -19,8 +15,7 @@ use crate::values::generics::color::{
};
use crate::values::specified::Percentage;
use crate::values::{normalize, CustomIdent};
-use cssparser::color::OPAQUE;
-use cssparser::{color::PredefinedColorSpace, BasicParseErrorKind, ParseErrorKind, Parser, Token};
+use cssparser::{BasicParseErrorKind, ParseErrorKind, Parser, Token};
use std::fmt::{self, Write};
use std::io::Write as IoWrite;
use style_traits::{CssType, CssWriter, KeywordsCollectFn, ParseError, StyleParseErrorKind};
@@ -429,199 +424,6 @@ impl SystemColor {
}
}
-impl<T> From<ColorComponent<T>> for Option<T> {
- fn from(value: ColorComponent<T>) -> Self {
- match value {
- ColorComponent::None => None,
- ColorComponent::Value(value) => Some(value),
- }
- }
-}
-
-impl ColorComponent<NumberOrPercentage> {
- #[inline]
- fn into_alpha(self) -> Option<f32> {
- match self {
- ColorComponent::None => None,
- ColorComponent::Value(number_or_percentage) => {
- Some(normalize(number_or_percentage.to_number(1.0)).clamp(0.0, OPAQUE))
- },
- }
- }
-}
-
-impl FromParsedColor for Color {
- fn from_current_color() -> Self {
- Color::CurrentColor
- }
-
- fn from_rgba(
- red: ColorComponent<u8>,
- green: ColorComponent<u8>,
- blue: ColorComponent<u8>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self {
- macro_rules! c {
- ($c:expr) => {{
- match $c {
- ColorComponent::None => 0u8,
- ColorComponent::Value(value) => value,
- }
- }};
- }
-
- // Legacy rgb() doesn't support "none" alpha values and falls back to 0.
- let alpha = alpha.into_alpha().unwrap_or(0.0);
-
- AbsoluteColor::srgb_legacy(c!(red), c!(green), c!(blue), alpha).into()
- }
-
- fn from_hsl(
- hue: ColorComponent<NumberOrAngle>,
- saturation: ColorComponent<NumberOrPercentage>,
- lightness: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self {
- // Percent reference range for S and L: 0% = 0.0, 100% = 100.0
- const LIGHTNESS_RANGE: f32 = 100.0;
- const SATURATION_RANGE: f32 = 100.0;
-
- let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
- let saturation =
- saturation.map_value(|s| s.to_number(SATURATION_RANGE).clamp(0.0, SATURATION_RANGE));
- let lightness =
- lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE).clamp(0.0, LIGHTNESS_RANGE));
-
- AbsoluteColor::new(
- ColorSpace::Hsl,
- hue,
- saturation,
- lightness,
- alpha.into_alpha(),
- )
- .into()
- }
-
- fn from_hwb(
- hue: ColorComponent<NumberOrAngle>,
- whiteness: ColorComponent<NumberOrPercentage>,
- blackness: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self {
- // Percent reference range for W and B: 0% = 0.0, 100% = 100.0
- const WHITENESS_RANGE: f32 = 100.0;
- const BLACKNESS_RANGE: f32 = 100.0;
-
- let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
- let whiteness =
- whiteness.map_value(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE));
- let blackness =
- blackness.map_value(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE));
-
- AbsoluteColor::new(
- ColorSpace::Hwb,
- hue,
- whiteness,
- blackness,
- alpha.into_alpha(),
- )
- .into()
- }
-
- fn from_lab(
- lightness: ColorComponent<NumberOrPercentage>,
- a: ColorComponent<NumberOrPercentage>,
- b: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self {
- // for L: 0% = 0.0, 100% = 100.0
- // for a and b: -100% = -125, 100% = 125
- const LIGHTNESS_RANGE: f32 = 100.0;
- const A_B_RANGE: f32 = 125.0;
-
- let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
- let a = a.map_value(|a| a.to_number(A_B_RANGE));
- let b = b.map_value(|b| b.to_number(A_B_RANGE));
-
- AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha.into_alpha()).into()
- }
-
- fn from_lch(
- lightness: ColorComponent<NumberOrPercentage>,
- chroma: ColorComponent<NumberOrPercentage>,
- hue: ColorComponent<NumberOrAngle>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self {
- // for L: 0% = 0.0, 100% = 100.0
- // for C: 0% = 0, 100% = 150
- const LIGHTNESS_RANGE: f32 = 100.0;
- const CHROMA_RANGE: f32 = 150.0;
-
- let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
- let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE));
- let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
-
- AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha.into_alpha()).into()
- }
-
- fn from_oklab(
- lightness: ColorComponent<NumberOrPercentage>,
- a: ColorComponent<NumberOrPercentage>,
- b: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self {
- // for L: 0% = 0.0, 100% = 1.0
- // for a and b: -100% = -0.4, 100% = 0.4
- const LIGHTNESS_RANGE: f32 = 1.0;
- const A_B_RANGE: f32 = 0.4;
-
- let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
- let a = a.map_value(|a| a.to_number(A_B_RANGE));
- let b = b.map_value(|b| b.to_number(A_B_RANGE));
-
- AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha.into_alpha()).into()
- }
-
- fn from_oklch(
- lightness: ColorComponent<NumberOrPercentage>,
- chroma: ColorComponent<NumberOrPercentage>,
- hue: ColorComponent<NumberOrAngle>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self {
- // for L: 0% = 0.0, 100% = 1.0
- // for C: 0% = 0.0 100% = 0.4
- const LIGHTNESS_RANGE: f32 = 1.0;
- const CHROMA_RANGE: f32 = 0.4;
-
- let lightness = lightness.map_value(|l| l.to_number(LIGHTNESS_RANGE));
- let chroma = chroma.map_value(|c| c.to_number(CHROMA_RANGE));
- let hue = hue.map_value(|angle| normalize_hue(angle.degrees()));
-
- AbsoluteColor::new(
- ColorSpace::Oklch,
- lightness,
- chroma,
- hue,
- alpha.into_alpha(),
- )
- .into()
- }
-
- fn from_color_function(
- color_space: PredefinedColorSpace,
- c1: ColorComponent<NumberOrPercentage>,
- c2: ColorComponent<NumberOrPercentage>,
- c3: ColorComponent<NumberOrPercentage>,
- alpha: ColorComponent<NumberOrPercentage>,
- ) -> Self {
- let c1 = c1.map_value(|c| c.to_number(1.0));
- let c2 = c2.map_value(|c| c.to_number(1.0));
- let c3 = c3.map_value(|c| c.to_number(1.0));
-
- AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha.into_alpha()).into()
- }
-}
-
/// Whether to preserve authored colors during parsing. That's useful only if we
/// plan to serialize the color back.
#[derive(Copy, Clone)]
@@ -658,8 +460,7 @@ impl Color {
},
};
- let color_parser = ColorParser { context: &context };
- match input.try_parse(|i| parsing::parse_color_with(&color_parser, i)) {
+ match input.try_parse(|i| parsing::parse_color_with(context, i)) {
Ok(mut color) => {
if let Color::Absolute(ref mut absolute) = color {
// Because we can't set the `authored` value at construction time, we have to set it
@@ -827,12 +628,9 @@ impl Color {
loc: &cssparser::SourceLocation,
) -> Result<Self, ParseError<'i>> {
match cssparser::color::parse_hash_color(bytes) {
- Ok((r, g, b, a)) => Ok(Self::from_rgba(
- r.into(),
- g.into(),
- b.into(),
- ColorComponent::Value(NumberOrPercentage::Number { value: a }),
- )),
+ Ok((r, g, b, a)) => Ok(Self::from_absolute_color(AbsoluteColor::srgb_legacy(
+ r, g, b, a,
+ ))),
Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}
diff --git a/servo/components/style/values/specified/counters.rs b/servo/components/style/values/specified/counters.rs
index 7760be91d7..6e41497caf 100644
--- a/servo/components/style/values/specified/counters.rs
+++ b/servo/components/style/values/specified/counters.rs
@@ -192,29 +192,33 @@ impl Parse for Content {
return Ok(generics::Content::None);
}
- let mut content = vec![];
- let mut has_alt_content = false;
+ let mut items = thin_vec::ThinVec::new();
+ let mut alt_start = None;
loop {
- {
+ if alt_start.is_none() {
if let Ok(image) = input.try_parse(|i| Image::parse_forbid_none(context, i)) {
- content.push(generics::ContentItem::Image(image));
+ items.push(generics::ContentItem::Image(image));
continue;
}
}
- match input.next() {
- Ok(&Token::QuotedString(ref value)) => {
- content.push(generics::ContentItem::String(
+ let Ok(t) = input.next() else { break };
+ match *t {
+ Token::QuotedString(ref value) => {
+ items.push(generics::ContentItem::String(
value.as_ref().to_owned().into(),
));
},
- Ok(&Token::Function(ref name)) => {
+ Token::Function(ref name) => {
+ // FIXME(emilio): counter() / counters() should be valid per spec past
+ // the alt marker, but it's likely non-trivial to support and other
+ // browsers don't support it either, so restricting it for now.
let result = match_ignore_ascii_case! { &name,
- "counter" => input.parse_nested_block(|input| {
+ "counter" if alt_start.is_none() => input.parse_nested_block(|input| {
let name = CustomIdent::parse(input, &[])?;
let style = Content::parse_counter_style(context, input);
Ok(generics::ContentItem::Counter(name, style))
}),
- "counters" => input.parse_nested_block(|input| {
+ "counters" if alt_start.is_none() => input.parse_nested_block(|input| {
let name = CustomIdent::parse(input, &[])?;
input.expect_comma()?;
let separator = input.expect_string()?.as_ref().to_owned().into();
@@ -232,17 +236,16 @@ impl Parse for Content {
))
}
}?;
- content.push(result);
+ items.push(result);
},
- Ok(&Token::Ident(ref ident)) => {
- content.push(match_ignore_ascii_case! { &ident,
+ Token::Ident(ref ident) if alt_start.is_none() => {
+ items.push(match_ignore_ascii_case! { &ident,
"open-quote" => generics::ContentItem::OpenQuote,
"close-quote" => generics::ContentItem::CloseQuote,
"no-open-quote" => generics::ContentItem::NoOpenQuote,
"no-close-quote" => generics::ContentItem::NoCloseQuote,
#[cfg(feature = "gecko")]
- "-moz-alt-content" => {
- has_alt_content = true;
+ "-moz-alt-content" if context.in_ua_sheet() => {
generics::ContentItem::MozAltContent
},
"-moz-label-content" if context.chrome_rules_enabled() => {
@@ -256,17 +259,26 @@ impl Parse for Content {
}
});
},
- Err(_) => break,
- Ok(t) => {
+ Token::Delim('/')
+ if alt_start.is_none() &&
+ !items.is_empty() &&
+ static_prefs::pref!("layout.css.content.alt-text.enabled") =>
+ {
+ alt_start = Some(items.len());
+ },
+ ref t => {
let t = t.clone();
return Err(input.new_unexpected_token_error(t));
},
}
}
- // We don't allow to parse `-moz-alt-content` in multiple positions.
- if content.is_empty() || (has_alt_content && content.len() != 1) {
+ if items.is_empty() {
return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}
- Ok(generics::Content::Items(content.into()))
+ let alt_start = alt_start.unwrap_or(items.len());
+ Ok(generics::Content::Items(generics::GenericContentItems {
+ items,
+ alt_start,
+ }))
}
}