diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs new file mode 100644 index 00000000000..fc28e8a5994 --- /dev/null +++ b/components/style/custom_properties.rs @@ -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, +} + +/// Names (atoms) do not include the `--` prefix. +pub type Map = HashMap; + +pub fn parse(input: &mut Parser) -> Result { + 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) -> 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) + -> 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) + -> 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, inherited_custom_properties: &Option>, + 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()); +} diff --git a/components/style/lib.rs b/components/style/lib.rs index 99390a4bc3a..1d7622ce088 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -43,6 +43,7 @@ extern crate num; extern crate util; +mod custom_properties; pub mod stylesheets; pub mod parser; pub mod selector_matching; diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs index 857718e7a44..f0ddef0c503 100644 --- a/components/style/properties.mako.rs +++ b/components/style/properties.mako.rs @@ -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) -> Vec { 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) % 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), % 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) -> 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, % endfor + custom_properties: Option>, 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, 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, % 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, 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, % 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)