Add parsing of CSS Custom Properties.

This commit is contained in:
Simon Sapin 2015-07-22 17:13:19 +02:00
parent b7c88dd547
commit 09e60beb78
3 changed files with 175 additions and 10 deletions

View file

@ -0,0 +1,125 @@
/* 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 http://mozilla.org/MPL/2.0/. */
use cssparser::{Parser, Token};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use string_cache::Atom;
#[derive(Clone)]
pub struct Value {
/// In CSS syntax
pub value: String,
/// Custom property names in var() functions
pub references: HashSet<Atom>,
}
/// Names (atoms) do not include the `--` prefix.
pub type Map = HashMap<Atom, Value>;
pub fn parse(input: &mut Parser) -> Result<Value, ()> {
let start = input.position();
let mut references = HashSet::new();
try!(parse_declaration_value(input, &mut references));
Ok(Value {
value: input.slice_from(start).to_owned(),
references: references,
})
}
/// https://drafts.csswg.org/css-syntax-3/#typedef-declaration-value
fn parse_declaration_value(input: &mut Parser, references: &mut HashSet<Atom>) -> Result<(), ()> {
while let Ok(token) = input.next() {
match token {
Token::BadUrl |
Token::BadString |
Token::CloseParenthesis |
Token::CloseSquareBracket |
Token::CloseCurlyBracket |
Token::Semicolon |
Token::Delim('!') => {
return Err(())
}
Token::Function(ref name) if name == "var" => {
try!(parse_var_function(input, references));
}
Token::Function(_) |
Token::ParenthesisBlock |
Token::CurlyBracketBlock |
Token::SquareBracketBlock => {
try!(parse_declaration_value_block(input, references))
}
_ => {}
}
}
Ok(())
}
/// 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 HashSet<Atom>)
-> Result<(), ()> {
while let Ok(token) = input.next() {
match token {
Token::BadUrl |
Token::BadString |
Token::CloseParenthesis |
Token::CloseSquareBracket |
Token::CloseCurlyBracket => {
return Err(())
}
Token::Function(ref name) if name == "var" => {
try!(parse_var_function(input, references));
}
Token::Function(_) |
Token::ParenthesisBlock |
Token::CurlyBracketBlock |
Token::SquareBracketBlock => {
try!(parse_declaration_value_block(input, references))
}
_ => {}
}
}
Ok(())
}
// 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 HashSet<Atom>)
-> Result<(), ()> {
// https://drafts.csswg.org/css-variables/#typedef-custom-property-name
let name = try!(input.expect_ident());
let name = if name.starts_with("--") {
&name[2..]
} else {
return Err(())
};
if input.expect_comma().is_ok() {
try!(parse_declaration_value(input, references));
}
references.insert(Atom::from_slice(name));
Ok(())
}
pub fn cascade(custom_properties: &mut Option<Map>, inherited_custom_properties: &Option<Arc<Map>>,
name: &Atom, value: &Value) {
let map = match *custom_properties {
Some(ref mut map) => map,
None => {
*custom_properties = Some(match *inherited_custom_properties {
Some(ref arc) => (**arc).clone(),
None => HashMap::new(),
});
custom_properties.as_mut().unwrap()
}
};
map.entry(name.clone()).or_insert(value.clone());
}

View file

@ -43,6 +43,7 @@ extern crate num;
extern crate util;
mod custom_properties;
pub mod stylesheets;
pub mod parser;
pub mod selector_matching;

View file

@ -22,6 +22,7 @@ use util::logical_geometry::{LogicalMargin, PhysicalSide, WritingMode};
use euclid::SideOffsets2D;
use euclid::size::Size2D;
use fnv::FnvHasher;
use string_cache::Atom;
use computed_values;
use parser::{ParserContext, log_css_error};
@ -5610,6 +5611,7 @@ fn deduplicate_property_declarations(declarations: Vec<PropertyDeclaration>)
-> Vec<PropertyDeclaration> {
let mut deduplicated = vec![];
let mut seen = PropertyBitField::new();
let mut seen_custom = Vec::new();
for declaration in declarations.into_iter().rev() {
match declaration {
% for property in LONGHANDS:
@ -5624,6 +5626,12 @@ fn deduplicate_property_declarations(declarations: Vec<PropertyDeclaration>)
% endif
},
% endfor
PropertyDeclaration::Custom(ref name, _) => {
if seen_custom.contains(name) {
continue
}
seen_custom.push(name.clone())
}
}
deduplicated.push(declaration)
}
@ -5675,6 +5683,8 @@ pub enum PropertyDeclaration {
% for property in LONGHANDS:
${property.camel_case}(DeclaredValue<longhands::${property.ident}::SpecifiedValue>),
% endfor
/// Name (atom) does not include the `--` prefix.
Custom(Atom, ::custom_properties::Value),
}
@ -5725,6 +5735,15 @@ impl PropertyDeclaration {
pub fn parse(name: &str, context: &ParserContext, input: &mut Parser,
result_list: &mut Vec<PropertyDeclaration>) -> PropertyDeclarationParseResult {
if name.starts_with("--") {
if let Ok(value) = ::custom_properties::parse(input) {
let name = Atom::from_slice(&name[2..]);
result_list.push(PropertyDeclaration::Custom(name, value));
return PropertyDeclarationParseResult::ValidOrIgnoredDeclaration;
} else {
return PropertyDeclarationParseResult::InvalidValue;
}
}
match_ignore_ascii_case! { name,
% for property in LONGHANDS:
% if property.derived_from is None:
@ -5832,6 +5851,7 @@ pub struct ComputedValues {
% for style_struct in STYLE_STRUCTS:
${style_struct.ident}: Arc<style_structs::${style_struct.name}>,
% endfor
custom_properties: Option<Arc<::custom_properties::Map>>,
shareable: bool,
pub writing_mode: WritingMode,
pub root_font_size: Au,
@ -6050,6 +6070,7 @@ lazy_static! {
% endif
}),
% endfor
custom_properties: None,
shareable: true,
writing_mode: WritingMode::empty(),
root_font_size: longhands::font_size::get_initial_value(),
@ -6073,6 +6094,7 @@ fn cascade_with_cached_declarations(
let mut style_${style_struct.ident} = cached_style.${style_struct.ident}.clone();
% endif
% endfor
let mut custom_properties = None;
let mut seen = PropertyBitField::new();
// Declaration blocks are stored in increasing precedence order,
@ -6134,6 +6156,10 @@ fn cascade_with_cached_declarations(
% endif
% endfor
% endfor
PropertyDeclaration::Custom(ref name, ref value) => {
::custom_properties::cascade(
&mut custom_properties, &parent_style.custom_properties, name, value)
}
}
}
}
@ -6148,6 +6174,8 @@ fn cascade_with_cached_declarations(
% for style_struct in STYLE_STRUCTS:
${style_struct.ident}: style_${style_struct.ident},
% endfor
custom_properties: custom_properties
.map(Arc::new).or_else(|| parent_style.custom_properties.clone()),
shareable: shareable,
root_font_size: parent_style.root_font_size,
}
@ -6210,7 +6238,6 @@ pub fn cascade(viewport_size: Size2D<Au>,
Some(parent_style) => (false, parent_style),
None => (true, initial_values),
};
let mut context = {
let inherited_font_style = inherited_style.get_font();
computed::Context {
@ -6348,12 +6375,14 @@ pub fn cascade(viewport_size: Size2D<Au>,
% endif
.${style_struct.ident}.clone(),
% endfor
custom_properties: None,
shareable: false,
writing_mode: WritingMode::empty(),
root_font_size: context.root_font_size,
};
let mut cacheable = true;
let mut seen = PropertyBitField::new();
let mut custom_properties = None;
// Declaration blocks are stored in increasing precedence order, we want them in decreasing
// order here.
//
@ -6364,15 +6393,23 @@ pub fn cascade(viewport_size: Size2D<Au>,
for sub_list in applicable_declarations.iter().rev() {
// Declarations are already stored in reverse order.
for declaration in sub_list.declarations.iter() {
let discriminant = unsafe {
intrinsics::discriminant_value(declaration) as usize
};
(cascade_property[discriminant].unwrap())(declaration,
&mut style,
inherited_style,
&context,
&mut seen,
&mut cacheable);
match *declaration {
PropertyDeclaration::Custom(ref name, ref value) => {
::custom_properties::cascade(
&mut custom_properties, &inherited_style.custom_properties, name, value)
}
_ => {
let discriminant = unsafe {
intrinsics::discriminant_value(declaration) as usize
};
(cascade_property[discriminant].unwrap())(declaration,
&mut style,
inherited_style,
&context,
&mut seen,
&mut cacheable);
}
}
}
}
});
@ -6414,6 +6451,8 @@ pub fn cascade(viewport_size: Size2D<Au>,
% for style_struct in STYLE_STRUCTS:
${style_struct.ident}: style.${style_struct.ident},
% endfor
custom_properties: custom_properties
.map(Arc::new).or_else(|| inherited_style.custom_properties.clone()),
shareable: shareable,
root_font_size: context.root_font_size,
}, cacheable)