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.rs10
-rw-r--r--servo/components/style/properties_and_values/rule.rs245
-rw-r--r--servo/components/style/properties_and_values/syntax/ascii.rs60
-rw-r--r--servo/components/style/properties_and_values/syntax/data_type.rs91
-rw-r--r--servo/components/style/properties_and_values/syntax/mod.rs328
5 files changed, 734 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..00f7ac98f7
--- /dev/null
+++ b/servo/components/style/properties_and_values/mod.rs
@@ -0,0 +1,10 @@
+/* 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 rule;
+pub mod syntax;
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..3392e68124
--- /dev/null
+++ b/servo/components/style/properties_and_values/rule.rs
@@ -0,0 +1,245 @@
+/* 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 crate::custom_properties::{Name as CustomPropertyName, SpecifiedValue};
+use crate::error_reporting::ContextualParseError;
+use crate::parser::{Parse, ParserContext};
+use crate::properties_and_values::syntax::Descriptor as SyntaxDescriptor;
+use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
+use crate::str::CssStringWriter;
+use crate::values::serialize_atom_name;
+use cssparser::{
+ AtRuleParser, CowRcStr, DeclarationParser, ParseErrorKind, Parser, 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(
+ context: &ParserContext,
+ input: &mut Parser,
+ name: PropertyRuleName,
+ location: SourceLocation,
+) -> PropertyRuleData {
+ let mut rule = PropertyRuleData::empty(name, location);
+ let mut parser = PropertyRuleParser {
+ context,
+ rule: &mut rule,
+ };
+ 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(_))
+ ) {
+ ContextualParseError::UnsupportedValue(slice, error)
+ } else {
+ ContextualParseError::UnsupportedPropertyDescriptor(slice, error)
+ };
+ context.log_css_error(location, error);
+ }
+ }
+ rule
+}
+
+struct PropertyRuleParser<'a, 'b: 'a> {
+ context: &'a ParserContext<'b>,
+ rule: &'a mut PropertyRuleData,
+}
+
+/// 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, PartialEq)]
+ pub struct PropertyRuleData {
+ /// The custom property name.
+ pub name: PropertyRuleName,
+
+ $(
+ #[$doc]
+ pub $ident: Option<$ty>,
+ )*
+
+ /// Line and column of the @property rule source code.
+ pub source_location: SourceLocation,
+ }
+
+ impl PropertyRuleData {
+ /// Create an empty property rule
+ pub fn empty(name: PropertyRuleName, source_location: SourceLocation) -> Self {
+ PropertyRuleData {
+ name,
+ $(
+ $ident: None,
+ )*
+ source_location,
+ }
+ }
+
+ /// Serialization of declarations in PropertyRuleData
+ pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
+ $(
+ if let Some(ref value) = self.$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.rule.$ident = Some(value)
+ },
+ )*
+ _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
+ }
+ Ok(())
+ }
+ }
+ }
+}
+
+#[cfg(feature = "gecko")]
+property_descriptors! {
+ /// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-syntax-descriptor>
+ "syntax" syntax: SyntaxDescriptor,
+
+ /// <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,
+}
+
+impl PropertyRuleData {
+ /// Measure heap usage.
+ #[cfg(feature = "gecko")]
+ pub fn size_of(&self, _guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
+ self.name.0.size_of(ops) +
+ self.syntax.size_of(ops) +
+ self.inherits.size_of(ops) +
+ if let Some(ref initial_value) = self.initial_value {
+ initial_value.size_of(ops)
+ } else {
+ 0
+ }
+ }
+}
+
+impl ToCssWithGuard for PropertyRuleData {
+ /// <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 PropertyRuleData {
+ 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)]
+pub struct PropertyRuleName(pub Arc<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();
+ SpecifiedValue::parse(input)
+ }
+}
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..db5c1478db
--- /dev/null
+++ b/servo/components/style/properties_and_values/syntax/data_type.rs
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use super::{Component, ComponentName, Multiplier};
+
+/// <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,
+ /// 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 `<custom-ident>` value
+ CustomIdent,
+}
+
+impl DataType {
+ pub fn unpremultiply(&self) -> Option<Component> {
+ match *self {
+ DataType::TransformList => Some(Component {
+ name: ComponentName::DataType(DataType::TransformFunction),
+ multiplier: Some(Multiplier::Space),
+ }),
+ _ => None,
+ }
+ }
+
+ 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,
+ _ => return None,
+ })
+ }
+
+ pub fn to_str(&self) -> &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",
+ }
+ }
+}
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..96f3c0ff4f
--- /dev/null
+++ b/servo/components/style/properties_and_values/syntax/mod.rs
@@ -0,0 +1,328 @@
+/* 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;
+mod data_type;
+
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#parsing-syntax>
+#[derive(Debug, Clone, MallocSizeOf)]
+pub struct Descriptor {
+ components: Box<[Component]>,
+ css: String,
+}
+
+impl Descriptor {
+ /// Returns the universal syntax definition with the given CSS representation.
+ fn universal(css: &str) -> Self {
+ Self {
+ components: Default::default(),
+ css: String::from(css),
+ }
+ }
+
+ /// Returns the specified syntax string.
+ pub fn as_str(&self) -> &str {
+ &self.css
+ }
+}
+
+impl PartialEq for Descriptor {
+ fn eq(&self, other: &Self) -> bool {
+ self.components == other.components
+ }
+}
+
+impl Parse for Descriptor {
+ /// Parse a syntax descriptor.
+ fn parse<'i, 't>(
+ _context: &ParserContext,
+ parser: &mut CSSParser<'i, 't>,
+ ) -> Result<Self, StyleParseError<'i>> {
+ // 1. Strip leading and trailing ASCII whitespace from string.
+ let input = parser.expect_string()?;
+ match parse_descriptor(input) {
+ Ok(syntax) => Ok(syntax),
+ Err(err) => Err(parser.new_custom_error(StyleParseErrorKind::PropertySyntaxField(err))),
+ }
+ }
+}
+
+impl ToCss for Descriptor {
+ fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
+ where
+ W: Write,
+ {
+ self.css.to_css(dest)
+ }
+}
+
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#multipliers>
+#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
+pub enum Multiplier {
+ /// Indicates a space-separated list.
+ Space,
+ /// Indicates a comma-separated list.
+ 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),
+ }
+ }
+}
+
+/// <https://drafts.css-houdini.org/css-properties-values-api-1/#syntax-component-name>
+#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
+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()
+ }
+}
+
+/// Parse a syntax descriptor.
+#[inline]
+fn parse_descriptor(css: &str) -> Result<Descriptor, 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);
+ }
+
+ // 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(Descriptor::universal(css));
+ }
+
+ // 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(Descriptor {
+ components: components.into_boxed_slice(),
+ css: String::from(css),
+ })
+}
+
+struct Parser<'a> {
+ input: &'a str,
+ position: usize,
+ output: &'a mut Vec<Component>,
+}
+
+/// <https://drafts.csswg.org/css-syntax-3/#whitespace>
+fn is_whitespace(byte: u8) -> bool {
+ match byte {
+ b'\t' | b'\n' | b'\r' | b' ' => true,
+ _ => false,
+ }
+}
+
+/// <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 is_whitespace(c) => 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 location = input.current_source_location();
+ let name = input
+ .expect_ident()
+ .ok()
+ .and_then(|name| CustomIdent::from_ident(location, name, &[]).ok());
+ let name = match name {
+ Some(name) => name,
+ None => 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 })
+ }
+}