From 137e30b825749f879e940f2eb0885801a9ef15b1 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Mon, 10 Oct 2016 16:00:28 +0200 Subject: [PATCH] Introduce enums for identifying CSS properties. * `LonghandId` and `ShorthandId` are C-like enums * `Atom` is used for the name of custom properties. * `PropertyDeclarationId` is the identifier for `PropertyDeclaration`, after parsing and shorthand expansion. (Longhand or custom property.) * `PropertyId` represents any CSS property, e.g. in CSSOM. (Longhand, shorthand, or custom.) Using these instead of strings avoids some memory allocations and copies. --- components/script/dom/bindings/str.rs | 6 + components/script/dom/cssstyledeclaration.rs | 59 ++-- components/style/gecko_string_cache/mod.rs | 13 +- .../style/properties/declaration_block.rs | 197 ++++++------ components/style/properties/helpers.mako.rs | 8 +- .../style/properties/properties.mako.rs | 283 ++++++++++-------- .../properties/shorthand/serialize.mako.rs | 4 +- ports/geckolib/glue.rs | 34 ++- tests/unit/style/properties/serialization.rs | 8 +- tests/unit/style/stylesheets.rs | 2 +- 10 files changed, 335 insertions(+), 279 deletions(-) diff --git a/components/script/dom/bindings/str.rs b/components/script/dom/bindings/str.rs index d7984939e281..896c2af5b241 100644 --- a/components/script/dom/bindings/str.rs +++ b/components/script/dom/bindings/str.rs @@ -286,6 +286,12 @@ impl Into> for DOMString { } } +impl<'a> Into> for DOMString { + fn into(self) -> Cow<'a, str> { + self.0.into() + } +} + impl Extend for DOMString { fn extend(&mut self, iterable: I) where I: IntoIterator { self.0.extend(iterable) diff --git a/components/script/dom/cssstyledeclaration.rs b/components/script/dom/cssstyledeclaration.rs index c6280597b17b..c119f8a70e6b 100644 --- a/components/script/dom/cssstyledeclaration.rs +++ b/components/script/dom/cssstyledeclaration.rs @@ -16,8 +16,8 @@ use servo_atoms::Atom; use std::ascii::AsciiExt; use std::sync::Arc; use style::parser::ParserContextExtraData; -use style::properties::{Shorthand, Importance, PropertyDeclarationBlock}; -use style::properties::{is_supported_property, parse_one_declaration, parse_style_attribute}; +use style::properties::{Importance, PropertyDeclarationBlock, PropertyId}; +use style::properties::{parse_one_declaration, parse_style_attribute}; use style::selector_parser::PseudoElement; use style_traits::ToCss; @@ -111,6 +111,13 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { return self.get_computed_style(&property).unwrap_or(DOMString::new()); } + let id = if let Ok(id) = PropertyId::parse(property.into()) { + id + } else { + // Unkwown property + return DOMString::new() + }; + let style_attribute = self.owner.style_attribute().borrow(); let style_attribute = if let Some(ref lock) = *style_attribute { lock.read() @@ -120,12 +127,19 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { }; let mut string = String::new(); - style_attribute.property_value_to_css(&property, &mut string).unwrap(); + style_attribute.property_value_to_css(&id, &mut string).unwrap(); DOMString::from(string) } // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority fn GetPropertyPriority(&self, property: DOMString) -> DOMString { + let id = if let Ok(id) = PropertyId::parse(property.into()) { + id + } else { + // Unkwown property + return DOMString::new() + }; + let style_attribute = self.owner.style_attribute().borrow(); let style_attribute = if let Some(ref lock) = *style_attribute { lock.read() @@ -134,7 +148,7 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { return DOMString::new() }; - if style_attribute.property_priority(&property).important() { + if style_attribute.property_priority(&id).important() { DOMString::from("important") } else { // Step 4 @@ -154,9 +168,13 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { } // Step 3 - if !is_supported_property(&property) { - return Ok(()); - } + // FIXME: give ownership on `property` here when parse_one_declaration can take &PropertyId + let id = if let Ok(id) = PropertyId::parse((&*property).into()) { + id + } else { + // Unkwown property + return Ok(()) + }; let mut style_attribute = self.owner.style_attribute().borrow_mut(); @@ -171,7 +189,7 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { return Ok(()) }; - style_attribute.remove_property(&property); + style_attribute.remove_property(&id); empty = style_attribute.declarations.is_empty() } if empty { @@ -237,9 +255,12 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { } // Step 2 & 3 - if !is_supported_property(&property) { - return Ok(()); - } + let id = if let Ok(id) = PropertyId::parse(property.into()) { + id + } else { + // Unkwown property + return Ok(()) + }; // Step 4 let importance = match &*priority { @@ -253,10 +274,7 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { let mut style_attribute = lock.write(); // Step 5 & 6 - match Shorthand::from_name(&property) { - Some(shorthand) => style_attribute.set_importance(shorthand.longhands(), importance), - None => style_attribute.set_importance(&[&*property], importance), - } + style_attribute.set_importance(&id, importance); self.owner.set_style_attr(style_attribute.to_css_string()); let node = self.owner.upcast::(); @@ -277,6 +295,13 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { return Err(Error::NoModificationAllowed); } + let id = if let Ok(id) = PropertyId::parse(property.into()) { + id + } else { + // Unkwown property, cannot be there to remove. + return Ok(DOMString::new()) + }; + let mut style_attribute = self.owner.style_attribute().borrow_mut(); let mut string = String::new(); let empty; @@ -289,10 +314,10 @@ impl CSSStyleDeclarationMethods for CSSStyleDeclaration { }; // Step 3 - style_attribute.property_value_to_css(&property, &mut string).unwrap(); + style_attribute.property_value_to_css(&id, &mut string).unwrap(); // Step 4 & 5 - style_attribute.remove_property(&property); + style_attribute.remove_property(&id); self.owner.set_style_attr(style_attribute.to_css_string()); empty = style_attribute.declarations.is_empty() } diff --git a/components/style/gecko_string_cache/mod.rs b/components/style/gecko_string_cache/mod.rs index a87982506995..5165b21f5d40 100644 --- a/components/style/gecko_string_cache/mod.rs +++ b/components/style/gecko_string_cache/mod.rs @@ -9,10 +9,9 @@ use gecko_bindings::bindings::Gecko_Atomize; use gecko_bindings::bindings::Gecko_ReleaseAtom; use gecko_bindings::structs::nsIAtom; use heapsize::HeapSizeOf; -use std::ascii::AsciiExt; use std::borrow::{Cow, Borrow}; use std::char::{self, DecodeUtf16}; -use std::fmt; +use std::fmt::{self, Write}; use std::hash::{Hash, Hasher}; use std::iter::Cloned; use std::mem; @@ -111,14 +110,6 @@ impl WeakAtom { cb(&owned) } - #[inline] - pub fn eq_str_ignore_ascii_case(&self, s: &str) -> bool { - self.chars().map(|r| match r { - Ok(c) => c.to_ascii_lowercase() as u32, - Err(e) => e.unpaired_surrogate() as u32, - }).eq(s.chars().map(|c| c.to_ascii_lowercase() as u32)) - } - #[inline] pub fn to_string(&self) -> String { String::from_utf16(self.as_slice()).unwrap() @@ -154,7 +145,7 @@ impl fmt::Debug for WeakAtom { impl fmt::Display for WeakAtom { fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result { for c in self.chars() { - try!(write!(w, "{}", c.unwrap_or(char::REPLACEMENT_CHARACTER))) + try!(w.write_char(c.unwrap_or(char::REPLACEMENT_CHARACTER))) } Ok(()) } diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs index adabb526797b..6c6ec7348d92 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -7,7 +7,6 @@ use cssparser::{Parser, AtRuleParser, DeclarationParser, Delimiter}; use error_reporting::ParseErrorReporter; use parser::{ParserContext, ParserContextExtraData, log_css_error}; use servo_url::ServoUrl; -use std::ascii::AsciiExt; use std::boxed::Box as StdBox; use std::fmt; use style_traits::ToCss; @@ -65,90 +64,94 @@ impl PropertyDeclarationBlock { self.declarations.len() > self.important_count as usize } - pub fn get(&self, property_name: &str) -> Option< &(PropertyDeclaration, Importance)> { - self.declarations.iter().find(|&&(ref decl, _)| decl.matches(property_name)) + pub fn get(&self, property: PropertyDeclarationId) -> Option< &(PropertyDeclaration, Importance)> { + self.declarations.iter().find(|&&(ref decl, _)| decl.id() == property) } /// Find the value of the given property in this block and serialize it /// /// https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue - pub fn property_value_to_css(&self, property_name: &str, dest: &mut W) -> fmt::Result + pub fn property_value_to_css(&self, property: &PropertyId, dest: &mut W) -> fmt::Result where W: fmt::Write { - // Step 1 - let property = property_name.to_ascii_lowercase(); + // Step 1: done when parsing a string to PropertyId // Step 2 - if let Some(shorthand) = Shorthand::from_name(&property) { - // Step 2.1 - let mut list = Vec::new(); - let mut important_count = 0; - - // Step 2.2 - for longhand in shorthand.longhands() { - // Step 2.2.1 - let declaration = self.get(longhand); - - // Step 2.2.2 & 2.2.3 - match declaration { - Some(&(ref declaration, importance)) => { - list.push(declaration); - if importance.important() { - important_count += 1; - } - }, - None => return Ok(()), + match property.as_shorthand() { + Ok(shorthand) => { + // Step 2.1 + let mut list = Vec::new(); + let mut important_count = 0; + + // Step 2.2 + for &longhand in shorthand.longhands() { + // Step 2.2.1 + let declaration = self.get(PropertyDeclarationId::Longhand(longhand)); + + // Step 2.2.2 & 2.2.3 + match declaration { + Some(&(ref declaration, importance)) => { + list.push(declaration); + if importance.important() { + important_count += 1; + } + }, + None => return Ok(()), + } } - } - // Step 3.3.2.4 - // If there is one or more longhand with important, and one or more - // without important, we don't serialize it as a shorthand. - if important_count > 0 && important_count != list.len() { - return Ok(()); - } - - // Step 2.3 - // We don't print !important when serializing individual properties, - // so we treat this as a normal-importance property - let importance = Importance::Normal; - let appendable_value = shorthand.get_shorthand_appendable_value(list).unwrap(); - return append_declaration_value(dest, appendable_value, importance) - } + // Step 3.3.2.4 + // If there is one or more longhand with important, and one or more + // without important, we don't serialize it as a shorthand. + if important_count > 0 && important_count != list.len() { + return Ok(()); + } - if let Some(&(ref value, _importance)) = self.get(property_name) { - // Step 3 - value.to_css(dest) - } else { - // Step 4 - Ok(()) + // Step 2.3 + // We don't print !important when serializing individual properties, + // so we treat this as a normal-importance property + let importance = Importance::Normal; + let appendable_value = shorthand.get_shorthand_appendable_value(list).unwrap(); + append_declaration_value(dest, appendable_value, importance) + } + Err(longhand_or_custom) => { + if let Some(&(ref value, _importance)) = self.get(longhand_or_custom) { + // Step 3 + value.to_css(dest) + } else { + // Step 4 + Ok(()) + } + } } } /// https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority - pub fn property_priority(&self, property_name: &str) -> Importance { - // Step 1 - let property = property_name.to_ascii_lowercase(); + pub fn property_priority(&self, property: &PropertyId) -> Importance { + // Step 1: done when parsing a string to PropertyId // Step 2 - if let Some(shorthand) = Shorthand::from_name(&property) { - // Step 2.1 & 2.2 & 2.3 - if shorthand.longhands().iter().all(|l| { - self.get(l).map_or(false, |&(_, importance)| importance.important()) - }) { - Importance::Important - } else { - Importance::Normal + match property.as_shorthand() { + Ok(shorthand) => { + // Step 2.1 & 2.2 & 2.3 + if shorthand.longhands().iter().all(|&l| { + self.get(PropertyDeclarationId::Longhand(l)) + .map_or(false, |&(_, importance)| importance.important()) + }) { + Importance::Important + } else { + Importance::Normal + } + } + Err(longhand_or_custom) => { + // Step 3 + self.get(longhand_or_custom).map_or(Importance::Normal, |&(_, importance)| importance) } - } else { - // Step 3 - self.get(&property).map_or(Importance::Normal, |&(_, importance)| importance) } } - pub fn set_parsed_declaration(&mut self, declaration: PropertyDeclaration, - importance: Importance) { + pub fn set_parsed_declaration(&mut self, declaration: PropertyDeclaration, importance: Importance) { for slot in &mut *self.declarations { - if slot.0.name() == declaration.name() { + if slot.0.id() == declaration.id() { match (slot.1, importance) { (Importance::Normal, Importance::Important) => { self.important_count += 1; @@ -169,9 +172,9 @@ impl PropertyDeclarationBlock { } } - pub fn set_importance(&mut self, property_names: &[&str], new_importance: Importance) { + pub fn set_importance(&mut self, property: &PropertyId, new_importance: Importance) { for &mut (ref declaration, ref mut importance) in &mut self.declarations { - if property_names.iter().any(|p| declaration.matches(p)) { + if declaration.id().is_or_is_longhand_of(property) { match (*importance, new_importance) { (Importance::Normal, Importance::Important) => { self.important_count += 1; @@ -187,44 +190,34 @@ impl PropertyDeclarationBlock { } /// https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty - pub fn remove_property(&mut self, property_name: &str) { - // Step 2 - let property = property_name.to_ascii_lowercase(); - - match Shorthand::from_name(&property) { - // Step 4 - Some(shorthand) => self.remove_longhands(shorthand.longhands()), - // Step 5 - None => self.remove_longhands(&[&*property]), - } - } - - fn remove_longhands(&mut self, names: &[&str]) { + pub fn remove_property(&mut self, property: &PropertyId) { let important_count = &mut self.important_count; self.declarations.retain(|&(ref declaration, importance)| { - let retain = !names.iter().any(|n| declaration.matches(n)); - if !retain && importance.important() { + let remove = declaration.id().is_or_is_longhand_of(property); + if remove && importance.important() { *important_count -= 1 } - retain + !remove }) } /// Take a declaration block known to contain a single property and serialize it. - pub fn single_value_to_css(&self, property_name: &str, dest: &mut W) -> fmt::Result + pub fn single_value_to_css(&self, property: &PropertyId, dest: &mut W) -> fmt::Result where W: fmt::Write { - match self.declarations.len() { - 0 => Err(fmt::Error), - 1 if self.declarations[0].0.name().eq_str_ignore_ascii_case(property_name) => { - self.declarations[0].0.to_css(dest) + match property.as_shorthand() { + Err(_longhand_or_custom) => { + if self.declarations.len() == 1 { + self.declarations[0].0.to_css(dest) + } else { + Err(fmt::Error) + } } - _ => { + Ok(shorthand) => { // we use this function because a closure won't be `Clone` fn get_declaration(dec: &(PropertyDeclaration, Importance)) -> &PropertyDeclaration { &dec.0 } - let shorthand = try!(Shorthand::from_name(property_name).ok_or(fmt::Error)); if !self.declarations.iter().all(|decl| decl.0.shorthands().contains(&shorthand)) { return Err(fmt::Error) } @@ -254,7 +247,7 @@ impl ToCss for PropertyDeclarationBlock { // Step 3 for &(ref declaration, importance) in &*self.declarations { // Step 3.1 - let property = declaration.name(); + let property = declaration.id(); // Step 3.2 if already_serialized.contains(&property) { @@ -266,11 +259,11 @@ impl ToCss for PropertyDeclarationBlock { if !shorthands.is_empty() { // Step 3.3.1 let mut longhands = self.declarations.iter() - .filter(|d| !already_serialized.contains(&d.0.name())) + .filter(|d| !already_serialized.contains(&d.0.id())) .collect::>(); // Step 3.3.2 - for shorthand in shorthands { + for &shorthand in shorthands { let properties = shorthand.longhands(); // Substep 2 & 3 @@ -278,8 +271,7 @@ impl ToCss for PropertyDeclarationBlock { let mut important_count = 0; for &&(ref longhand, longhand_importance) in longhands.iter() { - let longhand_name = longhand.name(); - if properties.iter().any(|p| &longhand_name == *p) { + if longhand.id().is_longhand_of(shorthand) { current_longhands.push(longhand); if longhand_importance.important() { important_count += 1; @@ -325,7 +317,7 @@ impl ToCss for PropertyDeclarationBlock { for current_longhand in current_longhands { // Substep 9 - already_serialized.push(current_longhand.name()); + already_serialized.push(current_longhand.id()); let index_to_remove = longhands.iter().position(|l| l.0 == *current_longhand); if let Some(index) = index_to_remove { // Substep 10 @@ -348,9 +340,9 @@ impl ToCss for PropertyDeclarationBlock { // "error: unable to infer enough type information about `_`; // type annotations or generic parameter binding required [E0282]" // Use the same type as earlier call to reuse generated code. - try!(append_serialization::>>( + try!(append_serialization::>, _>( dest, - &property.to_string(), + &property, AppendableValue::Declaration(declaration), importance, &mut is_first_serialization)); @@ -367,7 +359,7 @@ impl ToCss for PropertyDeclarationBlock { pub enum AppendableValue<'a, I> where I: Iterator { Declaration(&'a PropertyDeclaration), - DeclarationsForShorthand(Shorthand, I), + DeclarationsForShorthand(ShorthandId, I), Css(&'a str) } @@ -407,13 +399,15 @@ pub fn append_declaration_value<'a, W, I> Ok(()) } -pub fn append_serialization<'a, W, I>(dest: &mut W, - property_name: &str, +pub fn append_serialization<'a, W, I, N>(dest: &mut W, + property_name: &N, appendable_value: AppendableValue<'a, I>, importance: Importance, is_first_serialization: &mut bool) -> fmt::Result - where W: fmt::Write, I: Iterator { + where W: fmt::Write, + I: Iterator, + N: ToCss { try!(handle_first_serialization(dest, is_first_serialization)); // Overflow does not behave like a normal shorthand. When overflow-x and overflow-y are not of equal @@ -422,7 +416,8 @@ pub fn append_serialization<'a, W, I>(dest: &mut W, return append_declaration_value(dest, appendable_value, importance); } - try!(write!(dest, "{}:", property_name)); + try!(property_name.to_css(dest)); + try!(dest.write_char(':')); // for normal parsed values, add a space between key: and value match &appendable_value { diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index 5d58fd6db5c8..6225c5b63e63 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -68,7 +68,7 @@ pub mod single_value { use cssparser::Parser; use parser::{Parse, ParserContext, ParserContextExtraData}; - use properties::{CSSWideKeyword, DeclaredValue, Shorthand}; + use properties::{CSSWideKeyword, DeclaredValue, ShorthandId}; use values::computed::{Context, ToComputedValue}; use values::{computed, specified}; ${caller.body()} @@ -182,7 +182,7 @@ % if not property.derived_from: use cssparser::Parser; use parser::{Parse, ParserContext, ParserContextExtraData}; - use properties::{CSSWideKeyword, DeclaredValue, Shorthand}; + use properties::{CSSWideKeyword, DeclaredValue, ShorthandId}; % endif use values::{Auto, Either, None_, Normal}; use cascade_info::CascadeInfo; @@ -380,7 +380,7 @@ #[allow(unused_imports)] use cssparser::Parser; use parser::ParserContext; - use properties::{longhands, PropertyDeclaration, DeclaredValue, Shorthand}; + use properties::{longhands, PropertyDeclaration, DeclaredValue, ShorthandId}; use std::fmt; use style_traits::ToCss; @@ -500,7 +500,7 @@ css: css.clone().into_owned(), first_token_type: first_token_type, base_url: context.base_url.clone(), - from_shorthand: Some(Shorthand::${shorthand.camel_case}), + from_shorthand: Some(ShorthandId::${shorthand.camel_case}), } )); % endfor diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 46688434651e..a14da5262a5b 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -10,7 +10,7 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> -use std::ascii::AsciiExt; +use std::borrow::Cow; use std::boxed::Box as StdBox; use std::collections::HashSet; use std::fmt::{self, Write}; @@ -40,6 +40,12 @@ use rule_tree::StrongRuleNode; use self::property_bit_field::PropertyBitField; pub use self::declaration_block::*; +#[cfg(feature = "gecko")] +#[macro_export] +macro_rules! property_name { + ($s: tt) => { atom!($s) } +} + <%! from data import Method, Keyword, to_rust_ident import os.path @@ -242,7 +248,7 @@ mod property_bit_field { css: &String, first_token_type: TokenSerializationType, base_url: &ServoUrl, - from_shorthand: Option, + from_shorthand: Option, custom_properties: &Option>, f: F, error_reporter: &mut StdBox, @@ -265,7 +271,7 @@ mod property_bit_field { } % for shorthand in data.shorthands: % if property in shorthand.sub_properties: - Some(Shorthand::${shorthand.camel_case}) => { + Some(ShorthandId::${shorthand.camel_case}) => { shorthands::${shorthand.ident}::parse_value(&context, input) .map(|result| match result.${property.ident} { Some(value) => DeclaredValue::Value(value), @@ -378,41 +384,76 @@ impl Parse for CSSWideKeyword { #[derive(Clone, Copy, Eq, PartialEq, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub enum Shorthand { +pub enum LonghandId { + % for i, property in enumerate(data.longhands): + ${property.camel_case} = ${i}, + % endfor +} + +impl LonghandId { + pub fn from_ascii_lowercase_name(name: &str) -> Result { + // FIXME: use rust-phf? + match name { + % for property in data.longhands: + "${property.name}" => Ok(LonghandId::${property.camel_case}), + % endfor + _ => Err(()) + } + } + + pub fn name(&self) -> &'static str { + match *self { + % for property in data.longhands: + LonghandId::${property.camel_case} => "${property.name}", + % endfor + } + } +} + +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum ShorthandId { % for property in data.shorthands: ${property.camel_case}, % endfor } -impl Shorthand { - pub fn from_name(name: &str) -> Option { - match_ignore_ascii_case! { name, +impl ToCss for ShorthandId { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + dest.write_str(self.name()) + } +} + +impl ShorthandId { + pub fn from_ascii_lowercase_name(name: &str) -> Result { + // FIXME: use rust-phf? + match name { % for property in data.shorthands: - "${property.name}" => Some(Shorthand::${property.camel_case}), + "${property.name}" => Ok(ShorthandId::${property.camel_case}), % endfor - _ => None + _ => Err(()) } } pub fn name(&self) -> &'static str { match *self { % for property in data.shorthands: - Shorthand::${property.camel_case} => "${property.name}", + ShorthandId::${property.camel_case} => "${property.name}", % endfor } } - pub fn longhands(&self) -> &'static [&'static str] { + pub fn longhands(&self) -> &'static [LonghandId] { % for property in data.shorthands: - static ${property.ident.upper()}: &'static [&'static str] = &[ + static ${property.ident.upper()}: &'static [LonghandId] = &[ % for sub in property.sub_properties: - "${sub.name}", + LonghandId::${sub.camel_case}, % endfor ]; % endfor match *self { % for property in data.shorthands: - Shorthand::${property.camel_case} => ${property.ident.upper()}, + ShorthandId::${property.camel_case} => ${property.ident.upper()}, % endfor } } @@ -421,7 +462,7 @@ impl Shorthand { where W: fmt::Write, I: Iterator { match *self { % for property in data.shorthands: - Shorthand::${property.camel_case} => { + ShorthandId::${property.camel_case} => { match shorthands::${property.ident}::LonghandsToSerialize::from_iter(declarations) { Ok(longhands) => longhands.to_css(dest), Err(_) => Err(fmt::Error) @@ -443,11 +484,9 @@ impl Shorthand { match self.get_shorthand_appendable_value(declarations) { None => Ok(false), Some(appendable_value) => { - let property_name = self.name(); - append_serialization( dest, - property_name, + &self, appendable_value, importance, is_first_serialization @@ -496,7 +535,7 @@ pub enum DeclaredValue { css: String, first_token_type: TokenSerializationType, base_url: ServoUrl, - from_shorthand: Option, + from_shorthand: Option, }, Initial, Inherit, @@ -533,6 +572,88 @@ impl ToCss for DeclaredValue { } } +#[derive(PartialEq, Clone)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub enum PropertyDeclarationId<'a> { + Longhand(LonghandId), + Custom(&'a ::custom_properties::Name), +} + +impl<'a> ToCss for PropertyDeclarationId<'a> { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + PropertyDeclarationId::Longhand(id) => dest.write_str(id.name()), + PropertyDeclarationId::Custom(name) => write!(dest, "--{}", name), + } + } +} + +impl<'a> PropertyDeclarationId<'a> { + pub fn is_or_is_longhand_of(&self, other: &PropertyId) -> bool { + match *self { + PropertyDeclarationId::Longhand(id) => { + match *other { + PropertyId::Longhand(other_id) => id == other_id, + PropertyId::Shorthand(shorthand) => shorthand.longhands().contains(&id), + PropertyId::Custom(_) => false, + } + } + PropertyDeclarationId::Custom(name) => { + matches!(*other, PropertyId::Custom(ref other_name) if name == other_name) + } + } + } + + pub fn is_longhand_of(&self, shorthand: ShorthandId) -> bool { + match *self { + PropertyDeclarationId::Longhand(ref id) => shorthand.longhands().contains(id), + _ => false, + } + } +} + +#[derive(Eq, PartialEq, Clone)] +pub enum PropertyId { + Longhand(LonghandId), + Shorthand(ShorthandId), + Custom(::custom_properties::Name), +} + +impl ToCss for PropertyId { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + PropertyId::Longhand(id) => dest.write_str(id.name()), + PropertyId::Shorthand(id) => dest.write_str(id.name()), + PropertyId::Custom(ref name) => write!(dest, "--{}", name), + } + } +} + +impl PropertyId { + /// Returns Err(()) for unknown non-custom properties + pub fn parse(s: Cow) -> Result { + if let Ok(name) = ::custom_properties::parse_name(&s) { + return Ok(PropertyId::Custom(::custom_properties::Name::from(name))) + } + let lower_case = ::str::cow_into_ascii_lowercase(s); + if let Ok(id) = LonghandId::from_ascii_lowercase_name(&lower_case) { + Ok(PropertyId::Longhand(id)) + } else if let Ok(id) = ShorthandId::from_ascii_lowercase_name(&lower_case) { + Ok(PropertyId::Shorthand(id)) + } else { + Err(()) + } + } + + pub fn as_shorthand(&self) -> Result { + match *self { + PropertyId::Shorthand(id) => Ok(id), + PropertyId::Longhand(id) => Err(PropertyDeclarationId::Longhand(id)), + PropertyId::Custom(ref name) => Err(PropertyDeclarationId::Custom(name)), + } + } +} + #[derive(PartialEq, Clone)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum PropertyDeclaration { @@ -566,51 +687,10 @@ pub enum PropertyDeclarationParseResult { ValidOrIgnoredDeclaration, } -#[derive(Eq, PartialEq, Clone)] -pub enum PropertyDeclarationName { - Longhand(&'static str), - Custom(::custom_properties::Name), - Internal -} - -impl PropertyDeclarationName { - pub fn eq_str_ignore_ascii_case(&self, other: &str) -> bool { - match *self { - PropertyDeclarationName::Longhand(s) => s.eq_ignore_ascii_case(other), - PropertyDeclarationName::Custom(ref n) => n.eq_str_ignore_ascii_case(other), - PropertyDeclarationName::Internal => false - } - } -} - -impl PartialEq for PropertyDeclarationName { - fn eq(&self, other: &str) -> bool { - match *self { - PropertyDeclarationName::Longhand(n) => n == other, - PropertyDeclarationName::Custom(ref n) => { - n.with_str(|s| ::custom_properties::parse_name(other) == Ok(s)) - } - PropertyDeclarationName::Internal => false, - } - } -} - -impl fmt::Display for PropertyDeclarationName { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - PropertyDeclarationName::Longhand(n) => f.write_str(n), - PropertyDeclarationName::Custom(ref n) => { - try!(f.write_str("--")); - n.with_str(|s| f.write_str(s)) - } - PropertyDeclarationName::Internal => Ok(()), - } - } -} - impl fmt::Debug for PropertyDeclaration { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(write!(f, "{}: ", self.name())); + try!(self.id().to_css(f)); + try!(f.write_str(": ")); match *self { % for property in data.longhands: % if not property.derived_from: @@ -643,45 +723,20 @@ impl ToCss for PropertyDeclaration { } impl PropertyDeclaration { - pub fn name(&self) -> PropertyDeclarationName { + pub fn id(&self) -> PropertyDeclarationId { match *self { % for property in data.longhands: - PropertyDeclaration::${property.camel_case}(..) => - % if not property.derived_from: - PropertyDeclarationName::Longhand("${property.name}"), - % else: - PropertyDeclarationName::Internal, - % endif + PropertyDeclaration::${property.camel_case}(..) => { + PropertyDeclarationId::Longhand(LonghandId::${property.camel_case}) + } % endfor PropertyDeclaration::Custom(ref name, _) => { - PropertyDeclarationName::Custom(name.clone()) + PropertyDeclarationId::Custom(name) } } } - #[inline] - pub fn discriminant_value(&self) -> usize { - match *self { - % for i, property in enumerate(data.longhands): - PropertyDeclaration::${property.camel_case}(..) => ${i}, - % endfor - PropertyDeclaration::Custom(..) => ${len(data.longhands)} - } - } - - pub fn value(&self) -> String { - let mut value = String::new(); - if let Err(_) = self.to_css(&mut value) { - panic!("unsupported property declaration: {}", self.name()); - } - - value - } - - /// If this is a pending-substitution value from the given shorthand, return that value - // Extra space here because < seems to be removed by Mako when immediately followed by &. - // ↓ - pub fn with_variables_from_shorthand(&self, shorthand: Shorthand) -> Option< &str> { + pub fn with_variables_from_shorthand(&self, shorthand: ShorthandId) -> Option< &str> { match *self { % for property in data.longhands: PropertyDeclaration::${property.camel_case}(ref value) => match *value { @@ -727,22 +782,6 @@ impl PropertyDeclaration { } } - pub fn matches(&self, name: &str) -> bool { - match *self { - % for property in data.longhands: - PropertyDeclaration::${property.camel_case}(..) => - % if not property.derived_from: - name.eq_ignore_ascii_case("${property.name}"), - % else: - false, - % endif - % endfor - PropertyDeclaration::Custom(ref declaration_name, _) => { - declaration_name.with_str(|s| ::custom_properties::parse_name(name) == Ok(s)) - } - } - } - /// The `in_keyframe_block` parameter controls this: /// /// https://drafts.csswg.org/css-animations/#keyframes @@ -858,7 +897,7 @@ impl PropertyDeclaration { } } - pub fn shorthands(&self) -> &'static [Shorthand] { + pub fn shorthands(&self) -> &'static [ShorthandId] { // first generate longhand to shorthands lookup map <% longhand_to_shorthand_map = {} @@ -875,9 +914,9 @@ impl PropertyDeclaration { // based on lookup results for each longhand, create result arrays % for property in data.longhands: - static ${property.ident.upper()}: &'static [Shorthand] = &[ + static ${property.ident.upper()}: &'static [ShorthandId] = &[ % for shorthand in longhand_to_shorthand_map.get(property.ident, []): - Shorthand::${shorthand}, + ShorthandId::${shorthand}, % endfor ]; % endfor @@ -1576,9 +1615,11 @@ pub fn apply_declarations<'a, F, I>(viewport_size: Size2D, ComputedValues::do_cascade_property(|cascade_property| { % for category_to_cascade_now in ["early", "other"]: for declaration in iter_declarations() { - if let PropertyDeclaration::Custom(..) = *declaration { - continue - } + let longhand_id = match declaration.id() { + PropertyDeclarationId::Longhand(id) => id, + PropertyDeclarationId::Custom(..) => continue, + }; + // The computed value of some properties depends on the // (sometimes computed) value of *other* properties. // @@ -1610,7 +1651,7 @@ pub fn apply_declarations<'a, F, I>(viewport_size: Size2D, continue } - let discriminant = declaration.discriminant_value(); + let discriminant = longhand_id as usize; (cascade_property[discriminant])(declaration, inherited_style, &mut context, @@ -1948,16 +1989,6 @@ pub fn modify_style_for_inline_absolute_hypothetical_fragment(style: &mut Arc bool { - match_ignore_ascii_case! { property, - % for property in data.shorthands + data.longhands: - "${property.name}" => true, - % endfor - _ => property.starts_with("--") - } -} - #[macro_export] macro_rules! css_properties_accessors { ($macro_name: ident) => { diff --git a/components/style/properties/shorthand/serialize.mako.rs b/components/style/properties/shorthand/serialize.mako.rs index e061796b80da..3dbf39a8ac34 100644 --- a/components/style/properties/shorthand/serialize.mako.rs +++ b/components/style/properties/shorthand/serialize.mako.rs @@ -2,7 +2,7 @@ * 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 properties::{AppendableValue, DeclaredValue, PropertyDeclaration, Shorthand}; +use properties::{AppendableValue, DeclaredValue, PropertyDeclaration, ShorthandId}; use style_traits::ToCss; use values::specified::{BorderStyle, CSSColor}; use std::fmt; @@ -88,7 +88,7 @@ fn serialize_directional_border(dest: &mut W, pub fn is_overflow_shorthand<'a, I>(appendable_value: &AppendableValue<'a, I>) -> bool where I: Iterator { if let AppendableValue::DeclarationsForShorthand(shorthand, _) = *appendable_value { - if let Shorthand::Overflow = shorthand { + if let ShorthandId::Overflow = shorthand { return true; } } diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 36c309dcdd04..bdb0cc4539e3 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -50,6 +50,7 @@ use style::parallel; use style::parser::{ParserContext, ParserContextExtraData}; use style::properties::{CascadeFlags, ComputedValues, Importance, PropertyDeclaration}; use style::properties::{PropertyDeclarationParseResult, PropertyDeclarationBlock}; +use style::properties::{PropertyId, LonghandId, ShorthandId}; use style::properties::{apply_declarations, parse_one_declaration}; use style::restyle_hints::RestyleHint; use style::selector_parser::PseudoElementCascadeType; @@ -610,7 +611,7 @@ pub extern "C" fn Servo_DeclarationBlock_SerializeOneValue( buffer: *mut nsAString) { let declarations = RwLock::::as_arc(&declarations); - let property = get_property_name_from_atom(property, is_custom); + let property = get_property_id_from_atom(property, is_custom); let mut string = String::new(); let rv = declarations.read().single_value_to_css(&property, &mut string); debug_assert!(rv.is_ok()); @@ -630,23 +631,28 @@ pub extern "C" fn Servo_DeclarationBlock_GetNthProperty(declarations: RawServoDe let declarations = RwLock::::as_arc(&declarations); if let Some(&(ref decl, _)) = declarations.read().declarations.get(index as usize) { let result = unsafe { result.as_mut().unwrap() }; - write!(result, "{}", decl.name()).unwrap(); + decl.id().to_css(result).unwrap(); true } else { false } } -// FIXME Methods of PropertyDeclarationBlock should take atoms directly. -// This function is just a temporary workaround before that finishes. -fn get_property_name_from_atom(atom: *mut nsIAtom, is_custom: bool) -> String { +fn get_property_id_from_atom(atom: *mut nsIAtom, is_custom: bool) -> PropertyId { let atom = Atom::from(atom); if !is_custom { - atom.to_string() + // FIXME: can we do this mapping without going through a UTF-8 string? + // Maybe even from nsCSSPropertyID directly? + let s = atom.to_string(); + if let Ok(id) = LonghandId::from_ascii_lowercase_name(&s) { + PropertyId::Longhand(id) + } else if let Ok(id) = ShorthandId::from_ascii_lowercase_name(&s) { + PropertyId::Shorthand(id) + } else { + panic!("got unknown property name {:?} from Gecko", s) + } } else { - let mut result = String::with_capacity(atom.len() as usize + 2); - write!(result, "--{}", atom).unwrap(); - result + PropertyId::Custom(atom) } } @@ -655,7 +661,7 @@ pub extern "C" fn Servo_DeclarationBlock_GetPropertyValue(declarations: RawServo property: *mut nsIAtom, is_custom: bool, value: *mut nsAString) { let declarations = RwLock::::as_arc(&declarations); - let property = get_property_name_from_atom(property, is_custom); + let property = get_property_id_from_atom(property, is_custom); declarations.read().property_value_to_css(&property, unsafe { value.as_mut().unwrap() }).unwrap(); } @@ -663,7 +669,7 @@ pub extern "C" fn Servo_DeclarationBlock_GetPropertyValue(declarations: RawServo pub extern "C" fn Servo_DeclarationBlock_GetPropertyIsImportant(declarations: RawServoDeclarationBlockBorrowed, property: *mut nsIAtom, is_custom: bool) -> bool { let declarations = RwLock::::as_arc(&declarations); - let property = get_property_name_from_atom(property, is_custom); + let property = get_property_id_from_atom(property, is_custom); declarations.read().property_priority(&property).important() } @@ -671,12 +677,12 @@ pub extern "C" fn Servo_DeclarationBlock_GetPropertyIsImportant(declarations: Ra pub extern "C" fn Servo_DeclarationBlock_SetProperty(declarations: RawServoDeclarationBlockBorrowed, property: *mut nsIAtom, is_custom: bool, value: *mut nsACString, is_important: bool) -> bool { - let property = get_property_name_from_atom(property, is_custom); + let property = get_property_id_from_atom(property, is_custom); let value = unsafe { value.as_ref().unwrap().as_str_unchecked() }; // FIXME Needs real URL and ParserContextExtraData. let base_url = &*DUMMY_BASE_URL; let extra_data = ParserContextExtraData::default(); - if let Ok(decls) = parse_one_declaration(&property, value, &base_url, + if let Ok(decls) = parse_one_declaration(&property.to_css_string(), value, &base_url, Box::new(StdoutErrorReporter), extra_data) { let mut declarations = RwLock::::as_arc(&declarations).write(); let importance = if is_important { Importance::Important } else { Importance::Normal }; @@ -693,7 +699,7 @@ pub extern "C" fn Servo_DeclarationBlock_SetProperty(declarations: RawServoDecla pub extern "C" fn Servo_DeclarationBlock_RemoveProperty(declarations: RawServoDeclarationBlockBorrowed, property: *mut nsIAtom, is_custom: bool) { let declarations = RwLock::::as_arc(&declarations); - let property = get_property_name_from_atom(property, is_custom); + let property = get_property_id_from_atom(property, is_custom); declarations.write().remove_property(&property); } diff --git a/tests/unit/style/properties/serialization.rs b/tests/unit/style/properties/serialization.rs index 6780716e8761..c27fe00dc61e 100644 --- a/tests/unit/style/properties/serialization.rs +++ b/tests/unit/style/properties/serialization.rs @@ -4,7 +4,7 @@ pub use std::sync::Arc; pub use style::computed_values::display::T::inline_block; -pub use style::properties::{DeclaredValue, PropertyDeclaration, PropertyDeclarationBlock, Importance}; +pub use style::properties::{DeclaredValue, PropertyDeclaration, PropertyDeclarationBlock, Importance, PropertyId}; pub use style::values::specified::{BorderStyle, BorderWidth, CSSColor, Length}; pub use style::values::specified::{LengthOrPercentage, LengthOrPercentageOrAuto, LengthOrPercentageOrAutoOrContent}; pub use style::properties::longhands::outline_color::computed_value::T as ComputedColor; @@ -1027,7 +1027,8 @@ mod shorthand_serialization { let mut s = String::new(); - let x = block.single_value_to_css("scroll-snap-type", &mut s); + let id = PropertyId::parse("scroll-snap-type".into()).unwrap(); + let x = block.single_value_to_css(&id, &mut s); assert_eq!(x.is_ok(), true); assert_eq!(s, ""); @@ -1049,7 +1050,8 @@ mod shorthand_serialization { let mut s = String::new(); - let x = block.single_value_to_css("scroll-snap-type", &mut s); + let id = PropertyId::parse("scroll-snap-type".into()).unwrap(); + let x = block.single_value_to_css(&id, &mut s); assert_eq!(x.is_ok(), true); assert_eq!(s, "mandatory"); diff --git a/tests/unit/style/stylesheets.rs b/tests/unit/style/stylesheets.rs index 476d2d4ecd45..a5a5be5a7d1c 100644 --- a/tests/unit/style/stylesheets.rs +++ b/tests/unit/style/stylesheets.rs @@ -16,8 +16,8 @@ use std::sync::atomic::AtomicBool; use style::error_reporting::ParseErrorReporter; use style::keyframes::{Keyframe, KeyframeSelector, KeyframePercentage}; use style::parser::ParserContextExtraData; -use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, DeclaredValue, longhands}; use style::properties::Importance; +use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, DeclaredValue, longhands}; use style::properties::longhands::animation_play_state; use style::stylesheets::{Origin, Namespaces}; use style::stylesheets::{Stylesheet, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};