mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Support vendor prefix keyframes rule.
If there are multiple prefixed/non-prefixed @keyframes with the same name; * non-prefixed rule overrides earlier rules. * prefixed rule overrides earlier prefixed rules.
This commit is contained in:
parent
17dc598d99
commit
973d8287a9
7 changed files with 73 additions and 23 deletions
|
@ -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,
|
||||
{
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -245,7 +245,8 @@ fn test_parse_stylesheet() {
|
|||
Importance::Normal),
|
||||
]))),
|
||||
})),
|
||||
]
|
||||
],
|
||||
vendor_prefix: None,
|
||||
})))
|
||||
|
||||
], &stylesheet.shared_lock),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue