mirror of
https://github.com/servo/servo.git
synced 2025-07-16 20:03:39 +01:00
Custom properties: handle premature EOF correctly.
This commit is contained in:
parent
69d398f29a
commit
020d03b656
2 changed files with 149 additions and 41 deletions
|
@ -5,6 +5,7 @@
|
||||||
use cssparser::{Delimiter, Parser, SourcePosition, ToCss, Token, TokenSerializationType};
|
use cssparser::{Delimiter, Parser, SourcePosition, ToCss, Token, TokenSerializationType};
|
||||||
use properties::DeclaredValue;
|
use properties::DeclaredValue;
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -94,19 +95,52 @@ impl ComputedValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(input: &mut Parser) -> Result<SpecifiedValue, ()> {
|
pub fn parse(input: &mut Parser) -> Result<SpecifiedValue, ()> {
|
||||||
let start = input.position();
|
|
||||||
let mut references = Some(HashSet::new());
|
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 {
|
Ok(SpecifiedValue {
|
||||||
css: input.slice_from(start).to_owned(),
|
css: css.into_owned(),
|
||||||
first_token_type: first,
|
first_token_type: first,
|
||||||
last_token_type: last,
|
last_token_type: last,
|
||||||
references: references.unwrap(),
|
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
|
/// https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value
|
||||||
pub fn parse_declaration_value(input: &mut Parser, references: &mut Option<HashSet<Name>>)
|
fn parse_declaration_value<'i, 't>
|
||||||
|
(input: &mut Parser<'i, 't>,
|
||||||
|
references: &mut Option<HashSet<Name>>,
|
||||||
|
missing_closing_characters: &mut String)
|
||||||
-> Result<(TokenSerializationType, TokenSerializationType), ()> {
|
-> Result<(TokenSerializationType, TokenSerializationType), ()> {
|
||||||
input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
|
input.parse_until_before(Delimiter::Bang | Delimiter::Semicolon, |input| {
|
||||||
// Need at least one token
|
// Need at least one token
|
||||||
|
@ -114,24 +148,46 @@ pub fn parse_declaration_value(input: &mut Parser, references: &mut Option<HashS
|
||||||
try!(input.next_including_whitespace());
|
try!(input.next_including_whitespace());
|
||||||
input.reset(start_position);
|
input.reset(start_position);
|
||||||
|
|
||||||
parse_declaration_value_block(input, references)
|
parse_declaration_value_block(input, references, missing_closing_characters)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like parse_declaration_value,
|
/// Like parse_declaration_value,
|
||||||
/// but accept `!` and `;` since they are only invalid at the top level
|
/// 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), ()> {
|
-> Result<(TokenSerializationType, TokenSerializationType), ()> {
|
||||||
let mut first_token_type = TokenSerializationType::nothing();
|
let mut token_start = input.position();
|
||||||
let mut last_token_type = TokenSerializationType::nothing();
|
let mut token = match input.next_including_whitespace_and_comments() {
|
||||||
while let Ok(token) = input.next_including_whitespace_and_comments() {
|
Ok(token) => token,
|
||||||
first_token_type.set_if_nothing(token.serialization_type());
|
Err(()) => return Ok((TokenSerializationType::nothing(), TokenSerializationType::nothing()))
|
||||||
// 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
|
let first_token_type = token.serialization_type();
|
||||||
// when it comes *after* an identifier (it would turn into a function)
|
loop {
|
||||||
// but a "last" token will only be concantenated *before* another unrelated token.
|
macro_rules! nested {
|
||||||
last_token_type = token.serialization_type();
|
() => {
|
||||||
match token {
|
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::BadUrl |
|
||||||
Token::BadString |
|
Token::BadString |
|
||||||
Token::CloseParenthesis |
|
Token::CloseParenthesis |
|
||||||
|
@ -139,35 +195,88 @@ fn parse_declaration_value_block(input: &mut Parser, references: &mut Option<Has
|
||||||
Token::CloseCurlyBracket => {
|
Token::CloseCurlyBracket => {
|
||||||
return Err(())
|
return Err(())
|
||||||
}
|
}
|
||||||
|
Token::Function(ref name) => {
|
||||||
Token::Function(ref name) if name.eq_ignore_ascii_case("var") => {
|
if name.eq_ignore_ascii_case("var") {
|
||||||
|
let position = input.position();
|
||||||
try!(input.parse_nested_block(|input| {
|
try!(input.parse_nested_block(|input| {
|
||||||
parse_var_function(input, references)
|
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 => {
|
Token::SquareBracketBlock => {
|
||||||
try!(input.parse_nested_block(|input| {
|
nested!();
|
||||||
parse_declaration_value_block(input, references)
|
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("<EFBFBD>") && 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("<EFBFBD>")
|
||||||
|
}
|
||||||
|
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))
|
// 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<(), ()> {
|
-> Result<(), ()> {
|
||||||
let name = try!(input.expect_ident());
|
let name = try!(input.expect_ident());
|
||||||
let name = try!(parse_name(&name));
|
let name = try!(parse_name(&name));
|
||||||
if input.expect_comma().is_ok() {
|
if input.try(|input| input.expect_comma()).is_ok() {
|
||||||
try!(parse_declaration_value(input, references));
|
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 {
|
if let Some(ref mut refs) = *references {
|
||||||
refs.insert(Atom::from_slice(name));
|
refs.insert(Atom::from_slice(name));
|
||||||
|
|
|
@ -211,10 +211,10 @@ pub mod longhands {
|
||||||
let var = input.seen_var_functions();
|
let var = input.seen_var_functions();
|
||||||
if specified.is_err() && var {
|
if specified.is_err() && var {
|
||||||
input.reset(start);
|
input.reset(start);
|
||||||
let (first_token_type, _) = try!(
|
let (first_token_type, css) = try!(
|
||||||
::custom_properties::parse_declaration_value(input, &mut None));
|
::custom_properties::parse_non_custom_with_var(input));
|
||||||
return Ok(DeclaredValue::WithVariables {
|
return Ok(DeclaredValue::WithVariables {
|
||||||
css: input.slice_from(start).to_owned(),
|
css: css.into_owned(),
|
||||||
first_token_type: first_token_type,
|
first_token_type: first_token_type,
|
||||||
base_url: context.base_url.clone(),
|
base_url: context.base_url.clone(),
|
||||||
from_shorthand: Shorthand::None,
|
from_shorthand: Shorthand::None,
|
||||||
|
@ -4914,13 +4914,12 @@ pub mod shorthands {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else if var {
|
} else if var {
|
||||||
input.reset(start);
|
input.reset(start);
|
||||||
let (first_token_type, _) = try!(
|
let (first_token_type, css) = try!(
|
||||||
::custom_properties::parse_declaration_value(input, &mut None));
|
::custom_properties::parse_non_custom_with_var(input));
|
||||||
let css = input.slice_from(start);
|
|
||||||
% for sub_property in shorthand.sub_properties:
|
% for sub_property in shorthand.sub_properties:
|
||||||
declarations.push(PropertyDeclaration::${sub_property.camel_case}(
|
declarations.push(PropertyDeclaration::${sub_property.camel_case}(
|
||||||
DeclaredValue::WithVariables {
|
DeclaredValue::WithVariables {
|
||||||
css: css.to_owned(),
|
css: css.clone().into_owned(),
|
||||||
first_token_type: first_token_type,
|
first_token_type: first_token_type,
|
||||||
base_url: context.base_url.clone(),
|
base_url: context.base_url.clone(),
|
||||||
from_shorthand: Shorthand::${shorthand.camel_case},
|
from_shorthand: Shorthand::${shorthand.camel_case},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue