diff options
-rw-r--r-- | components/style/gecko/media_queries.rs | 7 | ||||
-rw-r--r-- | components/style/keyframes.rs | 9 | ||||
-rw-r--r-- | components/style/str.rs | 7 | ||||
-rw-r--r-- | components/style/stylesheets.rs | 30 | ||||
-rw-r--r-- | components/style/stylist.rs | 15 | ||||
-rw-r--r-- | tests/unit/style/keyframes.rs | 25 | ||||
-rw-r--r-- | tests/unit/style/stylesheets.rs | 3 |
7 files changed, 73 insertions, 23 deletions
diff --git a/components/style/gecko/media_queries.rs b/components/style/gecko/media_queries.rs index ffa3c95ed4f..4aff56ee8d5 100644 --- a/components/style/gecko/media_queries.rs +++ b/components/style/gecko/media_queries.rs @@ -16,9 +16,9 @@ use gecko_bindings::structs::RawGeckoPresContextOwned; use media_queries::MediaType; use parser::ParserContext; use properties::ComputedValues; -use std::ascii::AsciiExt; use std::fmt::{self, Write}; use std::sync::Arc; +use str::starts_with_ignore_ascii_case; use string_cache::Atom; use style_traits::ToCss; use style_traits::viewport::ViewportConstraints; @@ -340,11 +340,6 @@ impl MediaExpressionValue { } } -fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool { - string.len() > prefix.len() && - string[0..prefix.len()].eq_ignore_ascii_case(prefix) -} - fn find_feature<F>(mut f: F) -> Option<&'static nsMediaFeature> where F: FnMut(&'static nsMediaFeature) -> bool, { diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 49f2cad25b7..542481b287c 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -18,7 +18,7 @@ use shared_lock::{SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard}; use std::fmt; use std::sync::Arc; use style_traits::ToCss; -use stylesheets::{CssRuleType, MemoryHoleReporter, Stylesheet}; +use stylesheets::{CssRuleType, MemoryHoleReporter, Stylesheet, VendorPrefix}; /// A number from 0 to 1, indicating the percentage of the animation when this /// keyframe should run. @@ -239,6 +239,8 @@ pub struct KeyframesAnimation { pub steps: Vec<KeyframesStep>, /// The properties that change in this animation. pub properties_changed: Vec<TransitionProperty>, + /// Vendor prefix type the @keyframes has. + pub vendor_prefix: Option<VendorPrefix>, } /// Get all the animated properties in a keyframes animation. @@ -275,11 +277,14 @@ impl KeyframesAnimation { /// /// Otherwise, this will compute and sort the steps used for the animation, /// and return the animation object. - pub fn from_keyframes(keyframes: &[Arc<Locked<Keyframe>>], guard: &SharedRwLockReadGuard) + pub fn from_keyframes(keyframes: &[Arc<Locked<Keyframe>>], + vendor_prefix: Option<VendorPrefix>, + guard: &SharedRwLockReadGuard) -> Self { let mut result = KeyframesAnimation { steps: vec![], properties_changed: vec![], + vendor_prefix: vendor_prefix, }; if keyframes.is_empty() { diff --git a/components/style/str.rs b/components/style/str.rs index f5778828d1a..96305a59f1f 100644 --- a/components/style/str.rs +++ b/components/style/str.rs @@ -7,6 +7,7 @@ #![deny(missing_docs)] use num_traits::ToPrimitive; +use std::ascii::AsciiExt; use std::convert::AsRef; use std::iter::{Filter, Peekable}; use std::str::Split; @@ -144,3 +145,9 @@ pub fn str_join<I, T>(strs: I, join: &str) -> String acc }) } + +/// Returns true if a given string has a given prefix with case-insensitive match. +pub fn starts_with_ignore_ascii_case(string: &str, prefix: &str) -> bool { + string.len() > prefix.len() && + string[0..prefix.len()].eq_ignore_ascii_case(prefix) +} diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index 53abf04ef69..3cab9329a7f 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -36,6 +36,7 @@ use std::cell::Cell; use std::fmt; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use str::starts_with_ignore_ascii_case; use style_traits::ToCss; use stylist::FnvHashMap; use supports::SupportsCondition; @@ -529,6 +530,8 @@ pub struct KeyframesRule { pub name: Atom, /// The keyframes specified for this CSS rule. pub keyframes: Vec<Arc<Locked<Keyframe>>>, + /// Vendor prefix type the @keyframes has. + pub vendor_prefix: Option<VendorPrefix>, } impl ToCssWithGuard for KeyframesRule { @@ -913,6 +916,15 @@ pub enum State { Invalid = 5, } +#[derive(Clone, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +/// Vendor prefix. +pub enum VendorPrefix { + /// -moz prefix. + Moz, + /// -webkit prefix. + WebKit, +} enum AtRulePrelude { /// A @font-face rule prelude. @@ -923,8 +935,8 @@ enum AtRulePrelude { Supports(SupportsCondition), /// A @viewport rule prelude. Viewport, - /// A @keyframes rule, with its animation name. - Keyframes(Atom), + /// A @keyframes rule, with its animation name and vendor prefix if exists. + Keyframes(Atom, Option<VendorPrefix>), /// A @page rule prelude. Page, } @@ -1111,14 +1123,21 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> { Err(()) } }, - "keyframes" => { + "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => { + let prefix = if starts_with_ignore_ascii_case(name, "-webkit-") { + Some(VendorPrefix::WebKit) + } else if starts_with_ignore_ascii_case(name, "-moz-") { + Some(VendorPrefix::Moz) + } else { + None + }; let name = match input.next() { Ok(Token::Ident(ref value)) if value != "none" => Atom::from(&**value), Ok(Token::QuotedString(value)) => Atom::from(&*value), _ => return Err(()) }; - Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(Atom::from(name)))) + Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(Atom::from(name), prefix))) }, "page" => { if cfg!(feature = "gecko") { @@ -1157,11 +1176,12 @@ impl<'a, 'b> AtRuleParser for NestedRuleParser<'a, 'b> { Ok(CssRule::Viewport(Arc::new(self.shared_lock.wrap( try!(ViewportRule::parse(&context, input)))))) } - AtRulePrelude::Keyframes(name) => { + AtRulePrelude::Keyframes(name, prefix) => { let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Keyframes)); Ok(CssRule::Keyframes(Arc::new(self.shared_lock.wrap(KeyframesRule { name: name, keyframes: parse_keyframe_list(&context, input, self.shared_lock), + vendor_prefix: prefix, })))) } AtRulePrelude::Page => { diff --git a/components/style/stylist.rs b/components/style/stylist.rs index 2781f50c287..b24ee89f303 100644 --- a/components/style/stylist.rs +++ b/components/style/stylist.rs @@ -348,10 +348,17 @@ impl Stylist { CssRule::Keyframes(ref keyframes_rule) => { let keyframes_rule = keyframes_rule.read_with(guard); debug!("Found valid keyframes rule: {:?}", *keyframes_rule); - let animation = KeyframesAnimation::from_keyframes( - &keyframes_rule.keyframes, guard); - debug!("Found valid keyframe animation: {:?}", animation); - self.animations.insert(keyframes_rule.name.clone(), animation); + + // Don't let a prefixed keyframes animation override a non-prefixed one. + let needs_insertion = keyframes_rule.vendor_prefix.is_none() || + self.animations.get(&keyframes_rule.name).map_or(true, |rule| + rule.vendor_prefix.is_some()); + if needs_insertion { + let animation = KeyframesAnimation::from_keyframes( + &keyframes_rule.keyframes, keyframes_rule.vendor_prefix.clone(), guard); + debug!("Found valid keyframe animation: {:?}", animation); + self.animations.insert(keyframes_rule.name.clone(), animation); + } } CssRule::FontFace(ref rule) => { extra_data.add_font_face(&rule, stylesheet.origin); diff --git a/tests/unit/style/keyframes.rs b/tests/unit/style/keyframes.rs index 9fe2f0d3f39..4f96b5f88a9 100644 --- a/tests/unit/style/keyframes.rs +++ b/tests/unit/style/keyframes.rs @@ -14,10 +14,13 @@ use style::values::specified::{LengthOrPercentageOrAuto, NoCalcLength}; fn test_empty_keyframe() { let shared_lock = SharedRwLock::new(); let keyframes = vec![]; - let animation = KeyframesAnimation::from_keyframes(&keyframes, &shared_lock.read()); + let animation = KeyframesAnimation::from_keyframes(&keyframes, + /* vendor_prefix = */ None, + &shared_lock.read()); let expected = KeyframesAnimation { steps: vec![], properties_changed: vec![], + vendor_prefix: None, }; assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected)); @@ -32,10 +35,13 @@ fn test_no_property_in_keyframe() { block: Arc::new(shared_lock.wrap(PropertyDeclarationBlock::new())) })), ]; - let animation = KeyframesAnimation::from_keyframes(&keyframes, &shared_lock.read()); + let animation = KeyframesAnimation::from_keyframes(&keyframes, + /* vendor_prefix = */ None, + &shared_lock.read()); let expected = KeyframesAnimation { steps: vec![], properties_changed: vec![], + vendor_prefix: None, }; assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected)); @@ -78,7 +84,9 @@ fn test_missing_property_in_initial_keyframe() { block: declarations_on_final_keyframe.clone(), })), ]; - let animation = KeyframesAnimation::from_keyframes(&keyframes, &shared_lock.read()); + let animation = KeyframesAnimation::from_keyframes(&keyframes, + /* vendor_prefix = */ None, + &shared_lock.read()); let expected = KeyframesAnimation { steps: vec![ KeyframesStep { @@ -93,6 +101,7 @@ fn test_missing_property_in_initial_keyframe() { }, ], properties_changed: vec![TransitionProperty::Width, TransitionProperty::Height], + vendor_prefix: None, }; assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected)); @@ -135,7 +144,9 @@ fn test_missing_property_in_final_keyframe() { block: declarations_on_final_keyframe.clone(), })), ]; - let animation = KeyframesAnimation::from_keyframes(&keyframes, &shared_lock.read()); + let animation = KeyframesAnimation::from_keyframes(&keyframes, + /* vendor_prefix = */ None, + &shared_lock.read()); let expected = KeyframesAnimation { steps: vec![ KeyframesStep { @@ -150,6 +161,7 @@ fn test_missing_property_in_final_keyframe() { }, ], properties_changed: vec![TransitionProperty::Width, TransitionProperty::Height], + vendor_prefix: None, }; assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected)); @@ -184,7 +196,9 @@ fn test_missing_keyframe_in_both_of_initial_and_final_keyframe() { block: declarations.clone(), })), ]; - let animation = KeyframesAnimation::from_keyframes(&keyframes, &shared_lock.read()); + let animation = KeyframesAnimation::from_keyframes(&keyframes, + /* vendor_prefix = */ None, + &shared_lock.read()); let expected = KeyframesAnimation { steps: vec![ KeyframesStep { @@ -209,6 +223,7 @@ fn test_missing_keyframe_in_both_of_initial_and_final_keyframe() { } ], properties_changed: vec![TransitionProperty::Width, TransitionProperty::Height], + vendor_prefix: None, }; assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected)); diff --git a/tests/unit/style/stylesheets.rs b/tests/unit/style/stylesheets.rs index ed5696003b9..8005139e676 100644 --- a/tests/unit/style/stylesheets.rs +++ b/tests/unit/style/stylesheets.rs @@ -245,7 +245,8 @@ fn test_parse_stylesheet() { Importance::Normal), ]))), })), - ] + ], + vendor_prefix: None, }))) ], &stylesheet.shared_lock), |