diff options
Diffstat (limited to 'servo/components/style/properties_and_values')
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, + ®istration.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)), + }) + } +} |