diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs new file mode 100644 index 00000000000..5f0cf6858be --- /dev/null +++ b/components/style/keyframes.rs @@ -0,0 +1,91 @@ +/* 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, Delimiter}; +use parser::ParserContext; +use properties::{PropertyDeclarationBlock, parse_property_declaration_list}; + +/// Parses a keyframes list, like: +/// 0%, 50% { +/// width: 50%; +/// } +/// +/// 40%, 60%, 100% { +/// width: 100%; +/// } +pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser) -> Result, ()> { + let mut keyframes = vec![]; + while !input.is_exhausted() { + keyframes.push(try!(Keyframe::parse(context, input))); + } + Ok(keyframes) +} + +/// A number from 1 to 100, indicating the percentage of the animation where +/// this keyframe should run. +#[derive(Debug, Copy, Clone, PartialEq, HeapSizeOf)] +pub struct KeyframePercentage(f32); + +impl KeyframePercentage { + #[inline] + pub fn new(value: f32) -> KeyframePercentage { + debug_assert!(value >= 0. && value <= 1.); + KeyframePercentage(value) + } + + fn parse(input: &mut Parser) -> Result { + let percentage = if input.try(|input| input.expect_ident_matching("from")).is_ok() { + KeyframePercentage::new(0.) + } else if input.try(|input| input.expect_ident_matching("to")).is_ok() { + KeyframePercentage::new(1.) + } else { + KeyframePercentage::new(try!(input.expect_percentage())) + }; + + Ok(percentage) + } +} + +/// A keyframes selector is a list of percentages or from/to symbols, which are +/// converted at parse time to percentages. +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub struct KeyframeSelector(Vec); +impl KeyframeSelector { + #[inline] + pub fn percentages(&self) -> &[KeyframePercentage] { + &self.0 + } + + /// A dummy public function so we can write a unit test for this. + pub fn new_for_unit_testing(percentages: Vec) -> KeyframeSelector { + KeyframeSelector(percentages) + } +} + +/// A keyframe. +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub struct Keyframe { + pub selector: KeyframeSelector, + pub declarations: PropertyDeclarationBlock, +} + +impl Keyframe { + pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { + let percentages = try!(input.parse_until_before(Delimiter::CurlyBracketBlock, |input| { + input.parse_comma_separated(|input| KeyframePercentage::parse(input)) + })); + let selector = KeyframeSelector(percentages); + + try!(input.expect_curly_bracket_block()); + + let declarations = input.parse_nested_block(|input| { + Ok(parse_property_declaration_list(context, input)) + }).unwrap(); + + Ok(Keyframe { + selector: selector, + declarations: declarations, + }) + } +} diff --git a/components/style/lib.rs b/components/style/lib.rs index bad7907f964..ae1e8c84d7c 100644 --- a/components/style/lib.rs +++ b/components/style/lib.rs @@ -76,6 +76,7 @@ pub mod dom; pub mod element_state; pub mod error_reporting; pub mod font_face; +pub mod keyframes; pub mod logical_geometry; pub mod matching; pub mod media_queries; diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 79fd8c1ae32..64bf1b38863 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -7,6 +7,7 @@ use dom::PresentationalHintsSynthetizer; use element_state::*; use error_reporting::StdoutErrorReporter; +use keyframes::Keyframe; use media_queries::{Device, MediaType}; use parser::ParserContextExtraData; use properties::{self, PropertyDeclaration, PropertyDeclarationBlock}; @@ -23,7 +24,7 @@ use std::hash::BuildHasherDefault; use std::process; use std::sync::Arc; use style_traits::viewport::ViewportConstraints; -use stylesheets::{CSSRuleIteratorExt, Origin, Stylesheet}; +use stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet}; use url::Url; use util::opts; use util::resource_files::read_resource_file; @@ -126,6 +127,11 @@ pub struct Stylist { PerPseudoElementSelectorMap, BuildHasherDefault<::fnv::FnvHasher>>, + /// A map with all the animations indexed by name. + animations: HashMap, + BuildHasherDefault<::fnv::FnvHasher>>, + /// Applicable declarations for a given non-eagerly cascaded pseudo-element. /// These are eagerly computed once, and then used to resolve the new /// computed values on the fly on layout. @@ -150,6 +156,7 @@ impl Stylist { element_map: PerPseudoElementSelectorMap::new(), pseudos_map: HashMap::with_hasher(Default::default()), + animations: HashMap::with_hasher(Default::default()), precomputed_pseudo_element_decls: HashMap::with_hasher(Default::default()), rules_source_order: 0, state_deps: DependencySet::new(), @@ -173,6 +180,7 @@ impl Stylist { self.element_map = PerPseudoElementSelectorMap::new(); self.pseudos_map = HashMap::with_hasher(Default::default()); + self.animations = HashMap::with_hasher(Default::default()); Impl::each_eagerly_cascaded_pseudo_element(|pseudo| { self.pseudos_map.insert(pseudo, PerPseudoElementSelectorMap::new()); }); @@ -233,17 +241,29 @@ impl Stylist { }; ); - for style_rule in stylesheet.effective_rules(&self.device).style() { - append!(style_rule, normal); - append!(style_rule, important); - rules_source_order += 1; - for selector in &style_rule.selectors { - self.state_deps.note_selector(selector.compound_selectors.clone()); + for rule in stylesheet.effective_rules(&self.device) { + match *rule { + CSSRule::Style(ref style_rule) => { + append!(style_rule, normal); + append!(style_rule, important); + rules_source_order += 1; + for selector in &style_rule.selectors { + self.state_deps.note_selector(selector.compound_selectors.clone()); + } + + self.rules_source_order = rules_source_order; + } + CSSRule::Keyframes(ref keyframes_rule) => { + // TODO: This *might* be optimised converting the + // Vec into something like Arc<[Keyframe]>. + self.animations.insert(keyframes_rule.name.clone(), + keyframes_rule.keyframes.clone()); + } + // We don't care about any other rule. + _ => {} } } - self.rules_source_order = rules_source_order; - Impl::each_precomputed_pseudo_element(|pseudo| { // TODO: Consider not doing this and just getting the rules on the // fly. It should be a bit slower, but we'd take rid of the diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index bdea2426405..09be8f542dd 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -9,6 +9,7 @@ use cssparser::{AtRuleType, RuleListParser}; use encoding::EncodingRef; use error_reporting::ParseErrorReporter; use font_face::{FontFaceRule, parse_font_face_block}; +use keyframes::{Keyframe, parse_keyframe_list}; use media_queries::{Device, MediaQueryList, parse_media_query_list}; use parser::{ParserContext, ParserContextExtraData, log_css_error}; use properties::{PropertyDeclarationBlock, parse_property_declaration_list}; @@ -62,6 +63,14 @@ pub enum CSSRule { Media(MediaRule), FontFace(FontFaceRule), Viewport(ViewportRule), + Keyframes(KeyframesRule), +} + + +#[derive(Debug, HeapSizeOf, PartialEq)] +pub struct KeyframesRule { + pub name: String, + pub keyframes: Vec, } #[derive(Debug, PartialEq)] @@ -71,6 +80,7 @@ pub struct MediaRule { pub rules: Vec>, } + impl MediaRule { #[inline] pub fn evaluate(&self, device: &Device) -> bool { @@ -127,7 +137,7 @@ impl Stylesheet { let mut input = Parser::new(css); input.look_for_viewport_percentages(); - let mut rules = Vec::new(); + let mut rules = vec![]; { let mut iter = RuleListParser::new_for_stylesheet(&mut input, rule_parser); while let Some(result) = iter.next() { @@ -142,6 +152,7 @@ impl Stylesheet { Some(namespace.clone()); } } + rules.push(rule); } Err(range) => { @@ -153,6 +164,7 @@ impl Stylesheet { } } } + Stylesheet { origin: origin, rules: rules, @@ -253,7 +265,7 @@ pub mod rule_filter { use std::marker::PhantomData; use super::super::font_face::FontFaceRule; use super::super::viewport::ViewportRule; - use super::{CSSRule, MediaRule, StyleRule}; + use super::{CSSRule, KeyframesRule, MediaRule, StyleRule}; macro_rules! rule_filter { ($variant:ident -> $value:ty) => { @@ -266,6 +278,8 @@ pub mod rule_filter { impl<'a, I, Impl: SelectorImpl + 'a> $variant<'a, I> where I: Iterator> { + + #[inline] pub fn new(iter: I) -> $variant<'a, I> { $variant { iter: iter, @@ -300,6 +314,7 @@ pub mod rule_filter { rule_filter!(Style -> StyleRule); rule_filter!(FontFace -> FontFaceRule); rule_filter!(Viewport -> ViewportRule); + rule_filter!(Keyframes -> KeyframesRule); } /// Extension methods for `CSSRule` iterators. @@ -315,6 +330,9 @@ pub trait CSSRuleIteratorExt<'a, Impl: SelectorImpl + 'a>: Iterator rule_filter::Viewport<'a, Self>; + + /// Yield only @keyframes rules. + fn keyframes(self) -> rule_filter::Keyframes<'a, Self>; } impl<'a, I, Impl: SelectorImpl + 'a> CSSRuleIteratorExt<'a, Impl> for I where I: Iterator> { @@ -337,6 +355,11 @@ impl<'a, I, Impl: SelectorImpl + 'a> CSSRuleIteratorExt<'a, Impl> for I where I: fn viewport(self) -> rule_filter::Viewport<'a, I> { rule_filter::Viewport::new(self) } + + #[inline] + fn keyframes(self) -> rule_filter::Keyframes<'a, I> { + rule_filter::Keyframes::new(self) + } } fn parse_nested_rules(context: &ParserContext, input: &mut Parser) -> Vec> { @@ -376,9 +399,14 @@ enum State { enum AtRulePrelude { + /// A @font-face rule prelude. FontFace, + /// A @media rule prelude, with its media queries. Media(MediaQueryList), + /// A @viewport rule prelude. Viewport, + /// A @keyframes rule, with its animation name. + Keyframes(String), } @@ -478,6 +506,10 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleParser for NestedRuleParser<'a, 'b, Impl> Err(()) } }, + "keyframes" => { + let name = try!(input.expect_ident()); + Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(name.into_owned()))) + }, _ => Err(()) } } @@ -496,11 +528,16 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleParser for NestedRuleParser<'a, 'b, Impl> AtRulePrelude::Viewport => { ViewportRule::parse(input, self.context).map(CSSRule::Viewport) } + AtRulePrelude::Keyframes(name) => { + Ok(CSSRule::Keyframes(KeyframesRule { + name: name, + keyframes: try!(parse_keyframe_list(&self.context, input)), + })) + } } } } - impl<'a, 'b, Impl: SelectorImpl> QualifiedRuleParser for NestedRuleParser<'a, 'b, Impl> { type Prelude = Vec>; type QualifiedRule = CSSRule; diff --git a/tests/unit/style/stylesheets.rs b/tests/unit/style/stylesheets.rs index a771cae0800..b7cc1156c85 100644 --- a/tests/unit/style/stylesheets.rs +++ b/tests/unit/style/stylesheets.rs @@ -9,11 +9,13 @@ use std::borrow::ToOwned; use std::sync::Arc; use std::sync::Mutex; use string_cache::{Atom, Namespace}; +use style::error_reporting::ParseErrorReporter; +use style::keyframes::{Keyframe, KeyframeSelector, KeyframePercentage}; use style::parser::ParserContextExtraData; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock, DeclaredValue, longhands}; -use style::stylesheets::{CSSRule, StyleRule, Origin}; -use style::error_reporting::ParseErrorReporter; use style::servo::Stylesheet; +use style::stylesheets::{CSSRule, StyleRule, KeyframesRule, Origin}; +use style::values::specified::{LengthOrPercentageOrAuto, Percentage}; use url::Url; #[test] @@ -24,7 +26,10 @@ fn test_parse_stylesheet() { input[type=hidden i] { display: none !important; } html , body /**/ { display: block; } #d1 > .ok { background: blue; } - "; + @keyframes foo { + from { width: 0% } + to { width: 100%} + }"; let url = Url::parse("about::test").unwrap(); let stylesheet = Stylesheet::from_str(css, url, Origin::UserAgent, Box::new(CSSErrorReporterTest), @@ -145,6 +150,34 @@ fn test_parse_stylesheet() { important: Arc::new(vec![]), }, }), + CSSRule::Keyframes(KeyframesRule { + name: "foo".into(), + keyframes: vec![ + Keyframe { + selector: KeyframeSelector::new_for_unit_testing( + vec![KeyframePercentage::new(0.)]), + declarations: PropertyDeclarationBlock { + normal: Arc::new(vec![ + PropertyDeclaration::Width(DeclaredValue::Value( + LengthOrPercentageOrAuto::Percentage(Percentage(0.)))), + ]), + important: Arc::new(vec![]), + } + }, + Keyframe { + selector: KeyframeSelector::new_for_unit_testing( + vec![KeyframePercentage::new(1.)]), + declarations: PropertyDeclarationBlock { + normal: Arc::new(vec![ + PropertyDeclaration::Width(DeclaredValue::Value( + LengthOrPercentageOrAuto::Percentage(Percentage(1.)))), + ]), + important: Arc::new(vec![]), + } + }, + ] + }) + ], }); }