summaryrefslogtreecommitdiffstats
path: root/servo/components/style/properties_and_values
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/properties_and_values')
-rw-r--r--servo/components/style/properties_and_values/mod.rs12
-rw-r--r--servo/components/style/properties_and_values/registry.rs104
-rw-r--r--servo/components/style/properties_and_values/rule.rs348
-rw-r--r--servo/components/style/properties_and_values/syntax/ascii.rs60
-rw-r--r--servo/components/style/properties_and_values/syntax/data_type.rs134
-rw-r--r--servo/components/style/properties_and_values/syntax/mod.rs392
-rw-r--r--servo/components/style/properties_and_values/value.rs626
7 files changed, 1676 insertions, 0 deletions
diff --git a/servo/components/style/properties_and_values/mod.rs b/servo/components/style/properties_and_values/mod.rs
new file mode 100644
index 0000000000..5b5e219d59
--- /dev/null
+++ b/servo/components/style/properties_and_values/mod.rs
@@ -0,0 +1,12 @@
+/* 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/. */
+
+//! Properties and Values
+//!
+//! https://drafts.css-houdini.org/css-properties-values-api-1/
+
+pub mod registry;
+pub mod rule;
+pub mod syntax;
+pub mod value;
diff --git a/servo/components/style/properties_and_values/registry.rs b/servo/components/style/properties_and_values/registry.rs
new file mode 100644
index 0000000000..e3cd552c9c
--- /dev/null
+++ b/servo/components/style/properties_and_values/registry.rs
@@ -0,0 +1,104 @@
+/* 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/. */
+
+//! Registered custom properties.
+
+use super::rule::{Inherits, InitialValue, PropertyRuleName};
+use super::syntax::Descriptor;
+use crate::selector_map::PrecomputedHashMap;
+use crate::stylesheets::UrlExtraData;
+use crate::Atom;
+use cssparser::SourceLocation;
+
+/// The metadata of a custom property registration that we need to do the cascade properly.
+#[derive(Debug, Clone, MallocSizeOf)]
+pub struct PropertyRegistrationData {
+ /// The syntax of the property.
+ pub syntax: Descriptor,
+ /// Whether the property inherits.
+ pub inherits: Inherits,
+ /// The initial value. Only missing for universal syntax.
+ #[ignore_malloc_size_of = "Arc"]
+ pub initial_value: Option<InitialValue>,
+}
+
+static UNREGISTERED: PropertyRegistrationData = PropertyRegistrationData {
+ syntax: Descriptor::universal(),
+ inherits: Inherits::True,
+ initial_value: None,
+};
+
+impl PropertyRegistrationData {
+ /// The data for an unregistered property.
+ pub fn unregistered() -> &'static Self {
+ &UNREGISTERED
+ }
+
+ /// Returns whether this property inherits.
+ #[inline]
+ pub fn inherits(&self) -> bool {
+ self.inherits == Inherits::True
+ }
+}
+
+/// A computed, already-validated property registration.
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#custom-property-registration>
+#[derive(Debug, Clone, MallocSizeOf)]
+pub struct PropertyRegistration {
+ /// The custom property name.
+ pub name: PropertyRuleName,
+ /// The actual information about the property.
+ pub data: PropertyRegistrationData,
+ /// The url data that is used to parse and compute the registration's initial value. Note that
+ /// it's not the url data that should be used to parse other values. Other values should use
+ /// the data of the style sheet where they came from.
+ pub url_data: UrlExtraData,
+ /// The source location of this registration, if it comes from a CSS rule.
+ pub source_location: SourceLocation,
+}
+
+impl PropertyRegistration {
+ /// Returns whether this property inherits.
+ #[inline]
+ pub fn inherits(&self) -> bool {
+ self.data.inherits == Inherits::True
+ }
+}
+
+/// The script registry of custom properties.
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#dom-window-registeredpropertyset-slot>
+#[derive(Default)]
+pub struct ScriptRegistry {
+ properties: PrecomputedHashMap<Atom, PropertyRegistration>,
+}
+
+impl ScriptRegistry {
+ /// Gets an already-registered custom property via script.
+ #[inline]
+ pub fn get(&self, name: &Atom) -> Option<&PropertyRegistration> {
+ self.properties.get(name)
+ }
+
+ /// Gets already-registered custom properties via script.
+ #[inline]
+ pub fn properties(&self) -> &PrecomputedHashMap<Atom, PropertyRegistration> {
+ &self.properties
+ }
+
+ /// Register a given property. As per
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-registerproperty-function>
+ /// we don't allow overriding the registration.
+ #[inline]
+ pub fn register(&mut self, registration: PropertyRegistration) {
+ let name = registration.name.0.clone();
+ let old = self.properties.insert(name, registration);
+ debug_assert!(old.is_none(), "Already registered? Should be an error");
+ }
+
+ /// Returns the properties hashmap.
+ #[inline]
+ pub fn get_all(&self) -> &PrecomputedHashMap<Atom, PropertyRegistration> {
+ &self.properties
+ }
+}
diff --git a/servo/components/style/properties_and_values/rule.rs b/servo/components/style/properties_and_values/rule.rs
new file mode 100644
index 0000000000..96617eccce
--- /dev/null
+++ b/servo/components/style/properties_and_values/rule.rs
@@ -0,0 +1,348 @@
+/* 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/. */
+
+//! The [`@property`] at-rule.
+//!
+//! https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule
+
+use super::{
+ registry::{PropertyRegistration, PropertyRegistrationData},
+ syntax::Descriptor,
+ value::{AllowComputationallyDependent, SpecifiedValue as SpecifiedRegisteredValue},
+};
+use crate::custom_properties::{Name as CustomPropertyName, SpecifiedValue};
+use crate::error_reporting::ContextualParseError;
+use crate::parser::{Parse, ParserContext};
+use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::values::{computed, serialize_atom_name};
+use cssparser::{
+ AtRuleParser, BasicParseErrorKind, CowRcStr, DeclarationParser, ParseErrorKind, Parser,
+ ParserInput, QualifiedRuleParser, RuleBodyItemParser, RuleBodyParser, SourceLocation,
+};
+use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
+use selectors::parser::SelectorParseErrorKind;
+use servo_arc::Arc;
+use std::fmt::{self, Write};
+use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
+use to_shmem::{SharedMemoryBuilder, ToShmem};
+
+/// Parse the block inside a `@property` rule.
+///
+/// Valid `@property` rules result in a registered custom property, as if `registerProperty()` had
+/// been called with equivalent parameters.
+pub fn parse_property_block<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ name: PropertyRuleName,
+ source_location: SourceLocation,
+) -> Result<PropertyRegistration, ParseError<'i>> {
+ let mut descriptors = PropertyDescriptors::default();
+ let mut parser = PropertyRuleParser {
+ context,
+ descriptors: &mut descriptors,
+ };
+ let mut iter = RuleBodyParser::new(input, &mut parser);
+ while let Some(declaration) = iter.next() {
+ if !context.error_reporting_enabled() {
+ continue;
+ }
+ if let Err((error, slice)) = declaration {
+ let location = error.location;
+ let error = if matches!(
+ error.kind,
+ ParseErrorKind::Custom(StyleParseErrorKind::PropertySyntaxField(_))
+ ) {
+ // If the provided string is not a valid syntax string (if it
+ // returns failure when consume a syntax definition is called on
+ // it), the descriptor is invalid and must be ignored.
+ ContextualParseError::UnsupportedValue(slice, error)
+ } else {
+ // Unknown descriptors are invalid and ignored, but do not
+ // invalidate the @property rule.
+ ContextualParseError::UnsupportedPropertyDescriptor(slice, error)
+ };
+ context.log_css_error(location, error);
+ }
+ }
+
+ // https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor:
+ //
+ // The syntax descriptor is required for the @property rule to be valid; if it’s
+ // missing, the @property rule is invalid.
+ let Some(syntax) = descriptors.syntax else {
+ return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
+ };
+
+ // https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor:
+ //
+ // The inherits descriptor is required for the @property rule to be valid; if it’s
+ // missing, the @property rule is invalid.
+ let Some(inherits) = descriptors.inherits else {
+ return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
+ };
+
+ if PropertyRegistration::validate_initial_value(&syntax, descriptors.initial_value.as_deref())
+ .is_err()
+ {
+ return Err(input.new_error(BasicParseErrorKind::AtRuleBodyInvalid));
+ }
+
+ Ok(PropertyRegistration {
+ name,
+ data: PropertyRegistrationData {
+ syntax,
+ inherits,
+ initial_value: descriptors.initial_value,
+ },
+ url_data: context.url_data.clone(),
+ source_location,
+ })
+}
+
+struct PropertyRuleParser<'a, 'b: 'a> {
+ context: &'a ParserContext<'b>,
+ descriptors: &'a mut PropertyDescriptors,
+}
+
+/// Default methods reject all at rules.
+impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyRuleParser<'a, 'b> {
+ type Prelude = ();
+ type AtRule = ();
+ type Error = StyleParseErrorKind<'i>;
+}
+
+impl<'a, 'b, 'i> QualifiedRuleParser<'i> for PropertyRuleParser<'a, 'b> {
+ type Prelude = ();
+ type QualifiedRule = ();
+ type Error = StyleParseErrorKind<'i>;
+}
+
+impl<'a, 'b, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
+ for PropertyRuleParser<'a, 'b>
+{
+ fn parse_qualified(&self) -> bool {
+ false
+ }
+ fn parse_declarations(&self) -> bool {
+ true
+ }
+}
+
+macro_rules! property_descriptors {
+ (
+ $( #[$doc: meta] $name: tt $ident: ident: $ty: ty, )*
+ ) => {
+ /// Data inside a `@property` rule.
+ ///
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule>
+ #[derive(Clone, Debug, Default, PartialEq)]
+ struct PropertyDescriptors {
+ $(
+ #[$doc]
+ $ident: Option<$ty>,
+ )*
+ }
+
+ impl PropertyRegistration {
+ fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
+ $(
+ let $ident = Option::<&$ty>::from(&self.data.$ident);
+ if let Some(ref value) = $ident {
+ dest.write_str(concat!($name, ": "))?;
+ value.to_css(&mut CssWriter::new(dest))?;
+ dest.write_str("; ")?;
+ }
+ )*
+ Ok(())
+ }
+ }
+
+ impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyRuleParser<'a, 'b> {
+ type Declaration = ();
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_value<'t>(
+ &mut self,
+ name: CowRcStr<'i>,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<(), ParseError<'i>> {
+ match_ignore_ascii_case! { &*name,
+ $(
+ $name => {
+ // DeclarationParser also calls parse_entirely so we’d normally not need
+ // to, but in this case we do because we set the value as a side effect
+ // rather than returning it.
+ let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
+ self.descriptors.$ident = Some(value)
+ },
+ )*
+ _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
+ }
+ Ok(())
+ }
+ }
+ }
+}
+
+property_descriptors! {
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor>
+ "syntax" syntax: Descriptor,
+
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor>
+ "inherits" inherits: Inherits,
+
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor>
+ "initial-value" initial_value: InitialValue,
+}
+
+/// Errors that can happen when registering a property.
+#[allow(missing_docs)]
+pub enum PropertyRegistrationError {
+ NoInitialValue,
+ InvalidInitialValue,
+ InitialValueNotComputationallyIndependent,
+}
+
+impl PropertyRegistration {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, _: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ MallocSizeOf::size_of(self, ops)
+ }
+
+ /// Computes the value of the computationally independent initial value.
+ pub fn compute_initial_value(
+ &self,
+ computed_context: &computed::Context,
+ ) -> Result<InitialValue, ()> {
+ let Some(ref initial) = self.data.initial_value else {
+ return Err(());
+ };
+
+ if self.data.syntax.is_universal() {
+ return Ok(Arc::clone(initial));
+ }
+
+ let mut input = ParserInput::new(initial.css_text());
+ let mut input = Parser::new(&mut input);
+ input.skip_whitespace();
+
+ match SpecifiedRegisteredValue::compute(
+ &mut input,
+ &self.data,
+ &self.url_data,
+ computed_context,
+ AllowComputationallyDependent::No,
+ ) {
+ Ok(computed) => Ok(Arc::new(computed)),
+ Err(_) => Err(()),
+ }
+ }
+
+ /// Performs syntax validation as per the initial value descriptor.
+ /// https://drafts.css-houdini.org/css-properties-values-api-1/#initial-value-descriptor
+ pub fn validate_initial_value(
+ syntax: &Descriptor,
+ initial_value: Option<&SpecifiedValue>,
+ ) -> Result<(), PropertyRegistrationError> {
+ use crate::properties::CSSWideKeyword;
+ // If the value of the syntax descriptor is the universal syntax definition, then the
+ // initial-value descriptor is optional. If omitted, the initial value of the property is
+ // the guaranteed-invalid value.
+ if syntax.is_universal() && initial_value.is_none() {
+ return Ok(());
+ }
+
+ // Otherwise, if the value of the syntax descriptor is not the universal syntax definition,
+ // the following conditions must be met for the @property rule to be valid:
+
+ // The initial-value descriptor must be present.
+ let Some(initial) = initial_value else {
+ return Err(PropertyRegistrationError::NoInitialValue);
+ };
+
+ // A value that references the environment or other variables is not computationally
+ // independent.
+ if initial.has_references() {
+ return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent);
+ }
+
+ let mut input = ParserInput::new(initial.css_text());
+ let mut input = Parser::new(&mut input);
+ input.skip_whitespace();
+
+ // The initial-value cannot include CSS-wide keywords.
+ if input.try_parse(CSSWideKeyword::parse).is_ok() {
+ return Err(PropertyRegistrationError::InitialValueNotComputationallyIndependent);
+ }
+
+ match SpecifiedRegisteredValue::parse(
+ &mut input,
+ syntax,
+ &initial.url_data,
+ AllowComputationallyDependent::No,
+ ) {
+ Ok(_) => {},
+ Err(_) => return Err(PropertyRegistrationError::InvalidInitialValue),
+ }
+
+ Ok(())
+ }
+}
+
+impl ToCssWithGuard for PropertyRegistration {
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#serialize-a-csspropertyrule>
+ fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
+ dest.write_str("@property ")?;
+ self.name.to_css(&mut CssWriter::new(dest))?;
+ dest.write_str(" { ")?;
+ self.decl_to_css(dest)?;
+ dest.write_char('}')
+ }
+}
+
+impl ToShmem for PropertyRegistration {
+ fn to_shmem(&self, _builder: &mut SharedMemoryBuilder) -> to_shmem::Result<Self> {
+ Err(String::from(
+ "ToShmem failed for PropertyRule: cannot handle @property rules",
+ ))
+ }
+}
+
+/// A custom property name wrapper that includes the `--` prefix in its serialization
+#[derive(Clone, Debug, PartialEq, MallocSizeOf)]
+pub struct PropertyRuleName(pub CustomPropertyName);
+
+impl ToCss for PropertyRuleName {
+ fn to_css<W: Write>(&self, dest: &mut CssWriter<W>) -> fmt::Result {
+ dest.write_str("--")?;
+ serialize_atom_name(&self.0, dest)
+ }
+}
+
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#inherits-descriptor>
+#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, ToCss)]
+pub enum Inherits {
+ /// `true` value for the `inherits` descriptor
+ True,
+ /// `false` value for the `inherits` descriptor
+ False,
+}
+
+/// Specifies the initial value of the custom property registration represented by the @property
+/// rule, controlling the property’s initial value.
+///
+/// The SpecifiedValue is wrapped in an Arc to avoid copying when using it.
+pub type InitialValue = Arc<SpecifiedValue>;
+
+impl Parse for InitialValue {
+ fn parse<'i, 't>(
+ context: &ParserContext,
+ input: &mut Parser<'i, 't>,
+ ) -> Result<Self, ParseError<'i>> {
+ input.skip_whitespace();
+ Ok(Arc::new(SpecifiedValue::parse(input, &context.url_data)?))
+ }
+}
diff --git a/servo/components/style/properties_and_values/syntax/ascii.rs b/servo/components/style/properties_and_values/syntax/ascii.rs
new file mode 100644
index 0000000000..e1a1b08535
--- /dev/null
+++ b/servo/components/style/properties_and_values/syntax/ascii.rs
@@ -0,0 +1,60 @@
+/* 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/. */
+
+/// Trims ASCII whitespace characters from a slice, and returns the trimmed input.
+pub fn trim_ascii_whitespace(input: &str) -> &str {
+ if input.is_empty() {
+ return input;
+ }
+
+ let mut start = 0;
+ {
+ let mut iter = input.as_bytes().iter();
+ loop {
+ let byte = match iter.next() {
+ Some(b) => b,
+ None => return "",
+ };
+
+ if !byte.is_ascii_whitespace() {
+ break;
+ }
+ start += 1;
+ }
+ }
+
+ let mut end = input.len();
+ assert!(start < end);
+ {
+ let mut iter = input.as_bytes()[start..].iter().rev();
+ loop {
+ let byte = match iter.next() {
+ Some(b) => b,
+ None => {
+ debug_assert!(false, "We should have caught this in the loop above!");
+ return "";
+ },
+ };
+
+ if !byte.is_ascii_whitespace() {
+ break;
+ }
+ end -= 1;
+ }
+ }
+
+ &input[start..end]
+}
+
+#[test]
+fn trim_ascii_whitespace_test() {
+ fn test(i: &str, o: &str) {
+ assert_eq!(trim_ascii_whitespace(i), o)
+ }
+
+ test("", "");
+ test(" ", "");
+ test(" a b c ", "a b c");
+ test(" \t \t \ta b c \t \t \t \t", "a b c");
+}
diff --git a/servo/components/style/properties_and_values/syntax/data_type.rs b/servo/components/style/properties_and_values/syntax/data_type.rs
new file mode 100644
index 0000000000..be331e2222
--- /dev/null
+++ b/servo/components/style/properties_and_values/syntax/data_type.rs
@@ -0,0 +1,134 @@
+/* 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/. */
+
+//! Used for parsing and serializing component names from the syntax string.
+
+use super::{Component, ComponentName, Multiplier};
+use std::fmt::{self, Debug, Write};
+use style_traits::{CssWriter, ToCss};
+
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#supported-names>
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
+pub enum DataType {
+ /// Any valid `<length>` value
+ Length,
+ /// `<number>` values
+ Number,
+ /// Any valid <percentage> value
+ Percentage,
+ /// Any valid `<length>` or `<percentage>` value, any valid `<calc()>` expression combining
+ /// `<length>` and `<percentage>` components.
+ LengthPercentage,
+ /// Any valid `<color>` value
+ Color,
+ /// Any valid `<image>` value
+ Image,
+ /// Any valid `<url>` value
+ Url,
+ /// Any valid `<integer>` value
+ Integer,
+ /// Any valid `<angle>` value
+ Angle,
+ /// Any valid `<time>` value
+ Time,
+ /// Any valid `<resolution>` value
+ Resolution,
+ /// Any valid `<transform-function>` value
+ TransformFunction,
+ /// Any valid `<custom-ident>` value
+ CustomIdent,
+ /// A list of valid `<transform-function>` values. Note that "<transform-list>" is a pre-multiplied
+ /// data type name equivalent to "<transform-function>+"
+ TransformList,
+ /// Any valid `<string>` value
+ ///
+ /// <https://github.com/w3c/css-houdini-drafts/issues/1103>
+ String,
+}
+
+impl DataType {
+ /// Converts a component name from a pre-multiplied data type to its un-pre-multiplied equivalent.
+ ///
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#pre-multiplied-data-type-name>
+ pub fn unpremultiply(&self) -> Option<Component> {
+ match *self {
+ DataType::TransformList => Some(Component {
+ name: ComponentName::DataType(DataType::TransformFunction),
+ multiplier: Some(Multiplier::Space),
+ }),
+ _ => None,
+ }
+ }
+
+ /// Parses a syntax component name.
+ pub fn from_str(ty: &str) -> Option<Self> {
+ Some(match ty.as_bytes() {
+ b"length" => DataType::Length,
+ b"number" => DataType::Number,
+ b"percentage" => DataType::Percentage,
+ b"length-percentage" => DataType::LengthPercentage,
+ b"color" => DataType::Color,
+ b"image" => DataType::Image,
+ b"url" => DataType::Url,
+ b"integer" => DataType::Integer,
+ b"angle" => DataType::Angle,
+ b"time" => DataType::Time,
+ b"resolution" => DataType::Resolution,
+ b"transform-function" => DataType::TransformFunction,
+ b"custom-ident" => DataType::CustomIdent,
+ b"transform-list" => DataType::TransformList,
+ b"string" => DataType::String,
+ _ => return None,
+ })
+ }
+
+ /// Returns true if this data type requires deferring computation to properly
+ /// resolve font-dependent lengths.
+ pub fn may_reference_font_relative_length(&self) -> bool {
+ match self {
+ DataType::Length |
+ DataType::LengthPercentage |
+ DataType::TransformFunction |
+ DataType::TransformList => true,
+ DataType::Number |
+ DataType::Percentage |
+ DataType::Color |
+ DataType::Image |
+ DataType::Url |
+ DataType::Integer |
+ DataType::Angle |
+ DataType::Time |
+ DataType::Resolution |
+ DataType::CustomIdent |
+ DataType::String => false,
+ }
+ }
+}
+
+impl ToCss for DataType {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ dest.write_char('<')?;
+ dest.write_str(match *self {
+ DataType::Length => "length",
+ DataType::Number => "number",
+ DataType::Percentage => "percentage",
+ DataType::LengthPercentage => "length-percentage",
+ DataType::Color => "color",
+ DataType::Image => "image",
+ DataType::Url => "url",
+ DataType::Integer => "integer",
+ DataType::Angle => "angle",
+ DataType::Time => "time",
+ DataType::Resolution => "resolution",
+ DataType::TransformFunction => "transform-function",
+ DataType::CustomIdent => "custom-ident",
+ DataType::TransformList => "transform-list",
+ DataType::String => "string",
+ })?;
+ dest.write_char('>')
+ }
+}
diff --git a/servo/components/style/properties_and_values/syntax/mod.rs b/servo/components/style/properties_and_values/syntax/mod.rs
new file mode 100644
index 0000000000..404c8caa7b
--- /dev/null
+++ b/servo/components/style/properties_and_values/syntax/mod.rs
@@ -0,0 +1,392 @@
+/* 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/. */
+
+//! Used for parsing and serializing the [`@property`] syntax string.
+//!
+//! <https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-syntax>
+
+use std::fmt::{self, Debug};
+use std::{borrow::Cow, fmt::Write};
+
+use crate::parser::{Parse, ParserContext};
+use crate::values::CustomIdent;
+use cssparser::{Parser as CSSParser, ParserInput as CSSParserInput};
+use style_traits::{
+ CssWriter, ParseError as StyleParseError, PropertySyntaxParseError as ParseError,
+ StyleParseErrorKind, ToCss,
+};
+
+use self::data_type::DataType;
+
+mod ascii;
+pub mod data_type;
+
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-syntax>
+#[derive(Debug, Clone, Default, MallocSizeOf, PartialEq)]
+pub struct Descriptor {
+ /// The parsed components, if any.
+ /// TODO: Could be a Box<[]> if that supported const construction.
+ pub components: Vec<Component>,
+ /// The specified css syntax, if any.
+ specified: Option<Box<str>>,
+}
+
+impl Descriptor {
+ /// Returns the universal descriptor.
+ pub const fn universal() -> Self {
+ Self {
+ components: Vec::new(),
+ specified: None,
+ }
+ }
+
+ /// Returns whether this is the universal syntax descriptor.
+ #[inline]
+ pub fn is_universal(&self) -> bool {
+ self.components.is_empty()
+ }
+
+ /// Returns the specified string, if any.
+ #[inline]
+ pub fn specified_string(&self) -> Option<&str> {
+ self.specified.as_deref()
+ }
+
+ /// Parse a syntax descriptor.
+ /// https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-definition
+ pub fn from_str(css: &str, save_specified: bool) -> Result<Self, ParseError> {
+ // 1. Strip leading and trailing ASCII whitespace from string.
+ let input = ascii::trim_ascii_whitespace(css);
+
+ // 2. If string's length is 0, return failure.
+ if input.is_empty() {
+ return Err(ParseError::EmptyInput);
+ }
+
+ let specified = if save_specified {
+ Some(Box::from(css))
+ } else {
+ None
+ };
+
+ // 3. If string's length is 1, and the only code point in string is U+002A
+ // ASTERISK (*), return the universal syntax descriptor.
+ if input.len() == 1 && input.as_bytes()[0] == b'*' {
+ return Ok(Self {
+ components: Default::default(),
+ specified,
+ });
+ }
+
+ // 4. Let stream be an input stream created from the code points of string,
+ // preprocessed as specified in [css-syntax-3]. Let descriptor be an
+ // initially empty list of syntax components.
+ //
+ // NOTE(emilio): Instead of preprocessing we cheat and treat new-lines and
+ // nulls in the parser specially.
+ let mut components = vec![];
+ {
+ let mut parser = Parser::new(input, &mut components);
+ // 5. Repeatedly consume the next input code point from stream.
+ parser.parse()?;
+ }
+ Ok(Self { components, specified })
+ }
+
+ /// Returns true if the syntax permits the value to be computed as a length.
+ pub fn may_compute_length(&self) -> bool {
+ for component in self.components.iter() {
+ match &component.name {
+ ComponentName::DataType(ref t) => {
+ if matches!(t, DataType::Length | DataType::LengthPercentage) {
+ return true;
+ }
+ },
+ ComponentName::Ident(_) => (),
+ };
+ }
+ false
+ }
+
+ /// Returns true if the syntax requires deferring computation to properly
+ /// resolve font-dependent lengths.
+ pub fn may_reference_font_relative_length(&self) -> bool {
+ for component in self.components.iter() {
+ match &component.name {
+ ComponentName::DataType(ref t) => {
+ if t.may_reference_font_relative_length() {
+ return true;
+ }
+ },
+ ComponentName::Ident(_) => (),
+ };
+ }
+ false
+ }
+}
+
+impl ToCss for Descriptor {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ if let Some(ref specified) = self.specified {
+ return specified.to_css(dest);
+ }
+
+ if self.is_universal() {
+ return dest.write_char('*');
+ }
+
+ let mut first = true;
+ for component in &*self.components {
+ if !first {
+ dest.write_str(" | ")?;
+ }
+ component.to_css(dest)?;
+ first = false;
+ }
+
+ Ok(())
+ }
+}
+
+impl Parse for Descriptor {
+ /// Parse a syntax descriptor.
+ fn parse<'i>(
+ _: &ParserContext,
+ parser: &mut CSSParser<'i, '_>,
+ ) -> Result<Self, StyleParseError<'i>> {
+ let input = parser.expect_string()?;
+ Descriptor::from_str(input.as_ref(), /* save_specified = */ true)
+ .map_err(|err| parser.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err)))
+ }
+}
+
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#multipliers>
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue)]
+pub enum Multiplier {
+ /// Indicates a space-separated list.
+ Space,
+ /// Indicates a comma-separated list.
+ Comma,
+}
+
+impl ToCss for Multiplier {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ dest.write_char(match *self {
+ Multiplier::Space => '+',
+ Multiplier::Comma => '#',
+ })
+ }
+}
+
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component>
+#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
+pub struct Component {
+ name: ComponentName,
+ multiplier: Option<Multiplier>,
+}
+
+impl Component {
+ /// Returns the component's name.
+ #[inline]
+ pub fn name(&self) -> &ComponentName {
+ &self.name
+ }
+
+ /// Returns the component's multiplier, if one exists.
+ #[inline]
+ pub fn multiplier(&self) -> Option<Multiplier> {
+ self.multiplier
+ }
+
+ /// If the component is premultiplied, return the un-premultiplied component.
+ #[inline]
+ pub fn unpremultiplied(&self) -> Cow<Self> {
+ match self.name.unpremultiply() {
+ Some(component) => {
+ debug_assert!(
+ self.multiplier.is_none(),
+ "Shouldn't have parsed a multiplier for a pre-multiplied data type name",
+ );
+ Cow::Owned(component)
+ },
+ None => Cow::Borrowed(self),
+ }
+ }
+}
+
+impl ToCss for Component {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.name().to_css(dest)?;
+ self.multiplier().to_css(dest)
+ }
+}
+
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component-name>
+#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)]
+pub enum ComponentName {
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#data-type-name>
+ DataType(DataType),
+ /// <https://drafts.csswg.org/css-values-4/#custom-idents>
+ Ident(CustomIdent),
+}
+
+impl ComponentName {
+ fn unpremultiply(&self) -> Option<Component> {
+ match *self {
+ ComponentName::DataType(ref t) => t.unpremultiply(),
+ ComponentName::Ident(..) => None,
+ }
+ }
+
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#pre-multiplied-data-type-name>
+ fn is_pre_multiplied(&self) -> bool {
+ self.unpremultiply().is_some()
+ }
+}
+
+struct Parser<'a> {
+ input: &'a str,
+ position: usize,
+ output: &'a mut Vec<Component>,
+}
+
+/// <https://drafts.csswg.org/css-syntax-3/#letter>
+fn is_letter(byte: u8) -> bool {
+ match byte {
+ b'A'..=b'Z' | b'a'..=b'z' => true,
+ _ => false,
+ }
+}
+
+/// <https://drafts.csswg.org/css-syntax-3/#non-ascii-code-point>
+fn is_non_ascii(byte: u8) -> bool {
+ byte >= 0x80
+}
+
+/// <https://drafts.csswg.org/css-syntax-3/#name-start-code-point>
+fn is_name_start(byte: u8) -> bool {
+ is_letter(byte) || is_non_ascii(byte) || byte == b'_'
+}
+
+impl<'a> Parser<'a> {
+ fn new(input: &'a str, output: &'a mut Vec<Component>) -> Self {
+ Self {
+ input,
+ position: 0,
+ output,
+ }
+ }
+
+ fn peek(&self) -> Option<u8> {
+ self.input.as_bytes().get(self.position).cloned()
+ }
+
+ fn parse(&mut self) -> Result<(), ParseError> {
+ // 5. Repeatedly consume the next input code point from stream:
+ loop {
+ let component = self.parse_component()?;
+ self.output.push(component);
+ self.skip_whitespace();
+
+ let byte = match self.peek() {
+ None => return Ok(()),
+ Some(b) => b,
+ };
+
+ if byte != b'|' {
+ return Err(ParseError::ExpectedPipeBetweenComponents);
+ }
+
+ self.position += 1;
+ }
+ }
+
+ fn skip_whitespace(&mut self) {
+ loop {
+ match self.peek() {
+ Some(c) if c.is_ascii_whitespace() => self.position += 1,
+ _ => return,
+ }
+ }
+ }
+
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-data-type-name>
+ fn parse_data_type_name(&mut self) -> Result<DataType, ParseError> {
+ let start = self.position;
+ loop {
+ let byte = match self.peek() {
+ Some(b) => b,
+ None => return Err(ParseError::UnclosedDataTypeName),
+ };
+ if byte != b'>' {
+ self.position += 1;
+ continue;
+ }
+ let ty = match DataType::from_str(&self.input[start..self.position]) {
+ Some(ty) => ty,
+ None => return Err(ParseError::UnknownDataTypeName),
+ };
+ self.position += 1;
+ return Ok(ty);
+ }
+ }
+
+ fn parse_name(&mut self) -> Result<ComponentName, ParseError> {
+ let b = match self.peek() {
+ Some(b) => b,
+ None => return Err(ParseError::UnexpectedEOF),
+ };
+
+ if b == b'<' {
+ self.position += 1;
+ return Ok(ComponentName::DataType(self.parse_data_type_name()?));
+ }
+
+ if b != b'\\' && !is_name_start(b) {
+ return Err(ParseError::InvalidNameStart);
+ }
+
+ let input = &self.input[self.position..];
+ let mut input = CSSParserInput::new(input);
+ let mut input = CSSParser::new(&mut input);
+ let name = match CustomIdent::parse(&mut input, &[]) {
+ Ok(name) => name,
+ Err(_) => return Err(ParseError::InvalidName),
+ };
+ self.position += input.position().byte_index();
+ return Ok(ComponentName::Ident(name));
+ }
+
+ fn parse_multiplier(&mut self) -> Option<Multiplier> {
+ let multiplier = match self.peek()? {
+ b'+' => Multiplier::Space,
+ b'#' => Multiplier::Comma,
+ _ => return None,
+ };
+ self.position += 1;
+ Some(multiplier)
+ }
+
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#consume-a-syntax-component>
+ fn parse_component(&mut self) -> Result<Component, ParseError> {
+ // Consume as much whitespace as possible from stream.
+ self.skip_whitespace();
+ let name = self.parse_name()?;
+ let multiplier = if name.is_pre_multiplied() {
+ None
+ } else {
+ self.parse_multiplier()
+ };
+ Ok(Component { name, multiplier })
+ }
+}
diff --git a/servo/components/style/properties_and_values/value.rs b/servo/components/style/properties_and_values/value.rs
new file mode 100644
index 0000000000..8e9d78b8cc
--- /dev/null
+++ b/servo/components/style/properties_and_values/value.rs
@@ -0,0 +1,626 @@
+/* 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/. */
+
+//! Parsing for registered custom properties.
+
+use std::fmt::{self, Write};
+
+use super::{
+ registry::PropertyRegistrationData,
+ syntax::{
+ data_type::DataType, Component as SyntaxComponent, ComponentName, Descriptor, Multiplier,
+ },
+};
+use crate::custom_properties::ComputedValue as ComputedPropertyValue;
+use crate::parser::{Parse, ParserContext};
+use crate::properties;
+use crate::stylesheets::{CssRuleType, Origin, UrlExtraData};
+use crate::values::{
+ animated::{self, Animate, Procedure},
+ computed::{self, ToComputedValue},
+ specified, CustomIdent,
+};
+use cssparser::{BasicParseErrorKind, ParseErrorKind, Parser as CSSParser, TokenSerializationType};
+use selectors::matching::QuirksMode;
+use servo_arc::Arc;
+use smallvec::SmallVec;
+use style_traits::{
+ owned_str::OwnedStr, CssWriter, ParseError as StyleParseError, ParsingMode,
+ PropertySyntaxParseError, StyleParseErrorKind, ToCss,
+};
+
+/// A single component of the computed value.
+pub type ComputedValueComponent = GenericValueComponent<
+ computed::Length,
+ computed::Number,
+ computed::Percentage,
+ computed::LengthPercentage,
+ computed::Color,
+ computed::Image,
+ computed::url::ComputedUrl,
+ computed::Integer,
+ computed::Angle,
+ computed::Time,
+ computed::Resolution,
+ computed::Transform,
+>;
+
+/// A single component of the specified value.
+pub type SpecifiedValueComponent = GenericValueComponent<
+ specified::Length,
+ specified::Number,
+ specified::Percentage,
+ specified::LengthPercentage,
+ specified::Color,
+ specified::Image,
+ specified::url::SpecifiedUrl,
+ specified::Integer,
+ specified::Angle,
+ specified::Time,
+ specified::Resolution,
+ specified::Transform,
+>;
+
+impl<L, N, P, LP, C, Image, U, Integer, A, T, R, Transform>
+ GenericValueComponent<L, N, P, LP, C, Image, U, Integer, A, T, R, Transform>
+{
+ fn serialization_types(&self) -> (TokenSerializationType, TokenSerializationType) {
+ let first_token_type = match self {
+ Self::Length(_) | Self::Angle(_) | Self::Time(_) | Self::Resolution(_) => {
+ TokenSerializationType::Dimension
+ },
+ Self::Number(_) | Self::Integer(_) => TokenSerializationType::Number,
+ Self::Percentage(_) | Self::LengthPercentage(_) => TokenSerializationType::Percentage,
+ Self::Color(_) |
+ Self::Image(_) |
+ Self::Url(_) |
+ Self::TransformFunction(_) |
+ Self::TransformList(_) => TokenSerializationType::Function,
+ Self::CustomIdent(_) => TokenSerializationType::Ident,
+ Self::String(_) => TokenSerializationType::Other,
+ };
+ let last_token_type = if first_token_type == TokenSerializationType::Function {
+ TokenSerializationType::Other
+ } else {
+ first_token_type
+ };
+ (first_token_type, last_token_type)
+ }
+}
+
+/// A generic enum used for both specified value components and computed value components.
+#[derive(Animate, Clone, ToCss, ToComputedValue, Debug, MallocSizeOf, PartialEq)]
+#[animation(no_bound(Image, Url))]
+pub enum GenericValueComponent<
+ Length,
+ Number,
+ Percentage,
+ LengthPercentage,
+ Color,
+ Image,
+ Url,
+ Integer,
+ Angle,
+ Time,
+ Resolution,
+ TransformFunction,
+> {
+ /// A <length> value
+ Length(Length),
+ /// A <number> value
+ Number(Number),
+ /// A <percentage> value
+ Percentage(Percentage),
+ /// A <length-percentage> value
+ LengthPercentage(LengthPercentage),
+ /// A <color> value
+ Color(Color),
+ /// An <image> value
+ #[animation(error)]
+ Image(Image),
+ /// A <url> value
+ #[animation(error)]
+ Url(Url),
+ /// An <integer> value
+ Integer(Integer),
+ /// An <angle> value
+ Angle(Angle),
+ /// A <time> value
+ Time(Time),
+ /// A <resolution> value
+ Resolution(Resolution),
+ /// A <transform-function> value
+ TransformFunction(TransformFunction),
+ /// A <custom-ident> value
+ #[animation(error)]
+ CustomIdent(CustomIdent),
+ /// A <transform-list> value, equivalent to <transform-function>+
+ TransformList(ComponentList<Self>),
+ /// A <string> value
+ #[animation(error)]
+ String(OwnedStr),
+}
+
+/// A list of component values, including the list's multiplier.
+#[derive(Clone, ToComputedValue, Debug, MallocSizeOf, PartialEq)]
+pub struct ComponentList<Component> {
+ /// Multiplier
+ pub multiplier: Multiplier,
+ /// The list of components contained.
+ pub components: crate::OwnedSlice<Component>,
+}
+
+impl<Component: Animate> Animate for ComponentList<Component> {
+ fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
+ if self.multiplier != other.multiplier {
+ return Err(());
+ }
+ let components = animated::lists::by_computed_value::animate(&self.components, &other.components, procedure)?;
+ Ok(Self {
+ multiplier: self.multiplier,
+ components,
+ })
+ }
+}
+
+impl<Component: ToCss> ToCss for ComponentList<Component> {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ let mut iter = self.components.iter();
+ let Some(first) = iter.next() else {
+ return Ok(());
+ };
+ first.to_css(dest)?;
+
+ // The separator implied by the multiplier for this list.
+ let separator = match self.multiplier {
+ // <https://drafts.csswg.org/cssom-1/#serialize-a-whitespace-separated-list>
+ Multiplier::Space => " ",
+ // <https://drafts.csswg.org/cssom-1/#serialize-a-comma-separated-list>
+ Multiplier::Comma => ", ",
+ };
+ for component in iter {
+ dest.write_str(separator)?;
+ component.to_css(dest)?;
+ }
+ Ok(())
+ }
+}
+
+/// A specified registered custom property value.
+#[derive(Animate, ToComputedValue, ToCss, Clone, Debug, MallocSizeOf, PartialEq)]
+pub enum Value<Component> {
+ /// A single specified component value whose syntax descriptor component did not have a
+ /// multiplier.
+ Component(Component),
+ /// A specified value whose syntax descriptor was the universal syntax definition.
+ #[animation(error)]
+ Universal(#[ignore_malloc_size_of = "Arc"] Arc<ComputedPropertyValue>),
+ /// A list of specified component values whose syntax descriptor component had a multiplier.
+ List(#[animation(field_bound)] ComponentList<Component>),
+}
+
+/// Specified custom property value.
+pub type SpecifiedValue = Value<SpecifiedValueComponent>;
+
+/// 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>(
+ input: &mut CSSParser<'i, 't>,
+ registration: &PropertyRegistrationData,
+ url_data: &UrlExtraData,
+ context: &computed::Context,
+ allow_computationally_dependent: AllowComputationallyDependent,
+ ) -> Result<ComputedValue, ()> {
+ debug_assert!(!registration.syntax.is_universal(), "Shouldn't be needed");
+ let Ok(value) = Self::parse(
+ input,
+ &registration.syntax,
+ url_data,
+ allow_computationally_dependent,
+ ) else {
+ return Err(());
+ };
+
+ Ok(value.to_computed_value(context))
+ }
+
+ /// Parse and validate a registered custom property value according to its syntax descriptor,
+ /// and check for computational independence.
+ pub fn parse<'i, 't>(
+ mut input: &mut CSSParser<'i, 't>,
+ syntax: &Descriptor,
+ url_data: &UrlExtraData,
+ 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 mut values = SmallComponentVec::new();
+ let mut multiplier = None;
+ {
+ 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 {
+ multiplier,
+ components: values.to_vec().into(),
+ })
+ } else {
+ Self::Component(values[0].clone())
+ };
+ Ok(computed_value)
+ }
+}
+
+impl ComputedValue {
+ fn serialization_types(&self) -> (TokenSerializationType, TokenSerializationType) {
+ match self {
+ Self::Component(component) => component.serialization_types(),
+ Self::Universal(_) => unreachable!(),
+ Self::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 {
+ return Arc::clone(var);
+ }
+ Arc::new(self.to_variable_value(url_data))
+ }
+
+ 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.
+ let serialization_types = self.serialization_types();
+ ComputedPropertyValue::new(
+ self.to_css_string(),
+ url_data,
+ serialization_types.0,
+ serialization_types.1,
+ )
+ }
+}
+
+/// Whether the computed value parsing should allow computationaly dependent values like 3em or
+/// var(-foo).
+///
+/// https://drafts.css-houdini.org/css-properties-values-api-1/#computationally-independent
+pub enum AllowComputationallyDependent {
+ /// Only computationally independent values are allowed.
+ No,
+ /// Computationally independent and dependent values are allowed.
+ Yes,
+}
+
+type SmallComponentVec = SmallVec<[SpecifiedValueComponent; 1]>;
+
+struct Parser<'a> {
+ syntax: &'a Descriptor,
+ output: &'a mut SmallComponentVec,
+ output_multiplier: &'a mut Option<Multiplier>,
+}
+
+impl<'a> Parser<'a> {
+ fn new(
+ syntax: &'a Descriptor,
+ output: &'a mut SmallComponentVec,
+ output_multiplier: &'a mut Option<Multiplier>,
+ ) -> Self {
+ Self {
+ syntax,
+ output,
+ output_multiplier,
+ }
+ }
+
+ fn parse<'i, 't>(
+ &mut self,
+ input: &mut CSSParser<'i, 't>,
+ url_data: &UrlExtraData,
+ allow_computationally_dependent: AllowComputationallyDependent,
+ ) -> Result<(), StyleParseError<'i>> {
+ use self::AllowComputationallyDependent::*;
+ let parsing_mode = match allow_computationally_dependent {
+ No => ParsingMode::DISALLOW_FONT_RELATIVE,
+ Yes => ParsingMode::DEFAULT,
+ };
+ let ref context = ParserContext::new(
+ Origin::Author,
+ url_data,
+ Some(CssRuleType::Style),
+ parsing_mode,
+ QuirksMode::NoQuirks,
+ /* namespaces = */ Default::default(),
+ None,
+ None,
+ );
+ for component in self.syntax.components.iter() {
+ let result = input.try_parse(|input| {
+ input.parse_entirely(|input| {
+ Self::parse_value(context, input, &component.unpremultiplied())
+ })
+ });
+ let Ok(values) = result else { continue };
+ self.output.extend(values);
+ *self.output_multiplier = component.multiplier();
+ break;
+ }
+ if self.output.is_empty() {
+ return Err(input.new_error(BasicParseErrorKind::EndOfInput));
+ }
+ Ok(())
+ }
+
+ fn parse_value<'i, 't>(
+ context: &ParserContext,
+ input: &mut CSSParser<'i, 't>,
+ component: &SyntaxComponent,
+ ) -> Result<SmallComponentVec, StyleParseError<'i>> {
+ let mut values = SmallComponentVec::new();
+ values.push(Self::parse_component_without_multiplier(
+ context, input, component,
+ )?);
+
+ if let Some(multiplier) = component.multiplier() {
+ loop {
+ let result = Self::expect_multiplier(input, &multiplier);
+ if Self::expect_multiplier_yielded_eof_error(&result) {
+ break;
+ }
+ result?;
+ values.push(Self::parse_component_without_multiplier(
+ context, input, component,
+ )?);
+ }
+ }
+ Ok(values)
+ }
+
+ fn parse_component_without_multiplier<'i, 't>(
+ context: &ParserContext,
+ input: &mut CSSParser<'i, 't>,
+ component: &SyntaxComponent,
+ ) -> Result<SpecifiedValueComponent, StyleParseError<'i>> {
+ let data_type = match component.name() {
+ ComponentName::DataType(ty) => ty,
+ ComponentName::Ident(ref name) => {
+ let ident = CustomIdent::parse(input, &[])?;
+ if ident != *name {
+ return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
+ }
+ return Ok(SpecifiedValueComponent::CustomIdent(ident));
+ },
+ };
+
+ let value = match data_type {
+ DataType::Length => {
+ SpecifiedValueComponent::Length(specified::Length::parse(context, input)?)
+ },
+ DataType::Number => {
+ SpecifiedValueComponent::Number(specified::Number::parse(context, input)?)
+ },
+ DataType::Percentage => {
+ SpecifiedValueComponent::Percentage(specified::Percentage::parse(context, input)?)
+ },
+ DataType::LengthPercentage => SpecifiedValueComponent::LengthPercentage(
+ specified::LengthPercentage::parse(context, input)?,
+ ),
+ DataType::Color => {
+ SpecifiedValueComponent::Color(specified::Color::parse(context, input)?)
+ },
+ DataType::Image => {
+ SpecifiedValueComponent::Image(specified::Image::parse(context, input)?)
+ },
+ DataType::Url => {
+ SpecifiedValueComponent::Url(specified::url::SpecifiedUrl::parse(context, input)?)
+ },
+ DataType::Integer => {
+ SpecifiedValueComponent::Integer(specified::Integer::parse(context, input)?)
+ },
+ DataType::Angle => {
+ SpecifiedValueComponent::Angle(specified::Angle::parse(context, input)?)
+ },
+ DataType::Time => {
+ SpecifiedValueComponent::Time(specified::Time::parse(context, input)?)
+ },
+ DataType::Resolution => {
+ SpecifiedValueComponent::Resolution(specified::Resolution::parse(context, input)?)
+ },
+ DataType::TransformFunction => SpecifiedValueComponent::TransformFunction(
+ specified::Transform::parse(context, input)?,
+ ),
+ DataType::CustomIdent => {
+ let name = CustomIdent::parse(input, &[])?;
+ SpecifiedValueComponent::CustomIdent(name)
+ },
+ DataType::TransformList => {
+ let mut values = vec![];
+ let Some(multiplier) = component.unpremultiplied().multiplier() else {
+ debug_assert!(false, "Unpremultiplied <transform-list> had no multiplier?");
+ return Err(
+ input.new_custom_error(StyleParseErrorKind::PropertySyntaxField(
+ PropertySyntaxParseError::UnexpectedEOF,
+ )),
+ );
+ };
+ debug_assert_matches!(multiplier, Multiplier::Space);
+ loop {
+ values.push(SpecifiedValueComponent::TransformFunction(
+ specified::Transform::parse(context, input)?,
+ ));
+ let result = Self::expect_multiplier(input, &multiplier);
+ if Self::expect_multiplier_yielded_eof_error(&result) {
+ break;
+ }
+ result?;
+ }
+ let list = ComponentList {
+ multiplier,
+ components: values.into(),
+ };
+ SpecifiedValueComponent::TransformList(list)
+ },
+ DataType::String => {
+ let string = input.expect_string()?;
+ SpecifiedValueComponent::String(string.as_ref().to_owned().into())
+ },
+ };
+ Ok(value)
+ }
+
+ fn expect_multiplier_yielded_eof_error<'i>(result: &Result<(), StyleParseError<'i>>) -> bool {
+ matches!(
+ result,
+ Err(StyleParseError {
+ kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput),
+ ..
+ })
+ )
+ }
+
+ fn expect_multiplier<'i, 't>(
+ input: &mut CSSParser<'i, 't>,
+ multiplier: &Multiplier,
+ ) -> Result<(), StyleParseError<'i>> {
+ match multiplier {
+ Multiplier::Space => {
+ input.expect_whitespace()?;
+ if input.is_exhausted() {
+ // If there was trailing whitespace, do not interpret it as a multiplier
+ return Err(input.new_error(BasicParseErrorKind::EndOfInput));
+ }
+ Ok(())
+ },
+ Multiplier::Comma => Ok(input.expect_comma()?),
+ }
+ }
+}
+
+
+/// An animated value for custom property.
+#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
+pub struct CustomAnimatedValue {
+ /// The name of the custom property.
+ 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(())
+ }
+ 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(),
+ })
+ }
+}
+
+impl CustomAnimatedValue {
+ pub(crate) fn from_computed(
+ name: &crate::custom_properties::Name,
+ value: &Arc<ComputedPropertyValue>,
+ ) -> Self {
+ Self {
+ name: name.clone(),
+ // FIXME: Should probably preserve type-ness in ComputedPropertyValue.
+ value: ComputedValue::Universal(value.clone()),
+ url_data: value.url_data.clone(),
+ }
+ }
+
+ pub(crate) fn from_declaration(
+ declaration: &properties::CustomDeclaration,
+ context: &mut computed::Context,
+ _initial: &properties::ComputedValues,
+ ) -> Option<Self> {
+ let value = match declaration.value {
+ properties::CustomDeclarationValue::Value(ref v) => v,
+ // FIXME: This should be made to work to the extent possible like for non-custom
+ // properties (using `initial` at least to handle unset / inherit).
+ properties::CustomDeclarationValue::CSSWideKeyword(..) => return None,
+ };
+
+ debug_assert!(
+ context.builder.stylist.is_some(),
+ "Need a Stylist to get property registration!"
+ );
+ 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() {
+ None
+ } else {
+ let mut input = cssparser::ParserInput::new(&value.css);
+ let mut input = CSSParser::new(&mut input);
+ SpecifiedValue::get_computed_value(
+ &mut input,
+ registration,
+ &value.url_data,
+ context,
+ AllowComputationallyDependent::Yes,
+ ).ok()
+ };
+
+ let url_data = value.url_data.clone();
+ let value = computed_value.unwrap_or_else(|| ComputedValue::Universal(Arc::clone(value)));
+ Some(Self {
+ name: declaration.name.clone(),
+ url_data,
+ value,
+ })
+ }
+
+ 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)),
+ })
+ }
+}