--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -11,19 +11,19 @@ use font_metrics::get_metrics_provider_f
use gecko_bindings::bindings;
use gecko_bindings::structs::{nsCSSKeyword, nsCSSProps_KTableEntry, nsCSSValue, nsCSSUnit, nsStringBuffer};
use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
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;
use values::{CSSFloat, specified};
use values::computed::{self, ToComputedValue};
/// The `Device` in Gecko wraps a pres context, has a default values computed,
/// and contains all the viewport rule state.
@@ -335,21 +335,16 @@ impl MediaExpressionValue {
let string = str::from_utf8_unchecked(buffer);
dest.write_str(string)
}
}
}
}
-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,
{
// FIXME(emilio): With build-time bindgen, we would be able to use
// structs::nsMediaFeatures_features. That would unfortunately break MSVC
// builds, or require one bindings file per platform.
//
// I'm not into any of those, so meanwhile let's use a FFI function.
--- a/servo/components/style/keyframes.rs
+++ b/servo/components/style/keyframes.rs
@@ -13,17 +13,17 @@ use properties::{Importance, PropertyDec
use properties::{PropertyDeclarationId, LonghandId, ParsedDeclaration};
use properties::LonghandIdSet;
use properties::animated_properties::TransitionProperty;
use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
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.
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct KeyframePercentage(pub f32);
impl ::std::cmp::Ord for KeyframePercentage {
@@ -234,16 +234,18 @@ impl KeyframesStep {
/// It only takes into account animable properties.
#[derive(Debug)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct KeyframesAnimation {
/// The difference steps of the animation.
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.
fn get_animated_properties(keyframes: &[Arc<Locked<Keyframe>>], guard: &SharedRwLockReadGuard)
-> Vec<TransitionProperty> {
let mut ret = vec![];
let mut seen = LonghandIdSet::new();
// NB: declarations are already deduplicated, so we don't have to check for
@@ -270,21 +272,24 @@ impl KeyframesAnimation {
/// Create a keyframes animation from a given list of keyframes.
///
/// This will return a keyframe animation with empty steps and
/// properties_changed if the list of keyframes is empty, or there are no
// animated properties obtained from the keyframes.
///
/// 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() {
return result;
}
result.properties_changed = get_animated_properties(keyframes, guard);
if result.properties_changed.is_empty() {
--- a/servo/components/style/str.rs
+++ b/servo/components/style/str.rs
@@ -2,16 +2,17 @@
* 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/. */
//! String utils for attributes and similar stuff.
#![deny(missing_docs)]
use num_traits::ToPrimitive;
+use std::ascii::AsciiExt;
use std::convert::AsRef;
use std::iter::{Filter, Peekable};
use std::str::Split;
/// A static slice of characters.
pub type StaticCharVec = &'static [char];
/// A static slice of `str`s.
@@ -139,8 +140,15 @@ pub fn str_join<I, T>(strs: I, join: &st
T: AsRef<str>,
{
strs.into_iter().enumerate().fold(String::new(), |mut acc, (i, s)| {
if i > 0 { acc.push_str(join); }
acc.push_str(s.as_ref());
acc
})
}
+
+/// Returns true
+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)
+}
+
--- a/servo/components/style/stylesheets.rs
+++ b/servo/components/style/stylesheets.rs
@@ -31,16 +31,17 @@ use selectors::parser::SelectorList;
use servo_config::prefs::PREFS;
#[cfg(not(feature = "gecko"))]
use servo_url::ServoUrl;
use shared_lock::{SharedRwLock, Locked, ToCssWithGuard, SharedRwLockReadGuard};
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;
use values::specified::url::SpecifiedUrl;
use viewport::ViewportRule;
/// Extra data that the backend may need to resolve url values.
@@ -524,16 +525,18 @@ impl ToCssWithGuard for ImportRule {
///
/// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes
#[derive(Debug)]
pub struct KeyframesRule {
/// The name of the current animation.
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 {
// Serialization of KeyframesRule is not specced.
fn to_css<W>(&self, guard: &SharedRwLockReadGuard, dest: &mut W) -> fmt::Result
where W: fmt::Write {
try!(dest.write_str("@keyframes "));
try!(dest.write_str(&*self.name.to_string()));
@@ -908,28 +911,37 @@ impl<'b> TopLevelRuleParser<'b> {
pub enum State {
Start = 1,
Imports = 2,
Namespaces = 3,
Body = 4,
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.
FontFace,
/// A @media rule prelude, with its media queries.
Media(Arc<Locked<MediaList>>),
/// An @supports rule, with its conditional
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,
}
impl<'a> AtRuleParser for TopLevelRuleParser<'a> {
type Prelude = AtRulePrelude;
type AtRule = CssRule;
@@ -1106,24 +1118,31 @@ impl<'a, 'b> AtRuleParser for NestedRule
},
"viewport" => {
if is_viewport_enabled() {
Ok(AtRuleType::WithBlock(AtRulePrelude::Viewport))
} else {
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") {
Ok(AtRuleType::WithBlock(AtRulePrelude::Page))
} else {
Err(())
}
},
@@ -1152,21 +1171,22 @@ impl<'a, 'b> AtRuleParser for NestedRule
enabled: enabled,
}))))
}
AtRulePrelude::Viewport => {
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Viewport));
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 => {
let context = ParserContext::new_with_rule_type(self.context, Some(CssRuleType::Page));
let declarations = parse_property_declaration_list(&context, input);
Ok(CssRule::Page(Arc::new(self.shared_lock.wrap(PageRule(
Arc::new(self.shared_lock.wrap(declarations))
)))))
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -343,20 +343,27 @@ impl Stylist {
}
CssRule::Import(ref import) => {
let import = import.read_with(guard);
self.add_stylesheet(&import.stylesheet, guard, extra_data)
}
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);
}
// We don't care about any other rule.
_ => {}
}
});
--- a/servo/tests/unit/style/keyframes.rs
+++ b/servo/tests/unit/style/keyframes.rs
@@ -9,38 +9,44 @@ use style::properties::{PropertyDeclarat
use style::properties::animated_properties::TransitionProperty;
use style::shared_lock::SharedRwLock;
use style::values::specified::{LengthOrPercentageOrAuto, NoCalcLength};
#[test]
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));
}
#[test]
fn test_no_property_in_keyframe() {
let shared_lock = SharedRwLock::new();
let keyframes = vec![
Arc::new(shared_lock.wrap(Keyframe {
selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(1.)]),
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));
}
#[test]
fn test_missing_property_in_initial_keyframe() {
let shared_lock = SharedRwLock::new();
@@ -73,31 +79,34 @@ fn test_missing_property_in_initial_keyf
block: declarations_on_initial_keyframe.clone(),
})),
Arc::new(shared_lock.wrap(Keyframe {
selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(1.)]),
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 {
start_percentage: KeyframePercentage(0.),
value: KeyframesStepValue::Declarations { block: declarations_on_initial_keyframe },
declared_timing_function: false,
},
KeyframesStep {
start_percentage: KeyframePercentage(1.),
value: KeyframesStepValue::Declarations { block: declarations_on_final_keyframe },
declared_timing_function: false,
},
],
properties_changed: vec![TransitionProperty::Width, TransitionProperty::Height],
+ vendor_prefix: None,
};
assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected));
}
#[test]
fn test_missing_property_in_final_keyframe() {
let shared_lock = SharedRwLock::new();
@@ -130,31 +139,34 @@ fn test_missing_property_in_final_keyfra
block: declarations_on_initial_keyframe.clone(),
})),
Arc::new(shared_lock.wrap(Keyframe {
selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(1.)]),
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 {
start_percentage: KeyframePercentage(0.),
value: KeyframesStepValue::Declarations { block: declarations_on_initial_keyframe },
declared_timing_function: false,
},
KeyframesStep {
start_percentage: KeyframePercentage(1.),
value: KeyframesStepValue::Declarations { block: declarations_on_final_keyframe },
declared_timing_function: false,
},
],
properties_changed: vec![TransitionProperty::Width, TransitionProperty::Height],
+ vendor_prefix: None,
};
assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected));
}
#[test]
fn test_missing_keyframe_in_both_of_initial_and_final_keyframe() {
let shared_lock = SharedRwLock::new();
@@ -179,17 +191,19 @@ fn test_missing_keyframe_in_both_of_init
selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(0.)]),
block: Arc::new(shared_lock.wrap(PropertyDeclarationBlock::new()))
})),
Arc::new(shared_lock.wrap(Keyframe {
selector: KeyframeSelector::new_for_unit_testing(vec![KeyframePercentage::new(0.5)]),
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 {
start_percentage: KeyframePercentage(0.),
value: KeyframesStepValue::Declarations {
block: Arc::new(shared_lock.wrap(
// XXX: Should we use ComputedValues in this case?
PropertyDeclarationBlock::new()
@@ -204,12 +218,13 @@ fn test_missing_keyframe_in_both_of_init
},
KeyframesStep {
start_percentage: KeyframePercentage(1.),
value: KeyframesStepValue::ComputedValues,
declared_timing_function: false,
}
],
properties_changed: vec![TransitionProperty::Width, TransitionProperty::Height],
+ vendor_prefix: None,
};
assert_eq!(format!("{:#?}", animation), format!("{:#?}", expected));
}
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -255,17 +255,18 @@ fn test_parse_stylesheet() {
LengthOrPercentageOrAuto::Percentage(Percentage(1.))),
Importance::Normal),
(PropertyDeclaration::AnimationPlayState(
animation_play_state::SpecifiedValue(
vec![animation_play_state::SingleSpecifiedValue::running])),
Importance::Normal),
]))),
})),
- ]
+ ],
+ vendor_prefix: None,
})))
], &stylesheet.shared_lock),
};
assert_eq!(format!("{:#?}", stylesheet), format!("{:#?}", expected));
}