aboutsummaryrefslogtreecommitdiffstats
path: root/components/style/custom_properties.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/style/custom_properties.rs')
-rw-r--r--components/style/custom_properties.rs188
1 files changed, 152 insertions, 36 deletions
diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs
index b5f7c3f3232..fbcf4c46b0e 100644
--- a/components/style/custom_properties.rs
+++ b/components/style/custom_properties.rs
@@ -5,6 +5,7 @@
use cssparser::{Delimiter, Parser, SourcePosition, ToCss, Token, TokenSerializationType};
use properties::DeclaredValue;
use std::ascii::AsciiExt;
+use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::sync::Arc;
@@ -23,7 +24,7 @@ pub fn parse_name(s: &str) -> Result<&str, ()> {
}
}
-#[derive(Clone, PartialEq)]
+#[derive(Clone, PartialEq, Debug)]
pub struct SpecifiedValue {
css: String,
@@ -41,7 +42,7 @@ pub struct BorrowedSpecifiedValue<'a> {
references: Option<&'a HashSet<Name>>,
}
-#[derive(Clone, HeapSizeOf)]
+#[derive(Clone, HeapSizeOf, Debug)]
pub struct ComputedValue {
css: String,
first_token_type: TokenSerializationType,
@@ -73,6 +74,11 @@ impl ComputedValue {
fn push(&mut self, css: &str, css_first_token_type: TokenSerializationType,
css_last_token_type: TokenSerializationType) {
+ // This happens e.g. between to subsequent var() functions: `var(--a)var(--b)`.
+ // In that case, css_*_token_type is non-sensical.
+ if css.is_empty() {
+ return
+ }
self.first_token_type.set_if_nothing(css_first_token_type);
// If self.first_token_type was nothing,
// self.last_token_type is also nothing and this will be false:
@@ -94,44 +100,99 @@ impl ComputedValue {
}
pub fn parse(input: &mut Parser) -> Result<SpecifiedValue, ()> {
- let start = input.position();
let mut references = Some(HashSet::new());
- let (first, last) = try!(parse_declaration_value(input, &mut references));
+ let (first, css, last) = try!(parse_self_contained_declaration_value(input, &mut references));
Ok(SpecifiedValue {
- css: input.slice_from(start).to_owned(),
+ css: css.into_owned(),
first_token_type: first,
last_token_type: last,
references: references.unwrap(),
})
}
+/// Parse the value of a non-custom property that contains `var()` references.
+pub fn parse_non_custom_with_var<'i, 't>
+ (input: &mut Parser<'i, 't>)
+ -> Result<(TokenSerializationType, Cow<'i, str>), ()> {
+ let (first_token_type, css, _) = try!(parse_self_contained_declaration_value(input, &mut None));
+ Ok((first_token_type, css))
+}
+
+fn parse_self_contained_declaration_value<'i, 't>
+ (input: &mut Parser<'i, 't>,
+ references: &mut Option<HashSet<Name>>)
+ -> Result<(
+ TokenSerializationType,
+ Cow<'i, str>,
+ TokenSerializationType
+ ), ()> {
+ let start_position = input.position();
+ let mut missing_closing_characters = String::new();
+ let (first, last) = try!(
+ parse_declaration_value(input, references, &mut missing_closing_characters));
+ let mut css: Cow<str> = input.slice_from(start_position).into();
+ if !missing_closing_characters.is_empty() {
+ // Unescaped backslash at EOF in a quoted string is ignored.
+ if css.ends_with("\\") && matches!(missing_closing_characters.as_bytes()[0], b'"' | b'\'') {
+ css.to_mut().pop();
+ }
+ css.to_mut().push_str(&missing_closing_characters);
+ }
+ Ok((first, css, last))
+}
+
/// https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value
-pub fn parse_declaration_value(input: &mut Parser, references: &mut Option<HashSet<Name>>)
- -> Result<(TokenSerializationType, TokenSerializationType), ()> {
+fn parse_declaration_value<'i, 't>
+ (input: &mut Parser<'i, 't>,
+ references: &mut Option<HashSet<Name>>,
+ missing_closing_characters: &mut String)
+ -> Result<(TokenSerializationType, TokenSerializationType), ()> {
input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
// Need at least one token
let start_position = input.position();
try!(input.next_including_whitespace());
input.reset(start_position);
- parse_declaration_value_block(input, references)
+ parse_declaration_value_block(input, references, missing_closing_characters)
})
}
/// Like parse_declaration_value,
/// but accept `!` and `;` since they are only invalid at the top level
-fn parse_declaration_value_block(input: &mut Parser, references: &mut Option<HashSet<Name>>)
+fn parse_declaration_value_block(input: &mut Parser,
+ references: &mut Option<HashSet<Name>>,
+ missing_closing_characters: &mut String)
-> Result<(TokenSerializationType, TokenSerializationType), ()> {
- let mut first_token_type = TokenSerializationType::nothing();
- let mut last_token_type = TokenSerializationType::nothing();
- while let Ok(token) = input.next_including_whitespace_and_comments() {
- first_token_type.set_if_nothing(token.serialization_type());
- // This may be OpenParen when it should be Other (for the closing paren)
- // but that doesn’t make a difference since OpenParen is only special
- // when it comes *after* an identifier (it would turn into a function)
- // but a "last" token will only be concantenated *before* another unrelated token.
- last_token_type = token.serialization_type();
- match token {
+ let mut token_start = input.position();
+ let mut token = match input.next_including_whitespace_and_comments() {
+ Ok(token) => token,
+ Err(()) => return Ok((TokenSerializationType::nothing(), TokenSerializationType::nothing()))
+ };
+ let first_token_type = token.serialization_type();
+ loop {
+ macro_rules! nested {
+ () => {
+ try!(input.parse_nested_block(|input| {
+ parse_declaration_value_block(input, references, missing_closing_characters)
+ }))
+ }
+ }
+ macro_rules! check_closed {
+ ($closing: expr) => {
+ if !input.slice_from(token_start).ends_with($closing) {
+ missing_closing_characters.push_str($closing)
+ }
+ }
+ }
+ let last_token_type = match token {
+ Token::Comment(_) => {
+ let token_slice = input.slice_from(token_start);
+ if !token_slice.ends_with("*/") {
+ missing_closing_characters.push_str(
+ if token_slice.ends_with("*") { "/" } else { "*/" })
+ }
+ token.serialization_type()
+ }
Token::BadUrl |
Token::BadString |
Token::CloseParenthesis |
@@ -139,35 +200,90 @@ fn parse_declaration_value_block(input: &mut Parser, references: &mut Option<Has
Token::CloseCurlyBracket => {
return Err(())
}
-
- Token::Function(ref name) if name.eq_ignore_ascii_case("var") => {
- try!(input.parse_nested_block(|input| {
- parse_var_function(input, references)
- }));
+ Token::Function(ref name) => {
+ if name.eq_ignore_ascii_case("var") {
+ let position = input.position();
+ try!(input.parse_nested_block(|input| {
+ parse_var_function(input, references)
+ }));
+ input.reset(position);
+ }
+ nested!();
+ check_closed!(")");
+ Token::CloseParenthesis.serialization_type()
+ }
+ Token::ParenthesisBlock => {
+ nested!();
+ check_closed!(")");
+ Token::CloseParenthesis.serialization_type()
+ }
+ Token::CurlyBracketBlock => {
+ nested!();
+ check_closed!("}");
+ Token::CloseCurlyBracket.serialization_type()
}
-
- Token::Function(_) |
- Token::ParenthesisBlock |
- Token::CurlyBracketBlock |
Token::SquareBracketBlock => {
- try!(input.parse_nested_block(|input| {
- parse_declaration_value_block(input, references)
- }));
+ nested!();
+ check_closed!("]");
+ Token::CloseSquareBracket.serialization_type()
+ }
+ Token::QuotedString(_) => {
+ let token_slice = input.slice_from(token_start);
+ let quote = &token_slice[..1];
+ debug_assert!(matches!(quote, "\"" | "'"));
+ if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
+ missing_closing_characters.push_str(quote)
+ }
+ token.serialization_type()
+ }
+ Token::Ident(ref value) |
+ Token::AtKeyword(ref value) |
+ Token::Hash(ref value) |
+ Token::IDHash(ref value) |
+ Token::UnquotedUrl(ref value) |
+ Token::Dimension(_, ref value) => {
+ if value.ends_with("�") && input.slice_from(token_start).ends_with("\\") {
+ // Unescaped backslash at EOF in these contexts is interpreted as U+FFFD
+ // Check the value in case the final backslash was itself escaped.
+ // Serialize as escaped U+FFFD, which is also interpreted as U+FFFD.
+ // (Unescaped U+FFFD would also work, but removing the backslash is annoying.)
+ missing_closing_characters.push_str("�")
+ }
+ if matches!(token, Token::UnquotedUrl(_)) {
+ check_closed!(")");
+ }
+ token.serialization_type()
+ }
+ _ => {
+ token.serialization_type()
}
+ };
- _ => {}
+ token_start = input.position();
+ token = if let Ok(token) = input.next_including_whitespace_and_comments() {
+ token
+ } else {
+ return Ok((first_token_type, last_token_type))
}
}
- Ok((first_token_type, last_token_type))
}
// If the var function is valid, return Ok((custom_property_name, fallback))
-fn parse_var_function<'i, 't>(input: &mut Parser<'i, 't>, references: &mut Option<HashSet<Name>>)
+fn parse_var_function<'i, 't>(input: &mut Parser<'i, 't>,
+ references: &mut Option<HashSet<Name>>)
-> Result<(), ()> {
let name = try!(input.expect_ident());
let name = try!(parse_name(&name));
- if input.expect_comma().is_ok() {
- try!(parse_declaration_value(input, references));
+ if input.try(|input| input.expect_comma()).is_ok() {
+ // Exclude `!` and `;` at the top level
+ // https://drafts.csswg.org/css-syntax/#typedef-declaration-value
+ try!(input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
+ // At least one non-comment token.
+ try!(input.next_including_whitespace());
+ // Skip until the end.
+ while let Ok(_) = input.next_including_whitespace_and_comments() {}
+ Ok(())
+ }));
}
if let Some(ref mut refs) = *references {
refs.insert(Atom::from_slice(name));