summaryrefslogtreecommitdiffstats
path: root/servo
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:14:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:14:29 +0000
commitfbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 (patch)
tree4c1ccaf5486d4f2009f9a338a98a83e886e29c97 /servo
parentReleasing progress-linux version 124.0.1-1~progress7.99u1. (diff)
downloadfirefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.tar.xz
firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.zip
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'servo')
-rw-r--r--servo/components/selectors/parser.rs30
-rw-r--r--servo/components/style/build_gecko.rs12
-rw-r--r--servo/components/style/color/component.rs48
-rw-r--r--servo/components/style/color/mod.rs92
-rw-r--r--servo/components/style/color/parsing.rs1092
-rw-r--r--servo/components/style/color/to_css.rs251
-rw-r--r--servo/components/style/custom_properties.rs317
-rw-r--r--servo/components/style/custom_properties_map.rs22
-rw-r--r--servo/components/style/dom.rs23
-rw-r--r--servo/components/style/driver.rs42
-rw-r--r--servo/components/style/gecko/media_features.rs5
-rwxr-xr-xservo/components/style/gecko/regen_atoms.py2
-rw-r--r--servo/components/style/gecko/wrapper.rs14
-rw-r--r--servo/components/style/properties/Mako-1.1.2-py2.py3-none-any.whlbin75521 -> 0 bytes
-rw-r--r--servo/components/style/properties/build.py1
-rw-r--r--servo/components/style/properties/cascade.rs2
-rw-r--r--servo/components/style/properties/gecko.mako.rs3
-rw-r--r--servo/components/style/properties/longhands/font.mako.rs6
-rw-r--r--servo/components/style/properties/longhands/ui.mako.rs14
-rw-r--r--servo/components/style/properties/shorthands/svg.mako.rs19
-rw-r--r--servo/components/style/properties/shorthands/ui.mako.rs41
-rw-r--r--servo/components/style/properties_and_values/rule.rs11
-rw-r--r--servo/components/style/properties_and_values/value.rs181
-rw-r--r--servo/components/style/sharing/mod.rs7
-rw-r--r--servo/components/style/values/computed/animation.rs4
-rw-r--r--servo/components/style/values/computed/color.rs5
-rw-r--r--servo/components/style/values/computed/length_percentage.rs13
-rw-r--r--servo/components/style/values/computed/mod.rs2
-rw-r--r--servo/components/style/values/specified/animation.rs38
-rw-r--r--servo/components/style/values/specified/calc.rs57
-rw-r--r--servo/components/style/values/specified/color.rs335
-rw-r--r--servo/components/style/values/specified/font.rs14
-rw-r--r--servo/components/style/values/specified/mod.rs2
-rw-r--r--servo/components/style_traits/values.rs1
-rw-r--r--servo/ports/geckolib/Cargo.toml1
-rw-r--r--servo/ports/geckolib/cbindgen.toml13
-rw-r--r--servo/ports/geckolib/glue.rs210
-rw-r--r--servo/ports/geckolib/lib.rs1
38 files changed, 1519 insertions, 1412 deletions
diff --git a/servo/components/selectors/parser.rs b/servo/components/selectors/parser.rs
index 792d4eb8bc..9b0acb0671 100644
--- a/servo/components/selectors/parser.rs
+++ b/servo/components/selectors/parser.rs
@@ -2018,34 +2018,10 @@ impl<Impl: SelectorImpl> Component<Impl> {
}
}
- /// Whether this component is valid after a pseudo-element. Only intended
- /// for sanity-checking.
- pub fn maybe_allowed_after_pseudo_element(&self) -> bool {
- match *self {
- Component::NonTSPseudoClass(..) => true,
- Component::Negation(ref selectors) |
- Component::Is(ref selectors) |
- Component::Where(ref selectors) => selectors.slice().iter().all(|selector| {
- selector
- .iter_raw_match_order()
- .all(|c| c.maybe_allowed_after_pseudo_element())
- }),
- _ => false,
- }
- }
-
- /// Whether a given selector should match for stateless pseudo-elements.
- ///
- /// This is a bit subtle: Only selectors that return true in
- /// `maybe_allowed_after_pseudo_element` should end up here, and
- /// `NonTSPseudoClass` never matches (as it is a stateless pseudo after
- /// all).
+ /// Whether a given selector (to the right of a pseudo-element) should match for stateless
+ /// pseudo-elements. Note that generally nothing matches for those, but since we have :not(),
+ /// we still need to traverse nested selector lists.
fn matches_for_stateless_pseudo_element(&self) -> bool {
- debug_assert!(
- self.maybe_allowed_after_pseudo_element(),
- "Someone messed up pseudo-element parsing: {:?}",
- *self
- );
match *self {
Component::Negation(ref selectors) => !selectors.slice().iter().all(|selector| {
selector
diff --git a/servo/components/style/build_gecko.rs b/servo/components/style/build_gecko.rs
index a83c5dbc6d..1574f7b6b1 100644
--- a/servo/components/style/build_gecko.rs
+++ b/servo/components/style/build_gecko.rs
@@ -127,17 +127,7 @@ impl BuilderExt for Builder {
.size_t_is_usize(true)
.disable_untagged_union();
- let rustfmt_path = env::var_os("RUSTFMT")
- // This can be replaced with
- // > .filter(|p| !p.is_empty()).map(PathBuf::from)
- // once we can use 1.27+.
- .and_then(|p| {
- if p.is_empty() {
- None
- } else {
- Some(PathBuf::from(p))
- }
- });
+ let rustfmt_path = env::var_os("RUSTFMT").filter(|p| !p.is_empty()).map(PathBuf::from);
if let Some(path) = rustfmt_path {
builder = builder.with_rustfmt(path);
}
diff --git a/servo/components/style/color/component.rs b/servo/components/style/color/component.rs
new file mode 100644
index 0000000000..9f101a460c
--- /dev/null
+++ b/servo/components/style/color/component.rs
@@ -0,0 +1,48 @@
+/* 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/. */
+
+//! Parse/serialize and resolve a single color component.
+
+/// A single color component.
+#[derive(Clone, MallocSizeOf, PartialEq, ToShmem)]
+pub enum ColorComponent<ValueType> {
+ /// The "none" keyword.
+ None,
+ /// A absolute value.
+ Value(ValueType),
+}
+
+impl<ValueType> ColorComponent<ValueType> {
+ /// Return true if the component is "none".
+ #[inline]
+ pub fn is_none(&self) -> bool {
+ matches!(self, Self::None)
+ }
+
+ /// If the component contains a value, map it to another value.
+ pub fn map_value<OutType>(
+ self,
+ f: impl FnOnce(ValueType) -> OutType,
+ ) -> ColorComponent<OutType> {
+ match self {
+ Self::None => ColorComponent::None,
+ Self::Value(value) => ColorComponent::Value(f(value)),
+ }
+ }
+ /// Return the component as its value.
+ pub fn into_value(self) -> ValueType {
+ match self {
+ Self::None => panic!("value not available when component is None"),
+ Self::Value(value) => value,
+ }
+ }
+
+ /// Return the component as its value or a default value.
+ pub fn into_value_or(self, default: ValueType) -> ValueType {
+ match self {
+ Self::None => default,
+ Self::Value(value) => value,
+ }
+ }
+}
diff --git a/servo/components/style/color/mod.rs b/servo/components/style/color/mod.rs
index 797a1cb00f..6f35daaa8a 100644
--- a/servo/components/style/color/mod.rs
+++ b/servo/components/style/color/mod.rs
@@ -4,14 +4,15 @@
//! Color support functions.
+pub mod component;
/// cbindgen:ignore
pub mod convert;
pub mod mix;
pub mod parsing;
+mod to_css;
+use component::ColorComponent;
use cssparser::color::PredefinedColorSpace;
-use std::fmt::{self, Write};
-use style_traits::{CssWriter, ToCss};
/// The 3 components that make up a color. (Does not include the alpha component)
#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
@@ -260,6 +261,22 @@ impl From<Option<f32>> for ComponentDetails {
}
}
+impl From<ColorComponent<f32>> for ComponentDetails {
+ fn from(value: ColorComponent<f32>) -> Self {
+ if let ColorComponent::Value(value) = value {
+ Self {
+ value,
+ is_none: false,
+ }
+ } else {
+ Self {
+ value: 0.0,
+ is_none: true,
+ }
+ }
+ }
+}
+
impl AbsoluteColor {
/// A fully transparent color in the legacy syntax.
pub const TRANSPARENT_BLACK: Self = Self {
@@ -540,74 +557,3 @@ impl From<PredefinedColorSpace> for ColorSpace {
}
}
}
-
-impl ToCss for AbsoluteColor {
- fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
- where
- W: Write,
- {
- match self.color_space {
- ColorSpace::Srgb if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) => {
- // The "none" keyword is not supported in the rgb/rgba legacy syntax.
- cssparser::ToCss::to_css(
- &parsing::RgbaLegacy::from_floats(
- self.components.0,
- self.components.1,
- self.components.2,
- self.alpha,
- ),
- dest,
- )
- },
- ColorSpace::Hsl | ColorSpace::Hwb => self.into_srgb_legacy().to_css(dest),
- ColorSpace::Lab => cssparser::ToCss::to_css(
- &parsing::Lab::new(self.c0(), self.c1(), self.c2(), self.alpha()),
- dest,
- ),
- ColorSpace::Lch => cssparser::ToCss::to_css(
- &parsing::Lch::new(self.c0(), self.c1(), self.c2(), self.alpha()),
- dest,
- ),
- ColorSpace::Oklab => cssparser::ToCss::to_css(
- &parsing::Oklab::new(self.c0(), self.c1(), self.c2(), self.alpha()),
- dest,
- ),
- ColorSpace::Oklch => cssparser::ToCss::to_css(
- &parsing::Oklch::new(self.c0(), self.c1(), self.c2(), self.alpha()),
- dest,
- ),
- _ => {
- let color_space = match self.color_space {
- ColorSpace::Srgb => {
- debug_assert!(
- !self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
- "legacy srgb is not a color function"
- );
- PredefinedColorSpace::Srgb
- },
- ColorSpace::SrgbLinear => PredefinedColorSpace::SrgbLinear,
- ColorSpace::DisplayP3 => PredefinedColorSpace::DisplayP3,
- ColorSpace::A98Rgb => PredefinedColorSpace::A98Rgb,
- ColorSpace::ProphotoRgb => PredefinedColorSpace::ProphotoRgb,
- ColorSpace::Rec2020 => PredefinedColorSpace::Rec2020,
- ColorSpace::XyzD50 => PredefinedColorSpace::XyzD50,
- ColorSpace::XyzD65 => PredefinedColorSpace::XyzD65,
-
- _ => {
- unreachable!("other color spaces do not support color() syntax")
- },
- };
-
- let color_function = parsing::ColorFunction {
- color_space,
- c1: self.c0(),
- c2: self.c1(),
- c3: self.c2(),
- alpha: self.alpha(),
- };
- let color = parsing::Color::ColorFunction(color_function);
- cssparser::ToCss::to_css(&color, dest)
- },
- }
- }
-}
diff --git a/servo/components/style/color/parsing.rs b/servo/components/style/color/parsing.rs
index f60b44c5b6..1ae32c4ad7 100644
--- a/servo/components/style/color/parsing.rs
+++ b/servo/components/style/color/parsing.rs
@@ -8,21 +8,20 @@
//! Relative colors, color-mix, system colors, and other such things require better calc() support
//! and integration.
-use super::{
- convert::{hsl_to_rgb, hwb_to_rgb, normalize_hue},
- ColorComponents,
-};
-use crate::values::normalize;
+use crate::color::component::ColorComponent;
use cssparser::color::{
- clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, serialize_color_alpha,
- PredefinedColorSpace, OPAQUE,
+ clamp_floor_256_f32, clamp_unit_f32, parse_hash_color, PredefinedColorSpace, OPAQUE,
};
-use cssparser::{match_ignore_ascii_case, CowRcStr, ParseError, Parser, ToCss, Token};
-#[cfg(feature = "serde")]
-use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use std::f32::consts::PI;
-use std::fmt;
+use cssparser::{match_ignore_ascii_case, CowRcStr, Parser, Token};
use std::str::FromStr;
+use style_traits::{ParseError, StyleParseErrorKind};
+
+impl From<u8> for ColorComponent<u8> {
+ #[inline]
+ fn from(value: u8) -> Self {
+ ColorComponent::Value(value)
+ }
+}
/// Return the named color with the given name.
///
@@ -34,13 +33,23 @@ pub fn parse_color_keyword<Output>(ident: &str) -> Result<Output, ()>
where
Output: FromParsedColor,
{
- Ok(match_ignore_ascii_case! { ident ,
- "transparent" => Output::from_rgba(0, 0, 0, 0.0),
+ Ok(match_ignore_ascii_case! { ident,
+ "transparent" => Output::from_rgba(
+ 0u8.into(),
+ 0u8.into(),
+ 0u8.into(),
+ ColorComponent::Value(NumberOrPercentage::Number { value: 0.0 }),
+ ),
"currentcolor" => Output::from_current_color(),
_ => {
let (r, g, b) = cssparser::color::parse_named_color(ident)?;
- Output::from_rgba(r, g, b, OPAQUE)
- }
+ Output::from_rgba(
+ r.into(),
+ g.into(),
+ b.into(),
+ ColorComponent::Value(NumberOrPercentage::Number { value: OPAQUE }),
+ )
+ },
})
}
@@ -49,16 +58,22 @@ where
pub fn parse_color_with<'i, 't, P>(
color_parser: &P,
input: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
let location = input.current_source_location();
let token = input.next()?;
match *token {
- Token::Hash(ref value) | Token::IDHash(ref value) => {
- parse_hash_color(value.as_bytes()).map(|(r, g, b, a)| P::Output::from_rgba(r, g, b, a))
- },
+ Token::Hash(ref value) | Token::IDHash(ref value) => parse_hash_color(value.as_bytes())
+ .map(|(r, g, b, a)| {
+ P::Output::from_rgba(
+ r.into(),
+ g.into(),
+ b.into(),
+ ColorComponent::Value(NumberOrPercentage::Number { value: a }),
+ )
+ }),
Token::Ident(ref value) => parse_color_keyword(value),
Token::Function(ref name) => {
let name = name.clone();
@@ -77,35 +92,19 @@ fn parse_color_function<'i, 't, P>(
color_parser: &P,
name: CowRcStr<'i>,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
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),
-
- // for L: 0% = 0.0, 100% = 100.0
- // for a and b: -100% = -125, 100% = 125
- "lab" => parse_lab_like(color_parser, arguments, 100.0, 125.0, P::Output::from_lab),
-
- // for L: 0% = 0.0, 100% = 100.0
- // for C: 0% = 0, 100% = 150
- "lch" => parse_lch_like(color_parser, arguments, 100.0, 150.0, P::Output::from_lch),
-
- // for L: 0% = 0.0, 100% = 1.0
- // for a and b: -100% = -0.4, 100% = 0.4
- "oklab" => parse_lab_like(color_parser, arguments, 1.0, 0.4, P::Output::from_oklab),
-
- // for L: 0% = 0.0, 100% = 1.0
- // for C: 0% = 0.0 100% = 0.4
- "oklch" => parse_lch_like(color_parser, arguments, 1.0, 0.4, P::Output::from_oklch),
-
+ "lab" => parse_lab_like(color_parser, arguments, P::Output::from_lab),
+ "lch" => parse_lch_like(color_parser, arguments, P::Output::from_lch),
+ "oklab" => parse_lab_like(color_parser, arguments, P::Output::from_oklab),
+ "oklch" => parse_lch_like(color_parser, arguments, P::Output::from_oklch),
"color" => parse_color_with_color_space(color_parser, arguments),
-
_ => return Err(arguments.new_unexpected_token_error(Token::Ident(name))),
}?;
@@ -114,50 +113,47 @@ where
Ok(color)
}
-/// Parse the alpha component by itself from either number or percentage,
-/// clipping the result to [0.0..1.0].
-#[inline]
-fn parse_alpha_component<'i, 't, P>(
- color_parser: &P,
- arguments: &mut Parser<'i, 't>,
-) -> Result<f32, ParseError<'i, P::Error>>
-where
- P: ColorParser<'i>,
-{
- // Percent reference range for alpha: 0% = 0.0, 100% = 1.0
- let alpha = color_parser
- .parse_number_or_percentage(arguments)?
- .to_number(1.0);
- Ok(normalize(alpha).clamp(0.0, OPAQUE))
-}
-
fn parse_legacy_alpha<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<f32, ParseError<'i, P::Error>>
+) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>>
where
P: ColorParser<'i>,
{
- Ok(if !arguments.is_exhausted() {
+ if !arguments.is_exhausted() {
arguments.expect_comma()?;
- parse_alpha_component(color_parser, arguments)?
+ color_parser.parse_number_or_percentage(arguments, false)
} else {
- OPAQUE
- })
+ Ok(ColorComponent::Value(NumberOrPercentage::Number {
+ value: OPAQUE,
+ }))
+ }
}
fn parse_modern_alpha<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<Option<f32>, ParseError<'i, P::Error>>
+) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>>
where
P: ColorParser<'i>,
{
if !arguments.is_exhausted() {
arguments.expect_delim('/')?;
- parse_none_or(arguments, |p| parse_alpha_component(color_parser, p))
+ color_parser.parse_number_or_percentage(arguments, true)
} else {
- Ok(Some(OPAQUE))
+ 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, ()> {
+ Ok(match self {
+ Self::Value(NumberOrPercentage::Percentage { .. }) => true,
+ _ => false,
+ })
}
}
@@ -165,58 +161,58 @@ where
fn parse_rgb<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
- let maybe_red = parse_none_or(arguments, |p| color_parser.parse_number_or_percentage(p))?;
+ let location = arguments.current_source_location();
+
+ let maybe_red = color_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.
- let is_legacy_syntax = maybe_red.is_some() && arguments.try_parse(|p| p.expect_comma()).is_ok();
+ let is_legacy_syntax =
+ !maybe_red.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok();
let (red, green, blue, alpha) = if is_legacy_syntax {
- let (red, green, blue) = match maybe_red.unwrap() {
- NumberOrPercentage::Number { value } => {
- let red = clamp_floor_256_f32(value);
- let green = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
- arguments.expect_comma()?;
- let blue = clamp_floor_256_f32(color_parser.parse_number(arguments)?);
- (red, green, blue)
- },
- NumberOrPercentage::Percentage { unit_value } => {
- let red = clamp_unit_f32(unit_value);
- let green = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
- arguments.expect_comma()?;
- let blue = clamp_unit_f32(color_parser.parse_percentage(arguments)?);
- (red, green, blue)
- },
+ 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
+ .parse_percentage(arguments, false)?
+ .map_value(clamp_unit_f32);
+ arguments.expect_comma()?;
+ let blue = color_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
+ .parse_number(arguments, false)?
+ .map_value(clamp_floor_256_f32);
+ arguments.expect_comma()?;
+ let blue = color_parser
+ .parse_number(arguments, false)?
+ .map_value(clamp_floor_256_f32);
+ (red, green, blue)
};
let alpha = parse_legacy_alpha(color_parser, arguments)?;
(red, green, blue, alpha)
} else {
- #[inline]
- fn get_component_value(c: Option<NumberOrPercentage>) -> u8 {
- c.map(|c| match c {
- NumberOrPercentage::Number { value } => clamp_floor_256_f32(value),
- NumberOrPercentage::Percentage { unit_value } => clamp_unit_f32(unit_value),
- })
- .unwrap_or(0)
- }
-
- let red = get_component_value(maybe_red);
+ 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 green = get_component_value(parse_none_or(arguments, |p| {
- color_parser.parse_number_or_percentage(p)
- })?);
-
- let blue = get_component_value(parse_none_or(arguments, |p| {
- color_parser.parse_number_or_percentage(p)
- })?);
-
- let alpha = parse_modern_alpha(color_parser, arguments)?.unwrap_or(0.0);
+ let alpha = parse_modern_alpha(color_parser, arguments)?;
(red, green, blue, alpha)
};
@@ -231,40 +227,39 @@ where
fn parse_hsl<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
- // 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 maybe_hue = parse_none_or(arguments, |p| color_parser.parse_angle_or_number(p))?;
+ let hue = color_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.
- let is_legacy_syntax = maybe_hue.is_some() && arguments.try_parse(|p| p.expect_comma()).is_ok();
-
- let saturation: Option<f32>;
- let lightness: Option<f32>;
+ let is_legacy_syntax = !hue.is_none() && arguments.try_parse(|p| p.expect_comma()).is_ok();
- let alpha = if is_legacy_syntax {
- saturation = Some(color_parser.parse_percentage(arguments)? * SATURATION_RANGE);
+ let (saturation, lightness, alpha) = if is_legacy_syntax {
+ let saturation = color_parser
+ .parse_percentage(arguments, false)?
+ .map_value(|unit_value| NumberOrPercentage::Percentage { unit_value });
arguments.expect_comma()?;
- lightness = Some(color_parser.parse_percentage(arguments)? * LIGHTNESS_RANGE);
- Some(parse_legacy_alpha(color_parser, arguments)?)
+ let lightness = color_parser
+ .parse_percentage(arguments, false)?
+ .map_value(|unit_value| NumberOrPercentage::Percentage { unit_value });
+ (
+ saturation,
+ lightness,
+ parse_legacy_alpha(color_parser, arguments)?,
+ )
} else {
- saturation = parse_none_or(arguments, |p| color_parser.parse_number_or_percentage(p))?
- .map(|v| v.to_number(SATURATION_RANGE));
- lightness = parse_none_or(arguments, |p| color_parser.parse_number_or_percentage(p))?
- .map(|v| v.to_number(LIGHTNESS_RANGE));
- parse_modern_alpha(color_parser, arguments)?
+ 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 hue = maybe_hue.map(|h| normalize_hue(h.degrees()));
- let saturation = saturation.map(|s| s.clamp(0.0, SATURATION_RANGE));
- let lightness = lightness.map(|l| l.clamp(0.0, LIGHTNESS_RANGE));
-
Ok(P::Output::from_hsl(hue, saturation, lightness, alpha))
}
@@ -275,40 +270,34 @@ where
fn parse_hwb<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
- // 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, whiteness, blackness, alpha) = parse_components(
color_parser,
arguments,
- P::parse_angle_or_number,
+ P::parse_number_or_angle,
P::parse_number_or_percentage,
P::parse_number_or_percentage,
)?;
- let hue = hue.map(|h| normalize_hue(h.degrees()));
- let whiteness = whiteness.map(|w| w.to_number(WHITENESS_RANGE).clamp(0.0, WHITENESS_RANGE));
- let blackness = blackness.map(|b| b.to_number(BLACKNESS_RANGE).clamp(0.0, BLACKNESS_RANGE));
-
Ok(P::Output::from_hwb(hue, whiteness, blackness, alpha))
}
-type IntoColorFn<Output> =
- fn(l: Option<f32>, a: Option<f32>, b: Option<f32>, alpha: Option<f32>) -> Output;
+type IntoLabFn<Output> = fn(
+ l: ColorComponent<NumberOrPercentage>,
+ a: ColorComponent<NumberOrPercentage>,
+ b: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
+) -> Output;
#[inline]
fn parse_lab_like<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
- lightness_range: f32,
- a_b_range: f32,
- into_color: IntoColorFn<P::Output>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+ into_color: IntoLabFn<P::Output>,
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
@@ -320,21 +309,22 @@ where
P::parse_number_or_percentage,
)?;
- let lightness = lightness.map(|l| l.to_number(lightness_range));
- let a = a.map(|a| a.to_number(a_b_range));
- let b = b.map(|b| b.to_number(a_b_range));
-
Ok(into_color(lightness, a, b, alpha))
}
+type IntoLchFn<Output> = fn(
+ l: ColorComponent<NumberOrPercentage>,
+ a: ColorComponent<NumberOrPercentage>,
+ b: ColorComponent<NumberOrAngle>,
+ alpha: ColorComponent<NumberOrPercentage>,
+) -> Output;
+
#[inline]
fn parse_lch_like<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
- lightness_range: f32,
- chroma_range: f32,
- into_color: IntoColorFn<P::Output>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+ into_color: IntoLchFn<P::Output>,
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
@@ -343,13 +333,9 @@ where
arguments,
P::parse_number_or_percentage,
P::parse_number_or_percentage,
- P::parse_angle_or_number,
+ P::parse_number_or_angle,
)?;
- let lightness = lightness.map(|l| l.to_number(lightness_range));
- let chroma = chroma.map(|c| c.to_number(chroma_range));
- let hue = hue.map(|h| normalize_hue(h.degrees()));
-
Ok(into_color(lightness, chroma, hue, alpha))
}
@@ -358,7 +344,7 @@ where
fn parse_color_with_color_space<'i, 't, P>(
color_parser: &P,
arguments: &mut Parser<'i, 't>,
-) -> Result<P::Output, ParseError<'i, P::Error>>
+) -> Result<P::Output, ParseError<'i>>
where
P: ColorParser<'i>,
{
@@ -378,10 +364,6 @@ where
P::parse_number_or_percentage,
)?;
- let c1 = c1.map(|c| c.to_number(1.0));
- let c2 = c2.map(|c| c.to_number(1.0));
- let c3 = c3.map(|c| c.to_number(1.0));
-
Ok(P::Output::from_color_function(
color_space,
c1,
@@ -391,8 +373,15 @@ where
))
}
-type ComponentParseResult<'i, R1, R2, R3, Error> =
- Result<(Option<R1>, Option<R2>, Option<R3>, Option<f32>), ParseError<'i, Error>>;
+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<'i, 't, P, F1, F2, F3, R1, R2, R3>(
@@ -401,567 +390,22 @@ pub fn parse_components<'i, 't, P, F1, F2, F3, R1, R2, R3>(
f1: F1,
f2: F2,
f3: F3,
-) -> ComponentParseResult<'i, R1, R2, R3, P::Error>
+) -> ComponentParseResult<'i, R1, R2, R3>
where
P: ColorParser<'i>,
- F1: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R1, ParseError<'i, P::Error>>,
- F2: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R2, ParseError<'i, P::Error>>,
- F3: FnOnce(&P, &mut Parser<'i, 't>) -> Result<R3, ParseError<'i, P::Error>>,
+ F1: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R1>, ParseError<'i>>,
+ F2: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R2>, ParseError<'i>>,
+ F3: FnOnce(&P, &mut Parser<'i, 't>, bool) -> Result<ColorComponent<R3>, ParseError<'i>>,
{
- let r1 = parse_none_or(input, |p| f1(color_parser, p))?;
- let r2 = parse_none_or(input, |p| f2(color_parser, p))?;
- let r3 = parse_none_or(input, |p| f3(color_parser, p))?;
+ 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))
}
-fn parse_none_or<'i, 't, F, T, E>(input: &mut Parser<'i, 't>, thing: F) -> Result<Option<T>, E>
-where
- F: FnOnce(&mut Parser<'i, 't>) -> Result<T, E>,
-{
- match input.try_parse(|p| p.expect_ident_matching("none")) {
- Ok(_) => Ok(None),
- Err(_) => Ok(Some(thing(input)?)),
- }
-}
-
-/// A [`ModernComponent`] can serialize to `none`, `nan`, `infinity` and
-/// floating point values.
-struct ModernComponent<'a>(&'a Option<f32>);
-
-impl<'a> ToCss for ModernComponent<'a> {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- if let Some(value) = self.0 {
- if value.is_finite() {
- value.to_css(dest)
- } else if value.is_nan() {
- dest.write_str("calc(NaN)")
- } else {
- debug_assert!(value.is_infinite());
- if value.is_sign_negative() {
- dest.write_str("calc(-infinity)")
- } else {
- dest.write_str("calc(infinity)")
- }
- }
- } else {
- dest.write_str("none")
- }
- }
-}
-
-/// A color with red, green, blue, and alpha components, in a byte each.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct RgbaLegacy {
- /// The red component.
- pub red: u8,
- /// The green component.
- pub green: u8,
- /// The blue component.
- pub blue: u8,
- /// The alpha component.
- pub alpha: f32,
-}
-
-impl RgbaLegacy {
- /// Constructs a new RGBA value from float components. It expects the red,
- /// green, blue and alpha channels in that order, and all values will be
- /// clamped to the 0.0 ... 1.0 range.
- #[inline]
- pub fn from_floats(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
- Self::new(
- clamp_unit_f32(red),
- clamp_unit_f32(green),
- clamp_unit_f32(blue),
- alpha.clamp(0.0, OPAQUE),
- )
- }
-
- /// Same thing, but with `u8` values instead of floats in the 0 to 1 range.
- #[inline]
- pub const fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
- Self {
- red,
- green,
- blue,
- alpha,
- }
- }
-}
-
-#[cfg(feature = "serde")]
-impl Serialize for RgbaLegacy {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.red, self.green, self.blue, self.alpha).serialize(serializer)
- }
-}
-
-#[cfg(feature = "serde")]
-impl<'de> Deserialize<'de> for RgbaLegacy {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (r, g, b, a) = Deserialize::deserialize(deserializer)?;
- Ok(RgbaLegacy::new(r, g, b, a))
- }
-}
-
-impl ToCss for RgbaLegacy {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- let has_alpha = self.alpha != OPAQUE;
-
- dest.write_str(if has_alpha { "rgba(" } else { "rgb(" })?;
- self.red.to_css(dest)?;
- dest.write_str(", ")?;
- self.green.to_css(dest)?;
- dest.write_str(", ")?;
- self.blue.to_css(dest)?;
-
- // Legacy syntax does not allow none components.
- serialize_color_alpha(dest, Some(self.alpha), true)?;
-
- dest.write_char(')')
- }
-}
-
-/// Color specified by hue, saturation and lightness components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Hsl {
- /// The hue component.
- pub hue: Option<f32>,
- /// The saturation component.
- pub saturation: Option<f32>,
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-impl Hsl {
- /// Construct a new HSL color from it's components.
- pub fn new(
- hue: Option<f32>,
- saturation: Option<f32>,
- lightness: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- hue,
- saturation,
- lightness,
- alpha,
- }
- }
-}
-
-impl ToCss for Hsl {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- // HSL serializes to RGB, so we have to convert it.
- let ColorComponents(red, green, blue) = hsl_to_rgb(&ColorComponents(
- self.hue.unwrap_or(0.0) / 360.0,
- self.saturation.unwrap_or(0.0),
- self.lightness.unwrap_or(0.0),
- ));
-
- RgbaLegacy::from_floats(red, green, blue, self.alpha.unwrap_or(OPAQUE)).to_css(dest)
- }
-}
-
-#[cfg(feature = "serde")]
-impl Serialize for Hsl {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.hue, self.saturation, self.lightness, self.alpha).serialize(serializer)
- }
-}
-
-#[cfg(feature = "serde")]
-impl<'de> Deserialize<'de> for Hsl {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (lightness, a, b, alpha) = Deserialize::deserialize(deserializer)?;
- Ok(Self::new(lightness, a, b, alpha))
- }
-}
-
-/// Color specified by hue, whiteness and blackness components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Hwb {
- /// The hue component.
- pub hue: Option<f32>,
- /// The whiteness component.
- pub whiteness: Option<f32>,
- /// The blackness component.
- pub blackness: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-impl Hwb {
- /// Construct a new HWB color from it's components.
- pub fn new(
- hue: Option<f32>,
- whiteness: Option<f32>,
- blackness: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- hue,
- whiteness,
- blackness,
- alpha,
- }
- }
-}
-
-impl ToCss for Hwb {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- // HWB serializes to RGB, so we have to convert it.
- let ColorComponents(red, green, blue) = hwb_to_rgb(&ColorComponents(
- self.hue.unwrap_or(0.0) / 360.0,
- self.whiteness.unwrap_or(0.0),
- self.blackness.unwrap_or(0.0),
- ));
-
- RgbaLegacy::from_floats(red, green, blue, self.alpha.unwrap_or(OPAQUE)).to_css(dest)
- }
-}
-
-#[cfg(feature = "serde")]
-impl Serialize for Hwb {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.hue, self.whiteness, self.blackness, self.alpha).serialize(serializer)
- }
-}
-
-#[cfg(feature = "serde")]
-impl<'de> Deserialize<'de> for Hwb {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (lightness, whiteness, blackness, alpha) = Deserialize::deserialize(deserializer)?;
- Ok(Self::new(lightness, whiteness, blackness, alpha))
- }
-}
-
-// NOTE: LAB and OKLAB is not declared inside the [impl_lab_like] macro,
-// because it causes cbindgen to ignore them.
-
-/// Color specified by lightness, a- and b-axis components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Lab {
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The a-axis component.
- pub a: Option<f32>,
- /// The b-axis component.
- pub b: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-/// Color specified by lightness, a- and b-axis components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Oklab {
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The a-axis component.
- pub a: Option<f32>,
- /// The b-axis component.
- pub b: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-macro_rules! impl_lab_like {
- ($cls:ident, $fname:literal) => {
- impl $cls {
- /// Construct a new Lab color format with lightness, a, b and alpha components.
- pub fn new(
- lightness: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- lightness,
- a,
- b,
- alpha,
- }
- }
- }
-
- #[cfg(feature = "serde")]
- impl Serialize for $cls {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.lightness, self.a, self.b, self.alpha).serialize(serializer)
- }
- }
-
- #[cfg(feature = "serde")]
- impl<'de> Deserialize<'de> for $cls {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (lightness, a, b, alpha) = Deserialize::deserialize(deserializer)?;
- Ok(Self::new(lightness, a, b, alpha))
- }
- }
-
- impl ToCss for $cls {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- dest.write_str($fname)?;
- dest.write_str("(")?;
- ModernComponent(&self.lightness).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.a).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.b).to_css(dest)?;
- serialize_color_alpha(dest, self.alpha, false)?;
- dest.write_char(')')
- }
- }
- };
-}
-
-impl_lab_like!(Lab, "lab");
-impl_lab_like!(Oklab, "oklab");
-
-// NOTE: LCH and OKLCH is not declared inside the [impl_lch_like] macro,
-// because it causes cbindgen to ignore them.
-
-/// Color specified by lightness, chroma and hue components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Lch {
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The chroma component.
- pub chroma: Option<f32>,
- /// The hue component.
- pub hue: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-/// Color specified by lightness, chroma and hue components.
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct Oklch {
- /// The lightness component.
- pub lightness: Option<f32>,
- /// The chroma component.
- pub chroma: Option<f32>,
- /// The hue component.
- pub hue: Option<f32>,
- /// The alpha component.
- pub alpha: Option<f32>,
-}
-
-macro_rules! impl_lch_like {
- ($cls:ident, $fname:literal) => {
- impl $cls {
- /// Construct a new color with lightness, chroma and hue components.
- pub fn new(
- lightness: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- lightness,
- chroma,
- hue,
- alpha,
- }
- }
- }
-
- #[cfg(feature = "serde")]
- impl Serialize for $cls {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- (self.lightness, self.chroma, self.hue, self.alpha).serialize(serializer)
- }
- }
-
- #[cfg(feature = "serde")]
- impl<'de> Deserialize<'de> for $cls {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let (lightness, chroma, hue, alpha) = Deserialize::deserialize(deserializer)?;
- Ok(Self::new(lightness, chroma, hue, alpha))
- }
- }
-
- impl ToCss for $cls {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- dest.write_str($fname)?;
- dest.write_str("(")?;
- ModernComponent(&self.lightness).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.chroma).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.hue).to_css(dest)?;
- serialize_color_alpha(dest, self.alpha, false)?;
- dest.write_char(')')
- }
- }
- };
-}
-
-impl_lch_like!(Lch, "lch");
-impl_lch_like!(Oklch, "oklch");
-
-/// A color specified by the color() function.
-/// <https://drafts.csswg.org/css-color-4/#color-function>
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub struct ColorFunction {
- /// The color space for this color.
- pub color_space: PredefinedColorSpace,
- /// The first component of the color. Either red or x.
- pub c1: Option<f32>,
- /// The second component of the color. Either green or y.
- pub c2: Option<f32>,
- /// The third component of the color. Either blue or z.
- pub c3: Option<f32>,
- /// The alpha component of the color.
- pub alpha: Option<f32>,
-}
-
-impl ColorFunction {
- /// Construct a new color function definition with the given color space and
- /// color components.
- pub fn new(
- color_space: PredefinedColorSpace,
- c1: Option<f32>,
- c2: Option<f32>,
- c3: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Self {
- color_space,
- c1,
- c2,
- c3,
- alpha,
- }
- }
-}
-
-impl ToCss for ColorFunction {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- dest.write_str("color(")?;
- self.color_space.to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.c1).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.c2).to_css(dest)?;
- dest.write_char(' ')?;
- ModernComponent(&self.c3).to_css(dest)?;
-
- serialize_color_alpha(dest, self.alpha, false)?;
-
- dest.write_char(')')
- }
-}
-
-/// Describes one of the value <color> values according to the CSS
-/// specification.
-///
-/// Most components are `Option<_>`, so when the value is `None`, that component
-/// serializes to the "none" keyword.
-///
-/// <https://drafts.csswg.org/css-color-4/#color-type>
-#[derive(Clone, Copy, PartialEq, Debug)]
-pub enum Color {
- /// The 'currentcolor' keyword.
- CurrentColor,
- /// Specify sRGB colors directly by their red/green/blue/alpha chanels.
- Rgba(RgbaLegacy),
- /// Specifies a color in sRGB using hue, saturation and lightness components.
- Hsl(Hsl),
- /// Specifies a color in sRGB using hue, whiteness and blackness components.
- Hwb(Hwb),
- /// Specifies a CIELAB color by CIE Lightness and its a- and b-axis hue
- /// coordinates (red/green-ness, and yellow/blue-ness) using the CIE LAB
- /// rectangular coordinate model.
- Lab(Lab),
- /// Specifies a CIELAB color by CIE Lightness, Chroma, and hue using the
- /// CIE LCH cylindrical coordinate model.
- Lch(Lch),
- /// Specifies an Oklab color by Oklab Lightness and its a- and b-axis hue
- /// coordinates (red/green-ness, and yellow/blue-ness) using the Oklab
- /// rectangular coordinate model.
- Oklab(Oklab),
- /// Specifies an Oklab color by Oklab Lightness, Chroma, and hue using
- /// the OKLCH cylindrical coordinate model.
- Oklch(Oklch),
- /// Specifies a color in a predefined color space.
- ColorFunction(ColorFunction),
-}
-
-impl ToCss for Color {
- fn to_css<W>(&self, dest: &mut W) -> fmt::Result
- where
- W: fmt::Write,
- {
- match *self {
- Color::CurrentColor => dest.write_str("currentcolor"),
- Color::Rgba(rgba) => rgba.to_css(dest),
- Color::Hsl(hsl) => hsl.to_css(dest),
- Color::Hwb(hwb) => hwb.to_css(dest),
- Color::Lab(lab) => lab.to_css(dest),
- Color::Lch(lch) => lch.to_css(dest),
- Color::Oklab(lab) => lab.to_css(dest),
- Color::Oklch(lch) => lch.to_css(dest),
- Color::ColorFunction(color_function) => color_function.to_css(dest),
- }
- }
-}
-
/// Either a number or a percentage.
pub enum NumberOrPercentage {
/// `<number>`.
@@ -989,7 +433,7 @@ impl NumberOrPercentage {
}
/// Either an angle or a number.
-pub enum AngleOrNumber {
+pub enum NumberOrAngle {
/// `<number>`.
Number {
/// The numeric value parsed, as a float.
@@ -1002,13 +446,13 @@ pub enum AngleOrNumber {
},
}
-impl AngleOrNumber {
- /// Return the angle in degrees. `AngleOrNumber::Number` is returned as
+impl NumberOrAngle {
+ /// Return the angle in degrees. `NumberOrAngle::Number` is returned as
/// degrees, because it is the canonical unit.
pub fn degrees(&self) -> f32 {
match *self {
- AngleOrNumber::Number { value } => value,
- AngleOrNumber::Angle { degrees } => degrees,
+ Self::Number { value } => value,
+ Self::Angle { degrees } => degrees,
}
}
}
@@ -1021,37 +465,14 @@ pub trait ColorParser<'i> {
/// The type that the parser will construct on a successful parse.
type Output: FromParsedColor;
- /// A custom error type that can be returned from the parsing functions.
- type Error: 'i;
-
/// Parse an `<angle>` or `<number>`.
///
/// Returns the result in degrees.
- fn parse_angle_or_number<'t>(
+ fn parse_number_or_angle<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<AngleOrNumber, ParseError<'i, Self::Error>> {
- let location = input.current_source_location();
- Ok(match *input.next()? {
- Token::Number { value, .. } => AngleOrNumber::Number { value },
- Token::Dimension {
- value: v, ref unit, ..
- } => {
- let degrees = match_ignore_ascii_case! { unit,
- "deg" => v,
- "grad" => v * 360. / 400.,
- "rad" => v * 360. / (2. * PI),
- "turn" => v * 360.,
- _ => {
- return Err(location.new_unexpected_token_error(Token::Ident(unit.clone())))
- }
- };
-
- AngleOrNumber::Angle { degrees }
- },
- ref t => return Err(location.new_unexpected_token_error(t.clone())),
- })
- }
+ allow_none: bool,
+ ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>>;
/// Parse a `<percentage>` value.
///
@@ -1059,47 +480,22 @@ pub trait ColorParser<'i> {
fn parse_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<f32, ParseError<'i, Self::Error>> {
- input.expect_percentage().map_err(From::from)
- }
+ allow_none: bool,
+ ) -> Result<ColorComponent<f32>, ParseError<'i>>;
/// Parse a `<number>` value.
fn parse_number<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<f32, ParseError<'i, Self::Error>> {
- input.expect_number().map_err(From::from)
- }
+ allow_none: bool,
+ ) -> Result<ColorComponent<f32>, ParseError<'i>>;
/// Parse a `<number>` value or a `<percentage>` value.
fn parse_number_or_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<NumberOrPercentage, ParseError<'i, Self::Error>> {
- let location = input.current_source_location();
- Ok(match *input.next()? {
- Token::Number { value, .. } => NumberOrPercentage::Number { value },
- Token::Percentage { unit_value, .. } => NumberOrPercentage::Percentage { unit_value },
- ref t => return Err(location.new_unexpected_token_error(t.clone())),
- })
- }
-}
-
-/// Default implementation of a [`ColorParser`]
-pub struct DefaultColorParser;
-
-impl<'i> ColorParser<'i> for DefaultColorParser {
- type Output = Color;
- type Error = ();
-}
-
-impl Color {
- /// Parse a <color> value, per CSS Color Module Level 3.
- ///
- /// FIXME(#2) Deprecated CSS2 System Colors are not supported yet.
- pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result<Color, ParseError<'i, ()>> {
- parse_color_with(&DefaultColorParser, input)
- }
+ allow_none: bool,
+ ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>>;
}
/// This trait is used by the [`ColorParser`] to construct colors of any type.
@@ -1108,139 +504,67 @@ pub trait FromParsedColor {
fn from_current_color() -> Self;
/// Construct a new color from red, green, blue and alpha components.
- fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self;
+ 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: Option<f32>,
- saturation: Option<f32>,
- lightness: Option<f32>,
- alpha: Option<f32>,
+ 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: Option<f32>,
- whiteness: Option<f32>,
- blackness: Option<f32>,
- alpha: Option<f32>,
+ 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: Option<f32>, a: Option<f32>, b: Option<f32>, alpha: Option<f32>)
- -> Self;
+ 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: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
+ 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: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
+ 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: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
+ 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: Option<f32>,
- c2: Option<f32>,
- c3: Option<f32>,
- alpha: Option<f32>,
+ c1: ColorComponent<NumberOrPercentage>,
+ c2: ColorComponent<NumberOrPercentage>,
+ c3: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self;
}
-
-impl FromParsedColor for Color {
- #[inline]
- fn from_current_color() -> Self {
- Color::CurrentColor
- }
-
- #[inline]
- fn from_rgba(red: u8, green: u8, blue: u8, alpha: f32) -> Self {
- Color::Rgba(RgbaLegacy::new(red, green, blue, alpha))
- }
-
- fn from_hsl(
- hue: Option<f32>,
- saturation: Option<f32>,
- lightness: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Hsl(Hsl::new(hue, saturation, lightness, alpha))
- }
-
- fn from_hwb(
- hue: Option<f32>,
- blackness: Option<f32>,
- whiteness: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Hwb(Hwb::new(hue, blackness, whiteness, alpha))
- }
-
- #[inline]
- fn from_lab(
- lightness: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Lab(Lab::new(lightness, a, b, alpha))
- }
-
- #[inline]
- fn from_lch(
- lightness: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Lch(Lch::new(lightness, chroma, hue, alpha))
- }
-
- #[inline]
- fn from_oklab(
- lightness: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Oklab(Oklab::new(lightness, a, b, alpha))
- }
-
- #[inline]
- fn from_oklch(
- lightness: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::Oklch(Oklch::new(lightness, chroma, hue, alpha))
- }
-
- #[inline]
- fn from_color_function(
- color_space: PredefinedColorSpace,
- c1: Option<f32>,
- c2: Option<f32>,
- c3: Option<f32>,
- alpha: Option<f32>,
- ) -> Self {
- Color::ColorFunction(ColorFunction::new(color_space, c1, c2, c3, alpha))
- }
-}
diff --git a/servo/components/style/color/to_css.rs b/servo/components/style/color/to_css.rs
new file mode 100644
index 0000000000..350d8386f1
--- /dev/null
+++ b/servo/components/style/color/to_css.rs
@@ -0,0 +1,251 @@
+/* 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/. */
+
+//! Write colors into CSS strings.
+
+use super::{AbsoluteColor, ColorFlags, ColorSpace};
+use crate::values::normalize;
+use cssparser::color::{clamp_unit_f32, serialize_color_alpha, OPAQUE};
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ToCss};
+
+/// A [`ModernComponent`] can serialize to `none`, `nan`, `infinity` and
+/// floating point values.
+struct ModernComponent<'a>(&'a Option<f32>);
+
+impl<'a> ToCss for ModernComponent<'a> {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ if let Some(value) = self.0 {
+ if value.is_finite() {
+ value.to_css(dest)
+ } else if value.is_nan() {
+ dest.write_str("calc(NaN)")
+ } else {
+ debug_assert!(value.is_infinite());
+ if value.is_sign_negative() {
+ dest.write_str("calc(-infinity)")
+ } else {
+ dest.write_str("calc(infinity)")
+ }
+ }
+ } else {
+ dest.write_str("none")
+ }
+ }
+}
+
+impl ToCss for AbsoluteColor {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ match self.color_space {
+ ColorSpace::Srgb if self.flags.contains(ColorFlags::IS_LEGACY_SRGB) => {
+ // The "none" keyword is not supported in the rgb/rgba legacy syntax.
+ let has_alpha = self.alpha != OPAQUE;
+
+ dest.write_str(if has_alpha { "rgba(" } else { "rgb(" })?;
+ clamp_unit_f32(self.components.0).to_css(dest)?;
+ dest.write_str(", ")?;
+ clamp_unit_f32(self.components.1).to_css(dest)?;
+ dest.write_str(", ")?;
+ clamp_unit_f32(self.components.2).to_css(dest)?;
+
+ // Legacy syntax does not allow none components.
+ serialize_color_alpha(dest, Some(self.alpha), true)?;
+
+ dest.write_char(')')
+ },
+ ColorSpace::Hsl | ColorSpace::Hwb => self.into_srgb_legacy().to_css(dest),
+ ColorSpace::Oklab | ColorSpace::Lab | ColorSpace::Oklch | ColorSpace::Lch => {
+ if let ColorSpace::Oklab | ColorSpace::Oklch = self.color_space {
+ dest.write_str("ok")?;
+ }
+ if let ColorSpace::Oklab | ColorSpace::Lab = self.color_space {
+ dest.write_str("lab(")?;
+ } else {
+ dest.write_str("lch(")?;
+ }
+ ModernComponent(&self.c0()).to_css(dest)?;
+ dest.write_char(' ')?;
+ ModernComponent(&self.c1()).to_css(dest)?;
+ dest.write_char(' ')?;
+ ModernComponent(&self.c2()).to_css(dest)?;
+ serialize_color_alpha(dest, self.alpha(), false)?;
+ dest.write_char(')')
+ },
+ _ => {
+ #[cfg(debug_assertions)]
+ match self.color_space {
+ ColorSpace::Srgb => {
+ debug_assert!(
+ !self.flags.contains(ColorFlags::IS_LEGACY_SRGB),
+ "legacy srgb is not a color function"
+ );
+ },
+ ColorSpace::SrgbLinear |
+ ColorSpace::DisplayP3 |
+ ColorSpace::A98Rgb |
+ ColorSpace::ProphotoRgb |
+ ColorSpace::Rec2020 |
+ ColorSpace::XyzD50 |
+ ColorSpace::XyzD65 => {
+ // These color spaces are allowed.
+ },
+ _ => {
+ unreachable!("other color spaces do not support color() syntax")
+ },
+ };
+
+ dest.write_str("color(")?;
+ self.color_space.to_css(dest)?;
+ dest.write_char(' ')?;
+ ModernComponent(&self.c0()).to_css(dest)?;
+ dest.write_char(' ')?;
+ ModernComponent(&self.c1()).to_css(dest)?;
+ dest.write_char(' ')?;
+ ModernComponent(&self.c2()).to_css(dest)?;
+
+ serialize_color_alpha(dest, self.alpha(), false)?;
+
+ dest.write_char(')')
+ },
+ }
+ }
+}
+
+impl AbsoluteColor {
+ /// Write a string to `dest` that represents a color as an author would
+ /// enter it.
+ /// NOTE: The format of the output is NOT according to any specification,
+ /// but makes assumptions about the best ways that authors would want to
+ /// enter color values in style sheets, devtools, etc.
+ pub fn write_author_preferred_value<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ macro_rules! precision {
+ ($v:expr) => {{
+ ($v * 100.0).round() / 100.0
+ }};
+ }
+ macro_rules! number {
+ ($c:expr) => {{
+ if let Some(v) = $c.map(normalize) {
+ precision!(v).to_css(dest)?;
+ } else {
+ write!(dest, "none")?;
+ }
+ }};
+ }
+ macro_rules! percentage {
+ ($c:expr) => {{
+ if let Some(v) = $c.map(normalize) {
+ precision!(v).to_css(dest)?;
+ dest.write_char('%')?;
+ } else {
+ write!(dest, "none")?;
+ }
+ }};
+ }
+ macro_rules! unit_percentage {
+ ($c:expr) => {{
+ if let Some(v) = $c.map(normalize) {
+ precision!(v * 100.0).to_css(dest)?;
+ dest.write_char('%')?;
+ } else {
+ write!(dest, "none")?;
+ }
+ }};
+ }
+ macro_rules! angle {
+ ($c:expr) => {{
+ if let Some(v) = $c.map(normalize) {
+ precision!(v).to_css(dest)?;
+ dest.write_str("deg")?;
+ } else {
+ write!(dest, "none")?;
+ }
+ }};
+ }
+
+ match self.color_space {
+ ColorSpace::Srgb => {
+ write!(dest, "rgb(")?;
+ unit_percentage!(self.c0());
+ dest.write_char(' ')?;
+ unit_percentage!(self.c1());
+ dest.write_char(' ')?;
+ unit_percentage!(self.c2());
+ serialize_color_alpha(dest, self.alpha(), false)?;
+ dest.write_char(')')
+ },
+ ColorSpace::Hsl | ColorSpace::Hwb => {
+ dest.write_str(if self.color_space == ColorSpace::Hsl {
+ "hsl("
+ } else {
+ "hwb("
+ })?;
+ angle!(self.c0());
+ dest.write_char(' ')?;
+ percentage!(self.c1());
+ dest.write_char(' ')?;
+ percentage!(self.c2());
+ serialize_color_alpha(dest, self.alpha(), false)?;
+ dest.write_char(')')
+ },
+ ColorSpace::Lab | ColorSpace::Oklab => {
+ if self.color_space == ColorSpace::Oklab {
+ dest.write_str("ok")?;
+ }
+ dest.write_str("lab(")?;
+ if self.color_space == ColorSpace::Lab {
+ percentage!(self.c0())
+ } else {
+ unit_percentage!(self.c0())
+ }
+ dest.write_char(' ')?;
+ number!(self.c1());
+ dest.write_char(' ')?;
+ number!(self.c2());
+ serialize_color_alpha(dest, self.alpha(), false)?;
+ dest.write_char(')')
+ },
+ ColorSpace::Lch | ColorSpace::Oklch => {
+ if self.color_space == ColorSpace::Oklch {
+ dest.write_str("ok")?;
+ }
+ dest.write_str("lch(")?;
+ number!(self.c0());
+ dest.write_char(' ')?;
+ number!(self.c1());
+ dest.write_char(' ')?;
+ angle!(self.c2());
+ serialize_color_alpha(dest, self.alpha(), false)?;
+ dest.write_char(')')
+ },
+ ColorSpace::SrgbLinear |
+ ColorSpace::DisplayP3 |
+ ColorSpace::A98Rgb |
+ ColorSpace::ProphotoRgb |
+ ColorSpace::Rec2020 |
+ ColorSpace::XyzD50 |
+ ColorSpace::XyzD65 => {
+ dest.write_str("color(")?;
+ self.color_space.to_css(dest)?;
+ dest.write_char(' ')?;
+ number!(self.c0());
+ dest.write_char(' ')?;
+ number!(self.c1());
+ dest.write_char(' ')?;
+ number!(self.c2());
+ serialize_color_alpha(dest, self.alpha(), false)?;
+ dest.write_char(')')
+ },
+ }
+ }
+}
diff --git a/servo/components/style/custom_properties.rs b/servo/components/style/custom_properties.rs
index cb3b9685ae..b6523dd489 100644
--- a/servo/components/style/custom_properties.rs
+++ b/servo/components/style/custom_properties.rs
@@ -15,7 +15,10 @@ use crate::properties::{
};
use crate::properties_and_values::{
registry::PropertyRegistrationData,
- value::{AllowComputationallyDependent, SpecifiedValue as SpecifiedRegisteredValue},
+ value::{
+ AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
+ SpecifiedValue as SpecifiedRegisteredValue,
+ },
};
use crate::selector_map::{PrecomputedHashMap, PrecomputedHashSet};
use crate::stylesheets::UrlExtraData;
@@ -122,7 +125,12 @@ macro_rules! lnf_int_variable {
}};
}
-static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 7] = [
+static CHROME_ENVIRONMENT_VARIABLES: [EnvironmentVariable; 8] = [
+ lnf_int_variable!(
+ atom!("-moz-gtk-csd-titlebar-button-spacing"),
+ TitlebarButtonSpacing,
+ int_pixels
+ ),
lnf_int_variable!(
atom!("-moz-gtk-csd-titlebar-radius"),
TitlebarRadius,
@@ -245,7 +253,7 @@ impl ComputedCustomProperties {
}
/// Return the name and value of the property at specified index, if any.
- pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<Arc<VariableValue>>)> {
+ pub fn property_at(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
// Just expose the custom property items from custom_properties.inherited, followed
// by custom property items from custom_properties.non_inherited.
self.inherited
@@ -259,7 +267,7 @@ impl ComputedCustomProperties {
&mut self,
registration: &PropertyRegistrationData,
name: &Name,
- value: Arc<VariableValue>,
+ value: ComputedRegisteredValue,
) {
self.map_mut(registration).insert(name, value)
}
@@ -288,7 +296,7 @@ impl ComputedCustomProperties {
&self,
registration: &PropertyRegistrationData,
name: &Name,
- ) -> Option<&Arc<VariableValue>> {
+ ) -> Option<&ComputedRegisteredValue> {
if registration.inherits() {
self.inherited.get(name)
} else {
@@ -669,7 +677,9 @@ fn parse_declaration_value_block<'i, 't>(
let mut prev_reference_index: Option<usize> = None;
loop {
let token_start = input.position();
- let Ok(token) = input.next_including_whitespace_and_comments() else { break };
+ let Ok(token) = input.next_including_whitespace_and_comments() else {
+ break;
+ };
let prev_token_type = last_token_type;
let serialization_type = token.serialization_type();
@@ -809,7 +819,8 @@ fn parse_declaration_value_block<'i, 't>(
check_closed!(")");
prev_reference_index = Some(our_ref_index);
let reference = &mut references.refs[our_ref_index];
- reference.end = input.position().byte_index() - input_start.byte_index() + missing_closing_characters.len();
+ reference.end = input.position().byte_index() - input_start.byte_index() +
+ missing_closing_characters.len();
reference.fallback = fallback;
if is_var {
references.any_var = true;
@@ -885,7 +896,11 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
/// Create a new builder, inheriting from a given custom properties map.
///
/// We expose this publicly mostly for @keyframe blocks.
- pub fn new_with_properties(stylist: &'a Stylist, custom_properties: ComputedCustomProperties, computed_context: &'a mut computed::Context<'b>) -> Self {
+ pub fn new_with_properties(
+ stylist: &'a Stylist,
+ custom_properties: ComputedCustomProperties,
+ computed_context: &'a mut computed::Context<'b>,
+ ) -> Self {
Self {
seen: PrecomputedHashSet::default(),
reverted: Default::default(),
@@ -915,7 +930,9 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
// Reuse flags from computing registered custom properties initial values, such as
// whether they depend on viewport units.
- context.style().add_flags(stylist.get_custom_property_initial_values_flags());
+ context
+ .style()
+ .add_flags(stylist.get_custom_property_initial_values_flags());
Self::new_with_properties(stylist, properties, context)
}
@@ -943,8 +960,8 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
let map = &mut self.custom_properties;
let registration = self.stylist.get_custom_property_registration(&name);
- match *value {
- CustomDeclarationValue::Value(ref unparsed_value) => {
+ match value {
+ CustomDeclarationValue::Value(unparsed_value) => {
let has_custom_property_references = unparsed_value.references.any_var;
let registered_length_property =
registration.syntax.may_reference_font_relative_length();
@@ -970,21 +987,19 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
self.computed_context,
);
}
- map.insert(registration, name, Arc::clone(unparsed_value));
+ let value = ComputedRegisteredValue::universal(Arc::clone(unparsed_value));
+ map.insert(registration, name, value);
},
CustomDeclarationValue::CSSWideKeyword(keyword) => match keyword {
CSSWideKeyword::RevertLayer | CSSWideKeyword::Revert => {
- let origin_revert = keyword == CSSWideKeyword::Revert;
+ let origin_revert = matches!(keyword, CSSWideKeyword::Revert);
self.seen.remove(name);
self.reverted.insert(name, (priority, origin_revert));
},
CSSWideKeyword::Initial => {
// For non-inherited custom properties, 'initial' was handled in value_may_affect_style.
debug_assert!(registration.inherits(), "Should've been handled earlier");
- map.remove(registration, name);
- if let Some(ref initial_value) = registration.initial_value {
- map.insert(registration, name, initial_value.clone());
- }
+ remove_and_insert_initial_value(name, registration, map);
},
CSSWideKeyword::Inherit => {
// For inherited custom properties, 'inherit' was handled in value_may_affect_style.
@@ -1006,7 +1021,11 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
/// Note a non-custom property with variable reference that may in turn depend on that property.
/// e.g. `font-size` depending on a custom property that may be a registered property using `em`.
- pub fn note_potentially_cyclic_non_custom_dependency(&mut self, id: LonghandId, decl: &VariableDeclaration) {
+ pub fn note_potentially_cyclic_non_custom_dependency(
+ &mut self,
+ id: LonghandId,
+ decl: &VariableDeclaration,
+ ) {
// With unit algebra in `calc()`, references aren't limited to `font-size`.
// For example, `--foo: 100ex; font-weight: calc(var(--foo) / 1ex);`,
// or `--foo: 1em; zoom: calc(var(--foo) * 30px / 2em);`
@@ -1020,8 +1039,7 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
},
LonghandId::LineHeight => {
if self.computed_context.is_root_element() {
- NonCustomReferences::ROOT_LH_UNITS |
- NonCustomReferences::ROOT_FONT_UNITS
+ NonCustomReferences::ROOT_LH_UNITS | NonCustomReferences::ROOT_FONT_UNITS
} else {
NonCustomReferences::LH_UNITS | NonCustomReferences::FONT_UNITS
}
@@ -1033,15 +1051,24 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
return;
}
- let variables: Vec<Atom> = refs.refs.iter().filter_map(|reference| {
- if !reference.is_var {
- return None;
- }
- if !self.stylist.get_custom_property_registration(&reference.name).syntax.may_compute_length() {
- return None;
- }
- Some(reference.name.clone())
- }).collect();
+ let variables: Vec<Atom> = refs
+ .refs
+ .iter()
+ .filter_map(|reference| {
+ if !reference.is_var {
+ return None;
+ }
+ if !self
+ .stylist
+ .get_custom_property_registration(&reference.name)
+ .syntax
+ .may_compute_length()
+ {
+ return None;
+ }
+ Some(reference.name.clone())
+ })
+ .collect();
references.for_each(|idx| {
let entry = &mut self.references_from_non_custom_properties[idx];
let was_none = entry.is_none();
@@ -1098,8 +1125,12 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
debug_assert!(registration.inherits(), "Should've been handled earlier");
// Don't bother overwriting an existing value with the initial value specified in
// the registration.
- if Some(existing_value) == registration.initial_value.as_ref() {
- return false;
+ if let Some(initial_value) = self
+ .stylist
+ .get_custom_property_initial_values()
+ .get(registration, name)
+ {
+ return existing_value != initial_value;
}
},
(Some(_), &CustomDeclarationValue::CSSWideKeyword(CSSWideKeyword::Inherit)) => {
@@ -1120,8 +1151,16 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
(Some(existing_value), &CustomDeclarationValue::Value(ref value)) => {
// Don't bother overwriting an existing value with the same
// specified value.
- if existing_value == value {
- return false;
+ if let Some(existing_value) = existing_value.as_universal() {
+ return existing_value != value;
+ }
+ if let Ok(value) = compute_value(
+ &value.css,
+ &value.url_data,
+ registration,
+ self.computed_context,
+ ) {
+ return existing_value.v != value.v;
}
},
_ => {},
@@ -1164,7 +1203,8 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
self.stylist,
self.computed_context,
);
- self.computed_context.builder.invalid_non_custom_properties = invalid_non_custom_properties;
+ self.computed_context.builder.invalid_non_custom_properties =
+ invalid_non_custom_properties;
}
self.custom_properties.shrink_to_fit();
@@ -1178,7 +1218,8 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
inherited: if self
.computed_context
.inherited_custom_properties()
- .inherited == self.custom_properties.inherited
+ .inherited ==
+ self.custom_properties.inherited
{
self.computed_context
.inherited_custom_properties()
@@ -1217,6 +1258,9 @@ impl<'a, 'b: 'a> CustomPropertiesBuilder<'a, 'b> {
// have to worry about resolving in a wrong order.
for (k, v) in deferred.iter() {
let Some(v) = v else { continue };
+ let Some(v) = v.as_universal() else {
+ unreachable!("Computing should have been deferred!")
+ };
substitute_references_if_needed_and_apply(
k,
v,
@@ -1340,6 +1384,7 @@ fn substitute_all(
VarType::Custom(ref name) => {
let registration = context.stylist.get_custom_property_registration(name);
let value = context.map.get(registration, name)?;
+ let value = value.as_universal()?;
let non_custom_references = value
.references
@@ -1476,22 +1521,16 @@ fn substitute_all(
.invalid_non_custom_properties
.insert(LonghandId::FontSize);
}
- if context.non_custom_references.intersects(
- NonCustomReferences::LH_UNITS |
- NonCustomReferences::ROOT_LH_UNITS,
- ) {
+ if context
+ .non_custom_references
+ .intersects(NonCustomReferences::LH_UNITS | NonCustomReferences::ROOT_LH_UNITS)
+ {
context
.invalid_non_custom_properties
.insert(LonghandId::LineHeight);
}
// This variable is in loop. Resolve to invalid.
- handle_invalid_at_computed_value_time(
- name,
- context.map,
- context.computed_context.inherited_custom_properties(),
- context.stylist,
- context.computed_context.is_root_element(),
- );
+ handle_invalid_at_computed_value_time(name, context.map, context.computed_context);
};
loop {
let var_index = context
@@ -1534,7 +1573,7 @@ fn substitute_all(
return None;
}
- if let Some(ref v) = value.as_ref() {
+ if let Some(ref v) = value {
let registration = context.stylist.get_custom_property_registration(&name);
let registered_length_property =
registration.syntax.may_reference_font_relative_length();
@@ -1542,7 +1581,8 @@ fn substitute_all(
if !context.non_custom_references.is_empty() && registered_length_property {
if let Some(deferred) = &mut context.deferred_properties {
// This property directly depends on a non-custom property, defer resolving it.
- deferred.insert(registration, &name, (*v).clone());
+ let deferred_property = ComputedRegisteredValue::universal(Arc::clone(v));
+ deferred.insert(registration, &name, deferred_property);
context.map.remove(registration, &name);
defer = true;
}
@@ -1553,11 +1593,14 @@ fn substitute_all(
continue;
}
if let Some(deferred) = &mut context.deferred_properties {
- let registration =
- context.stylist.get_custom_property_registration(&reference.name);
+ let registration = context
+ .stylist
+ .get_custom_property_registration(&reference.name);
if deferred.get(registration, &reference.name).is_some() {
// This property depends on a custom property that depends on a non-custom property, defer.
- deferred.insert(registration, &name, Arc::clone(v));
+ let deferred_property =
+ ComputedRegisteredValue::universal(Arc::clone(v));
+ deferred.insert(registration, &name, deferred_property);
context.map.remove(registration, &name);
defer = true;
break;
@@ -1610,22 +1653,27 @@ fn substitute_all(
fn handle_invalid_at_computed_value_time(
name: &Name,
custom_properties: &mut ComputedCustomProperties,
- inherited: &ComputedCustomProperties,
- stylist: &Stylist,
- is_root_element: bool,
+ computed_context: &computed::Context,
) {
+ let stylist = computed_context.style().stylist.unwrap();
let registration = stylist.get_custom_property_registration(&name);
if !registration.syntax.is_universal() {
// For the root element, inherited maps are empty. We should just
// use the initial value if any, rather than removing the name.
- if registration.inherits() && !is_root_element {
+ if registration.inherits() && !computed_context.is_root_element() {
+ let inherited = computed_context.inherited_custom_properties();
if let Some(value) = inherited.get(registration, name) {
- custom_properties.insert(registration, name, Arc::clone(value));
+ custom_properties.insert(registration, name, value.clone());
return;
}
- } else {
- if let Some(ref initial_value) = registration.initial_value {
- custom_properties.insert(registration, name, Arc::clone(initial_value));
+ } else if let Some(ref initial_value) = registration.initial_value {
+ if let Ok(initial_value) = compute_value(
+ &initial_value.css,
+ &initial_value.url_data,
+ registration,
+ computed_context,
+ ) {
+ custom_properties.insert(registration, name, initial_value);
return;
}
}
@@ -1644,35 +1692,43 @@ fn substitute_references_if_needed_and_apply(
let registration = stylist.get_custom_property_registration(&name);
if !value.has_references() && registration.syntax.is_universal() {
// Trivial path: no references and no need to compute the value, just apply it directly.
- custom_properties.insert(registration, name, Arc::clone(value));
+ let computed_value = ComputedRegisteredValue::universal(Arc::clone(value));
+ custom_properties.insert(registration, name, computed_value);
return;
}
let inherited = computed_context.inherited_custom_properties();
- let value = match substitute_internal(value, custom_properties, stylist, registration, computed_context) {
+ let url_data = &value.url_data;
+ let value = match substitute_internal(
+ value,
+ custom_properties,
+ stylist,
+ registration,
+ computed_context,
+ ) {
Ok(v) => v,
Err(..) => {
- handle_invalid_at_computed_value_time(
- name,
- custom_properties,
- inherited,
- stylist,
- computed_context.is_root_element(),
- );
+ handle_invalid_at_computed_value_time(name, custom_properties, computed_context);
return;
},
- }.into_value(&value.url_data);
+ }
+ .into_value(url_data);
// If variable fallback results in a wide keyword, deal with it now.
{
- let mut input = ParserInput::new(&value.css);
+ let css = value.to_variable_value().css;
+ let mut input = ParserInput::new(&css);
let mut input = Parser::new(&mut input);
if let Ok(kw) = input.try_parse(CSSWideKeyword::parse) {
// TODO: It's unclear what this should do for revert / revert-layer, see
// https://github.com/w3c/csswg-drafts/issues/9131. For now treating as unset
// seems fine?
- match (kw, registration.inherits(), computed_context.is_root_element()) {
+ match (
+ kw,
+ registration.inherits(),
+ computed_context.is_root_element(),
+ ) {
(CSSWideKeyword::Initial, _, _) |
(CSSWideKeyword::Revert, false, _) |
(CSSWideKeyword::RevertLayer, false, _) |
@@ -1681,10 +1737,7 @@ fn substitute_references_if_needed_and_apply(
(CSSWideKeyword::RevertLayer, true, true) |
(CSSWideKeyword::Unset, true, true) |
(CSSWideKeyword::Inherit, _, true) => {
- custom_properties.remove(registration, name);
- if let Some(ref initial_value) = registration.initial_value {
- custom_properties.insert(registration, name, Arc::clone(initial_value));
- }
+ remove_and_insert_initial_value(name, registration, custom_properties);
},
(CSSWideKeyword::Revert, true, false) |
(CSSWideKeyword::RevertLayer, true, false) |
@@ -1692,7 +1745,7 @@ fn substitute_references_if_needed_and_apply(
(CSSWideKeyword::Unset, true, false) => {
match inherited.get(registration, name) {
Some(value) => {
- custom_properties.insert(registration, name, Arc::clone(value));
+ custom_properties.insert(registration, name, value.clone());
},
None => {
custom_properties.remove(registration, name);
@@ -1704,45 +1757,81 @@ fn substitute_references_if_needed_and_apply(
}
}
- custom_properties.insert(registration, name, Arc::new(value));
+ custom_properties.insert(registration, name, value);
+}
+
+enum Substitution<'a> {
+ Universal(UniversalSubstitution<'a>),
+ Computed(ComputedRegisteredValue),
+}
+
+impl<'a> Default for Substitution<'a> {
+ fn default() -> Self {
+ Self::Universal(UniversalSubstitution::default())
+ }
}
#[derive(Default)]
-struct Substitution<'a> {
+struct UniversalSubstitution<'a> {
css: Cow<'a, str>,
first_token_type: TokenSerializationType,
last_token_type: TokenSerializationType,
}
+impl<'a> UniversalSubstitution<'a> {
+ fn from_value(v: VariableValue) -> Self {
+ UniversalSubstitution {
+ css: Cow::from(v.css),
+ first_token_type: v.first_token_type,
+ last_token_type: v.last_token_type,
+ }
+ }
+}
+
impl<'a> Substitution<'a> {
fn new(
css: &'a str,
first_token_type: TokenSerializationType,
last_token_type: TokenSerializationType,
) -> Self {
- Self {
+ Self::Universal(UniversalSubstitution {
css: Cow::Borrowed(css),
first_token_type,
last_token_type,
- }
+ })
}
- fn from_value(v: VariableValue) -> Substitution<'static> {
- debug_assert!(!v.has_references(), "Computed values shouldn't have references");
- Substitution {
- css: Cow::from(v.css),
- first_token_type: v.first_token_type,
- last_token_type: v.last_token_type,
+ fn into_universal(self) -> UniversalSubstitution<'a> {
+ match self {
+ Substitution::Universal(substitution) => substitution,
+ Substitution::Computed(computed) => {
+ UniversalSubstitution::from_value(computed.to_variable_value())
+ },
}
}
- fn into_value(self, url_data: &UrlExtraData) -> VariableValue {
- VariableValue {
- css: self.css.into_owned(),
- first_token_type: self.first_token_type,
- last_token_type: self.last_token_type,
- url_data: url_data.clone(),
- references: Default::default(),
+ fn from_value(v: VariableValue) -> Self {
+ debug_assert!(
+ !v.has_references(),
+ "Computed values shouldn't have references"
+ );
+ let substitution = UniversalSubstitution::from_value(v);
+ Self::Universal(substitution)
+ }
+
+ fn into_value(self, url_data: &UrlExtraData) -> ComputedRegisteredValue {
+ match self {
+ Substitution::Universal(substitution) => {
+ let value = Arc::new(VariableValue {
+ css: substitution.css.into_owned(),
+ first_token_type: substitution.first_token_type,
+ last_token_type: substitution.last_token_type,
+ url_data: url_data.clone(),
+ references: Default::default(),
+ });
+ ComputedRegisteredValue::universal(value)
+ },
+ Substitution::Computed(computed) => computed,
}
}
}
@@ -1752,20 +1841,32 @@ fn compute_value(
url_data: &UrlExtraData,
registration: &PropertyRegistrationData,
computed_context: &computed::Context,
-) -> Result<Substitution<'static>, ()> {
+) -> Result<ComputedRegisteredValue, ()> {
debug_assert!(!registration.syntax.is_universal());
let mut input = ParserInput::new(&css);
let mut input = Parser::new(&mut input);
- let value = SpecifiedRegisteredValue::compute(
+ SpecifiedRegisteredValue::compute(
&mut input,
registration,
url_data,
computed_context,
AllowComputationallyDependent::Yes,
- )?;
- Ok(Substitution::from_value(value))
+ )
+}
+
+/// Removes the named registered custom property and inserts its uncomputed initial value.
+fn remove_and_insert_initial_value(
+ name: &Name,
+ registration: &PropertyRegistrationData,
+ custom_properties: &mut ComputedCustomProperties,
+) {
+ custom_properties.remove(registration, name);
+ if let Some(ref initial_value) = registration.initial_value {
+ let value = ComputedRegisteredValue::universal(Arc::clone(initial_value));
+ custom_properties.insert(registration, name, value);
+ }
}
fn do_substitute_chunk<'a>(
@@ -1792,7 +1893,8 @@ fn do_substitute_chunk<'a>(
{
let result = &css[start..end];
if !registration.syntax.is_universal() {
- return compute_value(result, url_data, registration, computed_context);
+ let computed_value = compute_value(result, url_data, registration, computed_context)?;
+ return Ok(Substitution::Computed(computed_value));
}
return Ok(Substitution::new(result, first_token_type, last_token_type));
}
@@ -1824,6 +1926,7 @@ fn do_substitute_chunk<'a>(
return Ok(substitution);
}
+ let substitution = substitution.into_universal();
substituted.push(
&substitution.css,
substitution.first_token_type,
@@ -1837,7 +1940,9 @@ fn do_substitute_chunk<'a>(
substituted.push(&css[cur_pos..end], next_token_type, last_token_type)?;
}
if !registration.syntax.is_universal() {
- return compute_value(&substituted.css, url_data, registration, computed_context);
+ let computed_value =
+ compute_value(&substituted.css, url_data, registration, computed_context)?;
+ return Ok(Substitution::Computed(computed_value));
}
Ok(Substitution::from_value(substituted))
}
@@ -1855,7 +1960,8 @@ fn substitute_one_reference<'a>(
if reference.is_var {
registration = stylist.get_custom_property_registration(&reference.name);
if let Some(v) = custom_properties.get(registration, &reference.name) {
- debug_assert!(!v.has_references(), "Should be already computed");
+ #[cfg(debug_assertions)]
+ debug_assert!(v.is_parsed(registration), "Should be already computed");
if registration.syntax.is_universal() {
// Skip references that are inside the outer variable (in fallback for example).
while references
@@ -1881,11 +1987,7 @@ fn substitute_one_reference<'a>(
)?;
}
}
- return Ok(Substitution {
- css: Cow::from(&v.css),
- first_token_type: v.first_token_type,
- last_token_type: v.last_token_type,
- });
+ return Ok(Substitution::Computed(v.clone()));
}
} else {
registration = PropertyRegistrationData::unregistered();
@@ -1899,7 +2001,9 @@ fn substitute_one_reference<'a>(
}
}
- let Some(ref fallback) = reference.fallback else { return Err(()) };
+ let Some(ref fallback) = reference.fallback else {
+ return Err(());
+ };
do_substitute_chunk(
css,
@@ -1955,5 +2059,6 @@ pub fn substitute<'a>(
PropertyRegistrationData::unregistered(),
computed_context,
)?;
+ let v = v.into_universal();
Ok(v.css)
}
diff --git a/servo/components/style/custom_properties_map.rs b/servo/components/style/custom_properties_map.rs
index 04ca8e1b3d..13c5374308 100644
--- a/servo/components/style/custom_properties_map.rs
+++ b/servo/components/style/custom_properties_map.rs
@@ -4,7 +4,8 @@
//! The structure that contains the custom properties of a given element.
-use crate::custom_properties::{Name, VariableValue};
+use crate::custom_properties::Name;
+use crate::properties_and_values::value::ComputedValue as ComputedRegisteredValue;
use crate::selector_map::PrecomputedHasher;
use indexmap::IndexMap;
use servo_arc::Arc;
@@ -22,7 +23,8 @@ impl Default for CustomPropertiesMap {
}
/// We use None in the value to represent a removed entry.
-type OwnMap = IndexMap<Name, Option<Arc<VariableValue>>, BuildHasherDefault<PrecomputedHasher>>;
+type OwnMap =
+ IndexMap<Name, Option<ComputedRegisteredValue>, BuildHasherDefault<PrecomputedHasher>>;
// IndexMap equality doesn't consider ordering, which we want to account for. Also, for the same
// reason, IndexMap equality comparisons are slower than needed.
@@ -69,12 +71,12 @@ const ANCESTOR_COUNT_LIMIT: usize = 4;
/// An iterator over the custom properties.
pub struct Iter<'a> {
current: &'a Inner,
- current_iter: indexmap::map::Iter<'a, Name, Option<Arc<VariableValue>>>,
+ current_iter: indexmap::map::Iter<'a, Name, Option<ComputedRegisteredValue>>,
descendants: smallvec::SmallVec<[&'a Inner; ANCESTOR_COUNT_LIMIT]>,
}
impl<'a> Iterator for Iter<'a> {
- type Item = (&'a Name, &'a Option<Arc<VariableValue>>);
+ type Item = (&'a Name, &'a Option<ComputedRegisteredValue>);
fn next(&mut self) -> Option<Self::Item> {
loop {
@@ -141,14 +143,14 @@ impl Inner {
self.len
}
- fn get(&self, name: &Name) -> Option<&Arc<VariableValue>> {
+ fn get(&self, name: &Name) -> Option<&ComputedRegisteredValue> {
if let Some(p) = self.own_properties.get(name) {
return p.as_ref();
}
self.parent.as_ref()?.get(name)
}
- fn insert(&mut self, name: &Name, value: Option<Arc<VariableValue>>) {
+ fn insert(&mut self, name: &Name, value: Option<ComputedRegisteredValue>) {
let new = self.own_properties.insert(name.clone(), value).is_none();
if new && self.parent.as_ref().map_or(true, |p| p.get(name).is_none()) {
self.len += 1;
@@ -177,7 +179,7 @@ impl CustomPropertiesMap {
}
/// Returns the property name and value at a given index.
- pub fn get_index(&self, index: usize) -> Option<(&Name, &Option<Arc<VariableValue>>)> {
+ pub fn get_index(&self, index: usize) -> Option<(&Name, &Option<ComputedRegisteredValue>)> {
if index >= self.len() {
return None;
}
@@ -186,11 +188,11 @@ impl CustomPropertiesMap {
}
/// Returns a given property value by name.
- pub fn get(&self, name: &Name) -> Option<&Arc<VariableValue>> {
+ pub fn get(&self, name: &Name) -> Option<&ComputedRegisteredValue> {
self.0.get(name)
}
- fn do_insert(&mut self, name: &Name, value: Option<Arc<VariableValue>>) {
+ fn do_insert(&mut self, name: &Name, value: Option<ComputedRegisteredValue>) {
if let Some(inner) = Arc::get_mut(&mut self.0) {
return inner.insert(name, value);
}
@@ -214,7 +216,7 @@ impl CustomPropertiesMap {
}
/// Inserts an element in the map.
- pub fn insert(&mut self, name: &Name, value: Arc<VariableValue>) {
+ pub fn insert(&mut self, name: &Name, value: ComputedRegisteredValue) {
self.do_insert(name, Some(value))
}
diff --git a/servo/components/style/dom.rs b/servo/components/style/dom.rs
index 554d79fdb3..358d788845 100644
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -207,6 +207,17 @@ pub trait TNode: Sized + Copy + Clone + Debug + NodeInfo + PartialEq {
}
}
+ /// Returns the depth of this node in the DOM.
+ fn depth(&self) -> usize {
+ let mut depth = 0;
+ let mut curr = *self;
+ while let Some(parent) = curr.traversal_parent() {
+ depth += 1;
+ curr = parent.as_node();
+ }
+ depth
+ }
+
/// Get this node's parent element from the perspective of a restyle
/// traversal.
fn traversal_parent(&self) -> Option<Self::ConcreteElement>;
@@ -398,18 +409,6 @@ pub trait TElement:
true
}
- /// Returns the depth of this element in the DOM.
- fn depth(&self) -> usize {
- let mut depth = 0;
- let mut curr = *self;
- while let Some(parent) = curr.traversal_parent() {
- depth += 1;
- curr = parent;
- }
-
- depth
- }
-
/// Get this node's parent element from the perspective of a restyle
/// traversal.
fn traversal_parent(&self) -> Option<Self> {
diff --git a/servo/components/style/driver.rs b/servo/components/style/driver.rs
index 95447ce08e..a2407cb209 100644
--- a/servo/components/style/driver.rs
+++ b/servo/components/style/driver.rs
@@ -46,16 +46,28 @@ fn report_statistics(stats: &PerThreadTraversalStatistics) {
gecko_stats.mStylesReused += stats.styles_reused;
}
-fn with_pool_in_place_scope<'scope, R>(
+fn with_pool_in_place_scope<'scope>(
work_unit_max: usize,
pool: Option<&rayon::ThreadPool>,
- closure: impl FnOnce(Option<&rayon::ScopeFifo<'scope>>) -> R,
-) -> R {
+ closure: impl FnOnce(Option<&rayon::ScopeFifo<'scope>>) + Send + 'scope,
+) {
if work_unit_max == 0 || pool.is_none() {
- closure(None)
+ closure(None);
} else {
- pool.unwrap()
- .in_place_scope_fifo(|scope| closure(Some(scope)))
+ let pool = pool.unwrap();
+ pool.in_place_scope_fifo(|scope| {
+ #[cfg(feature = "gecko")]
+ debug_assert_eq!(
+ pool.current_thread_index(),
+ Some(0),
+ "Main thread should be the first thread"
+ );
+ if cfg!(feature = "gecko") || pool.current_thread_index().is_some() {
+ closure(Some(scope));
+ } else {
+ scope.spawn_fifo(|scope| closure(Some(scope)));
+ }
+ });
}
}
@@ -108,6 +120,8 @@ where
// Process the nodes breadth-first. This helps keep similar traversal characteristics for the
// style sharing cache.
let work_unit_max = work_unit_max();
+
+ let send_root = unsafe { SendNode::new(root.as_node()) };
with_pool_in_place_scope(work_unit_max, pool, |maybe_scope| {
let mut tlc = scoped_tls.ensure(parallel::create_thread_local_context);
let mut context = StyleContext {
@@ -115,22 +129,16 @@ where
thread_local: &mut tlc,
};
- debug_assert_eq!(
- scoped_tls.current_thread_index(),
- 0,
- "Main thread should be the first thread"
- );
-
let mut discovered = VecDeque::with_capacity(work_unit_max * 2);
- discovered.push_back(unsafe { SendNode::new(root.as_node()) });
+ let current_dom_depth = send_root.depth();
+ let opaque_root = send_root.opaque();
+ discovered.push_back(send_root);
parallel::style_trees(
&mut context,
discovered,
- root.as_node().opaque(),
+ opaque_root,
work_unit_max,
- PerLevelTraversalData {
- current_dom_depth: root.depth(),
- },
+ PerLevelTraversalData { current_dom_depth },
maybe_scope,
traversal,
&scoped_tls,
diff --git a/servo/components/style/gecko/media_features.rs b/servo/components/style/gecko/media_features.rs
index c9ad30b28b..8de45d95c2 100644
--- a/servo/components/style/gecko/media_features.rs
+++ b/servo/components/style/gecko/media_features.rs
@@ -169,8 +169,7 @@ fn eval_color_gamut(context: &Context, query_value: Option<ColorGamut>) -> bool
// Match if our color gamut is at least as wide as the query value
query_value <=
match color_gamut {
- // EndGuard_ is not a valid color gamut, so the default color-gamut is used.
- ScreenColorGamut::Srgb | ScreenColorGamut::EndGuard_ => ColorGamut::Srgb,
+ ScreenColorGamut::Srgb => ColorGamut::Srgb,
ScreenColorGamut::P3 => ColorGamut::P3,
ScreenColorGamut::Rec2020 => ColorGamut::Rec2020,
}
@@ -566,6 +565,8 @@ pub enum Platform {
/// platforms and they already use the "linux" string elsewhere (e.g.,
/// toolkit/themes/linux).
Linux,
+ /// Matches any iOS version.
+ Ios,
/// Matches any macOS version.
Macos,
/// Matches any Windows version.
diff --git a/servo/components/style/gecko/regen_atoms.py b/servo/components/style/gecko/regen_atoms.py
index 61f2fc4c63..9a3bb48271 100755
--- a/servo/components/style/gecko/regen_atoms.py
+++ b/servo/components/style/gecko/regen_atoms.py
@@ -18,7 +18,7 @@ import build
# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, true, nsStaticAtom, PseudoElementAtom)`.
PATTERN = re.compile(
- '^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)',
+ r'^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)',
re.MULTILINE,
)
FILE = "include/nsGkAtomList.h"
diff --git a/servo/components/style/gecko/wrapper.rs b/servo/components/style/gecko/wrapper.rs
index 61352ef9c0..43cf6e7941 100644
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -865,7 +865,6 @@ impl<'le> GeckoElement<'le> {
after_change_style: &ComputedValues,
existing_transitions: &FxHashMap<OwnedPropertyDeclarationId, Arc<AnimationValue>>,
) -> bool {
- use crate::values::animated::{Animate, Procedure};
debug_assert!(!property_declaration_id.is_logical());
// If there is an existing transition, update only if the end value
@@ -882,20 +881,17 @@ impl<'le> GeckoElement<'le> {
return ***existing != after_value;
}
+ if combined_duration_seconds <= 0.0f32 {
+ return false;
+ }
+
let from =
AnimationValue::from_computed_values(property_declaration_id, before_change_style);
let to = AnimationValue::from_computed_values(property_declaration_id, after_change_style);
debug_assert_eq!(to.is_some(), from.is_some());
- combined_duration_seconds > 0.0f32 &&
- from != to &&
- from.unwrap()
- .animate(
- to.as_ref().unwrap(),
- Procedure::Interpolate { progress: 0.5 },
- )
- .is_ok()
+ from != to
}
/// Get slow selector flags required for nth-of invalidation.
diff --git a/servo/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl b/servo/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl
deleted file mode 100644
index 9593025a47..0000000000
--- a/servo/components/style/properties/Mako-1.1.2-py2.py3-none-any.whl
+++ /dev/null
Binary files differ
diff --git a/servo/components/style/properties/build.py b/servo/components/style/properties/build.py
index 6c3ee0cf66..42121a4eae 100644
--- a/servo/components/style/properties/build.py
+++ b/servo/components/style/properties/build.py
@@ -8,7 +8,6 @@ import re
import sys
BASE = os.path.dirname(__file__.replace("\\", "/"))
-sys.path.insert(0, os.path.join(BASE, "Mako-1.1.2-py2.py3-none-any.whl"))
sys.path.insert(0, BASE) # For importing `data.py`
from mako import exceptions
diff --git a/servo/components/style/properties/cascade.rs b/servo/components/style/properties/cascade.rs
index 59a8a65876..d4d48eaeb8 100644
--- a/servo/components/style/properties/cascade.rs
+++ b/servo/components/style/properties/cascade.rs
@@ -448,6 +448,8 @@ fn tweak_when_ignoring_colors(
// A few special-cases ahead.
match **declaration {
+ // Honor CSS-wide keywords like unset / revert / initial...
+ PropertyDeclaration::CSSWideKeyword(..) => return,
PropertyDeclaration::BackgroundColor(ref color) => {
// We honor system colors and transparent colors unconditionally.
//
diff --git a/servo/components/style/properties/gecko.mako.rs b/servo/components/style/properties/gecko.mako.rs
index f5ae0cade3..8d3f90f8f5 100644
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -1673,12 +1673,13 @@ mask-mode mask-repeat mask-clip mask-origin mask-composite mask-position-x mask-
animation-direction animation-fill-mode
animation-play-state animation-iteration-count
animation-timing-function animation-composition animation-timeline
- transition-duration transition-delay
+ transition-behavior transition-duration transition-delay
transition-timing-function transition-property
scroll-timeline-name scroll-timeline-axis
view-timeline-name view-timeline-axis view-timeline-inset""" %>
<%self:impl_trait style_struct_name="UI" skip_longhands="${skip_ui_longhands}">
+ ${impl_coordinated_property('transition', 'behavior', 'Behavior')}
${impl_coordinated_property('transition', 'delay', 'Delay')}
${impl_coordinated_property('transition', 'duration', 'Duration')}
${impl_coordinated_property('transition', 'timing_function', 'TimingFunction')}
diff --git a/servo/components/style/properties/longhands/font.mako.rs b/servo/components/style/properties/longhands/font.mako.rs
index f188af5b1f..4583de0bd7 100644
--- a/servo/components/style/properties/longhands/font.mako.rs
+++ b/servo/components/style/properties/longhands/font.mako.rs
@@ -305,10 +305,7 @@ ${helpers.predefined_type(
"MathDepth",
"0",
engines="gecko",
- gecko_pref="layout.css.math-depth.enabled",
- has_effect_on_gecko_scrollbars=False,
animation_value_type="none",
- enabled_in="ua",
spec="https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property",
affects="",
)}
@@ -318,11 +315,8 @@ ${helpers.single_keyword(
"normal compact",
engines="gecko",
gecko_enum_prefix="StyleMathStyle",
- gecko_pref="layout.css.math-style.enabled",
spec="https://mathml-refresh.github.io/mathml-core/#the-math-style-property",
- has_effect_on_gecko_scrollbars=False,
animation_value_type="none",
- enabled_in="ua",
needs_conversion=True,
affects="layout",
)}
diff --git a/servo/components/style/properties/longhands/ui.mako.rs b/servo/components/style/properties/longhands/ui.mako.rs
index 1150816ac0..58006e0d65 100644
--- a/servo/components/style/properties/longhands/ui.mako.rs
+++ b/servo/components/style/properties/longhands/ui.mako.rs
@@ -199,6 +199,20 @@ ${helpers.predefined_type(
affects="",
)}
+${helpers.predefined_type(
+ "transition-behavior",
+ "TransitionBehavior",
+ "computed::TransitionBehavior::normal()",
+ engines="gecko",
+ initial_specified_value="specified::TransitionBehavior::normal()",
+ vector=True,
+ need_index=True,
+ animation_value_type="none",
+ gecko_pref="layout.css.transition-behavior.enabled",
+ spec="https://drafts.csswg.org/css-transitions-2/#transition-behavior-property",
+ affects="",
+)}
+
<% animation_extra_prefixes = "moz:layout.css.prefixes.animations webkit" %>
${helpers.predefined_type(
diff --git a/servo/components/style/properties/shorthands/svg.mako.rs b/servo/components/style/properties/shorthands/svg.mako.rs
index cf34b116ee..195427dad5 100644
--- a/servo/components/style/properties/shorthands/svg.mako.rs
+++ b/servo/components/style/properties/shorthands/svg.mako.rs
@@ -144,8 +144,8 @@
// <mask-reference> ||
// <position> [ / <bg-size> ]? ||
// <repeat-style> ||
- // <geometry-box> ||
- // [ <geometry-box> | no-clip ] ||
+ // <coord-box> ||
+ // [ <coord-box> | no-clip ] ||
// <compositing-operator> ||
// <masking-mode>
// https://drafts.fxtf.org/css-masking-1/#the-mask
@@ -198,12 +198,21 @@
writer.item(repeat)?;
}
- // <geometry-box>
- if has_origin {
+ // <coord-box>
+ // Note:
+ // Even if 'mask-origin' is at its initial value 'border-box',
+ // we still have to serialize it to avoid ambiguity iF the
+ // 'mask-clip' longhand has some other <coord-box> value
+ // (i.e. neither 'border-box' nor 'no-clip'). (If we naively
+ // declined to serialize the 'mask-origin' value in this
+ // situation, then whatever value we serialize for 'mask-clip'
+ // would implicitly also represent 'mask-origin' and would be
+ // providing the wrong value for that longhand.)
+ if has_origin || (has_clip && *clip != Clip::NoClip) {
writer.item(origin)?;
}
- // [ <geometry-box> | no-clip ]
+ // [ <coord-box> | no-clip ]
if has_clip && *clip != From::from(*origin) {
writer.item(clip)?;
}
diff --git a/servo/components/style/properties/shorthands/ui.mako.rs b/servo/components/style/properties/shorthands/ui.mako.rs
index 1fdb5965fc..4a27d5e003 100644
--- a/servo/components/style/properties/shorthands/ui.mako.rs
+++ b/servo/components/style/properties/shorthands/ui.mako.rs
@@ -22,10 +22,10 @@ macro_rules! try_parse_one {
extra_prefixes="moz:layout.css.prefixes.transitions webkit"
sub_properties="transition-property transition-duration
transition-timing-function
- transition-delay"
+ transition-delay transition-behavior"
spec="https://drafts.csswg.org/css-transitions/#propdef-transition">
use crate::parser::Parse;
- % for prop in "delay duration property timing_function".split():
+ % for prop in "delay duration property timing_function behavior".split():
use crate::properties::longhands::transition_${prop};
% endfor
use crate::values::specified::TransitionProperty;
@@ -35,7 +35,7 @@ macro_rules! try_parse_one {
input: &mut Parser<'i, 't>,
) -> Result<Longhands, ParseError<'i>> {
struct SingleTransition {
- % for prop in "property duration timing_function delay".split():
+ % for prop in "property duration timing_function delay behavior".split():
transition_${prop}: transition_${prop}::SingleSpecifiedValue,
% endfor
}
@@ -45,7 +45,7 @@ macro_rules! try_parse_one {
input: &mut Parser<'i, 't>,
first: bool,
) -> Result<SingleTransition,ParseError<'i>> {
- % for prop in "property duration timing_function delay".split():
+ % for prop in "property duration timing_function delay behavior".split():
let mut ${prop} = None;
% endfor
@@ -56,6 +56,9 @@ macro_rules! try_parse_one {
try_parse_one!(context, input, duration, transition_duration);
try_parse_one!(context, input, timing_function, transition_timing_function);
try_parse_one!(context, input, delay, transition_delay);
+ if static_prefs::pref!("layout.css.transition-behavior.enabled") {
+ try_parse_one!(context, input, behavior, transition_behavior);
+ }
// Must check 'transition-property' after 'transition-timing-function' since
// 'transition-property' accepts any keyword.
if property.is_none() {
@@ -78,7 +81,7 @@ macro_rules! try_parse_one {
if parsed != 0 {
Ok(SingleTransition {
- % for prop in "property duration timing_function delay".split():
+ % for prop in "property duration timing_function delay behavior".split():
transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value
::get_initial_specified_value),
% endfor
@@ -88,7 +91,7 @@ macro_rules! try_parse_one {
}
}
- % for prop in "property duration timing_function delay".split():
+ % for prop in "property duration timing_function delay behavior".split():
let mut ${prop}s = Vec::new();
% endfor
@@ -105,13 +108,13 @@ macro_rules! try_parse_one {
Ok(transition)
})?;
for result in results {
- % for prop in "property duration timing_function delay".split():
+ % for prop in "property duration timing_function delay behavior".split():
${prop}s.push(result.transition_${prop});
% endfor
}
Ok(expanded! {
- % for prop in "property duration timing_function delay".split():
+ % for prop in "property duration timing_function delay behavior".split():
transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s.into()),
% endfor
})
@@ -133,12 +136,24 @@ macro_rules! try_parse_one {
return Ok(());
}
% endfor
+
+ if let Some(behavior) = self.transition_behavior {
+ if behavior.0.len() != 1 {
+ return Ok(());
+ }
+ }
} else {
% for name in "duration delay timing_function".split():
if self.transition_${name}.0.len() != property_len {
return Ok(());
}
% endfor
+
+ if let Some(behavior) = self.transition_behavior {
+ if behavior.0.len() != property_len {
+ return Ok(());
+ }
+ }
}
// Representative length.
@@ -152,7 +167,11 @@ macro_rules! try_parse_one {
let has_duration = !self.transition_duration.0[i].is_zero();
let has_timing = !self.transition_timing_function.0[i].is_ease();
let has_delay = !self.transition_delay.0[i].is_zero();
- let has_any = has_duration || has_timing || has_delay;
+ let has_behavior = match self.transition_behavior {
+ Some(behavior) => !behavior.0[i].is_normal(),
+ _ => false,
+ };
+ let has_any = has_duration || has_timing || has_delay || has_behavior;
let mut writer = SequenceWriter::new(dest, " ");
@@ -174,6 +193,10 @@ macro_rules! try_parse_one {
if has_delay {
writer.item(&self.transition_delay.0[i])?;
}
+
+ if has_behavior {
+ writer.item(&self.transition_behavior.unwrap().0[i])?;
+ }
}
Ok(())
}
diff --git a/servo/components/style/properties_and_values/rule.rs b/servo/components/style/properties_and_values/rule.rs
index 96617eccce..08693e6dd5 100644
--- a/servo/components/style/properties_and_values/rule.rs
+++ b/servo/components/style/properties_and_values/rule.rs
@@ -9,7 +9,10 @@
use super::{
registry::{PropertyRegistration, PropertyRegistrationData},
syntax::Descriptor,
- value::{AllowComputationallyDependent, SpecifiedValue as SpecifiedRegisteredValue},
+ value::{
+ AllowComputationallyDependent, ComputedValue as ComputedRegisteredValue,
+ SpecifiedValue as SpecifiedRegisteredValue,
+ },
};
use crate::custom_properties::{Name as CustomPropertyName, SpecifiedValue};
use crate::error_reporting::ContextualParseError;
@@ -216,13 +219,13 @@ impl PropertyRegistration {
pub fn compute_initial_value(
&self,
computed_context: &computed::Context,
- ) -> Result<InitialValue, ()> {
+ ) -> Result<ComputedRegisteredValue, ()> {
let Some(ref initial) = self.data.initial_value else {
return Err(());
};
if self.data.syntax.is_universal() {
- return Ok(Arc::clone(initial));
+ return Ok(ComputedRegisteredValue::universal(Arc::clone(initial)));
}
let mut input = ParserInput::new(initial.css_text());
@@ -236,7 +239,7 @@ impl PropertyRegistration {
computed_context,
AllowComputationallyDependent::No,
) {
- Ok(computed) => Ok(Arc::new(computed)),
+ Ok(computed) => Ok(computed),
Err(_) => Err(()),
}
}
diff --git a/servo/components/style/properties_and_values/value.rs b/servo/components/style/properties_and_values/value.rs
index 8e9d78b8cc..b66e268421 100644
--- a/servo/components/style/properties_and_values/value.rs
+++ b/servo/components/style/properties_and_values/value.rs
@@ -131,11 +131,13 @@ pub enum GenericValueComponent<
/// A <resolution> value
Resolution(Resolution),
/// A <transform-function> value
+ /// TODO(bug 1884606): <transform-function> `none` should not interpolate.
TransformFunction(TransformFunction),
/// A <custom-ident> value
#[animation(error)]
CustomIdent(CustomIdent),
/// A <transform-list> value, equivalent to <transform-function>+
+ /// TODO(bug 1884606): <transform-list> `none` should not interpolate.
TransformList(ComponentList<Self>),
/// A <string> value
#[animation(error)]
@@ -156,7 +158,11 @@ impl<Component: Animate> Animate for ComponentList<Component> {
if self.multiplier != other.multiplier {
return Err(());
}
- let components = animated::lists::by_computed_value::animate(&self.components, &other.components, procedure)?;
+ let components = animated::lists::by_computed_value::animate(
+ &self.components,
+ &other.components,
+ procedure,
+ )?;
Ok(Self {
multiplier: self.multiplier,
components,
@@ -190,9 +196,45 @@ impl<Component: ToCss> ToCss for ComponentList<Component> {
}
}
+/// A struct for a single specified registered custom property value that includes its original URL
+// data so the value can be uncomputed later.
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
+pub struct Value<Component> {
+ /// The registered custom property value.
+ pub(crate) v: ValueInner<Component>,
+ /// The URL data of the registered custom property from before it was computed. This is
+ /// necessary to uncompute registered custom properties.
+ #[css(skip)]
+ url_data: UrlExtraData,
+}
+
+impl<Component: Animate> Animate for Value<Component> {
+ fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
+ let v = self.v.animate(&other.v, procedure)?;
+ Ok(Value {
+ v,
+ url_data: self.url_data.clone(),
+ })
+ }
+}
+
+impl<Component> Value<Component> {
+ /// Creates a new registered custom property value.
+ pub fn new(v: ValueInner<Component>, url_data: UrlExtraData) -> Self {
+ Self { v, url_data }
+ }
+
+ /// Creates a new registered custom property value presumed to have universal syntax.
+ pub fn universal(var: Arc<ComputedPropertyValue>) -> Self {
+ let url_data = var.url_data.clone();
+ let v = ValueInner::Universal(var);
+ Self { v, url_data }
+ }
+}
+
/// A specified registered custom property value.
#[derive(Animate, ToComputedValue, ToCss, Clone, Debug, MallocSizeOf, PartialEq)]
-pub enum Value<Component> {
+pub enum ValueInner<Component> {
/// A single specified component value whose syntax descriptor component did not have a
/// multiplier.
Component(Component),
@@ -206,31 +248,31 @@ pub enum Value<Component> {
/// Specified custom property value.
pub type SpecifiedValue = Value<SpecifiedValueComponent>;
+impl ToComputedValue for SpecifiedValue {
+ type ComputedValue = ComputedValue;
+
+ fn to_computed_value(&self, context: &computed::Context) -> Self::ComputedValue {
+ Self::ComputedValue {
+ v: self.v.to_computed_value(context),
+ url_data: self.url_data.clone(),
+ }
+ }
+
+ fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+ Self {
+ v: ToComputedValue::from_computed_value(&computed.v),
+ url_data: computed.url_data.clone(),
+ }
+ }
+}
+
/// Computed custom property value.
pub type ComputedValue = Value<ComputedValueComponent>;
impl SpecifiedValue {
- /// Convert a Computed custom property value to a VariableValue.
- pub fn compute<'i, 't>(
- input: &mut CSSParser<'i, 't>,
- registration: &PropertyRegistrationData,
- url_data: &UrlExtraData,
- context: &computed::Context,
- allow_computationally_dependent: AllowComputationallyDependent,
- ) -> Result<ComputedPropertyValue, ()> {
- let value = Self::get_computed_value(
- input,
- registration,
- url_data,
- context,
- allow_computationally_dependent,
- )?;
- Ok(value.to_variable_value(url_data))
- }
-
/// Convert a registered custom property to a Computed custom property value, given input and a
/// property registration.
- fn get_computed_value<'i, 't>(
+ pub fn compute<'i, 't>(
input: &mut CSSParser<'i, 't>,
registration: &PropertyRegistrationData,
url_data: &UrlExtraData,
@@ -259,9 +301,11 @@ impl SpecifiedValue {
allow_computationally_dependent: AllowComputationallyDependent,
) -> Result<Self, StyleParseError<'i>> {
if syntax.is_universal() {
- return Ok(Self::Universal(Arc::new(ComputedPropertyValue::parse(
- &mut input, url_data,
- )?)));
+ let parsed = ComputedPropertyValue::parse(&mut input, url_data)?;
+ return Ok(SpecifiedValue {
+ v: ValueInner::Universal(Arc::new(parsed)),
+ url_data: url_data.clone(),
+ });
}
let mut values = SmallComponentVec::new();
@@ -270,45 +314,64 @@ impl SpecifiedValue {
let mut parser = Parser::new(syntax, &mut values, &mut multiplier);
parser.parse(&mut input, url_data, allow_computationally_dependent)?;
}
- let computed_value = if let Some(multiplier) = multiplier {
- Self::List(ComponentList {
+ let v = if let Some(multiplier) = multiplier {
+ ValueInner::List(ComponentList {
multiplier,
components: values.to_vec().into(),
})
} else {
- Self::Component(values[0].clone())
+ ValueInner::Component(values[0].clone())
};
- Ok(computed_value)
+ Ok(Self {
+ v,
+ url_data: url_data.clone(),
+ })
}
}
impl ComputedValue {
fn serialization_types(&self) -> (TokenSerializationType, TokenSerializationType) {
- match self {
- Self::Component(component) => component.serialization_types(),
- Self::Universal(_) => unreachable!(),
- Self::List(list) => list
+ match &self.v {
+ ValueInner::Component(component) => component.serialization_types(),
+ ValueInner::Universal(_) => unreachable!(),
+ ValueInner::List(list) => list
.components
.first()
.map_or(Default::default(), |f| f.serialization_types()),
}
}
- fn to_declared_value(&self, url_data: &UrlExtraData) -> Arc<ComputedPropertyValue> {
- if let Self::Universal(var) = self {
+ fn to_declared_value(&self) -> Arc<ComputedPropertyValue> {
+ if let ValueInner::Universal(ref var) = self.v {
return Arc::clone(var);
}
- Arc::new(self.to_variable_value(url_data))
+ Arc::new(self.to_variable_value())
+ }
+
+ /// Returns the contained variable value if it exists, otherwise `None`.
+ pub fn as_universal(&self) -> Option<&Arc<ComputedPropertyValue>> {
+ if let ValueInner::Universal(ref var) = self.v {
+ Some(var)
+ } else {
+ None
+ }
}
- fn to_variable_value(&self, url_data: &UrlExtraData) -> ComputedPropertyValue {
- debug_assert!(!matches!(self, Self::Universal(..)), "Shouldn't be needed");
- // TODO(zrhoffman, 1864736): Preserve the computed type instead of converting back to a
- // string.
+ /// Returns whether the the property is computed.
+ #[cfg(debug_assertions)]
+ pub fn is_parsed(&self, registration: &PropertyRegistrationData) -> bool {
+ registration.syntax.is_universal() || !matches!(self.v, ValueInner::Universal(_))
+ }
+
+ /// Convert to an untyped variable value.
+ pub fn to_variable_value(&self) -> ComputedPropertyValue {
+ if let ValueInner::Universal(ref value) = self.v {
+ return (**value).clone();
+ }
let serialization_types = self.serialization_types();
ComputedPropertyValue::new(
self.to_css_string(),
- url_data,
+ &self.url_data,
serialization_types.0,
serialization_types.1,
)
@@ -530,7 +593,6 @@ impl<'a> Parser<'a> {
}
}
-
/// An animated value for custom property.
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
pub struct CustomAnimatedValue {
@@ -538,25 +600,17 @@ pub struct CustomAnimatedValue {
pub(crate) name: crate::custom_properties::Name,
/// The computed value of the custom property.
value: ComputedValue,
- /// The url data where the value came from.
- /// FIXME: This seems like it should not be needed: registered properties don't need it, and
- /// unregistered properties animate discretely. But we need it so far because the computed
- /// value representation isn't typed.
- url_data: UrlExtraData,
}
impl Animate for CustomAnimatedValue {
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
if self.name != other.name {
- return Err(())
+ return Err(());
}
let value = self.value.animate(&other.value, procedure)?;
Ok(Self {
name: self.name.clone(),
value,
- // NOTE: This is sketchy AF, but it's ~fine, since values that can animate (non-universal)
- // don't need it.
- url_data: self.url_data.clone(),
})
}
}
@@ -564,13 +618,11 @@ impl Animate for CustomAnimatedValue {
impl CustomAnimatedValue {
pub(crate) fn from_computed(
name: &crate::custom_properties::Name,
- value: &Arc<ComputedPropertyValue>,
+ value: &ComputedValue,
) -> Self {
Self {
name: name.clone(),
- // FIXME: Should probably preserve type-ness in ComputedPropertyValue.
- value: ComputedValue::Universal(value.clone()),
- url_data: value.url_data.clone(),
+ value: value.clone(),
}
}
@@ -590,8 +642,11 @@ impl CustomAnimatedValue {
context.builder.stylist.is_some(),
"Need a Stylist to get property registration!"
);
- let registration =
- context.builder.stylist.unwrap().get_custom_property_registration(&declaration.name);
+ let registration = context
+ .builder
+ .stylist
+ .unwrap()
+ .get_custom_property_registration(&declaration.name);
// FIXME: Do we need to perform substitution here somehow?
let computed_value = if registration.syntax.is_universal() {
@@ -599,20 +654,22 @@ impl CustomAnimatedValue {
} else {
let mut input = cssparser::ParserInput::new(&value.css);
let mut input = CSSParser::new(&mut input);
- SpecifiedValue::get_computed_value(
+ SpecifiedValue::compute(
&mut input,
registration,
&value.url_data,
context,
AllowComputationallyDependent::Yes,
- ).ok()
+ )
+ .ok()
};
- let url_data = value.url_data.clone();
- let value = computed_value.unwrap_or_else(|| ComputedValue::Universal(Arc::clone(value)));
+ let value = computed_value.unwrap_or_else(|| ComputedValue {
+ v: ValueInner::Universal(Arc::clone(value)),
+ url_data: value.url_data.clone(),
+ });
Some(Self {
name: declaration.name.clone(),
- url_data,
value,
})
}
@@ -620,7 +677,7 @@ impl CustomAnimatedValue {
pub(crate) fn to_declaration(&self) -> properties::PropertyDeclaration {
properties::PropertyDeclaration::Custom(properties::CustomDeclaration {
name: self.name.clone(),
- value: properties::CustomDeclarationValue::Value(self.value.to_declared_value(&self.url_data)),
+ value: properties::CustomDeclarationValue::Value(self.value.to_declared_value()),
})
}
}
diff --git a/servo/components/style/sharing/mod.rs b/servo/components/style/sharing/mod.rs
index eeea135c06..28592a02c5 100644
--- a/servo/components/style/sharing/mod.rs
+++ b/servo/components/style/sharing/mod.rs
@@ -908,12 +908,7 @@ impl<E: TElement> StyleSharingCache<E> {
// RELEVANT_LINK_VISITED flag, so we can't share by rule node between visited and
// unvisited styles. We don't check for visitedness and just refuse to share for links
// entirely, so that visitedness doesn't affect timing.
- debug_assert_eq!(
- target.is_link(),
- candidate.element.is_link(),
- "Linkness mismatch"
- );
- if target.is_link() {
+ if target.is_link() || candidate.element.is_link() {
return None;
}
diff --git a/servo/components/style/values/computed/animation.rs b/servo/components/style/values/computed/animation.rs
index 626dbe5347..b626687a36 100644
--- a/servo/components/style/values/computed/animation.rs
+++ b/servo/components/style/values/computed/animation.rs
@@ -11,8 +11,8 @@ use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};
pub use crate::values::specified::animation::{
- AnimationName, ScrollAxis, ScrollTimelineName, TransitionProperty, AnimationComposition,
- AnimationDirection, AnimationFillMode, AnimationPlayState,
+ AnimationComposition, AnimationDirection, AnimationFillMode, AnimationName, AnimationPlayState,
+ ScrollAxis, ScrollTimelineName, TransitionBehavior, TransitionProperty,
};
/// A computed value for the `animation-iteration-count` property.
diff --git a/servo/components/style/values/computed/color.rs b/servo/components/style/values/computed/color.rs
index 9b5185d923..8aa50ba5ad 100644
--- a/servo/components/style/values/computed/color.rs
+++ b/servo/components/style/values/computed/color.rs
@@ -4,14 +4,13 @@
//! Computed color values.
-use crate::color::parsing::Color as CSSParserColor;
use crate::color::AbsoluteColor;
use crate::values::animated::ToAnimatedZero;
use crate::values::computed::percentage::Percentage;
use crate::values::generics::color::{
GenericCaretColor, GenericColor, GenericColorMix, GenericColorOrAuto,
};
-use std::fmt;
+use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};
pub use crate::values::specified::color::{ColorScheme, ForcedColorAdjust, PrintColorAdjust};
@@ -32,7 +31,7 @@ impl ToCss for Color {
{
match *self {
Self::Absolute(ref c) => c.to_css(dest),
- Self::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest),
+ Self::CurrentColor => dest.write_str("currentcolor"),
Self::ColorMix(ref m) => m.to_css(dest),
}
}
diff --git a/servo/components/style/values/computed/length_percentage.rs b/servo/components/style/values/computed/length_percentage.rs
index 898281a7ef..0dbd2de76d 100644
--- a/servo/components/style/values/computed/length_percentage.rs
+++ b/servo/components/style/values/computed/length_percentage.rs
@@ -443,6 +443,19 @@ impl LengthPercentage {
}
}
+ /// Converts to a `<percentage>` with given basis. Returns None if the basis is 0.
+ #[inline]
+ pub fn to_percentage_of(&self, basis: Length) -> Option<Percentage> {
+ if basis.px() == 0. {
+ return None;
+ }
+ Some(match self.unpack() {
+ Unpacked::Length(l) => Percentage(l.px() / basis.px()),
+ Unpacked::Percentage(p) => p,
+ Unpacked::Calc(ref c) => Percentage(c.resolve(basis).px() / basis.px()),
+ })
+ }
+
/// Returns the used value.
#[inline]
pub fn to_used_value(&self, containing_length: Au) -> Au {
diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs
index de5db2cdab..85aadb401f 100644
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -47,7 +47,7 @@ pub use self::angle::Angle;
pub use self::animation::{
AnimationIterationCount, AnimationName, AnimationTimeline, AnimationPlayState,
AnimationFillMode, AnimationComposition, AnimationDirection, ScrollAxis,
- ScrollTimelineName, TransitionProperty, ViewTimelineInset
+ ScrollTimelineName, TransitionBehavior, TransitionProperty, ViewTimelineInset
};
pub use self::background::{BackgroundRepeat, BackgroundSize};
pub use self::basic_shape::FillRule;
diff --git a/servo/components/style/values/specified/animation.rs b/servo/components/style/values/specified/animation.rs
index e7bbf26fb3..5a1f5003f3 100644
--- a/servo/components/style/values/specified/animation.rs
+++ b/servo/components/style/values/specified/animation.rs
@@ -112,6 +112,44 @@ impl TransitionProperty {
}
}
+/// A specified value for <transition-behavior-value>.
+///
+/// https://drafts.csswg.org/css-transitions-2/#transition-behavior-property
+#[derive(
+ Clone,
+ Copy,
+ Debug,
+ MallocSizeOf,
+ Parse,
+ PartialEq,
+ SpecifiedValueInfo,
+ ToComputedValue,
+ ToCss,
+ ToResolvedValue,
+ ToShmem,
+)]
+#[repr(u8)]
+pub enum TransitionBehavior {
+ /// Transitions will not be started for discrete properties, only for interpolable properties.
+ Normal,
+ /// Transitions will be started for discrete properties as well as interpolable properties.
+ AllowDiscrete,
+}
+
+impl TransitionBehavior {
+ /// Return normal, the initial value.
+ #[inline]
+ pub fn normal() -> Self {
+ Self::Normal
+ }
+
+ /// Return true if it is normal.
+ #[inline]
+ pub fn is_normal(&self) -> bool {
+ matches!(*self, Self::Normal)
+ }
+}
+
/// https://drafts.csswg.org/css-animations/#animation-iteration-count
#[derive(Copy, Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem)]
pub enum AnimationIterationCount {
diff --git a/servo/components/style/values/specified/calc.rs b/servo/components/style/values/specified/calc.rs
index 2660864319..17f043ac58 100644
--- a/servo/components/style/values/specified/calc.rs
+++ b/servo/components/style/values/specified/calc.rs
@@ -6,7 +6,6 @@
//!
//! [calc]: https://drafts.csswg.org/css-values/#calc-notation
-use crate::color::parsing::{AngleOrNumber, NumberOrPercentage};
use crate::parser::ParserContext;
use crate::values::generics::calc::{
self as generic, CalcNodeLeaf, CalcUnits, MinMaxOp, ModRemOp, PositivePercentageBasis,
@@ -185,7 +184,11 @@ impl generic::CalcNodeLeaf for Leaf {
let self_negative = self.is_negative();
if self_negative != other.is_negative() {
- return Some(if self_negative { cmp::Ordering::Less } else { cmp::Ordering::Greater });
+ return Some(if self_negative {
+ cmp::Ordering::Less
+ } else {
+ cmp::Ordering::Greater
+ });
}
match (self, other) {
@@ -482,7 +485,7 @@ impl CalcNode {
/// Parse a top-level `calc` expression, with all nested sub-expressions.
///
/// This is in charge of parsing, for example, `2 + 3 * 100%`.
- fn parse<'i, 't>(
+ pub fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
function: MathFunction,
@@ -524,13 +527,18 @@ impl CalcNode {
}
let value = Self::parse_argument(context, input, allowed_units)?;
- input.expect_comma()?;
- let step = Self::parse_argument(context, input, allowed_units)?;
+
+ // <step> defaults to the number 1 if not provided
+ // https://drafts.csswg.org/css-values-4/#funcdef-round
+ let step = input.try_parse(|input| {
+ input.expect_comma()?;
+ Self::parse_argument(context, input, allowed_units)
+ });
Ok(Self::Round {
strategy: strategy.unwrap_or(RoundingStrategy::Nearest),
value: Box::new(value),
- step: Box::new(step),
+ step: Box::new(step.unwrap_or(Self::Leaf(Leaf::Number(1.0)))),
})
},
MathFunction::Mod | MathFunction::Rem => {
@@ -1046,41 +1054,4 @@ impl CalcNode {
.to_resolution()
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
-
- /// Convenience parsing function for `<number>` or `<percentage>`.
- pub fn parse_number_or_percentage<'i, 't>(
- context: &ParserContext,
- input: &mut Parser<'i, 't>,
- function: MathFunction,
- ) -> Result<NumberOrPercentage, ParseError<'i>> {
- let node = Self::parse(context, input, function, CalcUnits::PERCENTAGE)?;
-
- if let Ok(value) = node.to_number() {
- return Ok(NumberOrPercentage::Number { value });
- }
-
- match node.to_percentage() {
- Ok(unit_value) => Ok(NumberOrPercentage::Percentage { unit_value }),
- Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
- }
- }
-
- /// Convenience parsing function for `<number>` or `<angle>`.
- pub fn parse_angle_or_number<'i, 't>(
- context: &ParserContext,
- input: &mut Parser<'i, 't>,
- function: MathFunction,
- ) -> Result<AngleOrNumber, ParseError<'i>> {
- let node = Self::parse(context, input, function, CalcUnits::ANGLE)?;
-
- if let Ok(angle) = node.to_angle() {
- let degrees = angle.degrees();
- return Ok(AngleOrNumber::Angle { degrees });
- }
-
- match node.to_number() {
- Ok(value) => Ok(AngleOrNumber::Number { value }),
- Err(()) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
- }
- }
}
diff --git a/servo/components/style/values/specified/color.rs b/servo/components/style/values/specified/color.rs
index 3a19a2f4a3..3694b4e9bc 100644
--- a/servo/components/style/values/specified/color.rs
+++ b/servo/components/style/values/specified/color.rs
@@ -5,19 +5,21 @@
//! Specified color values.
use super::AllowQuirks;
-use crate::color::parsing::{
- self, AngleOrNumber, Color as CSSParserColor, FromParsedColor, NumberOrPercentage,
-};
+use crate::color::component::ColorComponent;
+use crate::color::convert::normalize_hue;
+use crate::color::parsing::{self, FromParsedColor, NumberOrAngle, NumberOrPercentage};
use crate::color::{mix::ColorInterpolationMethod, AbsoluteColor, ColorSpace};
use crate::media_queries::Device;
use crate::parser::{Parse, ParserContext};
use crate::values::computed::{Color as ComputedColor, Context, ToComputedValue};
+use crate::values::generics::calc::CalcUnits;
use crate::values::generics::color::{
ColorMixFlags, GenericCaretColor, GenericColorMix, GenericColorOrAuto,
};
-use crate::values::specified::calc::CalcNode;
+use crate::values::specified::calc::{CalcNode, Leaf};
use crate::values::specified::Percentage;
use crate::values::{normalize, CustomIdent};
+use cssparser::color::OPAQUE;
use cssparser::{color::PredefinedColorSpace, BasicParseErrorKind, ParseErrorKind, Parser, Token};
use itoa;
use std::fmt::{self, Write};
@@ -428,94 +430,217 @@ 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(r: u8, g: u8, b: u8, a: f32) -> Self {
- AbsoluteColor::srgb_legacy(r, g, b, a).into()
+ 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: Option<f32>,
- saturation: Option<f32>,
- lightness: Option<f32>,
- alpha: Option<f32>,
+ hue: ColorComponent<NumberOrAngle>,
+ saturation: ColorComponent<NumberOrPercentage>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Hsl, hue, saturation, lightness, alpha).into()
+ // 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: Option<f32>,
- whiteness: Option<f32>,
- blackness: Option<f32>,
- alpha: Option<f32>,
+ hue: ColorComponent<NumberOrAngle>,
+ whiteness: ColorComponent<NumberOrPercentage>,
+ blackness: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Hwb, hue, whiteness, blackness, alpha).into()
+ // 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: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ a: ColorComponent<NumberOrPercentage>,
+ b: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Lab, lightness, a, b, alpha).into()
+ // 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: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ chroma: ColorComponent<NumberOrPercentage>,
+ hue: ColorComponent<NumberOrAngle>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Lch, lightness, chroma, hue, alpha).into()
+ // 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: Option<f32>,
- a: Option<f32>,
- b: Option<f32>,
- alpha: Option<f32>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ a: ColorComponent<NumberOrPercentage>,
+ b: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Oklab, lightness, a, b, alpha).into()
+ // 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: Option<f32>,
- chroma: Option<f32>,
- hue: Option<f32>,
- alpha: Option<f32>,
+ lightness: ColorComponent<NumberOrPercentage>,
+ chroma: ColorComponent<NumberOrPercentage>,
+ hue: ColorComponent<NumberOrAngle>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(ColorSpace::Oklch, lightness, chroma, hue, alpha).into()
+ // 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: Option<f32>,
- c2: Option<f32>,
- c3: Option<f32>,
- alpha: Option<f32>,
+ c1: ColorComponent<NumberOrPercentage>,
+ c2: ColorComponent<NumberOrPercentage>,
+ c3: ColorComponent<NumberOrPercentage>,
+ alpha: ColorComponent<NumberOrPercentage>,
) -> Self {
- AbsoluteColor::new(color_space.into(), c1, c2, c3, alpha).into()
+ 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()
}
}
struct ColorParser<'a, 'b: 'a>(&'a ParserContext<'b>);
+
impl<'a, 'b: 'a, 'i: 'a> parsing::ColorParser<'i> for ColorParser<'a, 'b> {
type Output = Color;
- type Error = StyleParseErrorKind<'i>;
- fn parse_angle_or_number<'t>(
+ fn parse_number_or_angle<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<AngleOrNumber, ParseError<'i>> {
+ allow_none: bool,
+ ) -> Result<ColorComponent<NumberOrAngle>, ParseError<'i>> {
use crate::values::specified::Angle;
let location = input.current_source_location();
let token = input.next()?.clone();
- match token {
+ Ok(match token {
+ Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
+ ColorComponent::None
+ },
Token::Dimension {
value, ref unit, ..
} => {
@@ -526,44 +651,125 @@ impl<'a, 'b: 'a, 'i: 'a> parsing::ColorParser<'i> for ColorParser<'a, 'b> {
Err(()) => return Err(location.new_unexpected_token_error(token.clone())),
};
- Ok(AngleOrNumber::Angle { degrees })
+ ColorComponent::Value(NumberOrAngle::Angle { degrees })
},
- Token::Number { value, .. } => Ok(AngleOrNumber::Number { value }),
+ Token::Number { value, .. } => ColorComponent::Value(NumberOrAngle::Number { value }),
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
- CalcNode::parse_angle_or_number(self.0, input, function)
+ let node = CalcNode::parse(self.0, input, function, CalcUnits::ANGLE)?;
+
+ // If we can resolve the calc node, then use the value.
+ match node.resolve() {
+ Ok(Leaf::Number(value)) => {
+ ColorComponent::Value(NumberOrAngle::Number { value })
+ },
+ Ok(Leaf::Angle(angle)) => ColorComponent::Value(NumberOrAngle::Angle {
+ degrees: angle.degrees(),
+ }),
+ _ => {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
+ },
+ }
},
t => return Err(location.new_unexpected_token_error(t)),
- }
+ })
}
- fn parse_percentage<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
- Ok(Percentage::parse(self.0, input)?.get())
+ fn parse_percentage<'t>(
+ &self,
+ input: &mut Parser<'i, 't>,
+ allow_none: bool,
+ ) -> Result<ColorComponent<f32>, ParseError<'i>> {
+ let location = input.current_source_location();
+
+ Ok(match *input.next()? {
+ Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
+ ColorComponent::None
+ },
+ Token::Percentage { unit_value, .. } => ColorComponent::Value(unit_value),
+ Token::Function(ref name) => {
+ let function = CalcNode::math_function(self.0, name, location)?;
+ let node = CalcNode::parse(self.0, input, function, CalcUnits::PERCENTAGE)?;
+
+ // If we can resolve the calc node, then use the value.
+ let Ok(resolved_leaf) = node.resolve() else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ };
+ if let Leaf::Percentage(value) = resolved_leaf {
+ ColorComponent::Value(value)
+ } else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+ },
+ ref t => return Err(location.new_unexpected_token_error(t.clone())),
+ })
}
- fn parse_number<'t>(&self, input: &mut Parser<'i, 't>) -> Result<f32, ParseError<'i>> {
- use crate::values::specified::Number;
+ fn parse_number<'t>(
+ &self,
+ input: &mut Parser<'i, 't>,
+ allow_none: bool,
+ ) -> Result<ColorComponent<f32>, ParseError<'i>> {
+ let location = input.current_source_location();
+
+ Ok(match *input.next()? {
+ Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
+ ColorComponent::None
+ },
+ Token::Number { value, .. } => ColorComponent::Value(value),
+ Token::Function(ref name) => {
+ let function = CalcNode::math_function(self.0, name, location)?;
+ let node = CalcNode::parse(self.0, input, function, CalcUnits::empty())?;
- Ok(Number::parse(self.0, input)?.get())
+ // If we can resolve the calc node, then use the value.
+ let Ok(resolved_leaf) = node.resolve() else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ };
+ if let Leaf::Number(value) = resolved_leaf {
+ ColorComponent::Value(value)
+ } else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+ },
+ ref t => return Err(location.new_unexpected_token_error(t.clone())),
+ })
}
fn parse_number_or_percentage<'t>(
&self,
input: &mut Parser<'i, 't>,
- ) -> Result<NumberOrPercentage, ParseError<'i>> {
+ allow_none: bool,
+ ) -> Result<ColorComponent<NumberOrPercentage>, ParseError<'i>> {
let location = input.current_source_location();
- match *input.next()? {
- Token::Number { value, .. } => Ok(NumberOrPercentage::Number { value }),
+ Ok(match *input.next()? {
+ Token::Ident(ref value) if allow_none && value.eq_ignore_ascii_case("none") => {
+ ColorComponent::None
+ },
+ Token::Number { value, .. } => {
+ ColorComponent::Value(NumberOrPercentage::Number { value })
+ },
Token::Percentage { unit_value, .. } => {
- Ok(NumberOrPercentage::Percentage { unit_value })
+ ColorComponent::Value(NumberOrPercentage::Percentage { unit_value })
},
Token::Function(ref name) => {
let function = CalcNode::math_function(self.0, name, location)?;
- CalcNode::parse_number_or_percentage(self.0, input, function)
+ let node = CalcNode::parse(self.0, input, function, CalcUnits::PERCENTAGE)?;
+
+ // If we can resolve the calc node, then use the value.
+ let Ok(resolved_leaf) = node.resolve() else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ };
+ if let Leaf::Percentage(unit_value) = resolved_leaf {
+ ColorComponent::Value(NumberOrPercentage::Percentage { unit_value })
+ } else if let Leaf::Number(value) = resolved_leaf {
+ ColorComponent::Value(NumberOrPercentage::Number { value })
+ } else {
+ return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
},
ref t => return Err(location.new_unexpected_token_error(t.clone())),
- }
+ })
}
}
@@ -700,7 +906,7 @@ impl ToCss for Color {
W: Write,
{
match *self {
- Color::CurrentColor => cssparser::ToCss::to_css(&CSSParserColor::CurrentColor, dest),
+ Color::CurrentColor => dest.write_str("currentcolor"),
Color::Absolute(ref absolute) => absolute.to_css(dest),
Color::ColorMix(ref mix) => mix.to_css(dest),
Color::LightDark(ref ld) => ld.to_css(dest),
@@ -772,7 +978,12 @@ 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, g, b, a)),
+ Ok((r, g, b, a)) => Ok(Self::from_rgba(
+ r.into(),
+ g.into(),
+ b.into(),
+ ColorComponent::Value(NumberOrPercentage::Number { value: a }),
+ )),
Err(()) => Err(loc.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
}
}
diff --git a/servo/components/style/values/specified/font.rs b/servo/components/style/values/specified/font.rs
index 2435682ce3..db3d871a0f 100644
--- a/servo/components/style/values/specified/font.rs
+++ b/servo/components/style/values/specified/font.rs
@@ -450,16 +450,6 @@ impl ToComputedValue for FontStretch {
}
}
-#[cfg(feature = "gecko")]
-fn math_depth_enabled(_context: &ParserContext) -> bool {
- static_prefs::pref!("layout.css.math-depth.enabled")
-}
-
-#[cfg(feature = "servo")]
-fn math_depth_enabled(_context: &ParserContext) -> bool {
- false
-}
-
/// CSS font keywords
#[derive(
Animate,
@@ -496,7 +486,7 @@ pub enum FontSizeKeyword {
XXXLarge,
/// Indicate whether to apply font-size: math is specified so that extra
/// scaling due to math-depth changes is applied during the cascade.
- #[parse(condition = "math_depth_enabled")]
+ #[cfg(feature="gecko")]
Math,
#[css(skip)]
None,
@@ -1018,7 +1008,7 @@ impl FontSize {
return Ok(FontSize::Length(lp));
}
- if let Ok(kw) = input.try_parse(|i| FontSizeKeyword::parse(context, i)) {
+ if let Ok(kw) = input.try_parse(|i| FontSizeKeyword::parse(i)) {
return Ok(FontSize::Keyword(KeywordInfo::new(kw)));
}
diff --git a/servo/components/style/values/specified/mod.rs b/servo/components/style/values/specified/mod.rs
index 7fc76b3c07..1a12ca56e7 100644
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -33,7 +33,7 @@ pub use self::angle::{AllowUnitlessZeroAngle, Angle};
pub use self::animation::{
AnimationIterationCount, AnimationName, AnimationTimeline, AnimationPlayState,
AnimationFillMode, AnimationComposition, AnimationDirection, ScrollAxis,
- ScrollTimelineName, TransitionProperty, ViewTimelineInset
+ ScrollTimelineName, TransitionBehavior, TransitionProperty, ViewTimelineInset
};
pub use self::background::{BackgroundRepeat, BackgroundSize};
pub use self::basic_shape::FillRule;
diff --git a/servo/components/style_traits/values.rs b/servo/components/style_traits/values.rs
index a004b577c1..58b50d6576 100644
--- a/servo/components/style_traits/values.rs
+++ b/servo/components/style_traits/values.rs
@@ -508,6 +508,7 @@ macro_rules! impl_to_css_for_predefined_type {
impl_to_css_for_predefined_type!(f32);
impl_to_css_for_predefined_type!(i8);
impl_to_css_for_predefined_type!(i32);
+impl_to_css_for_predefined_type!(u8);
impl_to_css_for_predefined_type!(u16);
impl_to_css_for_predefined_type!(u32);
impl_to_css_for_predefined_type!(::cssparser::Token<'a>);
diff --git a/servo/ports/geckolib/Cargo.toml b/servo/ports/geckolib/Cargo.toml
index ebf4178014..1bdaaa80b7 100644
--- a/servo/ports/geckolib/Cargo.toml
+++ b/servo/ports/geckolib/Cargo.toml
@@ -31,3 +31,4 @@ style = {path = "../../components/style", features = ["gecko"]}
style_traits = {path = "../../components/style_traits"}
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
to_shmem = {path = "../../components/to_shmem"}
+lazy_static = "1.0"
diff --git a/servo/ports/geckolib/cbindgen.toml b/servo/ports/geckolib/cbindgen.toml
index b703dc6c92..8e6818e421 100644
--- a/servo/ports/geckolib/cbindgen.toml
+++ b/servo/ports/geckolib/cbindgen.toml
@@ -140,6 +140,7 @@ include = [
"ScrollSnapStrictness",
"ScrollSnapType",
"ScrollTimelineName",
+ "TransitionBehavior",
"ViewTimelineInset",
"OverflowAnchor",
"OverflowClipBox",
@@ -365,9 +366,7 @@ renaming_overrides_prefixing = true
"CalcLengthPercentage" = """
inline CSSCoord ResolveToCSSPixels(CSSCoord aBasis) const;
-
- using CoordRounder = nscoord(*)(float);
- nscoord Resolve(nscoord aBasis, CoordRounder) const;
+ inline nscoord Resolve(nscoord aBasis) const;
"""
"GenericCalcNode" = """
@@ -428,10 +427,10 @@ renaming_overrides_prefixing = true
inline bool IsDefinitelyZero() const;
inline CSSCoord ResolveToCSSPixels(CSSCoord aPercentageBasisInCSSPixels) const;
template<typename T> inline CSSCoord ResolveToCSSPixelsWith(T aPercentageGetter) const;
- template<typename T, typename U>
- inline nscoord Resolve(T aPercentageGetter, U aRoundingFunction) const;
- template<typename T>
- inline nscoord Resolve(nscoord aPercentageBasis, T aRoundingFunction) const;
+ template<typename T, typename PercentRounder>
+ inline nscoord Resolve(T aPercentageGetter, PercentRounder) const;
+ template<typename PercentRounder>
+ inline nscoord Resolve(nscoord aPercentageBasis, PercentRounder) const;
template<typename T> inline nscoord Resolve(T aPercentageGetter) const;
inline nscoord Resolve(nscoord aPercentageBasis) const;
"""
diff --git a/servo/ports/geckolib/glue.rs b/servo/ports/geckolib/glue.rs
index a6c4ef1262..83c55ad9e0 100644
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -6,7 +6,7 @@ use super::error_reporter::ErrorReporter;
use super::stylesheet_loader::{AsyncStylesheetParser, StylesheetLoader};
use bincode::{deserialize, serialize};
use cssparser::ToCss as ParserToCss;
-use cssparser::{Parser, ParserInput, SourceLocation, UnicodeRange};
+use cssparser::{BasicParseError, ParseError as CssParseError, Parser, ParserInput, SourceLocation, UnicodeRange, Token};
use dom::{DocumentState, ElementState};
use malloc_size_of::MallocSizeOfOps;
use nsstring::{nsCString, nsString};
@@ -14,7 +14,6 @@ use selectors::matching::{ElementSelectorFlags, MatchingForInvalidation, Selecto
use selectors::{Element, OpaqueElement};
use servo_arc::{Arc, ArcBorrow};
use smallvec::SmallVec;
-use style::custom_properties::DeferFontRelativeCustomPropertyResolution;
use std::collections::BTreeSet;
use std::fmt::Write;
use std::iter;
@@ -26,6 +25,7 @@ use style::computed_value_flags::ComputedValueFlags;
use style::context::ThreadLocalStyleContext;
use style::context::{CascadeInputs, QuirksMode, SharedStyleContext, StyleContext};
use style::counter_style;
+use style::custom_properties::DeferFontRelativeCustomPropertyResolution;
use style::data::{self, ElementStyles};
use style::dom::{ShowSubtreeData, TDocument, TElement, TNode};
use style::driver;
@@ -204,6 +204,9 @@ pub unsafe extern "C" fn Servo_Initialize(
// Pretend that we're a Servo Layout thread, to make some assertions happy.
thread_state::initialize(thread_state::ThreadState::LAYOUT);
+ debug_assert!(is_main_thread());
+ lazy_static::initialize(&STYLE_THREAD_POOL);
+
// Perform some debug-only runtime assertions.
origin_flags::assert_flags_match();
traversal_flags::assert_traversal_flags_match();
@@ -1204,8 +1207,16 @@ pub struct ShouldTransitionResult {
}
#[inline]
-fn is_transitionable(prop: PropertyDeclarationId) -> bool {
- prop.is_animatable() && !prop.is_discrete_animatable()
+fn is_transitionable(prop: PropertyDeclarationId, behavior: computed::TransitionBehavior) -> bool {
+ if !prop.is_animatable() {
+ return false;
+ }
+
+ match behavior {
+ computed::TransitionBehavior::Normal => !prop.is_discrete_animatable(),
+ // If transition-behavior is allow-discrete, transitionable is the same as animatable.
+ computed::TransitionBehavior::AllowDiscrete => true,
+ }
}
#[no_mangle]
@@ -1213,6 +1224,7 @@ pub extern "C" fn Servo_ComputedValues_ShouldTransition(
old: &ComputedValues,
new: &ComputedValues,
prop: &structs::AnimatedPropertyID,
+ behavior: computed::TransitionBehavior,
old_transition_value: Option<&AnimationValue>,
start: &mut structs::RefPtr<AnimationValue>,
end: &mut structs::RefPtr<AnimationValue>,
@@ -1221,7 +1233,7 @@ pub extern "C" fn Servo_ComputedValues_ShouldTransition(
return Default::default();
};
let prop = prop.as_borrowed();
- if !is_transitionable(prop) {
+ if !is_transitionable(prop, behavior) {
return Default::default();
}
@@ -1241,7 +1253,10 @@ pub extern "C" fn Servo_ComputedValues_ShouldTransition(
let Some(old_value) = AnimationValue::from_computed_values(prop, old) else {
return Default::default();
};
- if old_value == new_value || !old_value.interpolable_with(&new_value) {
+ if old_value == new_value
+ || (matches!(behavior, computed::TransitionBehavior::Normal)
+ && !old_value.interpolable_with(&new_value))
+ {
return Default::default();
}
@@ -1263,8 +1278,9 @@ pub extern "C" fn Servo_ComputedValues_TransitionValueMatches(
let Some(prop) = OwnedPropertyDeclarationId::from_gecko_animated_property_id(prop) else {
return false;
};
+ // Note: the running transitions should be transitionable, so it is always allow-discrete.
let prop = prop.as_borrowed();
- if !is_transitionable(prop) {
+ if !is_transitionable(prop, computed::TransitionBehavior::AllowDiscrete) {
return false;
}
let Some(value) = AnimationValue::from_computed_values(prop, style) else {
@@ -1366,7 +1382,9 @@ pub unsafe extern "C" fn Servo_Property_IsInherited(
let longhand_id = match prop_id {
PropertyId::Custom(property_name) => {
let stylist = &per_doc_data.borrow().stylist;
- return stylist.get_custom_property_registration(&property_name).inherits()
+ return stylist
+ .get_custom_property_registration(&property_name)
+ .inherits();
},
PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
Ok(lh) => lh,
@@ -6156,9 +6174,9 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(
raw_data: &PerDocumentStyleData,
computed_keyframes: &mut nsTArray<structs::ComputedKeyframeValues>,
) {
- use style::properties::PropertyDeclaration;
- use style::custom_properties::CustomPropertiesBuilder;
use style::applicable_declarations::CascadePriority;
+ use style::custom_properties::CustomPropertiesBuilder;
+ use style::properties::PropertyDeclaration;
let data = raw_data.borrow();
let element = GeckoElement(element);
let pseudo = PseudoElement::from_pseudo_type(pseudo_type, None);
@@ -6197,8 +6215,8 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(
let mut seen = PropertyDeclarationIdSet::default();
let mut iter = PrioritizedPropertyIter::new(&keyframe.mPropertyValues);
- // FIXME: This is pretty much a hack. Instead, the AnimatedValue should be better
- // integrated in the cascade. This would allow us to fix revert() too.
+ // FIXME (bug 1883255): This is pretty much a hack. Instead, the AnimatedValue should be
+ // better integrated in the cascade.
{
let mut builder = CustomPropertiesBuilder::new_with_properties(
&data.stylist,
@@ -6207,10 +6225,11 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(
);
let priority = CascadePriority::same_tree_author_normal_at_root_layer();
for property in &mut iter {
- let is_custom = match PropertyId::from_gecko_animated_property_id(&property.mProperty) {
- Some(PropertyId::Custom(..)) => true,
- _ => false,
- };
+ let is_custom =
+ match PropertyId::from_gecko_animated_property_id(&property.mProperty) {
+ Some(PropertyId::Custom(..)) => true,
+ _ => false,
+ };
if !is_custom {
break; // Custom props are guaranteed to sort earlier.
}
@@ -6226,7 +6245,7 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(
}
}
iter.reset();
- let _deferred= builder.build(DeferFontRelativeCustomPropertyResolution::No);
+ let _deferred = builder.build(DeferFontRelativeCustomPropertyResolution::No);
debug_assert!(
_deferred.is_none(),
"Custom property processing deferred despite specifying otherwise?"
@@ -6265,7 +6284,8 @@ pub extern "C" fn Servo_GetComputedKeyframeValues(
ptr::write(
&mut animation_values[property_index],
structs::PropertyStyleAnimationValuePair {
- mProperty: property.to_gecko_animated_property_id(/* owned = */ true),
+ mProperty: property
+ .to_gecko_animated_property_id(/* owned = */ true),
mValue: structs::AnimationValue {
mServo: value.map_or(structs::RefPtr::null(), |v| {
structs::RefPtr::from_arc(Arc::new(v))
@@ -6331,10 +6351,7 @@ pub extern "C" fn Servo_GetAnimationValues(
let guard = global_style_data.shared_lock.read();
let guard = declarations.read_with(&guard);
- let iter = guard.to_animation_value_iter(
- &mut context,
- &default_values,
- );
+ let iter = guard.to_animation_value_iter(&mut context, &default_values);
animation_values.extend(iter.map(|v| structs::RefPtr::from_arc(Arc::new(v))));
}
@@ -6385,11 +6402,7 @@ pub extern "C" fn Servo_AnimationValue_Compute(
.next()
{
Some((decl, imp)) if imp == Importance::Normal => {
- let animation = AnimationValue::from_declaration(
- decl,
- &mut context,
- default_values,
- );
+ let animation = AnimationValue::from_declaration(decl, &mut context, default_values);
animation.map_or(Strong::null(), |value| Arc::new(value).into())
},
_ => Strong::null(),
@@ -7267,8 +7280,10 @@ pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorForInsertion(
let data = raw_data.borrow();
let quirks_mode: QuirksMode = data.stylist.quirks_mode();
- let inherited =
- inherit_relative_selector_search_direction(element.parent_element(), element.prev_sibling_element());
+ let inherited = inherit_relative_selector_search_direction(
+ element.parent_element(),
+ element.prev_sibling_element(),
+ );
// Technically, we're not handling breakouts, where the anchor is a (later-sibling) descendant.
// For descendant case, we're ok since it's a descendant of an element yet to be styled.
// For later-sibling descendant, `HAS_SLOW_SELECTOR_LATER_SIBLINGS` is set anyway.
@@ -7392,9 +7407,11 @@ fn get_siblings_of_element<'e>(
) -> (Option<GeckoElement<'e>>, Option<GeckoElement<'e>>) {
let node = match following_node {
Some(n) => n,
- None => return match element.as_node().parent_node() {
- Some(p) => (p.last_child_element(), None),
- None => (None, None),
+ None => {
+ return match element.as_node().parent_node() {
+ Some(p) => (p.last_child_element(), None),
+ None => (None, None),
+ }
},
};
@@ -7416,12 +7433,12 @@ pub extern "C" fn Servo_StyleSet_MaybeInvalidateRelativeSelectorForRemoval(
return;
}
let following_node = following_node.map(GeckoNode);
- let (prev_sibling, next_sibling) =
- get_siblings_of_element(element, &following_node);
+ let (prev_sibling, next_sibling) = get_siblings_of_element(element, &following_node);
let data = raw_data.borrow();
let quirks_mode: QuirksMode = data.stylist.quirks_mode();
- let inherited = inherit_relative_selector_search_direction(element.parent_element(), prev_sibling);
+ let inherited =
+ inherit_relative_selector_search_direction(element.parent_element(), prev_sibling);
if inherited.is_empty() {
return;
}
@@ -7952,6 +7969,74 @@ pub unsafe extern "C" fn Servo_ComputeColor(
}
#[no_mangle]
+pub unsafe extern "C" fn Servo_ColorTo(
+ from_color: &nsACString,
+ to_color_space: &nsACString,
+ result_color: &mut nsACString,
+ result_components: &mut nsTArray<f32>,
+ result_adjusted: &mut bool,
+ loader: *mut Loader,
+) -> bool {
+ // Figure out the color space.
+ let mut input = ParserInput::new(to_color_space.as_str_unchecked());
+ let mut input = Parser::new(&mut input);
+ let to_color_space = match ColorSpace::parse(&mut input) {
+ Ok(color_space) => color_space,
+ Err(_) => {
+ // Can't parse the color space? Fail the conversion.
+ return false;
+ },
+ };
+
+ let mut input = ParserInput::new(from_color.as_str_unchecked());
+ let mut input = Parser::new(&mut input);
+
+ let reporter = loader.as_mut().and_then(|loader| {
+ // Make an ErrorReporter that will report errors as being "from DOM".
+ ErrorReporter::new(ptr::null_mut(), loader, ptr::null_mut())
+ });
+
+ let context = ParserContext::new(
+ Origin::Author,
+ dummy_url_data(),
+ Some(CssRuleType::Style),
+ ParsingMode::DEFAULT,
+ QuirksMode::NoQuirks,
+ /* namespaces = */ Default::default(),
+ reporter.as_ref().map(|e| e as &dyn ParseErrorReporter),
+ None,
+ );
+
+ let specified = match specified::Color::parse(&context, &mut input) {
+ Ok(color) => color,
+ Err(_) => return false,
+ };
+
+ let color = match specified {
+ specified::Color::Absolute(ref absolute) => &absolute.color,
+ _ => {
+ // Can't do anything with a non-absolute color from here, so we
+ // fail the conversion.
+ return false;
+ },
+ };
+
+ let color = color.to_color_space(to_color_space);
+ let mut s = String::new();
+ color
+ .write_author_preferred_value(&mut CssWriter::new(&mut s))
+ .unwrap();
+ result_color.assign(&s);
+
+ result_components.assign_from_iter_pod(color.raw_components().iter().copied());
+
+ // For now we don't do gamut mapping, so always false.
+ *result_adjusted = false;
+
+ true
+}
+
+#[no_mangle]
pub extern "C" fn Servo_ResolveColor(
color: &computed::Color,
foreground: &style::color::AbsoluteColor,
@@ -8919,3 +9004,58 @@ pub extern "C" fn Servo_GetSelectorWarnings(
}
});
}
+
+#[no_mangle]
+pub extern "C" fn Servo_GetRuleBodyTextOffsets(
+ initial_text: &nsACString,
+ result_start_offset: &mut u32,
+ result_end_offset: &mut u32,
+) -> bool {
+ let css_text = unsafe { initial_text.as_str_unchecked() };
+ let mut input = ParserInput::new(&css_text);
+ let mut input = Parser::new(&mut input);
+
+ let mut start_offset = 0;
+ let mut found_start = false;
+
+ // Search forward for the opening brace.
+ while let Ok(token) = input.next() {
+ match *token {
+ Token::CurlyBracketBlock => {
+ start_offset = input.position().byte_index();
+ found_start = true;
+ break;
+ },
+ _ => {}
+ }
+
+ if token.is_parse_error() {
+ return false;
+ }
+ }
+
+
+ if !found_start {
+ return false;
+ }
+
+ let token_start = input.position();
+ // Parse the nested block to move the parser to the end of the block
+ let _ = input.parse_nested_block(
+ |_i| -> Result<(), CssParseError<'_, BasicParseError>> {
+ Ok(())
+ }
+ );
+ let mut end_offset = input.position().byte_index();
+ // We're not guaranteed to have a closing bracket, but when we do, we need to move
+ // the end offset before it.
+ let token_slice = input.slice_from(token_start);
+ if token_slice.ends_with("}") {
+ end_offset = end_offset - 1;
+ }
+
+ *result_start_offset = start_offset as u32;
+ *result_end_offset = end_offset as u32;
+
+ return true;
+}
diff --git a/servo/ports/geckolib/lib.rs b/servo/ports/geckolib/lib.rs
index c11f6ab36e..8f6f344d4d 100644
--- a/servo/ports/geckolib/lib.rs
+++ b/servo/ports/geckolib/lib.rs
@@ -23,6 +23,7 @@ extern crate style;
extern crate style_traits;
extern crate thin_vec;
extern crate to_shmem;
+extern crate lazy_static;
mod error_reporter;
#[allow(non_snake_case)]