From 058bfb39aeea823623e646c9bf4d2cc8e3f8a1bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 18 Jun 2016 15:04:16 +0200 Subject: [PATCH] style: Refactor the @keyframes parsing and add adequate computation for it. --- components/style/keyframes.rs | 100 +++++++++++++----- .../helpers/animated_properties.mako.rs | 12 +++ components/style/selector_matching.rs | 12 ++- tests/unit/style/stylesheets.rs | 22 ++-- 4 files changed, 103 insertions(+), 43 deletions(-) diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index a558020a748..8c3e90ef2a4 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -3,9 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cssparser::{Parser, Delimiter}; +use selectors::matching::DeclarationBlock; use parser::ParserContext; -use properties::{ComputedValues, PropertyDeclarationBlock, parse_property_declaration_list}; -use properties::animated_properties::AnimatedProperty; +use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock, parse_property_declaration_list}; +use properties::animated_properties::TransitionProperty; +use std::sync::Arc; /// Parses a keyframes list, like: /// 0%, 50% { @@ -30,9 +32,19 @@ pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser) -> Resul /// A number from 1 to 100, indicating the percentage of the animation where /// this keyframe should run. -#[derive(Debug, Copy, Clone, PartialEq, HeapSizeOf)] +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, HeapSizeOf)] pub struct KeyframePercentage(f32); +impl ::std::cmp::Ord for KeyframePercentage { + #[inline] + fn cmp(&self, other: &Self) -> ::std::cmp::Ordering { + // We know we have a number from 0 to 1, so unwrap() here is safe. + self.0.partial_cmp(&other.0).unwrap() + } +} + +impl ::std::cmp::Eq for KeyframePercentage { } + impl KeyframePercentage { #[inline] pub fn new(value: f32) -> KeyframePercentage { @@ -73,7 +85,7 @@ impl KeyframeSelector { #[derive(Debug, Clone, PartialEq, HeapSizeOf)] pub struct Keyframe { pub selector: KeyframeSelector, - pub declarations: PropertyDeclarationBlock, + pub declarations: Arc>, } impl Keyframe { @@ -89,25 +101,33 @@ impl Keyframe { Ok(parse_property_declaration_list(context, input)) }).unwrap(); + // NB: Other browsers seem to ignore important declarations in keyframe + // animations too. Ok(Keyframe { selector: selector, - declarations: declarations, + declarations: declarations.normal, }) } } /// A single step from a keyframe animation. #[derive(Debug, Clone, PartialEq, HeapSizeOf)] -pub struct ComputedKeyframesStep { +pub struct KeyframesStep { /// The percentage of the animation duration that should be taken for this /// step. duration_percentage: KeyframePercentage, - // XXX: Can we optimise this? Probably not such a show-stopper... Probably - // storing declared values could work/should be the thing to do? - /// The computed values at the beginning of the step. - begin: C, - /// The computed values at the end of the step. - end: C, + /// Declarations that will determine the final style during the step. + declarations: Arc>, +} + +impl KeyframesStep { + fn new(percentage: KeyframePercentage, + declarations: Arc>) -> Self { + KeyframesStep { + duration_percentage: percentage, + declarations: declarations, + } + } } /// This structure represents a list of animation steps computed from the list @@ -115,19 +135,32 @@ pub struct ComputedKeyframesStep { /// /// It only takes into account animable properties. #[derive(Debug, Clone, PartialEq, HeapSizeOf)] -pub struct ComputedKeyframesAnimation { - steps: Vec>, +pub struct KeyframesAnimation { + steps: Vec, /// The properties that change in this animation. - properties_changed: Vec, + properties_changed: Vec, } -fn get_animated_properties(keyframe: &Keyframe) -> Vec { - // TODO - vec![] +/// Get all the animated properties in a keyframes animation. Note that it's not +/// defined what happens when a property is not on a keyframe, so we only peek +/// the props of the first one. +/// +/// In practice, browsers seem to try to do their best job at it, so we might +/// want to go through all the actual keyframes and deduplicate properties. +fn get_animated_properties(keyframe: &Keyframe) -> Vec { + let mut ret = vec![]; + // NB: declarations are already deduplicated, so we don't have to check for + // it here. + for declaration in keyframe.declarations.iter() { + if let Some(property) = TransitionProperty::from_declaration(&declaration) { + ret.push(property); + } + } + ret } -impl ComputedKeyframesAnimation { - pub fn from_keyframes(keyframes: &[Keyframe]) -> Option> { +impl KeyframesAnimation { + pub fn from_keyframes(keyframes: &[Keyframe]) -> Option { debug_assert!(keyframes.len() > 1); let mut steps = vec![]; @@ -135,18 +168,37 @@ impl ComputedKeyframesAnimation { // appeareance, then sorting them, then updating with the real // "duration_percentage". let mut animated_properties = get_animated_properties(&keyframes[0]); - if animated_properties.is_empty() { return None; } for keyframe in keyframes { - for step in keyframe.selector.0.iter() { - + for percentage in keyframe.selector.0.iter() { + steps.push(KeyframesStep::new(*percentage, + keyframe.declarations.clone())); } } - Some(ComputedKeyframesAnimation { + steps.sort_by_key(|step| step.duration_percentage); + + if steps[0].duration_percentage != KeyframePercentage(0.0) { + // TODO: we could just insert a step from 0 and without declarations + // so we won't animate at the beginning. Seems like what other + // engines do, but might be a bit tricky so I'd rather leave it as a + // follow-up. + return None; + } + + let mut remaining = 1.0; + let mut last_step_end = 0.0; + debug_assert!(steps.len() > 1); + for current_step in &mut steps[1..] { + let new_duration_percentage = KeyframePercentage(current_step.duration_percentage.0 - last_step_end); + last_step_end = current_step.duration_percentage.0; + current_step.duration_percentage = new_duration_percentage; + } + + Some(KeyframesAnimation { steps: steps, properties_changed: animated_properties, }) diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 6a637948972..2b33e137f00 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -7,6 +7,7 @@ use bezier::Bezier; use cssparser::{Color as CSSParserColor, Parser, RGBA, ToCss}; use dom::{OpaqueNode, TRestyleDamage}; use euclid::{Point2D, Size2D}; +use properties::PropertyDeclaration; use properties::longhands; use properties::longhands::background_position::computed_value::T as BackgroundPosition; use properties::longhands::background_size::computed_value::T as BackgroundSize; @@ -68,6 +69,17 @@ impl TransitionProperty { } } + pub fn from_declaration(declaration: &PropertyDeclaration) -> Option { + match *declaration { + % for prop in data.longhands: + % if prop.animatable: + PropertyDeclaration::${prop.camel_case}(..) + => Some(TransitionProperty::${prop.camel_case}), + % endif + % endfor + _ => None, + } + } } impl ToCss for TransitionProperty { diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 5b2901d70ef..676b78de08e 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -8,7 +8,7 @@ use dom::PresentationalHintsSynthetizer; use element_state::*; use error_reporting::StdoutErrorReporter; use keyframes::Keyframe; -use keyframes::ComputedKeyframesAnimation; +use keyframes::KeyframesAnimation; use media_queries::{Device, MediaType}; use parser::ParserContextExtraData; use properties::{self, PropertyDeclaration, PropertyDeclarationBlock}; @@ -129,7 +129,7 @@ pub struct Stylist { BuildHasherDefault<::fnv::FnvHasher>>, /// A map with all the animations indexed by name. - animations: HashMap>, + animations: HashMap, /// Applicable declarations for a given non-eagerly cascaded pseudo-element. /// These are eagerly computed once, and then used to resolve the new @@ -253,9 +253,11 @@ impl Stylist { self.rules_source_order = rules_source_order; } CSSRule::Keyframes(ref keyframes_rule) => { - if let Some(computed) = ComputedKeyframesAnimation::from_keyframes(&keyframes_rule.keyframes) { + debug!("Found valid keyframes rule: {:?}", keyframes_rule); + if let Some(animation) = KeyframesAnimation::from_keyframes(&keyframes_rule.keyframes) { + debug!("Found valid keyframe animation: {:?}", animation); self.animations.insert(keyframes_rule.name.clone(), - computed); + animation); } } // We don't care about any other rule. @@ -459,7 +461,7 @@ impl Stylist { } #[inline] - pub fn animations(&self) -> &HashMap> { + pub fn animations(&self) -> &HashMap { &self.animations } } diff --git a/tests/unit/style/stylesheets.rs b/tests/unit/style/stylesheets.rs index b7cc1156c85..dc04712d1a8 100644 --- a/tests/unit/style/stylesheets.rs +++ b/tests/unit/style/stylesheets.rs @@ -156,24 +156,18 @@ fn test_parse_stylesheet() { 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![]), - } + declarations: Arc::new(vec![ + PropertyDeclaration::Width(DeclaredValue::Value( + LengthOrPercentageOrAuto::Percentage(Percentage(0.)))), + ]), }, 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![]), - } + declarations: Arc::new(vec![ + PropertyDeclaration::Width(DeclaredValue::Value( + LengthOrPercentageOrAuto::Percentage(Percentage(1.)))), + ]), }, ] })