From c1fd7432e9cc7aedf66c393a1e6ce07dd00aa8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 16 Jun 2016 16:14:09 +0200 Subject: [PATCH 01/28] style: Add @keyframe rule parsing. --- components/style/keyframes.rs | 91 +++++++++++++++++++++++++++ components/style/lib.rs | 1 + components/style/selector_matching.rs | 38 ++++++++--- components/style/stylesheets.rs | 43 ++++++++++++- tests/unit/style/stylesheets.rs | 39 +++++++++++- 5 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 components/style/keyframes.rs 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![]), + } + }, + ] + }) + ], }); } From 60192bb830cceef91709327b43bd96a5d1791405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 17 Jun 2016 03:51:57 +0200 Subject: [PATCH 02/28] style: Refactor to pass animations cleanly, land animation-name parsing as experimental --- .../dom/webidls/CSSStyleDeclaration.webidl | 3 + components/style/animation.rs | 2 +- components/style/matching.rs | 44 +++++++----- .../style/properties/longhand/box.mako.rs | 71 +++++++++++++++++++ .../style/properties/properties.mako.rs | 8 ++- components/style/selector_impl.rs | 15 ++++ components/style/selector_matching.rs | 14 ++-- components/style/stylesheets.rs | 1 - components/style/traversal.rs | 5 +- components/style/values.rs | 5 ++ components/style/viewport.rs | 5 +- ports/geckolib/selector_impl.rs | 9 +++ 12 files changed, 152 insertions(+), 30 deletions(-) diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index cf7c3ade7f2..b656fc15e43 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -327,4 +327,7 @@ partial interface CSSStyleDeclaration { [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-shrink; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString alignSelf; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString align-self; + + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-name; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationName; }; diff --git a/components/style/animation.rs b/components/style/animation.rs index 4a399a973f5..3cd994af327 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -817,7 +817,7 @@ fn can_interpolate_list(from_list: &[TransformOperation], fn interpolate_transform_list(from_list: &[TransformOperation], to_list: &[TransformOperation], time: f64) -> TransformList { - let mut result = vec!(); + let mut result = vec![]; if can_interpolate_list(from_list, to_list) { for (from, to) in from_list.iter().zip(to_list) { diff --git a/components/style/matching.rs b/components/style/matching.rs index 31d12ce4312..2e4b7922f7e 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -6,8 +6,8 @@ #![allow(unsafe_code)] -use animation::{self, Animation}; -use context::SharedStyleContext; +use animation; +use context::{SharedStyleContext, LocalStyleContext}; use data::PrivateStyleData; use dom::{TElement, TNode, TRestyleDamage}; use properties::{ComputedValues, PropertyDeclaration, cascade}; @@ -21,8 +21,7 @@ use smallvec::SmallVec; use std::collections::HashMap; use std::hash::{BuildHasherDefault, Hash, Hasher}; use std::slice::Iter; -use std::sync::mpsc::Sender; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use string_cache::{Atom, Namespace}; use util::arc_ptr_eq; use util::cache::{LRUCache, SimpleHashCache}; @@ -366,6 +365,10 @@ pub enum StyleSharingResult { trait PrivateMatchMethods: TNode where ::Impl: SelectorImplExt { + /// Actually cascades style for a node or a pseudo-element of a node. + /// + /// Note that animations only apply to nodes or ::before or ::after + /// pseudo-elements. fn cascade_node_pseudo_element(&self, context: &SharedStyleContext<::Impl>, parent_style: Option<&Arc>, @@ -373,13 +376,14 @@ trait PrivateMatchMethods: TNode mut style: Option<&mut Arc>, applicable_declarations_cache: &mut ApplicableDeclarationsCache, - new_animations_sender: &Mutex>, shareable: bool, animate_properties: bool) -> (Self::ConcreteRestyleDamage, Arc) { let mut cacheable = true; + let mut animations = None; if animate_properties { cacheable = !self.update_animations_for_cascade(context, &mut style) && cacheable; + animations = Some(context.stylist.animations()) } let mut this_style; @@ -387,14 +391,16 @@ trait PrivateMatchMethods: TNode Some(ref parent_style) => { let cache_entry = applicable_declarations_cache.find(applicable_declarations); let cached_computed_values = match cache_entry { - None => None, Some(ref style) => Some(&**style), + None => None, }; + let (the_style, is_cacheable) = cascade(context.viewport_size, applicable_declarations, shareable, Some(&***parent_style), cached_computed_values, + animations, context.error_reporter.clone()); cacheable = cacheable && is_cacheable; this_style = the_style @@ -405,6 +411,7 @@ trait PrivateMatchMethods: TNode shareable, None, None, + animations, context.error_reporter.clone()); cacheable = cacheable && is_cacheable; this_style = the_style @@ -417,7 +424,7 @@ trait PrivateMatchMethods: TNode if let Some(ref style) = style { let animations_started = animation::start_transitions_if_applicable::( - new_animations_sender, + &context.new_animations_sender, self.opaque(), &**style, &mut this_style); @@ -641,11 +648,9 @@ pub trait MatchMethods : TNode { unsafe fn cascade_node(&self, context: &SharedStyleContext<::Impl>, + local_context: &LocalStyleContext, parent: Option, - applicable_declarations: &ApplicableDeclarations<::Impl>, - applicable_declarations_cache: - &mut ApplicableDeclarationsCache, - new_animations_sender: &Mutex>) + applicable_declarations: &ApplicableDeclarations<::Impl>) where ::Impl: SelectorImplExt { // Get our parent's style. This must be unsafe so that we don't touch the parent's // borrow flags. @@ -653,13 +658,16 @@ pub trait MatchMethods : TNode { // FIXME(pcwalton): Isolate this unsafety into the `wrapper` module to allow // enforced safe, race-free access to the parent style. let parent_style = match parent { - None => None, Some(parent_node) => { let parent_style = (*parent_node.borrow_data_unchecked().unwrap()).style.as_ref().unwrap(); Some(parent_style) } + None => None, }; + let mut applicable_declarations_cache = + local_context.applicable_declarations_cache.borrow_mut(); + let damage; if self.is_text_node() { let mut data_ref = self.mutate_data().unwrap(); @@ -677,8 +685,7 @@ pub trait MatchMethods : TNode { parent_style, &applicable_declarations.normal, data.style.as_mut(), - applicable_declarations_cache, - new_animations_sender, + &mut applicable_declarations_cache, applicable_declarations.normal_shareable, true); @@ -690,15 +697,18 @@ pub trait MatchMethods : TNode { if !applicable_declarations_for_this_pseudo.is_empty() { + // NB: Transitions and animations should only work for + // pseudo-elements ::before and ::after + let should_animate_properties = + ::Impl::pseudo_is_before_or_after(&pseudo); let (new_damage, style) = self.cascade_node_pseudo_element( context, Some(data.style.as_ref().unwrap()), &*applicable_declarations_for_this_pseudo, data.per_pseudo.get_mut(&pseudo), - applicable_declarations_cache, - new_animations_sender, + &mut applicable_declarations_cache, false, - false); + should_animate_properties); data.per_pseudo.insert(pseudo, style); damage = damage | new_damage; diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index c1eba31c3a3..4d50e99594f 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -795,6 +795,77 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= pub use properties::longhands::transition_duration::{get_initial_value, parse, parse_one}; +<%helpers:longhand name="animation-name" experimental="True"> + use cssparser::ToCss; + use std::borrow::Cow; + use std::fmt; + + pub mod computed_value { + use cssparser::ToCss; + use std::fmt; + + #[derive(Debug, Clone, PartialEq, HeapSizeOf)] + pub struct T(pub Vec); + + impl ToCss for T { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + for (i, name) in self.0.iter().enumerate() { + if i != 0 { + try!(dest.write_str(", ")); + } + try!(dest.write_str(&name)); + } + Ok(()) + } + } + } + + // TODO: Use Cows? Probably more codegen work would be needed, and this + // could not be that worth it (animations arent *that* used). + #[derive(Debug, Clone, PartialEq, HeapSizeOf)] + pub struct SpecifiedValue(Vec); + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + for (i, name) in self.0.iter().enumerate() { + if i != 0 { + try!(dest.write_str(", ")); + } + try!(dest.write_str(&name)); + } + Ok(()) + } + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T(vec![]) + } + + pub fn parse(_: &ParserContext, input: &mut Parser) -> Result { + Ok(SpecifiedValue(try!(input.parse_comma_separated(|input| { + input.expect_ident().map(Cow::into_owned) + })))) + } + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + #[inline] + fn to_computed_value(&self, context: &Cx) -> computed_value::T { + let mut ret = vec![]; + if let Some(animations) = context.animations() { + for name in self.0.iter() { + if animations.contains_key(&**name) { + ret.push(name.clone()); + } + } + } + computed_value::T(ret) + } + } + + // CSSOM View Module // https://www.w3.org/TR/cssom-view-1/ ${helpers.single_keyword("scroll-behavior", diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 2e5ab399482..3df108e0e6a 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -12,7 +12,7 @@ use std::ascii::AsciiExt; use std::boxed::Box as StdBox; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::fmt; use std::fmt::Write; use std::sync::Arc; @@ -22,6 +22,7 @@ use cssparser::Color as CSSParserColor; use cssparser::{Parser, RGBA, AtRuleParser, DeclarationParser, Delimiter, DeclarationListParser, parse_important, ToCss, TokenSerializationType}; use error_reporting::ParseErrorReporter; +use keyframes::Keyframe; use url::Url; use euclid::side_offsets::SideOffsets2D; use euclid::size::Size2D; @@ -1594,12 +1595,14 @@ fn cascade_with_cached_declarations( parent_style: &C, cached_style: &C, custom_properties: Option>, + animations: Option<<&HashMap>>, mut error_reporter: StdBox) -> C { let mut context = computed::Context { is_root_element: false, viewport_size: viewport_size, inherited_style: parent_style, + animations: animations, style: C::new( custom_properties, shareable, @@ -1739,6 +1742,7 @@ pub fn cascade( shareable: bool, parent_style: Option<<&C>, cached_style: Option<<&C>, + animations: Option<<&HashMap>>, mut error_reporter: StdBox) -> (C, bool) { use properties::style_struct_traits::{Border, Box, Font, Outline}; @@ -1774,6 +1778,7 @@ pub fn cascade( parent_style, cached_style, custom_properties, + animations, error_reporter); return (style, false) } @@ -1782,6 +1787,7 @@ pub fn cascade( is_root_element: is_root_element, viewport_size: viewport_size, inherited_style: inherited_style, + animations: animations, style: C::new( custom_properties, shareable, diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index 29e3fa36927..a394ef987cc 100644 --- a/components/style/selector_impl.rs +++ b/components/style/selector_impl.rs @@ -90,6 +90,7 @@ pub trait SelectorImplExt : SelectorImpl + Sized { }) } + fn pseudo_is_before_or_after(pseudo: &Self::PseudoElement) -> bool; fn pseudo_class_state_flag(pc: &Self::NonTSPseudoClass) -> ElementState; @@ -109,6 +110,15 @@ pub enum PseudoElement { } impl PseudoElement { + #[inline] + pub fn is_before_or_after(&self) -> bool { + match *self { + PseudoElement::Before | + PseudoElement::After => true, + _ => false, + } + } + #[inline] pub fn cascade_type(&self) -> PseudoElementCascadeType { match *self { @@ -249,6 +259,11 @@ impl SelectorImplExt for ServoSelectorImpl { pc.state_flag() } + #[inline] + fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool { + pseudo.is_before_or_after() + } + #[inline] fn get_user_or_user_agent_stylesheets() -> &'static [Stylesheet] { &*USER_OR_USER_AGENT_STYLESHEETS diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 64bf1b38863..a3fe67b1b75 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -128,9 +128,7 @@ pub struct Stylist { BuildHasherDefault<::fnv::FnvHasher>>, /// A map with all the animations indexed by name. - animations: HashMap, - BuildHasherDefault<::fnv::FnvHasher>>, + animations: HashMap>, /// Applicable declarations for a given non-eagerly cascaded pseudo-element. /// These are eagerly computed once, and then used to resolve the new @@ -290,7 +288,8 @@ impl Stylist { let (computed, _) = properties::cascade(self.device.au_viewport_size(), &declarations, false, - parent.map(|p| &**p), None, + parent.map(|p| &**p), + None, None, Box::new(StdoutErrorReporter)); Some(Arc::new(computed)) } else { @@ -323,7 +322,7 @@ impl Stylist { let (computed, _) = properties::cascade(self.device.au_viewport_size(), &declarations, false, - Some(&**parent), None, + Some(&**parent), None, None, Box::new(StdoutErrorReporter)); Some(Arc::new(computed)) } @@ -457,6 +456,11 @@ impl Stylist { pub fn is_device_dirty(&self) -> bool { self.is_device_dirty } + + #[inline] + pub fn animations(&self) -> &HashMap> { + &self.animations + } } /// Map that contains the CSS rules for a given origin. diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index 09be8f542dd..c4a2ca24c3b 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -278,7 +278,6 @@ 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 { diff --git a/components/style/traversal.rs b/components/style/traversal.rs index 281806e2263..5e5b7d31948 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -219,10 +219,9 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C, // Perform the CSS cascade. unsafe { node.cascade_node(&context.shared_context(), + &context.local_context(), parent_opt, - &applicable_declarations, - &mut context.local_context().applicable_declarations_cache.borrow_mut(), - &context.shared_context().new_animations_sender); + &applicable_declarations); } // Add ourselves to the LRU cache. diff --git a/components/style/values.rs b/components/style/values.rs index f515a403ab4..0a62485a185 100644 --- a/components/style/values.rs +++ b/components/style/values.rs @@ -1561,8 +1561,10 @@ pub mod specified { pub mod computed { use app_units::Au; use euclid::size::Size2D; + use keyframes::Keyframe; use properties::ComputedValues; use properties::style_struct_traits::Font; + use std::collections::HashMap; use std::fmt; use super::LocalToCss; use super::specified::AngleOrCorner; @@ -1578,6 +1580,7 @@ pub mod computed { fn inherited_style(&self) -> &Self::ConcreteComputedValues; fn style(&self) -> &Self::ConcreteComputedValues; fn mutate_style(&mut self) -> &mut Self::ConcreteComputedValues; + fn animations(&self) -> Option<&HashMap>>; } pub struct Context<'a, C: ComputedValues> { @@ -1585,6 +1588,7 @@ pub mod computed { pub viewport_size: Size2D, pub inherited_style: &'a C, + pub animations: Option<&'a HashMap>>, /// Values access through this need to be in the properties "computed early": /// color, text-decoration, font-size, display, position, float, border-*-style, outline-style pub style: C, @@ -1597,6 +1601,7 @@ pub mod computed { fn inherited_style(&self) -> &C { &self.inherited_style } fn style(&self) -> &C { &self.style } fn mutate_style(&mut self) -> &mut C { &mut self.style } + fn animations(&self) -> Option<&HashMap>> { self.animations } } pub trait ToComputedValue { diff --git a/components/style/viewport.rs b/components/style/viewport.rs index f763600d399..1707721a362 100644 --- a/components/style/viewport.rs +++ b/components/style/viewport.rs @@ -562,8 +562,8 @@ pub trait MaybeNew { impl MaybeNew for ViewportConstraints { fn maybe_new(initial_viewport: TypedSize2D, - rule: &ViewportRule) - -> Option + rule: &ViewportRule) + -> Option { use std::cmp; @@ -648,6 +648,7 @@ impl MaybeNew for ViewportConstraints { viewport_size: initial_viewport, inherited_style: ServoComputedValues::initial_values(), style: ServoComputedValues::initial_values().clone(), + animations: None, }; // DEVICE-ADAPT ยง 9.3 Resolving 'extend-to-zoom' diff --git a/ports/geckolib/selector_impl.rs b/ports/geckolib/selector_impl.rs index 1c315df24c1..dda81ecec06 100644 --- a/ports/geckolib/selector_impl.rs +++ b/ports/geckolib/selector_impl.rs @@ -379,6 +379,15 @@ impl SelectorImplExt for GeckoSelectorImpl { fun(AnonBox(MozSVGText)); } + #[inline] + fn pseudo_is_before_or_after(pseudo: &PseudoElement) -> bool { + match *pseudo { + PseudoElement::Before | + PseudoElement::After => true, + _ => false, + } + } + #[inline] fn pseudo_class_state_flag(pc: &NonTSPseudoClass) -> ElementState { pc.state_flag() From c80084cd29a7ff25c1a544f8474733d7a126f54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 17 Jun 2016 04:00:16 +0200 Subject: [PATCH 03/28] style: Add animation-duration parsing under experimental flag --- .../script/dom/webidls/CSSStyleDeclaration.webidl | 2 ++ components/style/properties/longhand/box.mako.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index b656fc15e43..f13bcfa4048 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -330,4 +330,6 @@ partial interface CSSStyleDeclaration { [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-name; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationName; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-duration; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDuration; }; diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 4d50e99594f..898a1902548 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -809,6 +809,10 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= impl ToCss for T { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.0.is_empty() { + return dest.write_str("none") + } + for (i, name) in self.0.iter().enumerate() { if i != 0 { try!(dest.write_str(", ")); @@ -827,6 +831,10 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= impl ToCss for SpecifiedValue { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.0.is_empty() { + return dest.write_str("none") + } + for (i, name) in self.0.iter().enumerate() { if i != 0 { try!(dest.write_str(", ")); @@ -866,6 +874,12 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } +<%helpers:longhand name="animation-duration" experimental="True"> + pub use super::transition_duration::computed_value; + pub use super::transition_duration::{parse, get_initial_value}; + pub use super::transition_duration::SpecifiedValue; + + // CSSOM View Module // https://www.w3.org/TR/cssom-view-1/ ${helpers.single_keyword("scroll-behavior", From b6ecb1ccb1261e52cf0a72cc3f8fd3224bebd6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 17 Jun 2016 04:07:26 +0200 Subject: [PATCH 04/28] style: Add animation-timing-function parsing as experimental. --- components/script/dom/webidls/CSSStyleDeclaration.webidl | 2 ++ components/style/properties/longhand/box.mako.rs | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index f13bcfa4048..62baec54423 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -332,4 +332,6 @@ partial interface CSSStyleDeclaration { [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationName; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-duration; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDuration; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-timing-function; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationTimingFunction; }; diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 898a1902548..3a61b987eeb 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -880,6 +880,12 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= pub use super::transition_duration::SpecifiedValue; +<%helpers:longhand name="animation-timing-function" experimental="True"> + pub use super::transition_timing_function::computed_value; + pub use super::transition_timing_function::{parse, get_initial_value}; + pub use super::transition_timing_function::SpecifiedValue; + + // CSSOM View Module // https://www.w3.org/TR/cssom-view-1/ ${helpers.single_keyword("scroll-behavior", From f529786700c8c1e4724a2a06dbd494b3e6a0bdca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 17 Jun 2016 04:26:51 +0200 Subject: [PATCH 05/28] style: Add animation-iteration-count parsing under experimental flag --- .../dom/webidls/CSSStyleDeclaration.webidl | 2 + .../style/properties/longhand/box.mako.rs | 81 ++++++++++++++++--- 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index 62baec54423..84f9c821aad 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -334,4 +334,6 @@ partial interface CSSStyleDeclaration { [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDuration; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-timing-function; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationTimingFunction; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-iteration-count; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationIterationCount; }; diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 3a61b987eeb..6786436c0dd 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -270,6 +270,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= // TODO(pcwalton): Multiple transitions. <%helpers:longhand name="transition-duration"> + use values::computed::ComputedValueAsSpecified; use values::specified::Time; pub use self::computed_value::T as SpecifiedValue; @@ -286,15 +287,6 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct T(pub Vec); - impl ToComputedValue for T { - type ComputedValue = T; - - #[inline] - fn to_computed_value(&self, _: &Cx) -> T { - (*self).clone() - } - } - impl ToCss for T { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { if self.0.is_empty() { @@ -311,6 +303,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } } + impl ComputedValueAsSpecified for SpecifiedValue {} + #[inline] pub fn parse_one(input: &mut Parser) -> Result { Time::parse(input) @@ -886,6 +880,75 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= pub use super::transition_timing_function::SpecifiedValue; +<%helpers:longhand name="animation-iteration-count" experimental="True"> + use values::computed::ComputedValueAsSpecified; + + pub mod computed_value { + use cssparser::ToCss; + use std::fmt; + + #[derive(Debug, Clone, PartialEq, HeapSizeOf)] + pub enum AnimationIterationCount { + Number(u32), + Infinite, + } + + impl ToCss for AnimationIterationCount { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + AnimationIterationCount::Number(n) => write!(dest, "{}", n), + AnimationIterationCount::Infinite => dest.write_str("infinite"), + } + } + } + + #[derive(Debug, Clone, PartialEq, HeapSizeOf)] + pub struct T(pub Vec); + + impl ToCss for T { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.0.is_empty() { + return dest.write_str("none") + } + for (i, value) in self.0.iter().enumerate() { + if i != 0 { + try!(dest.write_str(", ")) + } + try!(value.to_css(dest)) + } + Ok(()) + } + } + } + + pub use self::computed_value::AnimationIterationCount; + pub use self::computed_value::T as SpecifiedValue; + + pub fn parse_one(input: &mut Parser) -> Result { + if input.try(|input| input.expect_ident_matching("infinite")).is_ok() { + Ok(AnimationIterationCount::Infinite) + } else { + let number = try!(input.expect_integer()); + if number < 0 { + return Err(()); + } + Ok(AnimationIterationCount::Number(number as u32)) + } + } + + + #[inline] + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one)))) + } + + pub fn get_initial_value() -> computed_value::T { + computed_value::T(vec![AnimationIterationCount::Number(1)]) + } + + impl ComputedValueAsSpecified for SpecifiedValue {} + + // CSSOM View Module // https://www.w3.org/TR/cssom-view-1/ ${helpers.single_keyword("scroll-behavior", From 818bc6d4a23184b83de9551996d3585d2db33062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 17 Jun 2016 05:25:50 +0200 Subject: [PATCH 06/28] style: parse the remaining animation longhands. --- .../dom/webidls/CSSStyleDeclaration.webidl | 8 +++ components/style/properties/data.py | 2 +- components/style/properties/helpers.mako.rs | 63 ++++++++++++++++++- .../style/properties/longhand/box.mako.rs | 18 ++++++ ports/geckolib/properties.mako.rs | 6 ++ 5 files changed, 94 insertions(+), 3 deletions(-) diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index 84f9c821aad..8d587b9760a 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -336,4 +336,12 @@ partial interface CSSStyleDeclaration { [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationTimingFunction; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-iteration-count; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationIterationCount; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-direction; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDirection; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-play-state; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationPlayState; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-fill-mode; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationFillMode; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-delay; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationDelay; }; diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 0ac35003ff9..4b2cc717407 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -13,7 +13,7 @@ def to_rust_ident(name): def to_camel_case(ident): - return re.sub("_([a-z])", lambda m: m.group(1).upper(), ident.strip("_").capitalize()) + return re.sub("(^|_|-)([a-z])", lambda m: m.group(2).upper(), ident.strip("_").strip("-")) class Keyword(object): diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index 86961c89192..b704ef9784b 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -2,7 +2,7 @@ * 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/. */ -<%! from data import Keyword, to_rust_ident %> +<%! from data import Keyword, to_rust_ident, to_camel_case %> <%def name="longhand(name, **kwargs)"> <%call expr="raw_longhand(name, **kwargs)"> @@ -181,9 +181,11 @@ % endfor } } - #[inline] pub fn get_initial_value() -> computed_value::T { + #[inline] + pub fn get_initial_value() -> computed_value::T { computed_value::T::${to_rust_ident(values.split()[0])} } + #[inline] pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { computed_value::T::parse(input) @@ -191,6 +193,63 @@ +<%def name="keyword_list(name, values, **kwargs)"> + <% + keyword_kwargs = {a: kwargs.pop(a, None) for a in [ + 'gecko_constant_prefix', 'extra_gecko_values', 'extra_servo_values' + ]} + %> + <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> + use values::computed::ComputedValueAsSpecified; + pub use self::computed_value::T as SpecifiedValue; + pub mod computed_value { + use cssparser::ToCss; + use std::fmt; + + #[derive(Debug, Clone, PartialEq, HeapSizeOf)] + pub struct T(pub Vec<${to_camel_case(name)}>); + + impl ToCss for T { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + debug_assert!(!self.0.is_empty(), "Always parses at least one"); + + for (index, item) in self.0.iter().enumerate() { + if index != 0 { + try!(dest.write_str(", ")); + } + + try!(item.to_css(dest)); + } + + Ok(()) + } + } + + define_css_keyword_enum! { ${to_camel_case(name)}: + % for value in data.longhands_by_name[name].keyword.values_for(product): + "${value}" => ${to_rust_ident(value)}, + % endfor + } + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T(vec![ + computed_value::${to_camel_case(name)}::${to_rust_ident(values.split()[0])} + ]) + } + + #[inline] + pub fn parse(_context: &ParserContext, input: &mut Parser) + -> Result { + Ok(SpecifiedValue(try!( + input.parse_comma_separated(computed_value::${to_camel_case(name)}::parse)))) + } + + impl ComputedValueAsSpecified for SpecifiedValue {} + + + <%def name="shorthand(name, sub_properties, experimental=False, **kwargs)"> <% shorthand = data.declare_shorthand(name, sub_properties.split(), experimental=experimental, diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 6786436c0dd..26014ee77b1 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -949,6 +949,24 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= impl ComputedValueAsSpecified for SpecifiedValue {} +${helpers.keyword_list("animation-direction", + "normal reverse alternate alternate-reverse", + experimental=True)} + +${helpers.keyword_list("animation-play-state", + "running paused", + experimental=True)} + +${helpers.keyword_list("animation-fill-mode", + "none forwards backwards both", + experimental=True)} + +<%helpers:longhand name="animation-delay" experimental="True"> + pub use super::transition_duration::computed_value; + pub use super::transition_duration::{parse, get_initial_value}; + pub use super::transition_duration::SpecifiedValue; + + // CSSOM View Module // https://www.w3.org/TR/cssom-view-1/ ${helpers.single_keyword("scroll-behavior", diff --git a/ports/geckolib/properties.mako.rs b/ports/geckolib/properties.mako.rs index 7a3f79e6748..79b200d0f1b 100644 --- a/ports/geckolib/properties.mako.rs +++ b/ports/geckolib/properties.mako.rs @@ -389,6 +389,12 @@ impl Debug for ${style_struct.gecko_struct_name} { force_stub += ["list-style-type", "text-overflow"] # These are booleans. force_stub += ["page-break-after", "page-break-before"] + # In a nsTArray, have to be done manually, but probably not too much work + # (the "filling them", not the "making them work") + force_stub += ["animation-name", "animation-duration", + "animation-timing-function", "animation-iteration-count", + "animation-direction", "animation-play-state", + "animation-fill-mode", "animation-delay"] # Types used with predefined_type()-defined properties that we can auto-generate. predefined_types = { From 6a362ae8e818d11869ec6ee7de3b249dee9bf01f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 17 Jun 2016 18:38:37 +0200 Subject: [PATCH 07/28] style: Refactor all the animated properties to use the style system properly --- components/style/animation.rs | 873 +----------------- components/style/keyframes.rs | 65 +- components/style/matching.rs | 5 +- components/style/properties/data.py | 53 +- .../helpers/animated_properties.mako.rs | 728 +++++++++++++++ .../style/properties/longhand/box.mako.rs | 259 +----- .../style/properties/properties.mako.rs | 26 +- .../style/properties/shorthand/box.mako.rs | 2 +- components/style/selector_matching.rs | 17 +- components/style/values.rs | 24 +- components/style/viewport.rs | 1 - 11 files changed, 929 insertions(+), 1124 deletions(-) create mode 100644 components/style/properties/helpers/animated_properties.mako.rs diff --git a/components/style/animation.rs b/components/style/animation.rs index 3cd994af327..e6e8a3ac89e 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -6,36 +6,17 @@ use app_units::Au; use bezier::Bezier; -use cssparser::{Color, RGBA}; -use dom::{OpaqueNode, TRestyleDamage}; use euclid::point::Point2D; -use properties::longhands::background_position::computed_value::T as BackgroundPosition; -use properties::longhands::border_spacing::computed_value::T as BorderSpacing; -use properties::longhands::clip::computed_value::ClipRect; -use properties::longhands::font_weight::computed_value::T as FontWeight; -use properties::longhands::line_height::computed_value::T as LineHeight; -use properties::longhands::text_shadow::computed_value::T as TextShadowList; -use properties::longhands::text_shadow::computed_value::TextShadow; -use properties::longhands::transform::computed_value::ComputedMatrix; -use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; -use properties::longhands::transform::computed_value::T as TransformList; -use properties::longhands::transition_property; -use properties::longhands::transition_property::computed_value::TransitionProperty; +use dom::{OpaqueNode, TRestyleDamage}; +use properties::animated_properties::{AnimatedProperty, TransitionProperty}; use properties::longhands::transition_timing_function::computed_value::StartEnd; use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; -use properties::longhands::vertical_align::computed_value::T as VerticalAlign; -use properties::longhands::visibility::computed_value::T as Visibility; -use properties::longhands::z_index::computed_value::T as ZIndex; use properties::style_struct_traits::Box; use properties::{ComputedValues, ServoComputedValues}; -use std::cmp::Ordering; -use std::iter::repeat; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use time; -use values::CSSFloat; -use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; -use values::computed::{CalcLengthOrPercentage, Length, LengthOrPercentage, Time}; +use values::computed::Time; /// State relating to an animation. #[derive(Clone)] @@ -74,13 +55,18 @@ impl PropertyAnimation { old_style: &ServoComputedValues, new_style: &mut ServoComputedValues) -> Vec { - let mut result = Vec::new(); - let transition_property = - new_style.as_servo().get_box().transition_property.0[transition_index]; + let mut result = vec![]; + let box_style = new_style.as_servo().get_box(); + let transition_property = box_style.transition_property.0[transition_index]; + let timing_function = *box_style.transition_timing_function.0.get_mod(transition_index); + let duration = *box_style.transition_duration.0.get_mod(transition_index); + + if transition_property != TransitionProperty::All { if let Some(property_animation) = PropertyAnimation::from_transition_property(transition_property, - transition_index, + timing_function, + duration, old_style, new_style) { result.push(property_animation) @@ -88,114 +74,40 @@ impl PropertyAnimation { return result } - for transition_property in - transition_property::computed_value::ALL_TRANSITION_PROPERTIES.iter() { + TransitionProperty::each(|transition_property| { if let Some(property_animation) = - PropertyAnimation::from_transition_property(*transition_property, - transition_index, + PropertyAnimation::from_transition_property(transition_property, + timing_function, + duration, old_style, new_style) { result.push(property_animation) } - } + }); result } fn from_transition_property(transition_property: TransitionProperty, - transition_index: usize, + timing_function: TransitionTimingFunction, + duration: Time, old_style: &ServoComputedValues, - new_style: &mut ServoComputedValues) + new_style: &ServoComputedValues) -> Option { - let box_style = new_style.get_box(); - macro_rules! match_transition { - ( $( [$name:ident; $structname:ident; $field:ident] ),* ) => { - match transition_property { - TransitionProperty::All => { - panic!("Don't use `TransitionProperty::All` with \ - `PropertyAnimation::from_transition_property`!") - } - $( - TransitionProperty::$name => { - AnimatedProperty::$name(old_style.$structname().$field, - new_style.$structname().$field) - } - )* - TransitionProperty::Clip => { - AnimatedProperty::Clip(old_style.get_effects().clip.0, - new_style.get_effects().clip.0) - } - TransitionProperty::LetterSpacing => { - AnimatedProperty::LetterSpacing(old_style.get_inheritedtext().letter_spacing.0, - new_style.get_inheritedtext().letter_spacing.0) - } - TransitionProperty::TextShadow => { - AnimatedProperty::TextShadow(old_style.get_inheritedtext().text_shadow.clone(), - new_style.get_inheritedtext().text_shadow.clone()) - } - TransitionProperty::Transform => { - AnimatedProperty::Transform(old_style.get_effects().transform.clone(), - new_style.get_effects().transform.clone()) - } - TransitionProperty::WordSpacing => { - AnimatedProperty::WordSpacing(old_style.get_inheritedtext().word_spacing.0, - new_style.get_inheritedtext().word_spacing.0) - } - } - } - } - let animated_property = match_transition!( - [BackgroundColor; get_background; background_color], - [BackgroundPosition; get_background; background_position], - [BorderBottomColor; get_border; border_bottom_color], - [BorderBottomWidth; get_border; border_bottom_width], - [BorderLeftColor; get_border; border_left_color], - [BorderLeftWidth; get_border; border_left_width], - [BorderRightColor; get_border; border_right_color], - [BorderRightWidth; get_border; border_right_width], - [BorderSpacing; get_inheritedtable; border_spacing], - [BorderTopColor; get_border; border_top_color], - [BorderTopWidth; get_border; border_top_width], - [Bottom; get_position; bottom], - [Color; get_color; color], - [FontSize; get_font; font_size], - [FontWeight; get_font; font_weight], - [Height; get_position; height], - [Left; get_position; left], - [LineHeight; get_inheritedtext; line_height], - [MarginBottom; get_margin; margin_bottom], - [MarginLeft; get_margin; margin_left], - [MarginRight; get_margin; margin_right], - [MarginTop; get_margin; margin_top], - [MaxHeight; get_position; max_height], - [MaxWidth; get_position; max_width], - [MinHeight; get_position; min_height], - [MinWidth; get_position; min_width], - [Opacity; get_effects; opacity], - [OutlineColor; get_outline; outline_color], - [OutlineWidth; get_outline; outline_width], - [PaddingBottom; get_padding; padding_bottom], - [PaddingLeft; get_padding; padding_left], - [PaddingRight; get_padding; padding_right], - [PaddingTop; get_padding; padding_top], - [Right; get_position; right], - [TextIndent; get_inheritedtext; text_indent], - [Top; get_position; top], - [VerticalAlign; get_box; vertical_align], - [Visibility; get_inheritedbox; visibility], - [Width; get_position; width], - [ZIndex; get_position; z_index]); + let animated_property = AnimatedProperty::from_transition_property(&transition_property, + old_style, + new_style); let property_animation = PropertyAnimation { property: animated_property, - timing_function: - *box_style.transition_timing_function.0.get_mod(transition_index), - duration: *box_style.transition_duration.0.get_mod(transition_index), + timing_function: timing_function, + duration: duration, }; - if property_animation.does_not_animate() { - None - } else { + + if property_animation.does_animate() { Some(property_animation) + } else { + None } } @@ -215,725 +127,12 @@ impl PropertyAnimation { } }; - macro_rules! match_property( - ( $( [$name:ident; $structname:ident; $field:ident] ),* ) => { - match self.property { - $( - AnimatedProperty::$name(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.$structname().$field = value - } - } - )* - AnimatedProperty::Clip(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.mutate_effects().clip.0 = value - } - } - AnimatedProperty::LetterSpacing(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.mutate_inheritedtext().letter_spacing.0 = value - } - } - AnimatedProperty::WordSpacing(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.mutate_inheritedtext().word_spacing.0 = value - } - } - } - }); - match_property!( - [BackgroundColor; mutate_background; background_color], - [BackgroundPosition; mutate_background; background_position], - [BorderBottomColor; mutate_border; border_bottom_color], - [BorderBottomWidth; mutate_border; border_bottom_width], - [BorderLeftColor; mutate_border; border_left_color], - [BorderLeftWidth; mutate_border; border_left_width], - [BorderRightColor; mutate_border; border_right_color], - [BorderRightWidth; mutate_border; border_right_width], - [BorderSpacing; mutate_inheritedtable; border_spacing], - [BorderTopColor; mutate_border; border_top_color], - [BorderTopWidth; mutate_border; border_top_width], - [Bottom; mutate_position; bottom], - [Color; mutate_color; color], - [FontSize; mutate_font; font_size], - [FontWeight; mutate_font; font_weight], - [Height; mutate_position; height], - [Left; mutate_position; left], - [LineHeight; mutate_inheritedtext; line_height], - [MarginBottom; mutate_margin; margin_bottom], - [MarginLeft; mutate_margin; margin_left], - [MarginRight; mutate_margin; margin_right], - [MarginTop; mutate_margin; margin_top], - [MaxHeight; mutate_position; max_height], - [MaxWidth; mutate_position; max_width], - [MinHeight; mutate_position; min_height], - [MinWidth; mutate_position; min_width], - [Opacity; mutate_effects; opacity], - [OutlineColor; mutate_outline; outline_color], - [OutlineWidth; mutate_outline; outline_width], - [PaddingBottom; mutate_padding; padding_bottom], - [PaddingLeft; mutate_padding; padding_left], - [PaddingRight; mutate_padding; padding_right], - [PaddingTop; mutate_padding; padding_top], - [Right; mutate_position; right], - [TextIndent; mutate_inheritedtext; text_indent], - [TextShadow; mutate_inheritedtext; text_shadow], - [Top; mutate_position; top], - [Transform; mutate_effects; transform], - [VerticalAlign; mutate_box; vertical_align], - [Visibility; mutate_inheritedbox; visibility], - [Width; mutate_position; width], - [ZIndex; mutate_position; z_index]); + self.property.update(style, progress); } #[inline] - fn does_not_animate(&self) -> bool { - self.property.does_not_animate() || self.duration == Time(0.0) - } -} - -#[derive(Clone, Debug)] -enum AnimatedProperty { - BackgroundColor(Color, Color), - BackgroundPosition(BackgroundPosition, BackgroundPosition), - BorderBottomColor(Color, Color), - BorderBottomWidth(Length, Length), - BorderLeftColor(Color, Color), - BorderLeftWidth(Length, Length), - BorderRightColor(Color, Color), - BorderRightWidth(Length, Length), - BorderSpacing(BorderSpacing, BorderSpacing), - BorderTopColor(Color, Color), - BorderTopWidth(Length, Length), - Bottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - Color(RGBA, RGBA), - Clip(Option, Option), - FontSize(Length, Length), - FontWeight(FontWeight, FontWeight), - Height(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - Left(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - LetterSpacing(Option, Option), - LineHeight(LineHeight, LineHeight), - MarginBottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MarginLeft(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MarginRight(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MarginTop(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MaxHeight(LengthOrPercentageOrNone, LengthOrPercentageOrNone), - MaxWidth(LengthOrPercentageOrNone, LengthOrPercentageOrNone), - MinHeight(LengthOrPercentage, LengthOrPercentage), - MinWidth(LengthOrPercentage, LengthOrPercentage), - Opacity(CSSFloat, CSSFloat), - OutlineColor(Color, Color), - OutlineWidth(Length, Length), - PaddingBottom(LengthOrPercentage, LengthOrPercentage), - PaddingLeft(LengthOrPercentage, LengthOrPercentage), - PaddingRight(LengthOrPercentage, LengthOrPercentage), - PaddingTop(LengthOrPercentage, LengthOrPercentage), - Right(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - TextIndent(LengthOrPercentage, LengthOrPercentage), - TextShadow(TextShadowList, TextShadowList), - Top(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - Transform(TransformList, TransformList), - VerticalAlign(VerticalAlign, VerticalAlign), - Visibility(Visibility, Visibility), - Width(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - WordSpacing(Option, Option), - ZIndex(ZIndex, ZIndex), -} - -impl AnimatedProperty { - #[inline] - fn does_not_animate(&self) -> bool { - match *self { - AnimatedProperty::Top(ref a, ref b) | - AnimatedProperty::Right(ref a, ref b) | - AnimatedProperty::Bottom(ref a, ref b) | - AnimatedProperty::Left(ref a, ref b) | - AnimatedProperty::MarginTop(ref a, ref b) | - AnimatedProperty::MarginRight(ref a, ref b) | - AnimatedProperty::MarginBottom(ref a, ref b) | - AnimatedProperty::MarginLeft(ref a, ref b) | - AnimatedProperty::Width(ref a, ref b) | - AnimatedProperty::Height(ref a, ref b) => a == b, - AnimatedProperty::MaxWidth(ref a, ref b) | - AnimatedProperty::MaxHeight(ref a, ref b) => a == b, - AnimatedProperty::MinWidth(ref a, ref b) | - AnimatedProperty::MinHeight(ref a, ref b) | - AnimatedProperty::TextIndent(ref a, ref b) => a == b, - AnimatedProperty::FontSize(ref a, ref b) | - AnimatedProperty::BorderTopWidth(ref a, ref b) | - AnimatedProperty::BorderRightWidth(ref a, ref b) | - AnimatedProperty::BorderBottomWidth(ref a, ref b) | - AnimatedProperty::BorderLeftWidth(ref a, ref b) => a == b, - AnimatedProperty::BorderTopColor(ref a, ref b) | - AnimatedProperty::BorderRightColor(ref a, ref b) | - AnimatedProperty::BorderBottomColor(ref a, ref b) | - AnimatedProperty::BorderLeftColor(ref a, ref b) | - AnimatedProperty::OutlineColor(ref a, ref b) | - AnimatedProperty::BackgroundColor(ref a, ref b) => a == b, - AnimatedProperty::PaddingTop(ref a, ref b) | - AnimatedProperty::PaddingRight(ref a, ref b) | - AnimatedProperty::PaddingBottom(ref a, ref b) | - AnimatedProperty::PaddingLeft(ref a, ref b) => a == b, - AnimatedProperty::LineHeight(ref a, ref b) => a == b, - AnimatedProperty::LetterSpacing(ref a, ref b) => a == b, - AnimatedProperty::BackgroundPosition(ref a, ref b) => a == b, - AnimatedProperty::BorderSpacing(ref a, ref b) => a == b, - AnimatedProperty::Clip(ref a, ref b) => a == b, - AnimatedProperty::Color(ref a, ref b) => a == b, - AnimatedProperty::FontWeight(ref a, ref b) => a == b, - AnimatedProperty::Opacity(ref a, ref b) => a == b, - AnimatedProperty::OutlineWidth(ref a, ref b) => a == b, - AnimatedProperty::TextShadow(ref a, ref b) => a == b, - AnimatedProperty::VerticalAlign(ref a, ref b) => a == b, - AnimatedProperty::Visibility(ref a, ref b) => a == b, - AnimatedProperty::WordSpacing(ref a, ref b) => a == b, - AnimatedProperty::ZIndex(ref a, ref b) => a == b, - AnimatedProperty::Transform(ref a, ref b) => a == b, - } - } -} - -/// A trait used to implement [interpolation][interpolated-types]. -/// -/// [interpolated-types]: https://drafts.csswg.org/css-transitions/#interpolated-types -trait Interpolate: Sized { - fn interpolate(&self, other: &Self, time: f64) -> Option; -} - -impl Interpolate for Au { - #[inline] - fn interpolate(&self, other: &Au, time: f64) -> Option { - Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32)) - } -} - -impl Interpolate for Option where T: Interpolate { - #[inline] - fn interpolate(&self, other: &Option, time: f64) -> Option> { - match (self, other) { - (&Some(ref this), &Some(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(Some(value)) - }) - } - (_, _) => None - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-number -impl Interpolate for f32 { - #[inline] - fn interpolate(&self, other: &f32, time: f64) -> Option { - Some(((*self as f64) + ((*other as f64) - (*self as f64)) * time) as f32) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-number -impl Interpolate for f64 { - #[inline] - fn interpolate(&self, other: &f64, time: f64) -> Option { - Some(*self + (*other - *self) * time) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-integer -impl Interpolate for i32 { - #[inline] - fn interpolate(&self, other: &i32, time: f64) -> Option { - let a = *self as f64; - let b = *other as f64; - Some((a + (b - a) * time).round() as i32) - } -} - -impl Interpolate for Angle { - #[inline] - fn interpolate(&self, other: &Angle, time: f64) -> Option { - self.radians().interpolate(&other.radians(), time).map(Angle) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-visibility -impl Interpolate for Visibility { - #[inline] - fn interpolate(&self, other: &Visibility, time: f64) - -> Option { - match (*self, *other) { - (Visibility::visible, _) | (_, Visibility::visible) => { - if time >= 0.0 && time <= 1.0 { - Some(Visibility::visible) - } else if time < 0.0 { - Some(*self) - } else { - Some(*other) - } - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-integer -impl Interpolate for ZIndex { - #[inline] - fn interpolate(&self, other: &ZIndex, time: f64) - -> Option { - match (*self, *other) { - (ZIndex::Number(ref this), - ZIndex::Number(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(ZIndex::Number(value)) - }) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-length -impl Interpolate for VerticalAlign { - #[inline] - fn interpolate(&self, other: &VerticalAlign, time: f64) - -> Option { - match (*self, *other) { - (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)), - VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => { - this.interpolate(other, time).and_then(|value| { - Some(VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value))) - }) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-simple-list -impl Interpolate for BorderSpacing { - #[inline] - fn interpolate(&self, other: &BorderSpacing, time: f64) - -> Option { - self.horizontal.interpolate(&other.horizontal, time).and_then(|horizontal| { - self.vertical.interpolate(&other.vertical, time).and_then(|vertical| { - Some(BorderSpacing { horizontal: horizontal, vertical: vertical }) - }) - }) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-color -impl Interpolate for RGBA { - #[inline] - fn interpolate(&self, other: &RGBA, time: f64) -> Option { - match (self.red.interpolate(&other.red, time), - self.green.interpolate(&other.green, time), - self.blue.interpolate(&other.blue, time), - self.alpha.interpolate(&other.alpha, time)) { - (Some(red), Some(green), Some(blue), Some(alpha)) => { - Some(RGBA { red: red, green: green, blue: blue, alpha: alpha }) - } - (_, _, _, _) => None - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-color -impl Interpolate for Color { - #[inline] - fn interpolate(&self, other: &Color, time: f64) -> Option { - match (*self, *other) { - (Color::RGBA(ref this), Color::RGBA(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(Color::RGBA(value)) - }) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for CalcLengthOrPercentage { - #[inline] - fn interpolate(&self, other: &CalcLengthOrPercentage, time: f64) - -> Option { - Some(CalcLengthOrPercentage { - length: self.length().interpolate(&other.length(), time), - percentage: self.percentage().interpolate(&other.percentage(), time), - }) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for LengthOrPercentage { - #[inline] - fn interpolate(&self, other: &LengthOrPercentage, time: f64) - -> Option { - match (*self, *other) { - (LengthOrPercentage::Length(ref this), - LengthOrPercentage::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentage::Length(value)) - }) - } - (LengthOrPercentage::Percentage(ref this), - LengthOrPercentage::Percentage(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentage::Percentage(value)) - }) - } - (this, other) => { - let this: CalcLengthOrPercentage = From::from(this); - let other: CalcLengthOrPercentage = From::from(other); - this.interpolate(&other, time).and_then(|value| { - Some(LengthOrPercentage::Calc(value)) - }) - } - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for LengthOrPercentageOrAuto { - #[inline] - fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64) - -> Option { - match (*self, *other) { - (LengthOrPercentageOrAuto::Length(ref this), - LengthOrPercentageOrAuto::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrAuto::Length(value)) - }) - } - (LengthOrPercentageOrAuto::Percentage(ref this), - LengthOrPercentageOrAuto::Percentage(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrAuto::Percentage(value)) - }) - } - (LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => { - Some(LengthOrPercentageOrAuto::Auto) - } - (this, other) => { - let this: Option = From::from(this); - let other: Option = From::from(other); - this.interpolate(&other, time).unwrap_or(None).and_then(|value| { - Some(LengthOrPercentageOrAuto::Calc(value)) - }) - } - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for LengthOrPercentageOrNone { - #[inline] - fn interpolate(&self, other: &LengthOrPercentageOrNone, time: f64) - -> Option { - match (*self, *other) { - (LengthOrPercentageOrNone::Length(ref this), - LengthOrPercentageOrNone::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrNone::Length(value)) - }) - } - (LengthOrPercentageOrNone::Percentage(ref this), - LengthOrPercentageOrNone::Percentage(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrNone::Percentage(value)) - }) - } - (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => { - Some(LengthOrPercentageOrNone::None) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-number -/// https://drafts.csswg.org/css-transitions/#animtype-length -impl Interpolate for LineHeight { - #[inline] - fn interpolate(&self, other: &LineHeight, time: f64) - -> Option { - match (*self, *other) { - (LineHeight::Length(ref this), - LineHeight::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LineHeight::Length(value)) - }) - } - (LineHeight::Number(ref this), - LineHeight::Number(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LineHeight::Number(value)) - }) - } - (LineHeight::Normal, LineHeight::Normal) => { - Some(LineHeight::Normal) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-font-weight -impl Interpolate for FontWeight { - #[inline] - fn interpolate(&self, other: &FontWeight, time: f64) - -> Option { - let a = (*self as u32) as f64; - let b = (*other as u32) as f64; - let weight = a + (b - a) * time; - Some(if weight < 150. { - FontWeight::Weight100 - } else if weight < 250. { - FontWeight::Weight200 - } else if weight < 350. { - FontWeight::Weight300 - } else if weight < 450. { - FontWeight::Weight400 - } else if weight < 550. { - FontWeight::Weight500 - } else if weight < 650. { - FontWeight::Weight600 - } else if weight < 750. { - FontWeight::Weight700 - } else if weight < 850. { - FontWeight::Weight800 - } else { - FontWeight::Weight900 - }) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-rect -impl Interpolate for ClipRect { - #[inline] - fn interpolate(&self, other: &ClipRect, time: f64) - -> Option { - match (self.top.interpolate(&other.top, time), - self.right.interpolate(&other.right, time), - self.bottom.interpolate(&other.bottom, time), - self.left.interpolate(&other.left, time)) { - (Some(top), Some(right), Some(bottom), Some(left)) => { - Some(ClipRect { top: top, right: right, bottom: bottom, left: left }) - }, - (_, _, _, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-simple-list -impl Interpolate for BackgroundPosition { - #[inline] - fn interpolate(&self, other: &BackgroundPosition, time: f64) - -> Option { - match (self.horizontal.interpolate(&other.horizontal, time), - self.vertical.interpolate(&other.vertical, time)) { - (Some(horizontal), Some(vertical)) => { - Some(BackgroundPosition { horizontal: horizontal, vertical: vertical }) - }, - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list -impl Interpolate for TextShadow { - #[inline] - fn interpolate(&self, other: &TextShadow, time: f64) - -> Option { - match (self.offset_x.interpolate(&other.offset_x, time), - self.offset_y.interpolate(&other.offset_y, time), - self.blur_radius.interpolate(&other.blur_radius, time), - self.color.interpolate(&other.color, time)) { - (Some(offset_x), Some(offset_y), Some(blur_radius), Some(color)) => { - Some(TextShadow { offset_x: offset_x, offset_y: offset_y, blur_radius: blur_radius, color: color }) - }, - (_, _, _, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list -impl Interpolate for TextShadowList { - #[inline] - fn interpolate(&self, other: &TextShadowList, time: f64) - -> Option { - let zero = TextShadow { - offset_x: Au(0), - offset_y: Au(0), - blur_radius: Au(0), - color: Color::RGBA(RGBA { - red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0 - }) - }; - - let interpolate_each = |(a, b): (&TextShadow, &TextShadow)| { - a.interpolate(b, time).unwrap() - }; - - Some(TextShadowList(match self.0.len().cmp(&other.0.len()) { - Ordering::Less => other.0.iter().chain(repeat(&zero)).zip(other.0.iter()).map(interpolate_each).collect(), - _ => self.0.iter().zip(other.0.iter().chain(repeat(&zero))).map(interpolate_each).collect(), - })) - } -} - -/// Check if it's possible to do a direct numerical interpolation -/// between these two transform lists. -/// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation -fn can_interpolate_list(from_list: &[TransformOperation], - to_list: &[TransformOperation]) -> bool { - // Lists must be equal length - if from_list.len() != to_list.len() { - return false; - } - - // Each transform operation must match primitive type in other list - for (from, to) in from_list.iter().zip(to_list) { - match (from, to) { - (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) | - (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) | - (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) | - (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) | - (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) | - (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {} - _ => { - return false; - } - } - } - - true -} - -/// Interpolate two transform lists. -/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms -fn interpolate_transform_list(from_list: &[TransformOperation], - to_list: &[TransformOperation], - time: f64) -> TransformList { - let mut result = vec![]; - - if can_interpolate_list(from_list, to_list) { - for (from, to) in from_list.iter().zip(to_list) { - match (from, to) { - (&TransformOperation::Matrix(from), - &TransformOperation::Matrix(_to)) => { - // TODO(gw): Implement matrix decomposition and interpolation - result.push(TransformOperation::Matrix(from)); - } - (&TransformOperation::Skew(fx, fy), - &TransformOperation::Skew(tx, ty)) => { - let ix = fx.interpolate(&tx, time).unwrap(); - let iy = fy.interpolate(&ty, time).unwrap(); - result.push(TransformOperation::Skew(ix, iy)); - } - (&TransformOperation::Translate(fx, fy, fz), - &TransformOperation::Translate(tx, ty, tz)) => { - let ix = fx.interpolate(&tx, time).unwrap(); - let iy = fy.interpolate(&ty, time).unwrap(); - let iz = fz.interpolate(&tz, time).unwrap(); - result.push(TransformOperation::Translate(ix, iy, iz)); - } - (&TransformOperation::Scale(fx, fy, fz), - &TransformOperation::Scale(tx, ty, tz)) => { - let ix = fx.interpolate(&tx, time).unwrap(); - let iy = fy.interpolate(&ty, time).unwrap(); - let iz = fz.interpolate(&tz, time).unwrap(); - result.push(TransformOperation::Scale(ix, iy, iz)); - } - (&TransformOperation::Rotate(fx, fy, fz, fa), - &TransformOperation::Rotate(_tx, _ty, _tz, _ta)) => { - // TODO(gw): Implement matrix decomposition and interpolation - result.push(TransformOperation::Rotate(fx, fy, fz, fa)); - } - (&TransformOperation::Perspective(fd), - &TransformOperation::Perspective(_td)) => { - // TODO(gw): Implement matrix decomposition and interpolation - result.push(TransformOperation::Perspective(fd)); - } - _ => { - // This should be unreachable due to the can_interpolate_list() call. - unreachable!(); - } - } - } - } else { - // TODO(gw): Implement matrix decomposition and interpolation - result.extend_from_slice(from_list); - } - - TransformList(Some(result)) -} - -/// Build an equivalent 'identity transform function list' based -/// on an existing transform list. -/// https://drafts.csswg.org/css-transforms/#none-transform-animation -fn build_identity_transform_list(list: &[TransformOperation]) -> Vec { - let mut result = vec!(); - - for operation in list { - match *operation { - TransformOperation::Matrix(..) => { - let identity = ComputedMatrix::identity(); - result.push(TransformOperation::Matrix(identity)); - } - TransformOperation::Skew(..) => { - result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0))); - } - TransformOperation::Translate(..) => { - result.push(TransformOperation::Translate(LengthOrPercentage::zero(), - LengthOrPercentage::zero(), - Au(0))); - } - TransformOperation::Scale(..) => { - result.push(TransformOperation::Scale(1.0, 1.0, 1.0)); - } - TransformOperation::Rotate(..) => { - result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0))); - } - TransformOperation::Perspective(..) => { - // http://dev.w3.org/csswg/css-transforms/#identity-transform-function - let identity = ComputedMatrix::identity(); - result.push(TransformOperation::Matrix(identity)); - } - } - } - - result -} - -/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms -impl Interpolate for TransformList { - #[inline] - fn interpolate(&self, other: &TransformList, time: f64) -> Option { - let result = match (&self.0, &other.0) { - (&Some(ref from_list), &Some(ref to_list)) => { - // https://drafts.csswg.org/css-transforms/#transform-transform-animation - interpolate_transform_list(from_list, &to_list, time) - } - (&Some(ref from_list), &None) => { - // https://drafts.csswg.org/css-transforms/#none-transform-animation - let to_list = build_identity_transform_list(from_list); - interpolate_transform_list(from_list, &to_list, time) - } - (&None, &Some(ref to_list)) => { - // https://drafts.csswg.org/css-transforms/#none-transform-animation - let from_list = build_identity_transform_list(to_list); - interpolate_transform_list(&from_list, to_list, time) - } - _ => { - // https://drafts.csswg.org/css-transforms/#none-none-animation - TransformList(None) - } - }; - - Some(result) + fn does_animate(&self) -> bool { + self.property.does_animate() && self.duration != Time(0.0) } } @@ -948,6 +147,7 @@ pub trait GetMod { impl GetMod for Vec { type Item = T; + #[inline] fn get_mod(&self, i: usize) -> &T { &(*self)[i % self.len()] } @@ -991,10 +191,9 @@ pub fn start_transitions_if_applicable(new_animations_sender: /// Updates a single animation and associated style based on the current time. If `damage` is /// provided, inserts the appropriate restyle damage. -pub fn update_style_for_animation>(animation: &Animation, - style: &mut Arc, - damage: Option<&mut Damage>) { +pub fn update_style_for_animation(animation: &Animation, + style: &mut Arc, + damage: Option<&mut Damage>) { let now = time::precise_time_s(); let mut progress = (now - animation.start_time) / animation.duration(); if progress > 1.0 { diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 5f0cf6858be..a558020a748 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -4,7 +4,8 @@ use cssparser::{Parser, Delimiter}; use parser::ParserContext; -use properties::{PropertyDeclarationBlock, parse_property_declaration_list}; +use properties::{ComputedValues, PropertyDeclarationBlock, parse_property_declaration_list}; +use properties::animated_properties::AnimatedProperty; /// Parses a keyframes list, like: /// 0%, 50% { @@ -19,6 +20,11 @@ pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser) -> Resul while !input.is_exhausted() { keyframes.push(try!(Keyframe::parse(context, input))); } + + if keyframes.len() < 2 { + return Err(()) + } + Ok(keyframes) } @@ -89,3 +95,60 @@ impl Keyframe { }) } } + +/// A single step from a keyframe animation. +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub struct ComputedKeyframesStep { + /// 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, +} + +/// This structure represents a list of animation steps computed from the list +/// of keyframes, in order. +/// +/// It only takes into account animable properties. +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub struct ComputedKeyframesAnimation { + steps: Vec>, + /// The properties that change in this animation. + properties_changed: Vec, +} + +fn get_animated_properties(keyframe: &Keyframe) -> Vec { + // TODO + vec![] +} + +impl ComputedKeyframesAnimation { + pub fn from_keyframes(keyframes: &[Keyframe]) -> Option> { + debug_assert!(keyframes.len() > 1); + let mut steps = vec![]; + + // NB: we do two passes, first storing the steps in the order of + // 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() { + + } + } + + Some(ComputedKeyframesAnimation { + steps: steps, + properties_changed: animated_properties, + }) + } +} diff --git a/components/style/matching.rs b/components/style/matching.rs index 2e4b7922f7e..e17af2a2fcb 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -400,7 +400,6 @@ trait PrivateMatchMethods: TNode shareable, Some(&***parent_style), cached_computed_values, - animations, context.error_reporter.clone()); cacheable = cacheable && is_cacheable; this_style = the_style @@ -411,7 +410,6 @@ trait PrivateMatchMethods: TNode shareable, None, None, - animations, context.error_reporter.clone()); cacheable = cacheable && is_cacheable; this_style = the_style @@ -482,8 +480,7 @@ trait PrivateMatchMethods: TNode if had_running_animations { let mut all_running_animations = context.running_animations.write().unwrap(); for running_animation in all_running_animations.get(&this_opaque).unwrap() { - animation::update_style_for_animation::(running_animation, style, None); + animation::update_style_for_animation::(running_animation, style, None); } all_running_animations.remove(&this_opaque); } diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 4b2cc717407..eb16f986319 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -16,6 +16,53 @@ def to_camel_case(ident): return re.sub("(^|_|-)([a-z])", lambda m: m.group(2).upper(), ident.strip("_").strip("-")) +# https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties +def is_known_animatable_property(name): + return name in [ + "-moz-outline-radius", "-moz-outline-radius-bottomleft", + "-moz-outline-radius-bottomright", "-moz-outline-radius-topleft", + "-moz-outline-radius-topright", "-webkit-text-fill-color", + "-webkit-text-stroke", "-webkit-text-stroke-color", + "-webkit-touch-callout", "all", "backdrop-filter", "background", + "background-color", "background-position", "background-size", "border", + "border-bottom", "border-bottom-color", "border-bottom-left-radius", + "border-bottom-right-radius", "border-bottom-width", "border-color", + "border-left", "border-left-color", "border-left-width", "border-radius", + "border-right", "border-right-color", "border-right-width", "border-top", + "border-top-color", "border-top-left-radius", "border-top-right-radius", + "border-top-width", "border-width", "bottom", "box-shadow", "clip", + "clip-path", "color", "column-count", "column-gap", "column-rule", + "column-rule-color", "column-rule-width", "column-width", "columns", + "filter", "flex", "flex-basis", "flex-grow", "flex-shrink", "font", + "font-size", "font-size-adjust", "font-stretch", "font-weight", + "grid-column-gap", "grid-gap", "grid-row-gap", "height", "left", + "letter-spacing", "line-height", "margin", "margin-bottom", + "margin-left", "margin-right", "margin-top", "mask", "mask-position", + "mask-size", "max-height", "max-width", "min-height", "min-width", + "motion-offset", "motion-rotation", "object-position", "opacity", + "order", "outline", "outline-color", "outline-offset", "outline-width", + "padding", "padding-bottom", "padding-left", "padding-right", + "padding-top", "perspective", "perspective-origin", "right", + "scroll-snap-coordinate", "scroll-snap-destination", + "shape-image-threshold", "shape-margin", "shape-outside", + "text-decoration", "text-decoration-color", "text-emphasis", + "text-emphasis-color", "text-indent", "text-shadow", "top", "transform", + "transform-origin", "vertical-align", "visibility", "width", + "word-spacing", "z-index" + ] + + +# FIXME: Servo doesn't support some animatable properties yet,those are in the +# following list, and can be implemented removing it from the list and +# implementing the Interpolate trait in helpers/animated_properties.mako.rs +def is_not_supported_animatable_property(name): + return name in [ + "flex-basis", "column-width", "column-height", "column-count", + "column-gap", "box-shadow", "clip", "filter", "transform-origin", + "perspective-origin", "font-stretch", "letter-spacing", "word-spacing", + "text-decoration" ] + + class Keyword(object): def __init__(self, name, values, gecko_constant_prefix=None, extra_gecko_values=None, extra_servo_values=None): @@ -47,7 +94,7 @@ class Keyword(object): class Longhand(object): def __init__(self, style_struct, name, derived_from=None, keyword=None, predefined_type=None, custom_cascade=False, experimental=False, internal=False, - need_clone=False, gecko_ffi_name=None): + need_clone=False, gecko_ffi_name=None, animatable=None): self.name = name self.keyword = keyword self.predefined_type = predefined_type @@ -60,6 +107,10 @@ class Longhand(object): self.need_clone = need_clone self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case self.derived_from = (derived_from or "").split() + if animatable is not None: + self.animatable = animatable + else: + self.animatable = is_known_animatable_property(name) and not is_not_supported_animatable_property(name) class Shorthand(object): diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs new file mode 100644 index 00000000000..6a637948972 --- /dev/null +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -0,0 +1,728 @@ +/* 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 app_units::Au; +use bezier::Bezier; +use cssparser::{Color as CSSParserColor, Parser, RGBA, ToCss}; +use dom::{OpaqueNode, TRestyleDamage}; +use euclid::{Point2D, Size2D}; +use properties::longhands; +use properties::longhands::background_position::computed_value::T as BackgroundPosition; +use properties::longhands::background_size::computed_value::T as BackgroundSize; +use properties::longhands::border_spacing::computed_value::T as BorderSpacing; +use properties::longhands::clip::computed_value::ClipRect; +use properties::longhands::font_weight::computed_value::T as FontWeight; +use properties::longhands::line_height::computed_value::T as LineHeight; +use properties::longhands::text_shadow::computed_value::T as TextShadowList; +use properties::longhands::text_shadow::computed_value::TextShadow; +use properties::longhands::transform::computed_value::ComputedMatrix; +use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; +use properties::longhands::transform::computed_value::T as TransformList; +use properties::longhands::transition_property; +use properties::longhands::transition_timing_function::computed_value::StartEnd; +use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; +use properties::longhands::vertical_align::computed_value::T as VerticalAlign; +use properties::longhands::visibility::computed_value::T as Visibility; +use properties::longhands::z_index::computed_value::T as ZIndex; +use properties::style_struct_traits::*; +use std::cmp::Ordering; +use std::fmt; +use std::iter::repeat; +use super::ComputedValues; +use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; +use values::computed::{BorderRadiusSize, LengthOrNone}; +use values::computed::{CalcLengthOrPercentage, Length, LengthOrPercentage, Time}; + +// NB: This needs to be here because it needs all the longhands generated +// beforehand. +#[derive(Copy, Clone, Debug, PartialEq, HeapSizeOf)] +pub enum TransitionProperty { + All, + % for prop in data.longhands: + % if prop.animatable: + ${prop.camel_case}, + % endif + % endfor +} + +impl TransitionProperty { + /// Iterates over each property that is not `All`. + pub fn each ()>(mut cb: F) { + % for prop in data.longhands: + % if prop.animatable: + cb(TransitionProperty::${prop.camel_case}); + % endif + % endfor + } + + pub fn parse(input: &mut Parser) -> Result { + match_ignore_ascii_case! { try!(input.expect_ident()), + "all" => Ok(TransitionProperty::All), + % for prop in data.longhands: + % if prop.animatable: + "${prop.name}" => Ok(TransitionProperty::${prop.camel_case}), + % endif + % endfor + _ => Err(()) + } + } + +} + +impl ToCss for TransitionProperty { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + TransitionProperty::All => dest.write_str("all"), + % for prop in data.longhands: + % if prop.animatable: + TransitionProperty::${prop.camel_case} => dest.write_str("${prop.name}"), + % endif + % endfor + } + } +} + +#[derive(Clone, Debug, PartialEq, HeapSizeOf)] +pub enum AnimatedProperty { + % for prop in data.longhands: + % if prop.animatable: + ${prop.camel_case}(longhands::${prop.ident}::computed_value::T, + longhands::${prop.ident}::computed_value::T), + % endif + % endfor +} + +impl AnimatedProperty { + pub fn does_animate(&self) -> bool { + match *self { + % for prop in data.longhands: + % if prop.animatable: + AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to, + % endif + % endfor + } + } + + pub fn update(&self, style: &mut C, progress: f64) { + match *self { + % for prop in data.longhands: + % if prop.animatable: + AnimatedProperty::${prop.camel_case}(ref from, ref to) => { + if let Some(value) = from.interpolate(to, progress) { + style.mutate_${prop.style_struct.ident.strip("_")}().set_${prop.ident}(value); + } + } + % endif + % endfor + } + } + + // NB: Transition properties need clone + pub fn from_transition_property(transition_property: &TransitionProperty, + old_style: &C, + new_style: &C) -> AnimatedProperty { + // TODO: Generalise this for GeckoLib, adding clone_xxx to the + // appropiate longhands. + let old_style = old_style.as_servo(); + let new_style = new_style.as_servo(); + match *transition_property { + TransitionProperty::All => panic!("Can't use TransitionProperty::All here."), + % for prop in data.longhands: + % if prop.animatable: + TransitionProperty::${prop.camel_case} => { + AnimatedProperty::${prop.camel_case}(old_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone(), + new_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone()) + } + % endif + % endfor + } + } +} + +pub trait Interpolate: Sized { + fn interpolate(&self, other: &Self, time: f64) -> Option; +} + +impl Interpolate for Au { + #[inline] + fn interpolate(&self, other: &Au, time: f64) -> Option { + Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32)) + } +} + +impl Interpolate for Option where T: Interpolate { + #[inline] + fn interpolate(&self, other: &Option, time: f64) -> Option> { + match (self, other) { + (&Some(ref this), &Some(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(Some(value)) + }) + } + (_, _) => None + } + } +} + +impl Interpolate for f32 { + #[inline] + fn interpolate(&self, other: &f32, time: f64) -> Option { + Some(((*self as f64) + ((*other as f64) - (*self as f64)) * time) as f32) + } +} + +impl Interpolate for f64 { + #[inline] + fn interpolate(&self, other: &f64, time: f64) -> Option { + Some(*self + (*other - *self) * time) + } +} + +impl Interpolate for i32 { + #[inline] + fn interpolate(&self, other: &i32, time: f64) -> Option { + let a = *self as f64; + let b = *other as f64; + Some((a + (b - a) * time).round() as i32) + } +} + +impl Interpolate for Angle { + #[inline] + fn interpolate(&self, other: &Angle, time: f64) -> Option { + self.radians().interpolate(&other.radians(), time).map(Angle) + } +} + +impl Interpolate for Visibility { + #[inline] + fn interpolate(&self, other: &Visibility, time: f64) + -> Option { + match (*self, *other) { + (Visibility::visible, _) | (_, Visibility::visible) => { + if time >= 0.0 && time <= 1.0 { + Some(Visibility::visible) + } else if time < 0.0 { + Some(*self) + } else { + Some(*other) + } + } + (_, _) => None, + } + } +} + +impl Interpolate for ZIndex { + #[inline] + fn interpolate(&self, other: &ZIndex, time: f64) + -> Option { + match (*self, *other) { + (ZIndex::Number(ref this), + ZIndex::Number(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(ZIndex::Number(value)) + }) + } + (_, _) => None, + } + } +} + +impl Interpolate for Size2D { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option { + let width = match self.width.interpolate(&other.width, time) { + Some(width) => width, + None => return None, + }; + + let height = match self.height.interpolate(&other.height, time) { + Some(height) => height, + None => return None, + }; + Some(Size2D::new(width, height)) + } +} + +impl Interpolate for Point2D { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option { + let x = match self.x.interpolate(&other.x, time) { + Some(x) => x, + None => return None, + }; + + let y = match self.y.interpolate(&other.y, time) { + Some(y) => y, + None => return None, + }; + + Some(Point2D::new(x, y)) + } +} + +impl Interpolate for BorderRadiusSize { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option { + self.0.interpolate(&other.0, time).map(BorderRadiusSize) + } +} + +impl Interpolate for VerticalAlign { + #[inline] + fn interpolate(&self, other: &VerticalAlign, time: f64) + -> Option { + match (*self, *other) { + (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)), + VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => { + this.interpolate(other, time).and_then(|value| { + Some(VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value))) + }) + } + (_, _) => None, + } + } +} + +impl Interpolate for BorderSpacing { + #[inline] + fn interpolate(&self, other: &BorderSpacing, time: f64) + -> Option { + self.horizontal.interpolate(&other.horizontal, time).and_then(|horizontal| { + self.vertical.interpolate(&other.vertical, time).and_then(|vertical| { + Some(BorderSpacing { horizontal: horizontal, vertical: vertical }) + }) + }) + } +} + +impl Interpolate for RGBA { + #[inline] + fn interpolate(&self, other: &RGBA, time: f64) -> Option { + match (self.red.interpolate(&other.red, time), + self.green.interpolate(&other.green, time), + self.blue.interpolate(&other.blue, time), + self.alpha.interpolate(&other.alpha, time)) { + (Some(red), Some(green), Some(blue), Some(alpha)) => { + Some(RGBA { red: red, green: green, blue: blue, alpha: alpha }) + } + (_, _, _, _) => None + } + } +} + +impl Interpolate for CSSParserColor { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option { + match (*self, *other) { + (CSSParserColor::RGBA(ref this), CSSParserColor::RGBA(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(CSSParserColor::RGBA(value)) + }) + } + (_, _) => None, + } + } +} + +impl Interpolate for CalcLengthOrPercentage { + #[inline] + fn interpolate(&self, other: &CalcLengthOrPercentage, time: f64) + -> Option { + Some(CalcLengthOrPercentage { + length: self.length().interpolate(&other.length(), time), + percentage: self.percentage().interpolate(&other.percentage(), time), + }) + } +} + +impl Interpolate for LengthOrPercentage { + #[inline] + fn interpolate(&self, other: &LengthOrPercentage, time: f64) + -> Option { + match (*self, *other) { + (LengthOrPercentage::Length(ref this), + LengthOrPercentage::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentage::Length(value)) + }) + } + (LengthOrPercentage::Percentage(ref this), + LengthOrPercentage::Percentage(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentage::Percentage(value)) + }) + } + (this, other) => { + let this: CalcLengthOrPercentage = From::from(this); + let other: CalcLengthOrPercentage = From::from(other); + this.interpolate(&other, time).and_then(|value| { + Some(LengthOrPercentage::Calc(value)) + }) + } + } + } +} + +impl Interpolate for LengthOrPercentageOrAuto { + #[inline] + fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64) + -> Option { + match (*self, *other) { + (LengthOrPercentageOrAuto::Length(ref this), + LengthOrPercentageOrAuto::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrAuto::Length(value)) + }) + } + (LengthOrPercentageOrAuto::Percentage(ref this), + LengthOrPercentageOrAuto::Percentage(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrAuto::Percentage(value)) + }) + } + (LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => { + Some(LengthOrPercentageOrAuto::Auto) + } + (this, other) => { + let this: Option = From::from(this); + let other: Option = From::from(other); + this.interpolate(&other, time).unwrap_or(None).and_then(|value| { + Some(LengthOrPercentageOrAuto::Calc(value)) + }) + } + } + } +} + +impl Interpolate for LengthOrPercentageOrNone { + #[inline] + fn interpolate(&self, other: &LengthOrPercentageOrNone, time: f64) + -> Option { + match (*self, *other) { + (LengthOrPercentageOrNone::Length(ref this), + LengthOrPercentageOrNone::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrNone::Length(value)) + }) + } + (LengthOrPercentageOrNone::Percentage(ref this), + LengthOrPercentageOrNone::Percentage(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrNone::Percentage(value)) + }) + } + (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => { + Some(LengthOrPercentageOrNone::None) + } + (_, _) => None, + } + } +} + +impl Interpolate for LineHeight { + #[inline] + fn interpolate(&self, other: &LineHeight, time: f64) + -> Option { + match (*self, *other) { + (LineHeight::Length(ref this), + LineHeight::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LineHeight::Length(value)) + }) + } + (LineHeight::Number(ref this), + LineHeight::Number(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LineHeight::Number(value)) + }) + } + (LineHeight::Normal, LineHeight::Normal) => { + Some(LineHeight::Normal) + } + (_, _) => None, + } + } +} + +/// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight +impl Interpolate for FontWeight { + #[inline] + fn interpolate(&self, other: &FontWeight, time: f64) + -> Option { + let a = (*self as u32) as f64; + let b = (*other as u32) as f64; + let weight = a + (b - a) * time; + Some(if weight < 150. { + FontWeight::Weight100 + } else if weight < 250. { + FontWeight::Weight200 + } else if weight < 350. { + FontWeight::Weight300 + } else if weight < 450. { + FontWeight::Weight400 + } else if weight < 550. { + FontWeight::Weight500 + } else if weight < 650. { + FontWeight::Weight600 + } else if weight < 750. { + FontWeight::Weight700 + } else if weight < 850. { + FontWeight::Weight800 + } else { + FontWeight::Weight900 + }) + } +} + +impl Interpolate for ClipRect { + #[inline] + fn interpolate(&self, other: &ClipRect, time: f64) + -> Option { + match (self.top.interpolate(&other.top, time), + self.right.interpolate(&other.right, time), + self.bottom.interpolate(&other.bottom, time), + self.left.interpolate(&other.left, time)) { + (Some(top), Some(right), Some(bottom), Some(left)) => { + Some(ClipRect { top: top, right: right, bottom: bottom, left: left }) + }, + (_, _, _, _) => None, + } + } +} + +impl Interpolate for BackgroundPosition { + #[inline] + fn interpolate(&self, other: &BackgroundPosition, time: f64) + -> Option { + match (self.horizontal.interpolate(&other.horizontal, time), + self.vertical.interpolate(&other.vertical, time)) { + (Some(horizontal), Some(vertical)) => { + Some(BackgroundPosition { horizontal: horizontal, vertical: vertical }) + }, + (_, _) => None, + } + } +} + +impl Interpolate for BackgroundSize { + fn interpolate(&self, other: &Self, time: f64) -> Option { + use properties::longhands::background_size::computed_value::ExplicitSize; + match (self, other) { + (&BackgroundSize::Explicit(ref me), &BackgroundSize::Explicit(ref other)) + => match (me.width.interpolate(&other.width, time), + me.height.interpolate(&other.height, time)) { + (Some(width), Some(height)) + => Some(BackgroundSize::Explicit( + ExplicitSize { width: width, height: height })), + _ => None, + }, + _ => None + } + } +} + +impl Interpolate for TextShadow { + #[inline] + fn interpolate(&self, other: &TextShadow, time: f64) + -> Option { + match (self.offset_x.interpolate(&other.offset_x, time), + self.offset_y.interpolate(&other.offset_y, time), + self.blur_radius.interpolate(&other.blur_radius, time), + self.color.interpolate(&other.color, time)) { + (Some(offset_x), Some(offset_y), Some(blur_radius), Some(color)) => { + Some(TextShadow { offset_x: offset_x, offset_y: offset_y, blur_radius: blur_radius, color: color }) + }, + (_, _, _, _) => None, + } + } +} + +impl Interpolate for TextShadowList { + #[inline] + fn interpolate(&self, other: &TextShadowList, time: f64) + -> Option { + let zero = TextShadow { + offset_x: Au(0), + offset_y: Au(0), + blur_radius: Au(0), + color: CSSParserColor::RGBA(RGBA { + red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0 + }) + }; + + let interpolate_each = |(a, b): (&TextShadow, &TextShadow)| { + a.interpolate(b, time).unwrap() + }; + + Some(TextShadowList(match self.0.len().cmp(&other.0.len()) { + Ordering::Less => other.0.iter().chain(repeat(&zero)).zip(other.0.iter()).map(interpolate_each).collect(), + _ => self.0.iter().zip(other.0.iter().chain(repeat(&zero))).map(interpolate_each).collect(), + })) + } +} + +/// Check if it's possible to do a direct numerical interpolation +/// between these two transform lists. +/// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation +fn can_interpolate_list(from_list: &[TransformOperation], + to_list: &[TransformOperation]) -> bool { + // Lists must be equal length + if from_list.len() != to_list.len() { + return false; + } + + // Each transform operation must match primitive type in other list + for (from, to) in from_list.iter().zip(to_list) { + match (from, to) { + (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) | + (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) | + (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) | + (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) | + (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) | + (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {} + _ => { + return false; + } + } + } + + true +} + +/// Interpolate two transform lists. +/// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms +fn interpolate_transform_list(from_list: &[TransformOperation], + to_list: &[TransformOperation], + time: f64) -> TransformList { + let mut result = vec![]; + + if can_interpolate_list(from_list, to_list) { + for (from, to) in from_list.iter().zip(to_list) { + match (from, to) { + (&TransformOperation::Matrix(from), + &TransformOperation::Matrix(_to)) => { + // TODO(gw): Implement matrix decomposition and interpolation + result.push(TransformOperation::Matrix(from)); + } + (&TransformOperation::Skew(fx, fy), + &TransformOperation::Skew(tx, ty)) => { + let ix = fx.interpolate(&tx, time).unwrap(); + let iy = fy.interpolate(&ty, time).unwrap(); + result.push(TransformOperation::Skew(ix, iy)); + } + (&TransformOperation::Translate(fx, fy, fz), + &TransformOperation::Translate(tx, ty, tz)) => { + let ix = fx.interpolate(&tx, time).unwrap(); + let iy = fy.interpolate(&ty, time).unwrap(); + let iz = fz.interpolate(&tz, time).unwrap(); + result.push(TransformOperation::Translate(ix, iy, iz)); + } + (&TransformOperation::Scale(fx, fy, fz), + &TransformOperation::Scale(tx, ty, tz)) => { + let ix = fx.interpolate(&tx, time).unwrap(); + let iy = fy.interpolate(&ty, time).unwrap(); + let iz = fz.interpolate(&tz, time).unwrap(); + result.push(TransformOperation::Scale(ix, iy, iz)); + } + (&TransformOperation::Rotate(fx, fy, fz, fa), + &TransformOperation::Rotate(_tx, _ty, _tz, _ta)) => { + // TODO(gw): Implement matrix decomposition and interpolation + result.push(TransformOperation::Rotate(fx, fy, fz, fa)); + } + (&TransformOperation::Perspective(fd), + &TransformOperation::Perspective(_td)) => { + // TODO(gw): Implement matrix decomposition and interpolation + result.push(TransformOperation::Perspective(fd)); + } + _ => { + // This should be unreachable due to the can_interpolate_list() call. + unreachable!(); + } + } + } + } else { + // TODO(gw): Implement matrix decomposition and interpolation + result.extend_from_slice(from_list); + } + + TransformList(Some(result)) +} + +/// Build an equivalent 'identity transform function list' based +/// on an existing transform list. +/// http://dev.w3.org/csswg/css-transforms/#none-transform-animation +fn build_identity_transform_list(list: &[TransformOperation]) -> Vec { + let mut result = vec!(); + + for operation in list { + match *operation { + TransformOperation::Matrix(..) => { + let identity = ComputedMatrix::identity(); + result.push(TransformOperation::Matrix(identity)); + } + TransformOperation::Skew(..) => { + result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0))); + } + TransformOperation::Translate(..) => { + result.push(TransformOperation::Translate(LengthOrPercentage::zero(), + LengthOrPercentage::zero(), + Au(0))); + } + TransformOperation::Scale(..) => { + result.push(TransformOperation::Scale(1.0, 1.0, 1.0)); + } + TransformOperation::Rotate(..) => { + result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0))); + } + TransformOperation::Perspective(..) => { + // http://dev.w3.org/csswg/css-transforms/#identity-transform-function + let identity = ComputedMatrix::identity(); + result.push(TransformOperation::Matrix(identity)); + } + } + } + + result +} + +impl Interpolate for LengthOrNone { + fn interpolate(&self, other: &Self, time: f64) -> Option { + match (*self, *other) { + (LengthOrNone::Length(ref len), LengthOrNone::Length(ref other)) => + len.interpolate(&other, time).map(LengthOrNone::Length), + _ => None, + } + } +} + +impl Interpolate for TransformList { + #[inline] + fn interpolate(&self, other: &TransformList, time: f64) -> Option { + // http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms + let result = match (&self.0, &other.0) { + (&Some(ref from_list), &Some(ref to_list)) => { + // Two lists of transforms + interpolate_transform_list(from_list, &to_list, time) + } + (&Some(ref from_list), &None) => { + // http://dev.w3.org/csswg/css-transforms/#none-transform-animation + let to_list = build_identity_transform_list(from_list); + interpolate_transform_list(from_list, &to_list, time) + } + (&None, &Some(ref to_list)) => { + // http://dev.w3.org/csswg/css-transforms/#none-transform-animation + let from_list = build_identity_transform_list(to_list); + interpolate_transform_list(&from_list, to_list, time) + } + _ => { + // http://dev.w3.org/csswg/css-transforms/#none-none-animation + TransformList(None) + } + }; + + Some(result) + } +} diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 26014ee77b1..b2eed2758cb 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -525,170 +525,17 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } -// TODO(pcwalton): Lots more properties. <%helpers:longhand name="transition-property"> - use self::computed_value::TransitionProperty; - pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue; pub use self::computed_value::T as SpecifiedValue; pub mod computed_value { use cssparser::ToCss; use std::fmt; - - pub use self::TransitionProperty as SingleComputedValue; - - #[derive(Copy, Clone, Debug, PartialEq)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub enum TransitionProperty { - All, - BackgroundColor, - BackgroundPosition, - BorderBottomColor, - BorderBottomWidth, - BorderLeftColor, - BorderLeftWidth, - BorderRightColor, - BorderRightWidth, - BorderSpacing, - BorderTopColor, - BorderTopWidth, - Bottom, - Color, - Clip, - FontSize, - FontWeight, - Height, - Left, - LetterSpacing, - LineHeight, - MarginBottom, - MarginLeft, - MarginRight, - MarginTop, - MaxHeight, - MaxWidth, - MinHeight, - MinWidth, - Opacity, - OutlineColor, - OutlineWidth, - PaddingBottom, - PaddingLeft, - PaddingRight, - PaddingTop, - Right, - TextIndent, - TextShadow, - Top, - Transform, - VerticalAlign, - Visibility, - Width, - WordSpacing, - ZIndex, - } - - pub static ALL_TRANSITION_PROPERTIES: [TransitionProperty; 45] = [ - TransitionProperty::BackgroundColor, - TransitionProperty::BackgroundPosition, - TransitionProperty::BorderBottomColor, - TransitionProperty::BorderBottomWidth, - TransitionProperty::BorderLeftColor, - TransitionProperty::BorderLeftWidth, - TransitionProperty::BorderRightColor, - TransitionProperty::BorderRightWidth, - TransitionProperty::BorderSpacing, - TransitionProperty::BorderTopColor, - TransitionProperty::BorderTopWidth, - TransitionProperty::Bottom, - TransitionProperty::Color, - TransitionProperty::Clip, - TransitionProperty::FontSize, - TransitionProperty::FontWeight, - TransitionProperty::Height, - TransitionProperty::Left, - TransitionProperty::LetterSpacing, - TransitionProperty::LineHeight, - TransitionProperty::MarginBottom, - TransitionProperty::MarginLeft, - TransitionProperty::MarginRight, - TransitionProperty::MarginTop, - TransitionProperty::MaxHeight, - TransitionProperty::MaxWidth, - TransitionProperty::MinHeight, - TransitionProperty::MinWidth, - TransitionProperty::Opacity, - TransitionProperty::OutlineColor, - TransitionProperty::OutlineWidth, - TransitionProperty::PaddingBottom, - TransitionProperty::PaddingLeft, - TransitionProperty::PaddingRight, - TransitionProperty::PaddingTop, - TransitionProperty::Right, - TransitionProperty::TextIndent, - TransitionProperty::TextShadow, - TransitionProperty::Top, - TransitionProperty::Transform, - TransitionProperty::VerticalAlign, - TransitionProperty::Visibility, - TransitionProperty::Width, - TransitionProperty::WordSpacing, - TransitionProperty::ZIndex, - ]; - - impl ToCss for TransitionProperty { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - TransitionProperty::All => dest.write_str("all"), - TransitionProperty::BackgroundColor => dest.write_str("background-color"), - TransitionProperty::BackgroundPosition => dest.write_str("background-position"), - TransitionProperty::BorderBottomColor => dest.write_str("border-bottom-color"), - TransitionProperty::BorderBottomWidth => dest.write_str("border-bottom-width"), - TransitionProperty::BorderLeftColor => dest.write_str("border-left-color"), - TransitionProperty::BorderLeftWidth => dest.write_str("border-left-width"), - TransitionProperty::BorderRightColor => dest.write_str("border-right-color"), - TransitionProperty::BorderRightWidth => dest.write_str("border-right-width"), - TransitionProperty::BorderSpacing => dest.write_str("border-spacing"), - TransitionProperty::BorderTopColor => dest.write_str("border-top-color"), - TransitionProperty::BorderTopWidth => dest.write_str("border-top-width"), - TransitionProperty::Bottom => dest.write_str("bottom"), - TransitionProperty::Color => dest.write_str("color"), - TransitionProperty::Clip => dest.write_str("clip"), - TransitionProperty::FontSize => dest.write_str("font-size"), - TransitionProperty::FontWeight => dest.write_str("font-weight"), - TransitionProperty::Height => dest.write_str("height"), - TransitionProperty::Left => dest.write_str("left"), - TransitionProperty::LetterSpacing => dest.write_str("letter-spacing"), - TransitionProperty::LineHeight => dest.write_str("line-height"), - TransitionProperty::MarginBottom => dest.write_str("margin-bottom"), - TransitionProperty::MarginLeft => dest.write_str("margin-left"), - TransitionProperty::MarginRight => dest.write_str("margin-right"), - TransitionProperty::MarginTop => dest.write_str("margin-top"), - TransitionProperty::MaxHeight => dest.write_str("max-height"), - TransitionProperty::MaxWidth => dest.write_str("max-width"), - TransitionProperty::MinHeight => dest.write_str("min-height"), - TransitionProperty::MinWidth => dest.write_str("min-width"), - TransitionProperty::Opacity => dest.write_str("opacity"), - TransitionProperty::OutlineColor => dest.write_str("outline-color"), - TransitionProperty::OutlineWidth => dest.write_str("outline-width"), - TransitionProperty::PaddingBottom => dest.write_str("padding-bottom"), - TransitionProperty::PaddingLeft => dest.write_str("padding-left"), - TransitionProperty::PaddingRight => dest.write_str("padding-right"), - TransitionProperty::PaddingTop => dest.write_str("padding-top"), - TransitionProperty::Right => dest.write_str("right"), - TransitionProperty::TextIndent => dest.write_str("text-indent"), - TransitionProperty::TextShadow => dest.write_str("text-shadow"), - TransitionProperty::Top => dest.write_str("top"), - TransitionProperty::Transform => dest.write_str("transform"), - TransitionProperty::VerticalAlign => dest.write_str("vertical-align"), - TransitionProperty::Visibility => dest.write_str("visibility"), - TransitionProperty::Width => dest.write_str("width"), - TransitionProperty::WordSpacing => dest.write_str("word-spacing"), - TransitionProperty::ZIndex => dest.write_str("z-index"), - } - } - } + // NB: Can't generate the type here because it needs all the longhands + // generated beforehand. + pub use properties::animated_properties::TransitionProperty; + pub use properties::animated_properties::TransitionProperty as SingleComputedValue; #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] @@ -715,61 +562,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= computed_value::T(Vec::new()) } - pub fn parse_one(input: &mut Parser) -> Result { - match_ignore_ascii_case! { - try!(input.expect_ident()), - "all" => Ok(TransitionProperty::All), - "background-color" => Ok(TransitionProperty::BackgroundColor), - "background-position" => Ok(TransitionProperty::BackgroundPosition), - "border-bottom-color" => Ok(TransitionProperty::BorderBottomColor), - "border-bottom-width" => Ok(TransitionProperty::BorderBottomWidth), - "border-left-color" => Ok(TransitionProperty::BorderLeftColor), - "border-left-width" => Ok(TransitionProperty::BorderLeftWidth), - "border-right-color" => Ok(TransitionProperty::BorderRightColor), - "border-right-width" => Ok(TransitionProperty::BorderRightWidth), - "border-spacing" => Ok(TransitionProperty::BorderSpacing), - "border-top-color" => Ok(TransitionProperty::BorderTopColor), - "border-top-width" => Ok(TransitionProperty::BorderTopWidth), - "bottom" => Ok(TransitionProperty::Bottom), - "color" => Ok(TransitionProperty::Color), - "clip" => Ok(TransitionProperty::Clip), - "font-size" => Ok(TransitionProperty::FontSize), - "font-weight" => Ok(TransitionProperty::FontWeight), - "height" => Ok(TransitionProperty::Height), - "left" => Ok(TransitionProperty::Left), - "letter-spacing" => Ok(TransitionProperty::LetterSpacing), - "line-height" => Ok(TransitionProperty::LineHeight), - "margin-bottom" => Ok(TransitionProperty::MarginBottom), - "margin-left" => Ok(TransitionProperty::MarginLeft), - "margin-right" => Ok(TransitionProperty::MarginRight), - "margin-top" => Ok(TransitionProperty::MarginTop), - "max-height" => Ok(TransitionProperty::MaxHeight), - "max-width" => Ok(TransitionProperty::MaxWidth), - "min-height" => Ok(TransitionProperty::MinHeight), - "min-width" => Ok(TransitionProperty::MinWidth), - "opacity" => Ok(TransitionProperty::Opacity), - "outline-color" => Ok(TransitionProperty::OutlineColor), - "outline-width" => Ok(TransitionProperty::OutlineWidth), - "padding-bottom" => Ok(TransitionProperty::PaddingBottom), - "padding-left" => Ok(TransitionProperty::PaddingLeft), - "padding-right" => Ok(TransitionProperty::PaddingRight), - "padding-top" => Ok(TransitionProperty::PaddingTop), - "right" => Ok(TransitionProperty::Right), - "text-indent" => Ok(TransitionProperty::TextIndent), - "text-shadow" => Ok(TransitionProperty::TextShadow), - "top" => Ok(TransitionProperty::Top), - "transform" => Ok(TransitionProperty::Transform), - "vertical-align" => Ok(TransitionProperty::VerticalAlign), - "visibility" => Ok(TransitionProperty::Visibility), - "width" => Ok(TransitionProperty::Width), - "word-spacing" => Ok(TransitionProperty::WordSpacing), - "z-index" => Ok(TransitionProperty::ZIndex), - _ => Err(()) - } - } - pub fn parse(_: &ParserContext, input: &mut Parser) -> Result { - Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one)))) + Ok(SpecifiedValue(try!(input.parse_comma_separated(SingleSpecifiedValue::parse)))) } impl ToComputedValue for SpecifiedValue { @@ -790,9 +584,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= <%helpers:longhand name="animation-name" experimental="True"> - use cssparser::ToCss; - use std::borrow::Cow; - use std::fmt; + use values::computed::ComputedValueAsSpecified; pub mod computed_value { use cssparser::ToCss; @@ -818,26 +610,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } } - // TODO: Use Cows? Probably more codegen work would be needed, and this - // could not be that worth it (animations arent *that* used). - #[derive(Debug, Clone, PartialEq, HeapSizeOf)] - pub struct SpecifiedValue(Vec); - - impl ToCss for SpecifiedValue { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - if self.0.is_empty() { - return dest.write_str("none") - } - - for (i, name) in self.0.iter().enumerate() { - if i != 0 { - try!(dest.write_str(", ")); - } - try!(dest.write_str(&name)); - } - Ok(()) - } - } + pub use self::computed_value::T as SpecifiedValue; #[inline] pub fn get_initial_value() -> computed_value::T { @@ -845,27 +618,13 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } pub fn parse(_: &ParserContext, input: &mut Parser) -> Result { + use std::borrow::Cow; Ok(SpecifiedValue(try!(input.parse_comma_separated(|input| { input.expect_ident().map(Cow::into_owned) })))) } - impl ToComputedValue for SpecifiedValue { - type ComputedValue = computed_value::T; - - #[inline] - fn to_computed_value(&self, context: &Cx) -> computed_value::T { - let mut ret = vec![]; - if let Some(animations) = context.animations() { - for name in self.0.iter() { - if animations.contains_key(&**name) { - ret.push(name.clone()); - } - } - } - computed_value::T(ret) - } - } + impl ComputedValueAsSpecified for SpecifiedValue {} <%helpers:longhand name="animation-duration" experimental="True"> diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 3df108e0e6a..26105138bcb 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -133,6 +133,10 @@ pub mod shorthands { <%include file="/shorthand/text.mako.rs" /> } +pub mod animated_properties { + <%include file="/helpers/animated_properties.mako.rs" /> +} + // TODO(SimonSapin): Convert this to a syntax extension rather than a Mako template. // Maybe submit for inclusion in libstd? @@ -1045,6 +1049,23 @@ impl PropertyDeclaration { PropertyDeclaration::Custom(_, _) => &[] } } + + /// Returns true if this property is one of the animable properties, false + /// otherwise. + pub fn is_animatable(&self) -> bool { + match *self { + % for property in data.longhands: + PropertyDeclaration::${property.camel_case}(_) => { + % if property.animatable: + true + % else: + false + % endif + } + % endfor + PropertyDeclaration::Custom(..) => false, + } + } } pub mod style_struct_traits { @@ -1595,14 +1616,12 @@ fn cascade_with_cached_declarations( parent_style: &C, cached_style: &C, custom_properties: Option>, - animations: Option<<&HashMap>>, mut error_reporter: StdBox) -> C { let mut context = computed::Context { is_root_element: false, viewport_size: viewport_size, inherited_style: parent_style, - animations: animations, style: C::new( custom_properties, shareable, @@ -1742,7 +1761,6 @@ pub fn cascade( shareable: bool, parent_style: Option<<&C>, cached_style: Option<<&C>, - animations: Option<<&HashMap>>, mut error_reporter: StdBox) -> (C, bool) { use properties::style_struct_traits::{Border, Box, Font, Outline}; @@ -1778,7 +1796,6 @@ pub fn cascade( parent_style, cached_style, custom_properties, - animations, error_reporter); return (style, false) } @@ -1787,7 +1804,6 @@ pub fn cascade( is_root_element: is_root_element, viewport_size: viewport_size, inherited_style: inherited_style, - animations: animations, style: C::new( custom_properties, shareable, diff --git a/components/style/properties/shorthand/box.mako.rs b/components/style/properties/shorthand/box.mako.rs index 48b49ca4811..27e923ba1aa 100644 --- a/components/style/properties/shorthand/box.mako.rs +++ b/components/style/properties/shorthand/box.mako.rs @@ -32,7 +32,7 @@ let (mut timing_function, mut delay) = (None, None); loop { if property.is_none() { - if let Ok(value) = input.try(|input| transition_property::parse_one(input)) { + if let Ok(value) = input.try(transition_property::SingleSpecifiedValue::parse) { property = Some(value); continue } diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index a3fe67b1b75..5b2901d70ef 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -8,6 +8,7 @@ use dom::PresentationalHintsSynthetizer; use element_state::*; use error_reporting::StdoutErrorReporter; use keyframes::Keyframe; +use keyframes::ComputedKeyframesAnimation; use media_queries::{Device, MediaType}; use parser::ParserContextExtraData; use properties::{self, PropertyDeclaration, PropertyDeclarationBlock}; @@ -128,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 @@ -252,10 +253,10 @@ impl Stylist { 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()); + if let Some(computed) = ComputedKeyframesAnimation::from_keyframes(&keyframes_rule.keyframes) { + self.animations.insert(keyframes_rule.name.clone(), + computed); + } } // We don't care about any other rule. _ => {} @@ -289,7 +290,7 @@ impl Stylist { properties::cascade(self.device.au_viewport_size(), &declarations, false, parent.map(|p| &**p), - None, None, + None, Box::new(StdoutErrorReporter)); Some(Arc::new(computed)) } else { @@ -322,7 +323,7 @@ impl Stylist { let (computed, _) = properties::cascade(self.device.au_viewport_size(), &declarations, false, - Some(&**parent), None, None, + Some(&**parent), None, Box::new(StdoutErrorReporter)); Some(Arc::new(computed)) } @@ -458,7 +459,7 @@ impl Stylist { } #[inline] - pub fn animations(&self) -> &HashMap> { + pub fn animations(&self) -> &HashMap> { &self.animations } } diff --git a/components/style/values.rs b/components/style/values.rs index 0a62485a185..5dea6f3cb7a 100644 --- a/components/style/values.rs +++ b/components/style/values.rs @@ -1391,16 +1391,13 @@ pub mod specified { pub fn parse_border_radius(input: &mut Parser) -> Result { input.try(BorderRadiusSize::parse).or_else(|()| { match_ignore_ascii_case! { try!(input.expect_ident()), - "thin" => - Ok(BorderRadiusSize::circle( - LengthOrPercentage::Length(Length::from_px(1.)))), - "medium" => - Ok(BorderRadiusSize::circle( - LengthOrPercentage::Length(Length::from_px(3.)))), - "thick" => - Ok(BorderRadiusSize::circle( - LengthOrPercentage::Length(Length::from_px(5.)))), - _ => Err(()) + "thin" => Ok(BorderRadiusSize::circle( + LengthOrPercentage::Length(Length::from_px(1.)))), + "medium" => Ok(BorderRadiusSize::circle( + LengthOrPercentage::Length(Length::from_px(3.)))), + "thick" => Ok(BorderRadiusSize::circle( + LengthOrPercentage::Length(Length::from_px(5.)))), + _ => Err(()) } }) } @@ -1561,10 +1558,8 @@ pub mod specified { pub mod computed { use app_units::Au; use euclid::size::Size2D; - use keyframes::Keyframe; use properties::ComputedValues; use properties::style_struct_traits::Font; - use std::collections::HashMap; use std::fmt; use super::LocalToCss; use super::specified::AngleOrCorner; @@ -1580,7 +1575,6 @@ pub mod computed { fn inherited_style(&self) -> &Self::ConcreteComputedValues; fn style(&self) -> &Self::ConcreteComputedValues; fn mutate_style(&mut self) -> &mut Self::ConcreteComputedValues; - fn animations(&self) -> Option<&HashMap>>; } pub struct Context<'a, C: ComputedValues> { @@ -1588,7 +1582,6 @@ pub mod computed { pub viewport_size: Size2D, pub inherited_style: &'a C, - pub animations: Option<&'a HashMap>>, /// Values access through this need to be in the properties "computed early": /// color, text-decoration, font-size, display, position, float, border-*-style, outline-style pub style: C, @@ -1601,7 +1594,6 @@ pub mod computed { fn inherited_style(&self) -> &C { &self.inherited_style } fn style(&self) -> &C { &self.style } fn mutate_style(&mut self) -> &mut C { &mut self.style } - fn animations(&self) -> Option<&HashMap>> { self.animations } } pub trait ToComputedValue { @@ -1757,7 +1749,7 @@ pub mod computed { } - #[derive(PartialEq, Clone, Copy)] + #[derive(Debug, PartialEq, Clone, Copy)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct BorderRadiusSize(pub Size2D); diff --git a/components/style/viewport.rs b/components/style/viewport.rs index 1707721a362..06411425922 100644 --- a/components/style/viewport.rs +++ b/components/style/viewport.rs @@ -648,7 +648,6 @@ impl MaybeNew for ViewportConstraints { viewport_size: initial_viewport, inherited_style: ServoComputedValues::initial_values(), style: ServoComputedValues::initial_values().clone(), - animations: None, }; // DEVICE-ADAPT ยง 9.3 Resolving 'extend-to-zoom' 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 08/28] 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.)))), + ]), }, ] }) From f389cf61c46754a94acaac0361427b37d01b8d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 18 Jun 2016 15:52:28 +0200 Subject: [PATCH 09/28] style: Make animation functions as generic as possible. --- components/style/animation.rs | 24 ++++++++++++------------ components/style/keyframes.rs | 2 ++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/components/style/animation.rs b/components/style/animation.rs index e6e8a3ac89e..f05a43b3ff2 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -51,10 +51,10 @@ impl PropertyAnimation { /// Creates a new property animation for the given transition index and old and new styles. /// Any number of animations may be returned, from zero (if the property did not animate) to /// one (for a single transition property) to arbitrarily many (for `all`). - pub fn from_transition(transition_index: usize, - old_style: &ServoComputedValues, - new_style: &mut ServoComputedValues) - -> Vec { + pub fn from_transition(transition_index: usize, + old_style: &C, + new_style: &mut C) + -> Vec { let mut result = vec![]; let box_style = new_style.as_servo().get_box(); let transition_property = box_style.transition_property.0[transition_index]; @@ -88,12 +88,12 @@ impl PropertyAnimation { result } - fn from_transition_property(transition_property: TransitionProperty, - timing_function: TransitionTimingFunction, - duration: Time, - old_style: &ServoComputedValues, - new_style: &ServoComputedValues) - -> Option { + fn from_transition_property(transition_property: TransitionProperty, + timing_function: TransitionTimingFunction, + duration: Time, + old_style: &C, + new_style: &C) + -> Option { let animated_property = AnimatedProperty::from_transition_property(&transition_property, old_style, new_style); @@ -111,7 +111,7 @@ impl PropertyAnimation { } } - pub fn update(&self, style: &mut ServoComputedValues, time: f64) { + pub fn update(&self, style: &mut C, time: f64) { let progress = match self.timing_function { TransitionTimingFunction::CubicBezier(p1, p2) => { // See `WebCore::AnimationBase::solveEpsilon(double)` in WebKit. @@ -167,7 +167,7 @@ pub fn start_transitions_if_applicable(new_animations_sender: let property_animations = PropertyAnimation::from_transition(i, old_style.as_servo(), new_style.as_servo_mut()); for property_animation in property_animations { // Set the property to the initial value. - property_animation.update(new_style.as_servo_mut(), 0.0); + property_animation.update(new_style, 0.0); // Kick off the animation. let now = time::precise_time_s(); diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 8c3e90ef2a4..03682b1e3af 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -121,6 +121,7 @@ pub struct KeyframesStep { } impl KeyframesStep { + #[inline] fn new(percentage: KeyframePercentage, declarations: Arc>) -> Self { KeyframesStep { @@ -156,6 +157,7 @@ fn get_animated_properties(keyframe: &Keyframe) -> Vec { ret.push(property); } } + ret } From 18f09289ce1fc6c9edfc211380f769cbb09abbd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 18 Jun 2016 22:17:31 +0200 Subject: [PATCH 10/28] style: Inline a few functions that are just another function call --- .../style/properties/properties.mako.rs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 26105138bcb..c35566165f1 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -1143,32 +1143,45 @@ pub mod style_structs { } % endfor % elif style_struct.trait_name == "Box": + #[inline] fn clone_display(&self) -> longhands::display::computed_value::T { self.display.clone() } + #[inline] fn clone_position(&self) -> longhands::position::computed_value::T { self.position.clone() } + #[inline] fn clone_float(&self) -> longhands::float::computed_value::T { self.float.clone() } + #[inline] fn clone_overflow_x(&self) -> longhands::overflow_x::computed_value::T { self.overflow_x.clone() } + #[inline] fn clone_overflow_y(&self) -> longhands::overflow_y::computed_value::T { self.overflow_y.clone() } + #[inline] + fn clone_animation_play_state(&self) -> longhands::animation_play_state::computed_value::T { + self.animation_play_state.clone() + } + #[inline] fn transition_count(&self) -> usize { self.transition_property.0.len() } % elif style_struct.trait_name == "Color": + #[inline] fn clone_color(&self) -> longhands::color::computed_value::T { self.color.clone() } % elif style_struct.trait_name == "Font": + #[inline] fn clone_font_size(&self) -> longhands::font_size::computed_value::T { self.font_size.clone() } + #[inline] fn clone_font_weight(&self) -> longhands::font_weight::computed_value::T { self.font_weight.clone() } @@ -1181,42 +1194,53 @@ pub mod style_structs { self.hash = hasher.finish() } % elif style_struct.trait_name == "InheritedBox": + #[inline] fn clone_direction(&self) -> longhands::direction::computed_value::T { self.direction.clone() } + #[inline] fn clone_writing_mode(&self) -> longhands::writing_mode::computed_value::T { self.writing_mode.clone() } + #[inline] fn clone_text_orientation(&self) -> longhands::text_orientation::computed_value::T { self.text_orientation.clone() } % elif style_struct.trait_name == "InheritedText" and product == "servo": + #[inline] fn clone__servo_text_decorations_in_effect(&self) -> longhands::_servo_text_decorations_in_effect::computed_value::T { self._servo_text_decorations_in_effect.clone() } % elif style_struct.trait_name == "Outline": + #[inline] fn clone_outline_style(&self) -> longhands::outline_style::computed_value::T { self.outline_style.clone() } + #[inline] fn outline_has_nonzero_width(&self) -> bool { self.outline_width != ::app_units::Au(0) } % elif style_struct.trait_name == "Position": + #[inline] fn clone_align_items(&self) -> longhands::align_items::computed_value::T { self.align_items.clone() } + #[inline] fn clone_align_self(&self) -> longhands::align_self::computed_value::T { self.align_self.clone() } % elif style_struct.trait_name == "Text": <% text_decoration_field = 'text_decoration' if product == 'servo' else 'text_decoration_line' %> + #[inline] fn has_underline(&self) -> bool { self.${text_decoration_field}.underline } + #[inline] fn has_overline(&self) -> bool { self.${text_decoration_field}.overline } + #[inline] fn has_line_through(&self) -> bool { self.${text_decoration_field}.line_through } From 0077eb147c7045e3cb6591535ca7b2c073726b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sun, 19 Jun 2016 02:15:25 +0200 Subject: [PATCH 11/28] style: Actually animate something! So this actually allows some more animations to be triggered. The bad part of this is that they're actually triggered always when the style is recalculated, so we're going to have at least some more state into the node, and the constellation, which would have to keep track of the animations states. --- components/style/animation.rs | 112 +++++++++++++++++- components/style/keyframes.rs | 10 +- components/style/matching.rs | 26 ++-- .../style/properties/longhand/box.mako.rs | 18 ++- 4 files changed, 141 insertions(+), 25 deletions(-) diff --git a/components/style/animation.rs b/components/style/animation.rs index f05a43b3ff2..0e82b986f94 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -8,6 +8,8 @@ use app_units::Au; use bezier::Bezier; use euclid::point::Point2D; use dom::{OpaqueNode, TRestyleDamage}; +use keyframes::KeyframesAnimation; +use keyframes::KeyframesStep; use properties::animated_properties::{AnimatedProperty, TransitionProperty}; use properties::longhands::transition_timing_function::computed_value::StartEnd; use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; @@ -17,10 +19,22 @@ use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use time; use values::computed::Time; +use selector_impl::SelectorImplExt; +use context::SharedStyleContext; +use selectors::matching::DeclarationBlock; +use properties; + +#[derive(Clone, Debug)] +pub enum AnimationKind { + Transition, + Keyframe, +} /// State relating to an animation. #[derive(Clone)] pub struct Animation { + /// The kind of animation, either a transition or a keyframe. + pub kind: AnimationKind, /// An opaque reference to the DOM node participating in the animation. pub node: OpaqueNode, /// A description of the property animation that is occurring. @@ -164,7 +178,7 @@ pub fn start_transitions_if_applicable(new_animations_sender: let mut had_animations = false; for i in 0..new_style.get_box().transition_count() { // Create any property animations, if applicable. - let property_animations = PropertyAnimation::from_transition(i, old_style.as_servo(), new_style.as_servo_mut()); + let property_animations = PropertyAnimation::from_transition(i, old_style, new_style); for property_animation in property_animations { // Set the property to the initial value. property_animation.update(new_style, 0.0); @@ -175,6 +189,7 @@ pub fn start_transitions_if_applicable(new_animations_sender: let start_time = now + (box_style.transition_delay.0.get_mod(i).seconds() as f64); new_animations_sender.lock().unwrap().send(Animation { + kind: AnimationKind::Transition, node: node, property_animation: property_animation, start_time: start_time, @@ -189,6 +204,99 @@ pub fn start_transitions_if_applicable(new_animations_sender: had_animations } +fn compute_style_for_animation_step(context: &SharedStyleContext, + step: &KeyframesStep, + old_style: &Impl::ComputedValues) -> Impl::ComputedValues { + let declaration_block = DeclarationBlock { + declarations: step.declarations.clone(), + source_order: 0, + specificity: ::std::u32::MAX, + }; + let (computed, _) = properties::cascade(context.viewport_size, + &[declaration_block], + false, + Some(old_style), + None, + context.error_reporter.clone()); + computed +} + +pub fn maybe_start_animations(context: &SharedStyleContext, + node: OpaqueNode, + new_style: &Impl::ComputedValues) -> bool +{ + let mut had_animations = false; + + for (i, name) in new_style.as_servo().get_box().animation_name.0.iter().enumerate() { + debug!("maybe_start_animations: name={}", name); + let total_duration = new_style.as_servo().get_box().animation_duration.0.get_mod(i).seconds(); + if total_duration == 0. { + continue + } + + // TODO: This should be factored out, too much indentation. + if let Some(ref animation) = context.stylist.animations().get(&**name) { + debug!("maybe_start_animations: found animation {}", name); + had_animations = true; + let mut last_keyframe_style = compute_style_for_animation_step(context, + &animation.steps[0], + new_style); + // Apply the style inmediately. TODO: clone()... + // *new_style = last_keyframe_style.clone(); + + let mut ongoing_animation_percentage = animation.steps[0].duration_percentage.0; + let delay = new_style.as_servo().get_box().animation_delay.0.get_mod(i).seconds(); + let animation_start = time::precise_time_s() + delay as f64; + + // TODO: We can probably be smarter here and batch steps out or + // something. + for step in &animation.steps[1..] { + for transition_property in &animation.properties_changed { + debug!("maybe_start_animations: processing animation prop {:?} for animation {}", transition_property, name); + + let new_keyframe_style = compute_style_for_animation_step(context, + step, + &last_keyframe_style); + // NB: This will get the previous frame timing function, or + // the old one if caught, which is what the spec says. + // + // We might need to reset to the initial timing function + // though. + let timing_function = + *last_keyframe_style.as_servo() + .get_box().animation_timing_function.0.get_mod(i); + + let percentage = step.duration_percentage.0; + let this_keyframe_duration = total_duration * percentage; + if let Some(property_animation) = PropertyAnimation::from_transition_property(*transition_property, + timing_function, + Time(this_keyframe_duration), + &last_keyframe_style, + &new_keyframe_style) { + debug!("maybe_start_animations: got property animation for prop {:?}", transition_property); + + let relative_start_time = ongoing_animation_percentage * total_duration; + let start_time = animation_start + relative_start_time as f64; + let end_time = start_time + (relative_start_time + this_keyframe_duration) as f64; + context.new_animations_sender.lock().unwrap().send(Animation { + kind: AnimationKind::Keyframe, + node: node, + property_animation: property_animation, + start_time: start_time, + end_time: end_time, + }).unwrap(); + } + + last_keyframe_style = new_keyframe_style; + ongoing_animation_percentage += percentage; + } + } + } + } + + had_animations +} + /// Updates a single animation and associated style based on the current time. If `damage` is /// provided, inserts the appropriate restyle damage. pub fn update_style_for_animation(animation: &Animation, @@ -204,7 +312,7 @@ pub fn update_style_for_animation(animation: &Animation, } let mut new_style = (*style).clone(); - animation.property_animation.update(Arc::make_mut(&mut new_style).as_servo_mut(), progress); + animation.property_animation.update(Arc::make_mut(&mut new_style), progress); if let Some(damage) = damage { *damage = *damage | Damage::compute(Some(style), &new_style); } diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 03682b1e3af..75d4e8ee572 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -33,7 +33,7 @@ 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, PartialOrd, HeapSizeOf)] -pub struct KeyframePercentage(f32); +pub struct KeyframePercentage(pub f32); impl ::std::cmp::Ord for KeyframePercentage { #[inline] @@ -115,9 +115,9 @@ impl Keyframe { pub struct KeyframesStep { /// The percentage of the animation duration that should be taken for this /// step. - duration_percentage: KeyframePercentage, + pub duration_percentage: KeyframePercentage, /// Declarations that will determine the final style during the step. - declarations: Arc>, + pub declarations: Arc>, } impl KeyframesStep { @@ -137,9 +137,9 @@ impl KeyframesStep { /// It only takes into account animable properties. #[derive(Debug, Clone, PartialEq, HeapSizeOf)] pub struct KeyframesAnimation { - steps: Vec, + pub steps: Vec, /// The properties that change in this animation. - properties_changed: Vec, + pub properties_changed: Vec, } /// Get all the animated properties in a keyframes animation. Note that it's not diff --git a/components/style/matching.rs b/components/style/matching.rs index e17af2a2fcb..2ed642de168 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -27,6 +27,8 @@ use util::arc_ptr_eq; use util::cache::{LRUCache, SimpleHashCache}; use util::opts; use util::vec::ForgetfulSink; +use properties::longhands::animation_play_state::computed_value::AnimationPlayState; +use properties::style_struct_traits::Box; fn create_common_style_affecting_attributes_from_element(element: &E) -> CommonStyleAffectingAttributes { @@ -53,7 +55,9 @@ fn create_common_style_affecting_attributes_from_element(element: & pub struct ApplicableDeclarations { pub normal: SmallVec<[DeclarationBlock; 16]>, - pub per_pseudo: HashMap, BuildHasherDefault<::fnv::FnvHasher>>, + pub per_pseudo: HashMap, + BuildHasherDefault<::fnv::FnvHasher>>, /// Whether the `normal` declarations are shareable with other nodes. pub normal_shareable: bool, @@ -364,7 +368,7 @@ pub enum StyleSharingResult { } trait PrivateMatchMethods: TNode - where ::Impl: SelectorImplExt { + where ::Impl: SelectorImplExt { /// Actually cascades style for a node or a pseudo-element of a node. /// /// Note that animations only apply to nodes or ::before or ::after @@ -380,10 +384,8 @@ trait PrivateMatchMethods: TNode animate_properties: bool) -> (Self::ConcreteRestyleDamage, Arc) { let mut cacheable = true; - let mut animations = None; if animate_properties { cacheable = !self.update_animations_for_cascade(context, &mut style) && cacheable; - animations = Some(context.stylist.animations()) } let mut this_style; @@ -420,12 +422,19 @@ trait PrivateMatchMethods: TNode // it did trigger a transition. if animate_properties { if let Some(ref style) = style { - let animations_started = + let mut animations_started = animation::start_transitions_if_applicable::( &context.new_animations_sender, self.opaque(), &**style, &mut this_style); + + // TODO: Take into account animation-play-state + animations_started |= animation::maybe_start_animations::<::Impl>( + &context, + self.opaque(), + &mut this_style); + cacheable = cacheable && !animations_started } } @@ -462,7 +471,7 @@ trait PrivateMatchMethods: TNode had_animations_to_expire = animations_to_expire.is_some(); if let Some(ref animations) = animations_to_expire { for animation in *animations { - animation.property_animation.update(Arc::make_mut(style).as_servo_mut(), 1.0); + animation.property_animation.update(Arc::make_mut(style), 1.0); } } } @@ -490,7 +499,8 @@ trait PrivateMatchMethods: TNode } impl PrivateMatchMethods for N - where ::Impl: SelectorImplExt {} + where ::Impl: + SelectorImplExt {} trait PrivateElementMatchMethods: TElement { fn share_style_with_candidate_if_possible(&self, @@ -648,7 +658,7 @@ pub trait MatchMethods : TNode { local_context: &LocalStyleContext, parent: Option, applicable_declarations: &ApplicableDeclarations<::Impl>) - where ::Impl: SelectorImplExt { + where ::Impl: SelectorImplExt { // Get our parent's style. This must be unsafe so that we don't touch the parent's // borrow flags. // diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index b2eed2758cb..bdd1d3d3aa5 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -583,7 +583,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= pub use properties::longhands::transition_duration::{get_initial_value, parse, parse_one}; -<%helpers:longhand name="animation-name" experimental="True"> +<%helpers:longhand name="animation-name"> use values::computed::ComputedValueAsSpecified; pub mod computed_value { @@ -627,19 +627,19 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= impl ComputedValueAsSpecified for SpecifiedValue {} -<%helpers:longhand name="animation-duration" experimental="True"> +<%helpers:longhand name="animation-duration"> pub use super::transition_duration::computed_value; pub use super::transition_duration::{parse, get_initial_value}; pub use super::transition_duration::SpecifiedValue; -<%helpers:longhand name="animation-timing-function" experimental="True"> +<%helpers:longhand name="animation-timing-function"> pub use super::transition_timing_function::computed_value; pub use super::transition_timing_function::{parse, get_initial_value}; pub use super::transition_timing_function::SpecifiedValue; -<%helpers:longhand name="animation-iteration-count" experimental="True"> +<%helpers:longhand name="animation-iteration-count"> use values::computed::ComputedValueAsSpecified; pub mod computed_value { @@ -709,18 +709,16 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= ${helpers.keyword_list("animation-direction", - "normal reverse alternate alternate-reverse", - experimental=True)} + "normal reverse alternate alternate-reverse")} ${helpers.keyword_list("animation-play-state", "running paused", - experimental=True)} + need_clone=True)} ${helpers.keyword_list("animation-fill-mode", - "none forwards backwards both", - experimental=True)} + "none forwards backwards both")} -<%helpers:longhand name="animation-delay" experimental="True"> +<%helpers:longhand name="animation-delay"> pub use super::transition_duration::computed_value; pub use super::transition_duration::{parse, get_initial_value}; pub use super::transition_duration::SpecifiedValue; From 5b27e46d04759e57d06ebe65e74d6a7191f0ab70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sun, 19 Jun 2016 16:51:02 +0200 Subject: [PATCH 12/28] style: Atomize animation names. --- components/style/animation.rs | 2 +- components/style/properties/longhand/box.mako.rs | 8 +++++--- components/style/selector_matching.rs | 5 +++-- components/style/stylesheets.rs | 6 +++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/components/style/animation.rs b/components/style/animation.rs index 0e82b986f94..d6604b40db5 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -235,7 +235,7 @@ pub fn maybe_start_animations(context: &SharedStyleContex } // TODO: This should be factored out, too much indentation. - if let Some(ref animation) = context.stylist.animations().get(&**name) { + if let Some(ref animation) = context.stylist.animations().get(&name) { debug!("maybe_start_animations: found animation {}", name); had_animations = true; let mut last_keyframe_style = compute_style_for_animation_step(context, diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index bdd1d3d3aa5..4be64fd50aa 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -589,9 +589,10 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= pub mod computed_value { use cssparser::ToCss; use std::fmt; + use string_cache::Atom; #[derive(Debug, Clone, PartialEq, HeapSizeOf)] - pub struct T(pub Vec); + pub struct T(pub Vec); impl ToCss for T { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { @@ -603,7 +604,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= if i != 0 { try!(dest.write_str(", ")); } - try!(dest.write_str(&name)); + // NB: to_string() needed due to geckolib backend. + try!(dest.write_str(&*name.to_string())); } Ok(()) } @@ -620,7 +622,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= pub fn parse(_: &ParserContext, input: &mut Parser) -> Result { use std::borrow::Cow; Ok(SpecifiedValue(try!(input.parse_comma_separated(|input| { - input.expect_ident().map(Cow::into_owned) + input.expect_ident().map(Atom::from) })))) } diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 676b78de08e..a4895e91ecb 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -24,6 +24,7 @@ use std::collections::HashMap; use std::hash::BuildHasherDefault; use std::process; use std::sync::Arc; +use string_cache::Atom; use style_traits::viewport::ViewportConstraints; use stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet}; use url::Url; @@ -129,7 +130,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 @@ -461,7 +462,7 @@ impl Stylist { } #[inline] - pub fn animations(&self) -> &HashMap { + pub fn animations(&self) -> &HashMap { &self.animations } } diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index c4a2ca24c3b..726db0251cf 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -69,7 +69,7 @@ pub enum CSSRule { #[derive(Debug, HeapSizeOf, PartialEq)] pub struct KeyframesRule { - pub name: String, + pub name: Atom, pub keyframes: Vec, } @@ -405,7 +405,7 @@ enum AtRulePrelude { /// A @viewport rule prelude. Viewport, /// A @keyframes rule, with its animation name. - Keyframes(String), + Keyframes(Atom), } @@ -507,7 +507,7 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleParser for NestedRuleParser<'a, 'b, Impl> }, "keyframes" => { let name = try!(input.expect_ident()); - Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(name.into_owned()))) + Ok(AtRuleType::WithBlock(AtRulePrelude::Keyframes(Atom::from(name)))) }, _ => Err(()) } From c16c5acade65f45141c4aa824d49cad3dff98b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sun, 19 Jun 2016 19:39:32 +0200 Subject: [PATCH 13/28] style: Rewrite the animation representation to allow having state in layout I have to make the appropriate changes in layout, but I'm running out of battery in the bus. --- components/constellation/constellation.rs | 2 +- components/layout/animation.rs | 31 +- components/layout_thread/lib.rs | 4 +- components/script/script_thread.rs | 2 +- components/style/animation.rs | 330 +++++++++++++++------- components/style/keyframes.rs | 31 +- components/style/matching.rs | 10 +- 7 files changed, 258 insertions(+), 152 deletions(-) diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index be73d3895d9..1439f24b0b2 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -1136,7 +1136,7 @@ impl Constellation let msg = LayoutControlMsg::TickAnimations; match self.pipelines.get(&pipeline_id) { Some(pipeline) => pipeline.layout_chan.send(msg), - None => return warn!("Pipeline {:?} got script tick after closure.", pipeline_id), + None => return warn!("Pipeline {:?} got layout tick after closure.", pipeline_id), } } }; diff --git a/components/layout/animation.rs b/components/layout/animation.rs index cc87a2824a4..b8e5bed96d4 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -23,7 +23,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender, expired_animations: &mut HashMap>, new_animations_receiver: &Receiver, pipeline_id: PipelineId) { - let mut new_running_animations = Vec::new(); + let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { new_running_animations.push(animation) } @@ -36,22 +36,24 @@ pub fn update_animation_state(constellation_chan: &IpcSender, // Expire old running animations. let now = time::precise_time_s(); - let mut keys_to_remove = Vec::new(); + let mut keys_to_remove = vec![]; for (key, running_animations) in running_animations.iter_mut() { let mut animations_still_running = vec![]; for running_animation in running_animations.drain(..) { if now < running_animation.end_time { animations_still_running.push(running_animation); continue + } else if running_animation.state.pending_iterations > 0 { + // if the animation should run again, just tick it... + let duration = running_animation.end_time - running_animation.start_time; + running_animation.start_time += duration; } - match expired_animations.entry(*key) { - Entry::Vacant(entry) => { - entry.insert(vec![running_animation]); - } - Entry::Occupied(mut entry) => entry.get_mut().push(running_animation), - } + expired_animations.entry(*key) + .or_insert_with(Vec::new) + .push(running_animation); } - if animations_still_running.len() == 0 { + + if animations_still_running.is_empty() { keys_to_remove.push(*key); } else { *running_animations = animations_still_running @@ -84,12 +86,15 @@ pub fn update_animation_state(constellation_chan: &IpcSender, /// Recalculates style for a set of animations. This does *not* run with the DOM lock held. pub fn recalc_style_for_animations(flow: &mut Flow, - animations: &HashMap>) { + animations: &mut HashMap>) { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { - if let Some(ref animations) = animations.get(&fragment.node) { - for animation in *animations { - update_style_for_animation(animation, &mut fragment.style, Some(&mut damage)); + if let Some(ref animations) = animations.get_mut(&fragment.node) { + for mut animation in *animations { + if !animation.is_paused() { + update_style_for_animation(animation, &mut fragment.style, Some(&mut damage)); + animation.increment_keyframe_if_applicable(); + } } } }); diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index fad57c499d3..67e21ab744b 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1302,13 +1302,13 @@ impl LayoutThread { if let Some(mut root_flow) = self.root_flow.clone() { // Perform an abbreviated style recalc that operates without access to the DOM. - let animations = self.running_animations.read().unwrap(); + let mut animations = self.running_animations.write().unwrap(); profile(time::ProfilerCategory::LayoutStyleRecalc, self.profiler_metadata(), self.time_profiler_chan.clone(), || { animation::recalc_style_for_animations(flow_ref::deref_mut(&mut root_flow), - &*animations) + &mut animations) }); } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 6697f334696..96555524c5d 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -671,7 +671,7 @@ impl ScriptThread { } // Store new resizes, and gather all other events. - let mut sequential = vec!(); + let mut sequential = vec![]; // Receive at least one message so we don't spinloop. let mut event = { diff --git a/components/style/animation.rs b/components/style/animation.rs index d6604b40db5..83c7fffc1e2 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -13,6 +13,8 @@ use keyframes::KeyframesStep; use properties::animated_properties::{AnimatedProperty, TransitionProperty}; use properties::longhands::transition_timing_function::computed_value::StartEnd; use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; +use properties::longhands::animation_play_state::computed_value::AnimationPlayState; +use properties::longhands::animation_iteration_count::computed_value::AnimationIterationCount; use properties::style_struct_traits::Box; use properties::{ComputedValues, ServoComputedValues}; use std::sync::mpsc::Sender; @@ -22,43 +24,60 @@ use values::computed::Time; use selector_impl::SelectorImplExt; use context::SharedStyleContext; use selectors::matching::DeclarationBlock; +use string_cache::Atom; use properties; -#[derive(Clone, Debug)] -pub enum AnimationKind { - Transition, - Keyframe, +/// This structure represents a keyframes animation current iteration state. +/// +/// If the iteration count is infinite, there's no other state, otherwise we +/// have to keep track the current iteration and the max iteration count. +#[derive(Debug, Clone)] +pub enum KeyframesIterationState { + Infinite, + // current, max + Finite(u32, u32), +} + +/// This structure represents the current keyframe animation state, i.e., the +/// duration, the current and maximum iteration count, and the state (either +/// playing or paused). +#[derive(Debug, Clone)] +pub struct KeyframesAnimationState { + pub started_at: f64, + pub duration: f64, + pub iteration_state: KeyframesIterationState, + pub paused: bool, } /// State relating to an animation. -#[derive(Clone)] -pub struct Animation { - /// The kind of animation, either a transition or a keyframe. - pub kind: AnimationKind, - /// An opaque reference to the DOM node participating in the animation. - pub node: OpaqueNode, +#[derive(Clone, Debug)] +pub enum Animation { + /// A transition is just a single frame triggered at a time, with a reflow. + /// + /// the f64 field is the start time as returned by `time::precise_time_s()`. + Transition(OpaqueNode, f64, AnimationFrame), + /// A keyframes animation is identified by a name, and can have a + /// node-dependent state (i.e. iteration count, etc.). + Keyframes(OpaqueNode, Atom, KeyframesAnimationState), +} + +/// A keyframes animation previously sent to layout. + +/// A single animation frame of a single property. +#[derive(Debug, Clone)] +pub struct AnimationFrame { /// A description of the property animation that is occurring. pub property_animation: PropertyAnimation, - /// The start time of the animation, as returned by `time::precise_time_s()`. - pub start_time: f64, - /// The end time of the animation, as returned by `time::precise_time_s()`. - pub end_time: f64, + /// The duration of the animation. This is either relative in the keyframes + /// case (a number between 0 and 1), or absolute in the transition case. + pub duration: f64, } -impl Animation { - /// Returns the duration of this animation in seconds. - #[inline] - pub fn duration(&self) -> f64 { - self.end_time - self.start_time - } -} - - -#[derive(Clone, Debug)] +#[derive(Debug, Clone)] pub struct PropertyAnimation { property: AnimatedProperty, timing_function: TransitionTimingFunction, - duration: Time, + duration: Time, // TODO: isn't this just repeated? } impl PropertyAnimation { @@ -167,9 +186,12 @@ impl GetMod for Vec { } } -/// Inserts transitions into the queue of running animations as applicable for the given style -/// difference. This is called from the layout worker threads. Returns true if any animations were -/// kicked off and false otherwise. +/// Inserts transitions into the queue of running animations as applicable for +/// the given style difference. This is called from the layout worker threads. +/// Returns true if any animations were kicked off and false otherwise. +// +// TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a +// cloneable part and a non-cloneable part.. pub fn start_transitions_if_applicable(new_animations_sender: &Mutex>, node: OpaqueNode, old_style: &C, @@ -188,16 +210,14 @@ pub fn start_transitions_if_applicable(new_animations_sender: let box_style = new_style.as_servo().get_box(); let start_time = now + (box_style.transition_delay.0.get_mod(i).seconds() as f64); - new_animations_sender.lock().unwrap().send(Animation { - kind: AnimationKind::Transition, - node: node, - property_animation: property_animation, - start_time: start_time, - end_time: start_time + - (box_style.transition_duration.0.get_mod(i).seconds() as f64), - }).unwrap(); + new_animations_sender + .lock().unwrap() + .send(Animation::Transition(node, start_time, AnimationFrame { + duration: box_style.transition_duration.0.get_mod(i).seconds() as f64, + property_animation: property_animation, + })).unwrap(); - had_animations = true + had_animations = true; } } @@ -227,95 +247,191 @@ pub fn maybe_start_animations(context: &SharedStyleContex { let mut had_animations = false; - for (i, name) in new_style.as_servo().get_box().animation_name.0.iter().enumerate() { + let box_style = new_style.as_servo().get_box(); + for (i, name) in box_style.animation_name.0.iter().enumerate() { debug!("maybe_start_animations: name={}", name); - let total_duration = new_style.as_servo().get_box().animation_duration.0.get_mod(i).seconds(); + let total_duration = box_style.animation_duration.0.get_mod(i).seconds(); if total_duration == 0. { continue } - // TODO: This should be factored out, too much indentation. if let Some(ref animation) = context.stylist.animations().get(&name) { - debug!("maybe_start_animations: found animation {}", name); - had_animations = true; - let mut last_keyframe_style = compute_style_for_animation_step(context, - &animation.steps[0], - new_style); - // Apply the style inmediately. TODO: clone()... - // *new_style = last_keyframe_style.clone(); - - let mut ongoing_animation_percentage = animation.steps[0].duration_percentage.0; - let delay = new_style.as_servo().get_box().animation_delay.0.get_mod(i).seconds(); + let delay = box_style.animation_delay.0.get_mod(i).seconds(); let animation_start = time::precise_time_s() + delay as f64; + let duration = box_style.animation_duration.0.get_mod(i).seconds(); + let iteration_state = match *box_style.animation_iteration_count.0.get_mod(i) { + AnimationIterationCount::Infinite => KeyframesIterationState::Infinite, + AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0, n), + }; + let paused = *box_style.animation_play_state.0.get_mod(i) == AnimationPlayState::paused; - // TODO: We can probably be smarter here and batch steps out or - // something. - for step in &animation.steps[1..] { - for transition_property in &animation.properties_changed { - debug!("maybe_start_animations: processing animation prop {:?} for animation {}", transition_property, name); - - let new_keyframe_style = compute_style_for_animation_step(context, - step, - &last_keyframe_style); - // NB: This will get the previous frame timing function, or - // the old one if caught, which is what the spec says. - // - // We might need to reset to the initial timing function - // though. - let timing_function = - *last_keyframe_style.as_servo() - .get_box().animation_timing_function.0.get_mod(i); - - let percentage = step.duration_percentage.0; - let this_keyframe_duration = total_duration * percentage; - if let Some(property_animation) = PropertyAnimation::from_transition_property(*transition_property, - timing_function, - Time(this_keyframe_duration), - &last_keyframe_style, - &new_keyframe_style) { - debug!("maybe_start_animations: got property animation for prop {:?}", transition_property); - - let relative_start_time = ongoing_animation_percentage * total_duration; - let start_time = animation_start + relative_start_time as f64; - let end_time = start_time + (relative_start_time + this_keyframe_duration) as f64; - context.new_animations_sender.lock().unwrap().send(Animation { - kind: AnimationKind::Keyframe, - node: node, - property_animation: property_animation, - start_time: start_time, - end_time: end_time, - }).unwrap(); - } - - last_keyframe_style = new_keyframe_style; - ongoing_animation_percentage += percentage; - } - } + context.new_animations_sender + .lock().unwrap() + .send(Animation::Keyframes(node, name.clone(), KeyframesAnimationState { + started_at: animation_start, + duration: duration as f64, + iteration_state: iteration_state, + paused: paused, + })).unwrap(); + had_animations = true; } } had_animations } -/// Updates a single animation and associated style based on the current time. If `damage` is -/// provided, inserts the appropriate restyle damage. -pub fn update_style_for_animation(animation: &Animation, - style: &mut Arc, - damage: Option<&mut Damage>) { - let now = time::precise_time_s(); - let mut progress = (now - animation.start_time) / animation.duration(); +/// Updates a given computed style for a given animation frame. Returns a bool +/// representing if the style was indeed updated. +pub fn update_style_for_animation_frame(mut new_style: &mut Arc, + now: f64, + start_time: f64, + frame: &AnimationFrame) -> bool { + let mut progress = (now - start_time) / frame.duration; if progress > 1.0 { progress = 1.0 } + if progress <= 0.0 { - return + return false; } - let mut new_style = (*style).clone(); - animation.property_animation.update(Arc::make_mut(&mut new_style), progress); - if let Some(damage) = damage { - *damage = *damage | Damage::compute(Some(style), &new_style); - } + frame.property_animation.update(Arc::make_mut(&mut new_style), progress); - *style = new_style + true +} +/// Updates a single animation and associated style based on the current time. If `damage` is +/// provided, inserts the appropriate restyle damage. +pub fn update_style_for_animation(context: &SharedStyleContext, + animation: &Animation, + style: &mut Arc, + damage: Option<&mut Damage>) +where Impl: SelectorImplExt, + Damage: TRestyleDamage { + let now = time::precise_time_s(); + match *animation { + Animation::Transition(_, start_time, ref frame) => { + let mut new_style = (*style).clone(); + let updated_style = update_style_for_animation_frame(&mut new_style, + now, start_time, + frame); + if updated_style { + if let Some(damage) = damage { + *damage = *damage | Damage::compute(Some(style), &new_style); + } + + *style = new_style + } + } + Animation::Keyframes(_, ref name, ref state) => { + debug_assert!(!state.paused); + let duration = state.duration; + let started_at = state.started_at; + + let animation = match context.stylist.animations().get(name) { + None => { + warn!("update_style_for_animation: Animation {:?} not found", name); + return; + } + Some(animation) => animation, + }; + + let maybe_index = style.as_servo() + .get_box().animation_name.0.iter() + .position(|animation_name| name == animation_name); + + let index = match maybe_index { + Some(index) => index, + None => { + warn!("update_style_for_animation: Animation {:?} not found in style", name); + return; + } + }; + + let total_duration = style.as_servo().get_box().animation_duration.0.get_mod(index).seconds() as f64; + if total_duration == 0. { + debug!("update_style_for_animation: zero duration for animation {:?}", name); + return; + } + + let mut total_progress = (now - started_at) / total_duration; + if total_progress < 0. { + warn!("Negative progress found for animation {:?}", name); + } + if total_progress > 1. { + total_progress = 1.; + } + + + let mut last_keyframe = None; + let mut target_keyframe = None; + + // TODO: we could maybe binary-search this? + for i in 1..animation.steps.len() { + if total_progress as f32 <= animation.steps[i].start_percentage.0 { + // We might have found our current keyframe. + target_keyframe = Some(&animation.steps[i]); + last_keyframe = target_keyframe; + } + } + + let target_keyframe = match target_keyframe { + Some(current) => current, + None => { + warn!("update_style_for_animation: No current keyframe found for animation {:?} at progress {}", name, total_progress); + return; + } + }; + + let last_keyframe = match last_keyframe { + Some(last_keyframe) => last_keyframe, + None => { + warn!("update_style_for_animation: No last keyframe found for animation {:?} at progress {}", name, total_progress); + return; + } + }; + + let relative_duration = (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0) as f64 * duration; + let last_keyframe_ended_at = state.started_at + (total_duration * last_keyframe.start_percentage.0 as f64); + let relative_progress = (now - last_keyframe_ended_at) / relative_duration; + + // TODO: How could we optimise it? Is it such a big deal? + let from_style = compute_style_for_animation_step(context, + last_keyframe, + &**style); + + // NB: The spec says that the timing function can be overwritten + // from the keyframe style. + let mut timing_function = *style.as_servo().get_box().animation_timing_function.0.get_mod(index); + if !from_style.as_servo().get_box().animation_timing_function.0.is_empty() { + timing_function = from_style.as_servo().get_box().animation_timing_function.0[0]; + } + + let mut target_style = compute_style_for_animation_step(context, + target_keyframe, + &from_style); + + let mut new_style = (*style).clone(); + let mut style_changed = false; + + for transition_property in &animation.properties_changed { + if let Some(property_animation) = PropertyAnimation::from_transition_property(*transition_property, + timing_function, + Time(relative_duration as f32), + &from_style, + &target_style) { + debug!("update_style_for_animation: got property animation for prop {:?}", transition_property); + property_animation.update(Arc::make_mut(&mut new_style), relative_progress); + style_changed = true; + } + } + + if style_changed { + if let Some(damage) = damage { + *damage = *damage | Damage::compute(Some(style), &new_style); + } + + *style = new_style; + } + } + } } diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 75d4e8ee572..501e3863960 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -113,9 +113,8 @@ impl Keyframe { /// A single step from a keyframe animation. #[derive(Debug, Clone, PartialEq, HeapSizeOf)] pub struct KeyframesStep { - /// The percentage of the animation duration that should be taken for this - /// step. - pub duration_percentage: KeyframePercentage, + /// The percentage of the animation duration when this step starts. + pub start_percentage: KeyframePercentage, /// Declarations that will determine the final style during the step. pub declarations: Arc>, } @@ -125,7 +124,7 @@ impl KeyframesStep { fn new(percentage: KeyframePercentage, declarations: Arc>) -> Self { KeyframesStep { - duration_percentage: percentage, + start_percentage: percentage, declarations: declarations, } } @@ -166,9 +165,6 @@ impl KeyframesAnimation { debug_assert!(keyframes.len() > 1); let mut steps = vec![]; - // NB: we do two passes, first storing the steps in the order of - // 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; @@ -181,24 +177,8 @@ impl KeyframesAnimation { } } - 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; - } + // Sort by the start percentage, so we can easily find a frame. + steps.sort_by_key(|step| step.start_percentage); Some(KeyframesAnimation { steps: steps, @@ -206,3 +186,4 @@ impl KeyframesAnimation { }) } } + diff --git a/components/style/matching.rs b/components/style/matching.rs index 2ed642de168..8e1a04577c4 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -6,7 +6,7 @@ #![allow(unsafe_code)] -use animation; +use animation::{self, Animation}; use context::{SharedStyleContext, LocalStyleContext}; use data::PrivateStyleData; use dom::{TElement, TNode, TRestyleDamage}; @@ -471,7 +471,10 @@ trait PrivateMatchMethods: TNode had_animations_to_expire = animations_to_expire.is_some(); if let Some(ref animations) = animations_to_expire { for animation in *animations { - animation.property_animation.update(Arc::make_mut(style), 1.0); + // TODO: revisit this code for keyframes + if let Animation::Transition(_, _, ref frame) = *animation { + frame.property_animation.update(Arc::make_mut(style), 1.0); + } } } } @@ -489,7 +492,8 @@ trait PrivateMatchMethods: TNode if had_running_animations { let mut all_running_animations = context.running_animations.write().unwrap(); for running_animation in all_running_animations.get(&this_opaque).unwrap() { - animation::update_style_for_animation::(running_animation, style, None); + animation::update_style_for_animation::::Impl>(context, running_animation, style, None); } all_running_animations.remove(&this_opaque); } From bc970596d6f49cff387ecce401094bf417797ace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Mon, 20 Jun 2016 17:30:43 +0200 Subject: [PATCH 14/28] layout: Make animations work... more or less. There's some maths I've done wrong, but it DOES animate some things, though they're only triggered past the first restyle, and we probably have some duplications where the animations arrive to layout. Anyway, got to go. --- components/layout/animation.rs | 66 +++++++++++++++++++++------------ components/layout_thread/lib.rs | 3 +- components/style/animation.rs | 35 +++++++++++++++-- 3 files changed, 77 insertions(+), 27 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index b8e5bed96d4..42a71206522 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -4,6 +4,7 @@ //! CSS transitions and animations. +use context::SharedLayoutContext; use flow::{self, Flow}; use gfx::display_list::OpaqueNode; use ipc_channel::ipc::IpcSender; @@ -13,7 +14,7 @@ use script_traits::{AnimationState, LayoutMsg as ConstellationMsg}; use std::collections::HashMap; use std::collections::hash_map::Entry; use std::sync::mpsc::Receiver; -use style::animation::{Animation, update_style_for_animation}; +use style::animation::{Animation, KeyframesAnimationState, KeyframesIterationState, update_style_for_animation}; use time; /// Processes any new animations that were discovered after style recalculation. @@ -39,15 +40,33 @@ pub fn update_animation_state(constellation_chan: &IpcSender, let mut keys_to_remove = vec![]; for (key, running_animations) in running_animations.iter_mut() { let mut animations_still_running = vec![]; - for running_animation in running_animations.drain(..) { - if now < running_animation.end_time { + for mut running_animation in running_animations.drain(..) { + let still_running = match running_animation { + Animation::Transition(_, started_at, ref frame) => { + now < started_at + frame.duration + } + Animation::Keyframes(_, _, ref mut state) => { + // This animation is still running, or we need to keep + // iterating. + now < state.started_at + state.duration || + match state.iteration_state { + KeyframesIterationState::Finite(ref mut current, ref max) => { + *current += 1; + *current < *max + } + KeyframesIterationState::Infinite => { + state.started_at += state.duration; + true + } + } + } + }; + + if still_running { animations_still_running.push(running_animation); continue - } else if running_animation.state.pending_iterations > 0 { - // if the animation should run again, just tick it... - let duration = running_animation.end_time - running_animation.start_time; - running_animation.start_time += duration; } + expired_animations.entry(*key) .or_insert_with(Vec::new) .push(running_animation); @@ -59,40 +78,41 @@ pub fn update_animation_state(constellation_chan: &IpcSender, *running_animations = animations_still_running } } + for key in keys_to_remove { running_animations.remove(&key).unwrap(); } // Add new running animations. for new_running_animation in new_running_animations { - match running_animations.entry(new_running_animation.node) { - Entry::Vacant(entry) => { - entry.insert(vec![new_running_animation]); - } - Entry::Occupied(mut entry) => entry.get_mut().push(new_running_animation), - } + running_animations.entry(*new_running_animation.node()) + .or_insert_with(Vec::new) + .push(new_running_animation); } - let animation_state; - if running_animations.is_empty() { - animation_state = AnimationState::NoAnimationsPresent; + let animation_state = if running_animations.is_empty() { + AnimationState::NoAnimationsPresent } else { - animation_state = AnimationState::AnimationsPresent; - } + AnimationState::AnimationsPresent + }; constellation_chan.send(ConstellationMsg::ChangeRunningAnimationsState(pipeline_id, animation_state)) .unwrap(); } /// Recalculates style for a set of animations. This does *not* run with the DOM lock held. -pub fn recalc_style_for_animations(flow: &mut Flow, +pub fn recalc_style_for_animations(context: &SharedLayoutContext, + flow: &mut Flow, animations: &mut HashMap>) { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { - if let Some(ref animations) = animations.get_mut(&fragment.node) { - for mut animation in *animations { + if let Some(ref mut animations) = animations.get_mut(&fragment.node) { + for ref mut animation in animations.iter_mut() { if !animation.is_paused() { - update_style_for_animation(animation, &mut fragment.style, Some(&mut damage)); + update_style_for_animation(&context.style_context, + animation, + &mut fragment.style, + Some(&mut damage)); animation.increment_keyframe_if_applicable(); } } @@ -102,6 +122,6 @@ pub fn recalc_style_for_animations(flow: &mut Flow, let base = flow::mut_base(flow); base.restyle_damage.insert(damage); for kid in base.children.iter_mut() { - recalc_style_for_animations(kid, animations) + recalc_style_for_animations(context, kid, animations) } } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 67e21ab744b..b37d35e905a 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1307,7 +1307,8 @@ impl LayoutThread { self.profiler_metadata(), self.time_profiler_chan.clone(), || { - animation::recalc_style_for_animations(flow_ref::deref_mut(&mut root_flow), + animation::recalc_style_for_animations(&layout_context, + flow_ref::deref_mut(&mut root_flow), &mut animations) }); } diff --git a/components/style/animation.rs b/components/style/animation.rs index 83c7fffc1e2..a0375b380ad 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -61,7 +61,30 @@ pub enum Animation { Keyframes(OpaqueNode, Atom, KeyframesAnimationState), } -/// A keyframes animation previously sent to layout. +impl Animation { + pub fn node(&self) -> &OpaqueNode { + match *self { + Animation::Transition(ref node, _, _) => node, + Animation::Keyframes(ref node, _, _) => node, + } + } + + pub fn is_paused(&self) -> bool { + match *self { + Animation::Transition(..) => false, + Animation::Keyframes(_, _, ref state) => state.paused, + } + } + + pub fn increment_keyframe_if_applicable(&mut self) { + if let Animation::Keyframes(_, _, ref mut state) = *self { + if let KeyframesIterationState::Finite(ref mut iterations, _) = state.iteration_state { + *iterations += 1; + } + } + } +} + /// A single animation frame of a single property. #[derive(Debug, Clone)] @@ -255,7 +278,8 @@ pub fn maybe_start_animations(context: &SharedStyleContex continue } - if let Some(ref animation) = context.stylist.animations().get(&name) { + if context.stylist.animations().get(&name).is_some() { + debug!("maybe_start_animations: animation {} found", name); let delay = box_style.animation_delay.0.get_mod(i).seconds(); let animation_start = time::precise_time_s() + delay as f64; let duration = box_style.animation_duration.0.get_mod(i).seconds(); @@ -307,9 +331,11 @@ pub fn update_style_for_animation(context: &SharedStyleContext) where Impl: SelectorImplExt, Damage: TRestyleDamage { + debug!("update_style_for_animation: entering"); let now = time::precise_time_s(); match *animation { Animation::Transition(_, start_time, ref frame) => { + debug!("update_style_for_animation: transition found"); let mut new_style = (*style).clone(); let updated_style = update_style_for_animation_frame(&mut new_style, now, start_time, @@ -323,6 +349,7 @@ where Impl: SelectorImplExt, } } Animation::Keyframes(_, ref name, ref state) => { + debug!("update_style_for_animation: animation found {:?}", name); debug_assert!(!state.paused); let duration = state.duration; let started_at = state.started_at; @@ -369,8 +396,8 @@ where Impl: SelectorImplExt, for i in 1..animation.steps.len() { if total_progress as f32 <= animation.steps[i].start_percentage.0 { // We might have found our current keyframe. - target_keyframe = Some(&animation.steps[i]); last_keyframe = target_keyframe; + target_keyframe = Some(&animation.steps[i]); } } @@ -414,6 +441,7 @@ where Impl: SelectorImplExt, let mut style_changed = false; for transition_property in &animation.properties_changed { + debug!("update_style_for_animation: scanning prop {:?} for animation {}", transition_property, name); if let Some(property_animation) = PropertyAnimation::from_transition_property(*transition_property, timing_function, Time(relative_duration as f32), @@ -426,6 +454,7 @@ where Impl: SelectorImplExt, } if style_changed { + debug!("update_style_for_animation: got style change in animation {:?}", name); if let Some(damage) = damage { *damage = *damage | Damage::compute(Some(style), &new_style); } From cb3da24f080d4cb52b0233d6215fc07f96bd49db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 21 Jun 2016 00:02:25 +0200 Subject: [PATCH 15/28] style: layout: Get actual keyframes working! There are a few shortcomings, for example: * We don't do the same as other browsers when some properties are not specified in some of the keyframes, though this is easy to work out (though a bit more expensive in the sense that we should apply all the previous keyframes' style instead of just the previous and the next. * To trigger the initial animation, a restyle is necessary. Should be easy to do an initial restyle off-hand or something like that, but for now this is worked-around adding a :hover rule to the node. Also, the animation is resetted when the node is hovered. That's a bug, but is probably not so difficult to test. * A few things, mainly animation-direction, are not supported yet, but shouldn't be that hard to support. Still a lot of work to do, but I think this approach might be ok. --- components/layout/animation.rs | 42 ++++++++++++++++++++++++++++++++-- components/style/animation.rs | 42 ++++++++++++++++++++++------------ 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index 42a71206522..b79121a0573 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -26,7 +26,44 @@ pub fn update_animation_state(constellation_chan: &IpcSender, pipeline_id: PipelineId) { let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { - new_running_animations.push(animation) + let should_push = match animation { + Animation::Transition(..) => true, + Animation::Keyframes(ref node, ref name, ref state) => { + // If the animation was already present in the list for the + // node, just update its state, else push the new animation to + // run. + if let Some(ref mut animations) = running_animations.get_mut(node) { + // TODO: This being linear is probably not optimal. + match animations.iter_mut().find(|anim| match **anim { + Animation::Keyframes(_, ref anim_name, _) => *name == *anim_name, + Animation::Transition(..) => false, + }) { + Some(mut anim) => { + debug!("update_animation_state: Found other animation {}", name); + match *anim { + Animation::Keyframes(_, _, ref mut anim_state) => { + // NB: The important part is not touching + // the started_at field. + anim_state.duration = state.duration; + anim_state.iteration_state = state.iteration_state.clone(); + anim_state.paused = state.paused; + anim_state.delay = state.delay; + false + } + _ => unreachable!(), + } + } + None => true, + } + } else { + true + } + } + }; + + if should_push { + new_running_animations.push(animation); + } } if running_animations.is_empty() && new_running_animations.is_empty() { @@ -54,8 +91,9 @@ pub fn update_animation_state(constellation_chan: &IpcSender, *current += 1; *current < *max } + // Just tick it again. KeyframesIterationState::Infinite => { - state.started_at += state.duration; + state.started_at += state.duration + state.delay; true } } diff --git a/components/style/animation.rs b/components/style/animation.rs index a0375b380ad..367dcb99b66 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -41,10 +41,12 @@ pub enum KeyframesIterationState { /// This structure represents the current keyframe animation state, i.e., the /// duration, the current and maximum iteration count, and the state (either /// playing or paused). +// TODO: unify the use of f32/f64 in this file. #[derive(Debug, Clone)] pub struct KeyframesAnimationState { pub started_at: f64, pub duration: f64, + pub delay: f64, pub iteration_state: KeyframesIterationState, pub paused: bool, } @@ -294,6 +296,7 @@ pub fn maybe_start_animations(context: &SharedStyleContex .send(Animation::Keyframes(node, name.clone(), KeyframesAnimationState { started_at: animation_start, duration: duration as f64, + delay: delay as f64, iteration_state: iteration_state, paused: paused, })).unwrap(); @@ -383,36 +386,46 @@ where Impl: SelectorImplExt, let mut total_progress = (now - started_at) / total_duration; if total_progress < 0. { warn!("Negative progress found for animation {:?}", name); + return; } if total_progress > 1. { total_progress = 1.; } + debug!("update_style_for_animation: anim \"{}\", steps: {:?}, state: {:?}, progress: {}", name, animation.steps, state, total_progress); - let mut last_keyframe = None; - let mut target_keyframe = None; + let mut last_keyframe_position = None; + let mut target_keyframe_position = None; - // TODO: we could maybe binary-search this? - for i in 1..animation.steps.len() { + // TODO: we could maybe binary-search this? Also, find is probably a + // bit more idiomatic here? + for i in 0..animation.steps.len() { if total_progress as f32 <= animation.steps[i].start_percentage.0 { // We might have found our current keyframe. - last_keyframe = target_keyframe; - target_keyframe = Some(&animation.steps[i]); + target_keyframe_position = Some(i); + if i != 0 { + last_keyframe_position = Some(i - 1); + } + break; } } - let target_keyframe = match target_keyframe { - Some(current) => current, + debug!("update_style_for_animation: keyframe from {:?} to {:?}", last_keyframe_position, target_keyframe_position); + + let target_keyframe = match target_keyframe_position { + Some(target) => &animation.steps[target], None => { - warn!("update_style_for_animation: No current keyframe found for animation {:?} at progress {}", name, total_progress); + // TODO: The 0. case falls here, maybe we should just resort + // to the first keyframe instead. + warn!("update_style_for_animation: No current keyframe found for animation \"{}\" at progress {}", name, total_progress); return; } }; - let last_keyframe = match last_keyframe { - Some(last_keyframe) => last_keyframe, + let last_keyframe = match last_keyframe_position { + Some(last) => &animation.steps[last], None => { - warn!("update_style_for_animation: No last keyframe found for animation {:?} at progress {}", name, total_progress); + warn!("update_style_for_animation: No last keyframe found for animation \"{}\" at progress {}", name, total_progress); return; } }; @@ -441,20 +454,21 @@ where Impl: SelectorImplExt, let mut style_changed = false; for transition_property in &animation.properties_changed { - debug!("update_style_for_animation: scanning prop {:?} for animation {}", transition_property, name); + debug!("update_style_for_animation: scanning prop {:?} for animation \"{}\"", transition_property, name); if let Some(property_animation) = PropertyAnimation::from_transition_property(*transition_property, timing_function, Time(relative_duration as f32), &from_style, &target_style) { debug!("update_style_for_animation: got property animation for prop {:?}", transition_property); + debug!("update_style_for_animation: {:?}", property_animation); property_animation.update(Arc::make_mut(&mut new_style), relative_progress); style_changed = true; } } if style_changed { - debug!("update_style_for_animation: got style change in animation {:?}", name); + debug!("update_style_for_animation: got style change in animation \"{}\"", name); if let Some(damage) = damage { *damage = *damage | Damage::compute(Some(style), &new_style); } From 9cf2e52d3604384e93a861ee218591a2c80c1816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 22 Jun 2016 16:21:37 +0200 Subject: [PATCH 16/28] tidy: Clean up warnings and tidy lints. --- components/layout/animation.rs | 3 +- components/style/animation.rs | 58 +++++++++------- components/style/keyframes.rs | 5 +- components/style/matching.rs | 5 +- components/style/properties/data.py | 67 ++++++++++--------- .../helpers/animated_properties.mako.rs | 12 ++-- .../style/properties/properties.mako.rs | 3 +- components/style/selector_matching.rs | 1 - 8 files changed, 78 insertions(+), 76 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index b79121a0573..337855599e6 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -12,9 +12,8 @@ use msg::constellation_msg::PipelineId; use script_layout_interface::restyle_damage::RestyleDamage; use script_traits::{AnimationState, LayoutMsg as ConstellationMsg}; use std::collections::HashMap; -use std::collections::hash_map::Entry; use std::sync::mpsc::Receiver; -use style::animation::{Animation, KeyframesAnimationState, KeyframesIterationState, update_style_for_animation}; +use style::animation::{Animation, KeyframesIterationState, update_style_for_animation}; use time; /// Processes any new animations that were discovered after style recalculation. diff --git a/components/style/animation.rs b/components/style/animation.rs index 367dcb99b66..9afea17b213 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -6,26 +6,24 @@ use app_units::Au; use bezier::Bezier; -use euclid::point::Point2D; +use context::SharedStyleContext; use dom::{OpaqueNode, TRestyleDamage}; -use keyframes::KeyframesAnimation; +use euclid::point::Point2D; use keyframes::KeyframesStep; use properties::animated_properties::{AnimatedProperty, TransitionProperty}; +use properties::longhands::animation_iteration_count::computed_value::AnimationIterationCount; +use properties::longhands::animation_play_state::computed_value::AnimationPlayState; use properties::longhands::transition_timing_function::computed_value::StartEnd; use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; -use properties::longhands::animation_play_state::computed_value::AnimationPlayState; -use properties::longhands::animation_iteration_count::computed_value::AnimationIterationCount; use properties::style_struct_traits::Box; -use properties::{ComputedValues, ServoComputedValues}; +use properties::{self, ComputedValues}; +use selector_impl::SelectorImplExt; +use selectors::matching::DeclarationBlock; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; +use string_cache::Atom; use time; use values::computed::Time; -use selector_impl::SelectorImplExt; -use context::SharedStyleContext; -use selectors::matching::DeclarationBlock; -use string_cache::Atom; -use properties; /// This structure represents a keyframes animation current iteration state. /// @@ -392,7 +390,8 @@ where Impl: SelectorImplExt, total_progress = 1.; } - debug!("update_style_for_animation: anim \"{}\", steps: {:?}, state: {:?}, progress: {}", name, animation.steps, state, total_progress); + debug!("update_style_for_animation: anim \"{}\", steps: {:?}, state: {:?}, progress: {}", + name, animation.steps, state, total_progress); let mut last_keyframe_position = None; let mut target_keyframe_position = None; @@ -410,14 +409,16 @@ where Impl: SelectorImplExt, } } - debug!("update_style_for_animation: keyframe from {:?} to {:?}", last_keyframe_position, target_keyframe_position); + debug!("update_style_for_animation: keyframe from {:?} to {:?}", + last_keyframe_position, target_keyframe_position); let target_keyframe = match target_keyframe_position { Some(target) => &animation.steps[target], None => { // TODO: The 0. case falls here, maybe we should just resort // to the first keyframe instead. - warn!("update_style_for_animation: No current keyframe found for animation \"{}\" at progress {}", name, total_progress); + warn!("update_style_for_animation: No current keyframe found for animation \"{}\" at progress {}", + name, total_progress); return; } }; @@ -425,12 +426,14 @@ where Impl: SelectorImplExt, let last_keyframe = match last_keyframe_position { Some(last) => &animation.steps[last], None => { - warn!("update_style_for_animation: No last keyframe found for animation \"{}\" at progress {}", name, total_progress); + warn!("update_style_for_animation: No last keyframe found for animation \"{}\" at progress {}", + name, total_progress); return; } }; - let relative_duration = (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0) as f64 * duration; + let relative_timespan = target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0; + let relative_duration = relative_timespan as f64 * duration; let last_keyframe_ended_at = state.started_at + (total_duration * last_keyframe.start_percentage.0 as f64); let relative_progress = (now - last_keyframe_ended_at) / relative_duration; @@ -446,25 +449,32 @@ where Impl: SelectorImplExt, timing_function = from_style.as_servo().get_box().animation_timing_function.0[0]; } - let mut target_style = compute_style_for_animation_step(context, - target_keyframe, - &from_style); + let target_style = compute_style_for_animation_step(context, + target_keyframe, + &from_style); let mut new_style = (*style).clone(); let mut style_changed = false; for transition_property in &animation.properties_changed { - debug!("update_style_for_animation: scanning prop {:?} for animation \"{}\"", transition_property, name); - if let Some(property_animation) = PropertyAnimation::from_transition_property(*transition_property, - timing_function, - Time(relative_duration as f32), - &from_style, - &target_style) { + debug!("update_style_for_animation: scanning prop {:?} for animation \"{}\"", + transition_property, name); + match PropertyAnimation::from_transition_property(*transition_property, + timing_function, + Time(relative_duration as f32), + &from_style, + &target_style) { + Some(property_animation) => { debug!("update_style_for_animation: got property animation for prop {:?}", transition_property); debug!("update_style_for_animation: {:?}", property_animation); property_animation.update(Arc::make_mut(&mut new_style), relative_progress); style_changed = true; } + None => { + debug!("update_style_for_animation: property animation {:?} not animating", + transition_property); + } + } } if style_changed { diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 501e3863960..07e504a5988 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -3,10 +3,9 @@ * 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, PropertyDeclaration, PropertyDeclarationBlock, parse_property_declaration_list}; use properties::animated_properties::TransitionProperty; +use properties::{PropertyDeclaration, parse_property_declaration_list}; use std::sync::Arc; /// Parses a keyframes list, like: @@ -165,7 +164,7 @@ impl KeyframesAnimation { debug_assert!(keyframes.len() > 1); let mut steps = vec![]; - let mut animated_properties = get_animated_properties(&keyframes[0]); + let animated_properties = get_animated_properties(&keyframes[0]); if animated_properties.is_empty() { return None; } diff --git a/components/style/matching.rs b/components/style/matching.rs index 8e1a04577c4..3d6d2bd147e 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -27,8 +27,6 @@ use util::arc_ptr_eq; use util::cache::{LRUCache, SimpleHashCache}; use util::opts; use util::vec::ForgetfulSink; -use properties::longhands::animation_play_state::computed_value::AnimationPlayState; -use properties::style_struct_traits::Box; fn create_common_style_affecting_attributes_from_element(element: &E) -> CommonStyleAffectingAttributes { @@ -662,7 +660,8 @@ pub trait MatchMethods : TNode { local_context: &LocalStyleContext, parent: Option, applicable_declarations: &ApplicableDeclarations<::Impl>) - where ::Impl: SelectorImplExt { + where ::Impl: SelectorImplExt + { // Get our parent's style. This must be unsafe so that we don't touch the parent's // borrow flags. // diff --git a/components/style/properties/data.py b/components/style/properties/data.py index eb16f986319..e778baf095b 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -19,36 +19,36 @@ def to_camel_case(ident): # https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties def is_known_animatable_property(name): return name in [ - "-moz-outline-radius", "-moz-outline-radius-bottomleft", - "-moz-outline-radius-bottomright", "-moz-outline-radius-topleft", - "-moz-outline-radius-topright", "-webkit-text-fill-color", - "-webkit-text-stroke", "-webkit-text-stroke-color", - "-webkit-touch-callout", "all", "backdrop-filter", "background", - "background-color", "background-position", "background-size", "border", - "border-bottom", "border-bottom-color", "border-bottom-left-radius", - "border-bottom-right-radius", "border-bottom-width", "border-color", - "border-left", "border-left-color", "border-left-width", "border-radius", - "border-right", "border-right-color", "border-right-width", "border-top", - "border-top-color", "border-top-left-radius", "border-top-right-radius", - "border-top-width", "border-width", "bottom", "box-shadow", "clip", - "clip-path", "color", "column-count", "column-gap", "column-rule", - "column-rule-color", "column-rule-width", "column-width", "columns", - "filter", "flex", "flex-basis", "flex-grow", "flex-shrink", "font", - "font-size", "font-size-adjust", "font-stretch", "font-weight", - "grid-column-gap", "grid-gap", "grid-row-gap", "height", "left", - "letter-spacing", "line-height", "margin", "margin-bottom", - "margin-left", "margin-right", "margin-top", "mask", "mask-position", - "mask-size", "max-height", "max-width", "min-height", "min-width", - "motion-offset", "motion-rotation", "object-position", "opacity", - "order", "outline", "outline-color", "outline-offset", "outline-width", - "padding", "padding-bottom", "padding-left", "padding-right", - "padding-top", "perspective", "perspective-origin", "right", - "scroll-snap-coordinate", "scroll-snap-destination", - "shape-image-threshold", "shape-margin", "shape-outside", - "text-decoration", "text-decoration-color", "text-emphasis", - "text-emphasis-color", "text-indent", "text-shadow", "top", "transform", - "transform-origin", "vertical-align", "visibility", "width", - "word-spacing", "z-index" + "-moz-outline-radius", "-moz-outline-radius-bottomleft", + "-moz-outline-radius-bottomright", "-moz-outline-radius-topleft", + "-moz-outline-radius-topright", "-webkit-text-fill-color", + "-webkit-text-stroke", "-webkit-text-stroke-color", + "-webkit-touch-callout", "all", "backdrop-filter", "background", + "background-color", "background-position", "background-size", "border", + "border-bottom", "border-bottom-color", "border-bottom-left-radius", + "border-bottom-right-radius", "border-bottom-width", "border-color", + "border-left", "border-left-color", "border-left-width", "border-radius", + "border-right", "border-right-color", "border-right-width", "border-top", + "border-top-color", "border-top-left-radius", "border-top-right-radius", + "border-top-width", "border-width", "bottom", "box-shadow", "clip", + "clip-path", "color", "column-count", "column-gap", "column-rule", + "column-rule-color", "column-rule-width", "column-width", "columns", + "filter", "flex", "flex-basis", "flex-grow", "flex-shrink", "font", + "font-size", "font-size-adjust", "font-stretch", "font-weight", + "grid-column-gap", "grid-gap", "grid-row-gap", "height", "left", + "letter-spacing", "line-height", "margin", "margin-bottom", + "margin-left", "margin-right", "margin-top", "mask", "mask-position", + "mask-size", "max-height", "max-width", "min-height", "min-width", + "motion-offset", "motion-rotation", "object-position", "opacity", + "order", "outline", "outline-color", "outline-offset", "outline-width", + "padding", "padding-bottom", "padding-left", "padding-right", + "padding-top", "perspective", "perspective-origin", "right", + "scroll-snap-coordinate", "scroll-snap-destination", + "shape-image-threshold", "shape-margin", "shape-outside", + "text-decoration", "text-decoration-color", "text-emphasis", + "text-emphasis-color", "text-indent", "text-shadow", "top", "transform", + "transform-origin", "vertical-align", "visibility", "width", + "word-spacing", "z-index" ] @@ -56,11 +56,12 @@ def is_known_animatable_property(name): # following list, and can be implemented removing it from the list and # implementing the Interpolate trait in helpers/animated_properties.mako.rs def is_not_supported_animatable_property(name): - return name in [ + return name in [ "flex-basis", "column-width", "column-height", "column-count", "column-gap", "box-shadow", "clip", "filter", "transform-origin", - "perspective-origin", "font-stretch", "letter-spacing", "word-spacing", - "text-decoration" ] + "perspective-origin", "font-stretch", "letter-spacing", "word-spacing", + "text-decoration" + ] class Keyword(object): diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 2b33e137f00..ffd89b52244 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -3,9 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use app_units::Au; -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; @@ -20,9 +18,6 @@ use properties::longhands::text_shadow::computed_value::TextShadow; use properties::longhands::transform::computed_value::ComputedMatrix; use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; use properties::longhands::transform::computed_value::T as TransformList; -use properties::longhands::transition_property; -use properties::longhands::transition_timing_function::computed_value::StartEnd; -use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; use properties::longhands::vertical_align::computed_value::T as VerticalAlign; use properties::longhands::visibility::computed_value::T as Visibility; use properties::longhands::z_index::computed_value::T as ZIndex; @@ -33,7 +28,7 @@ use std::iter::repeat; use super::ComputedValues; use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; use values::computed::{BorderRadiusSize, LengthOrNone}; -use values::computed::{CalcLengthOrPercentage, Length, LengthOrPercentage, Time}; +use values::computed::{CalcLengthOrPercentage, LengthOrPercentage}; // NB: This needs to be here because it needs all the longhands generated // beforehand. @@ -143,8 +138,9 @@ impl AnimatedProperty { % for prop in data.longhands: % if prop.animatable: TransitionProperty::${prop.camel_case} => { - AnimatedProperty::${prop.camel_case}(old_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone(), - new_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone()) + AnimatedProperty::${prop.camel_case}( + old_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone(), + new_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone()) } % endif % endfor diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index c35566165f1..e1a21572a85 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -12,7 +12,7 @@ use std::ascii::AsciiExt; use std::boxed::Box as StdBox; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::fmt; use std::fmt::Write; use std::sync::Arc; @@ -22,7 +22,6 @@ use cssparser::Color as CSSParserColor; use cssparser::{Parser, RGBA, AtRuleParser, DeclarationParser, Delimiter, DeclarationListParser, parse_important, ToCss, TokenSerializationType}; use error_reporting::ParseErrorReporter; -use keyframes::Keyframe; use url::Url; use euclid::side_offsets::SideOffsets2D; use euclid::size::Size2D; diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index a4895e91ecb..fc9c2eb6600 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -7,7 +7,6 @@ use dom::PresentationalHintsSynthetizer; use element_state::*; use error_reporting::StdoutErrorReporter; -use keyframes::Keyframe; use keyframes::KeyframesAnimation; use media_queries::{Device, MediaType}; use parser::ParserContextExtraData; From 33f581883b65e24f01c97189a059da991a699d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Wed, 22 Jun 2016 19:24:25 +0200 Subject: [PATCH 17/28] style: Support animation-direction. --- components/layout/animation.rs | 25 +++----- components/style/animation.rs | 104 ++++++++++++++++++++++++++++----- 2 files changed, 97 insertions(+), 32 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index 337855599e6..fecc4762d67 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -13,7 +13,7 @@ use script_layout_interface::restyle_damage::RestyleDamage; use script_traits::{AnimationState, LayoutMsg as ConstellationMsg}; use std::collections::HashMap; use std::sync::mpsc::Receiver; -use style::animation::{Animation, KeyframesIterationState, update_style_for_animation}; +use style::animation::{Animation, update_style_for_animation}; use time; /// Processes any new animations that were discovered after style recalculation. @@ -42,11 +42,11 @@ pub fn update_animation_state(constellation_chan: &IpcSender, match *anim { Animation::Keyframes(_, _, ref mut anim_state) => { // NB: The important part is not touching - // the started_at field. - anim_state.duration = state.duration; - anim_state.iteration_state = state.iteration_state.clone(); - anim_state.paused = state.paused; - anim_state.delay = state.delay; + // the started_at field, since we don't want + // to restart the animation. + let old_started_at = anim_state.started_at; + *anim_state = state.clone(); + anim_state.started_at = old_started_at; false } _ => unreachable!(), @@ -84,18 +84,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender, Animation::Keyframes(_, _, ref mut state) => { // This animation is still running, or we need to keep // iterating. - now < state.started_at + state.duration || - match state.iteration_state { - KeyframesIterationState::Finite(ref mut current, ref max) => { - *current += 1; - *current < *max - } - // Just tick it again. - KeyframesIterationState::Infinite => { - state.started_at += state.duration + state.delay; - true - } - } + now < state.started_at + state.duration || state.tick() } }; diff --git a/components/style/animation.rs b/components/style/animation.rs index 9afea17b213..342f211d167 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -11,6 +11,7 @@ use dom::{OpaqueNode, TRestyleDamage}; use euclid::point::Point2D; use keyframes::KeyframesStep; use properties::animated_properties::{AnimatedProperty, TransitionProperty}; +use properties::longhands::animation_direction::computed_value::AnimationDirection; use properties::longhands::animation_iteration_count::computed_value::AnimationIterationCount; use properties::longhands::animation_play_state::computed_value::AnimationPlayState; use properties::longhands::transition_timing_function::computed_value::StartEnd; @@ -42,11 +43,55 @@ pub enum KeyframesIterationState { // TODO: unify the use of f32/f64 in this file. #[derive(Debug, Clone)] pub struct KeyframesAnimationState { + /// The time this animation started at. pub started_at: f64, + /// The duration of this animation. pub duration: f64, + /// The delay of the animation. pub delay: f64, + /// The current iteration state for the animation. pub iteration_state: KeyframesIterationState, + /// Werther this animation is paused. pub paused: bool, + /// The declared animation direction of this animation. + pub direction: AnimationDirection, + /// The current animation direction. This can only be `normal` or `reverse`. + pub current_direction: AnimationDirection, +} + +impl KeyframesAnimationState { + /// Performs a tick in the animation state, i.e., increments the counter of + /// the current iteration count, updates times and then toggles the + /// direction if appropriate. + /// + /// Returns true if the animation should keep running. + pub fn tick(&mut self) -> bool { + let still_running = match self.iteration_state { + KeyframesIterationState::Finite(ref mut current, ref max) => { + *current += 1; + *current < *max + } + KeyframesIterationState::Infinite => true, + }; + + // Just tick it again updating the started_at field. + self.started_at += self.duration + self.delay; + + // Update the next iteration direction if applicable. + match self.direction { + AnimationDirection::alternate | + AnimationDirection::alternate_reverse => { + self.current_direction = match self.current_direction { + AnimationDirection::normal => AnimationDirection::reverse, + AnimationDirection::reverse => AnimationDirection::normal, + _ => unreachable!(), + }; + } + _ => {}, + } + + still_running + } } /// State relating to an animation. @@ -287,6 +332,16 @@ pub fn maybe_start_animations(context: &SharedStyleContex AnimationIterationCount::Infinite => KeyframesIterationState::Infinite, AnimationIterationCount::Number(n) => KeyframesIterationState::Finite(0, n), }; + + let animation_direction = *box_style.animation_direction.0.get_mod(i); + + let initial_direction = match animation_direction { + AnimationDirection::normal | + AnimationDirection::alternate => AnimationDirection::normal, + AnimationDirection::reverse | + AnimationDirection::alternate_reverse => AnimationDirection::reverse, + }; + let paused = *box_style.animation_play_state.0.get_mod(i) == AnimationPlayState::paused; context.new_animations_sender @@ -297,6 +352,8 @@ pub fn maybe_start_animations(context: &SharedStyleContex delay: delay as f64, iteration_state: iteration_state, paused: paused, + direction: animation_direction, + current_direction: initial_direction, })).unwrap(); had_animations = true; } @@ -393,20 +450,31 @@ where Impl: SelectorImplExt, debug!("update_style_for_animation: anim \"{}\", steps: {:?}, state: {:?}, progress: {}", name, animation.steps, state, total_progress); - let mut last_keyframe_position = None; - let mut target_keyframe_position = None; + // Get the target and the last keyframe position. + let last_keyframe_position; + let target_keyframe_position; + match state.current_direction { + AnimationDirection::normal => { + target_keyframe_position = + animation.steps.iter().position(|step| { + total_progress as f32 <= step.start_percentage.0 + }); - // TODO: we could maybe binary-search this? Also, find is probably a - // bit more idiomatic here? - for i in 0..animation.steps.len() { - if total_progress as f32 <= animation.steps[i].start_percentage.0 { - // We might have found our current keyframe. - target_keyframe_position = Some(i); - if i != 0 { - last_keyframe_position = Some(i - 1); - } - break; + last_keyframe_position = target_keyframe_position.and_then(|pos| { + if pos != 0 { Some(pos - 1) } else { None } + }); } + AnimationDirection::reverse => { + target_keyframe_position = + animation.steps.iter().rev().position(|step| { + total_progress as f32 <= 1. - step.start_percentage.0 + }).map(|pos| animation.steps.len() - pos - 1); + + last_keyframe_position = target_keyframe_position.and_then(|pos| { + if pos != animation.steps.len() - 1 { Some(pos + 1) } else { None } + }); + } + _ => unreachable!(), } debug!("update_style_for_animation: keyframe from {:?} to {:?}", @@ -432,9 +500,17 @@ where Impl: SelectorImplExt, } }; - let relative_timespan = target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0; + let relative_timespan = (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0).abs(); let relative_duration = relative_timespan as f64 * duration; - let last_keyframe_ended_at = state.started_at + (total_duration * last_keyframe.start_percentage.0 as f64); + let last_keyframe_ended_at = match state.current_direction { + AnimationDirection::normal => { + state.started_at + (total_duration * last_keyframe.start_percentage.0 as f64) + } + AnimationDirection::reverse => { + state.started_at + (total_duration * (1. - last_keyframe.start_percentage.0 as f64)) + } + _ => unreachable!(), + }; let relative_progress = (now - last_keyframe_ended_at) / relative_duration; // TODO: How could we optimise it? Is it such a big deal? From 2b2e58a868fd01a7338769b044eb98827f02e823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Thu, 23 Jun 2016 00:46:29 +0200 Subject: [PATCH 18/28] style: WTF I was thinking about when I wrote that? --- components/layout/animation.rs | 1 - components/style/animation.rs | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index fecc4762d67..7f0eaf140f9 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -139,7 +139,6 @@ pub fn recalc_style_for_animations(context: &SharedLayoutContext, animation, &mut fragment.style, Some(&mut damage)); - animation.increment_keyframe_if_applicable(); } } } diff --git a/components/style/animation.rs b/components/style/animation.rs index 342f211d167..1ad50cbc939 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -66,9 +66,11 @@ impl KeyframesAnimationState { /// /// Returns true if the animation should keep running. pub fn tick(&mut self) -> bool { + debug!("KeyframesAnimationState::tick"); let still_running = match self.iteration_state { KeyframesIterationState::Finite(ref mut current, ref max) => { *current += 1; + debug!("KeyframesAnimationState::tick: current={}, max={}", current, max); *current < *max } KeyframesIterationState::Infinite => true, @@ -120,14 +122,6 @@ impl Animation { Animation::Keyframes(_, _, ref state) => state.paused, } } - - pub fn increment_keyframe_if_applicable(&mut self) { - if let Animation::Keyframes(_, _, ref mut state) = *self { - if let KeyframesIterationState::Finite(ref mut iterations, _) = state.iteration_state { - *iterations += 1; - } - } - } } From 8527762b83b1f4f7395cbbdf829ede9ab8558292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 24 Jun 2016 00:07:42 +0200 Subject: [PATCH 19/28] style: Allow pausing and unpausing animations. There's a bit of flickering when unpausing where the node has the original state, but I'm not totally sure where it comes from, posibly from PropertyAnimation returning None due to no styles changing? --- components/layout/animation.rs | 36 +++---- components/layout_thread/lib.rs | 6 +- components/style/animation.rs | 177 ++++++++++++++++++++++++-------- components/style/matching.rs | 9 +- components/style/parallel.rs | 2 +- 5 files changed, 159 insertions(+), 71 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index 7f0eaf140f9..18e185aca84 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -17,12 +17,14 @@ use style::animation::{Animation, update_style_for_animation}; use time; /// Processes any new animations that were discovered after style recalculation. -/// Also expire any old animations that have completed, inserting them into `expired_animations`. +/// Also expire any old animations that have completed, inserting them into +/// `expired_animations`. pub fn update_animation_state(constellation_chan: &IpcSender, running_animations: &mut HashMap>, expired_animations: &mut HashMap>, new_animations_receiver: &Receiver, pipeline_id: PipelineId) { + let now = time::precise_time_s(); let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { let should_push = match animation { @@ -33,6 +35,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender, // run. if let Some(ref mut animations) = running_animations.get_mut(node) { // TODO: This being linear is probably not optimal. + // Also, we should move this logic somehow. match animations.iter_mut().find(|anim| match **anim { Animation::Keyframes(_, ref anim_name, _) => *name == *anim_name, Animation::Transition(..) => false, @@ -41,12 +44,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender, debug!("update_animation_state: Found other animation {}", name); match *anim { Animation::Keyframes(_, _, ref mut anim_state) => { - // NB: The important part is not touching - // the started_at field, since we don't want - // to restart the animation. - let old_started_at = anim_state.started_at; - *anim_state = state.clone(); - anim_state.started_at = old_started_at; + anim_state.update_from_other(&state); false } _ => unreachable!(), @@ -72,13 +70,12 @@ pub fn update_animation_state(constellation_chan: &IpcSender, } // Expire old running animations. - let now = time::precise_time_s(); let mut keys_to_remove = vec![]; for (key, running_animations) in running_animations.iter_mut() { let mut animations_still_running = vec![]; for mut running_animation in running_animations.drain(..) { - let still_running = match running_animation { - Animation::Transition(_, started_at, ref frame) => { + let still_running = !running_animation.is_expired() && match running_animation { + Animation::Transition(_, started_at, ref frame, _expired) => { now < started_at + frame.duration } Animation::Keyframes(_, _, ref mut state) => { @@ -126,20 +123,19 @@ pub fn update_animation_state(constellation_chan: &IpcSender, .unwrap(); } -/// Recalculates style for a set of animations. This does *not* run with the DOM lock held. +/// Recalculates style for a set of animations. This does *not* run with the DOM +/// lock held. pub fn recalc_style_for_animations(context: &SharedLayoutContext, flow: &mut Flow, - animations: &mut HashMap>) { + animations: &HashMap>) { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { - if let Some(ref mut animations) = animations.get_mut(&fragment.node) { - for ref mut animation in animations.iter_mut() { - if !animation.is_paused() { - update_style_for_animation(&context.style_context, - animation, - &mut fragment.style, - Some(&mut damage)); - } + if let Some(ref animations) = animations.get(&fragment.node) { + for animation in animations.iter() { + update_style_for_animation(&context.style_context, + animation, + &mut fragment.style, + Some(&mut damage)); } } }); diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index b37d35e905a..0f5e558305a 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -1290,7 +1290,7 @@ impl LayoutThread { self.tick_animations(&mut rw_data); } - pub fn tick_animations(&mut self, rw_data: &mut LayoutThreadData) { + fn tick_animations(&mut self, rw_data: &mut LayoutThreadData) { let reflow_info = Reflow { goal: ReflowGoal::ForDisplay, page_clip_rect: MAX_RECT, @@ -1302,14 +1302,14 @@ impl LayoutThread { if let Some(mut root_flow) = self.root_flow.clone() { // Perform an abbreviated style recalc that operates without access to the DOM. - let mut animations = self.running_animations.write().unwrap(); + let animations = self.running_animations.read().unwrap(); profile(time::ProfilerCategory::LayoutStyleRecalc, self.profiler_metadata(), self.time_profiler_chan.clone(), || { animation::recalc_style_for_animations(&layout_context, flow_ref::deref_mut(&mut root_flow), - &mut animations) + &animations) }); } diff --git a/components/style/animation.rs b/components/style/animation.rs index 1ad50cbc939..28dfb2b7a92 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -37,6 +37,18 @@ pub enum KeyframesIterationState { Finite(u32, u32), } +/// This structure represents wether an animation is actually running. +/// +/// An animation can be running, or paused at a given time. +#[derive(Debug, Clone)] +pub enum KeyframesRunningState { + /// This animation is paused. The inner field is the percentage of progress + /// when it was paused, from 0 to 1. + Paused(f64), + /// This animation is actually running. + Running, +} + /// This structure represents the current keyframe animation state, i.e., the /// duration, the current and maximum iteration count, and the state (either /// playing or paused). @@ -52,11 +64,13 @@ pub struct KeyframesAnimationState { /// The current iteration state for the animation. pub iteration_state: KeyframesIterationState, /// Werther this animation is paused. - pub paused: bool, + pub running_state: KeyframesRunningState, /// The declared animation direction of this animation. pub direction: AnimationDirection, /// The current animation direction. This can only be `normal` or `reverse`. pub current_direction: AnimationDirection, + /// Werther this keyframe animation is outdated due to a restyle. + pub expired: bool, } impl KeyframesAnimationState { @@ -67,17 +81,22 @@ impl KeyframesAnimationState { /// Returns true if the animation should keep running. pub fn tick(&mut self) -> bool { debug!("KeyframesAnimationState::tick"); - let still_running = match self.iteration_state { - KeyframesIterationState::Finite(ref mut current, ref max) => { - *current += 1; - debug!("KeyframesAnimationState::tick: current={}, max={}", current, max); - *current < *max - } - KeyframesIterationState::Infinite => true, - }; - // Just tick it again updating the started_at field. self.started_at += self.duration + self.delay; + match self.running_state { + // If it's paused, don't update direction or iteration count. + KeyframesRunningState::Paused(_) => return true, + KeyframesRunningState::Running => {}, + } + + if let KeyframesIterationState::Finite(ref mut current, ref max) = self.iteration_state { + *current += 1; + // NB: This prevent us from updating the direction, which might be + // needed for the correct handling of animation-fill-mode. + if *current >= *max { + return false; + } + } // Update the next iteration direction if applicable. match self.direction { @@ -92,7 +111,57 @@ impl KeyframesAnimationState { _ => {}, } - still_running + true + } + + /// Updates the appropiate state from other animation. + /// + /// This happens when an animation is re-submitted to layout, presumably + /// because of an state change. + /// + /// There are some bits of state we can't just replace, over all taking in + /// account times, so here's that logic. + pub fn update_from_other(&mut self, other: &Self) { + use self::KeyframesRunningState::*; + + debug!("KeyframesAnimationState::update_from_other({:?}, {:?})", self, other); + + // NB: We shall not touch the started_at field, since we don't want to + // restart the animation. + let old_started_at = self.started_at; + let old_duration = self.duration; + let old_direction = self.current_direction; + let old_running_state = self.running_state.clone(); + *self = other.clone(); + + let mut new_started_at = old_started_at; + + // If we're unpausing the animation, fake the start time so we seem to + // restore it. + // + // If the animation keeps paused, keep the old value. + // + // If we're pausing the animation, compute the progress value. + match (&mut self.running_state, old_running_state) { + (&mut Running, Paused(progress)) + => new_started_at = time::precise_time_s() - (self.duration * progress), + (&mut Paused(ref mut new), Paused(old)) + => *new = old, + (&mut Paused(ref mut progress), Running) + => *progress = (time::precise_time_s() - old_started_at) / old_duration, + _ => {}, + } + + self.current_direction = old_direction; + self.started_at = new_started_at; + } + + #[inline] + fn is_paused(&self) -> bool { + match self.running_state { + KeyframesRunningState::Paused(..) => true, + KeyframesRunningState::Running => false, + } } } @@ -102,24 +171,45 @@ pub enum Animation { /// A transition is just a single frame triggered at a time, with a reflow. /// /// the f64 field is the start time as returned by `time::precise_time_s()`. - Transition(OpaqueNode, f64, AnimationFrame), + /// + /// The `bool` field is werther this animation should no longer run. + Transition(OpaqueNode, f64, AnimationFrame, bool), /// A keyframes animation is identified by a name, and can have a /// node-dependent state (i.e. iteration count, etc.). Keyframes(OpaqueNode, Atom, KeyframesAnimationState), } impl Animation { + #[inline] + pub fn mark_as_expired(&mut self) { + debug_assert!(!self.is_expired()); + match *self { + Animation::Transition(_, _, _, ref mut expired) => *expired = true, + Animation::Keyframes(_, _, ref mut state) => state.expired = true, + } + } + + #[inline] + pub fn is_expired(&self) -> bool { + match *self { + Animation::Transition(_, _, _, expired) => expired, + Animation::Keyframes(_, _, ref state) => state.expired, + } + } + + #[inline] pub fn node(&self) -> &OpaqueNode { match *self { - Animation::Transition(ref node, _, _) => node, + Animation::Transition(ref node, _, _, _) => node, Animation::Keyframes(ref node, _, _) => node, } } + #[inline] pub fn is_paused(&self) -> bool { match *self { Animation::Transition(..) => false, - Animation::Keyframes(_, _, ref state) => state.paused, + Animation::Keyframes(_, _, ref state) => state.is_paused(), } } } @@ -277,7 +367,7 @@ pub fn start_transitions_if_applicable(new_animations_sender: .send(Animation::Transition(node, start_time, AnimationFrame { duration: box_style.transition_duration.0.get_mod(i).seconds() as f64, property_animation: property_animation, - })).unwrap(); + }, /* is_expired = */ false)).unwrap(); had_animations = true; } @@ -320,7 +410,8 @@ pub fn maybe_start_animations(context: &SharedStyleContex if context.stylist.animations().get(&name).is_some() { debug!("maybe_start_animations: animation {} found", name); let delay = box_style.animation_delay.0.get_mod(i).seconds(); - let animation_start = time::precise_time_s() + delay as f64; + let now = time::precise_time_s(); + let animation_start = now + delay as f64; let duration = box_style.animation_duration.0.get_mod(i).seconds(); let iteration_state = match *box_style.animation_iteration_count.0.get_mod(i) { AnimationIterationCount::Infinite => KeyframesIterationState::Infinite, @@ -336,7 +427,11 @@ pub fn maybe_start_animations(context: &SharedStyleContex AnimationDirection::alternate_reverse => AnimationDirection::reverse, }; - let paused = *box_style.animation_play_state.0.get_mod(i) == AnimationPlayState::paused; + let running_state = match *box_style.animation_play_state.0.get_mod(i) { + AnimationPlayState::paused => KeyframesRunningState::Paused(0.), + AnimationPlayState::running => KeyframesRunningState::Running, + }; + context.new_animations_sender .lock().unwrap() @@ -345,9 +440,10 @@ pub fn maybe_start_animations(context: &SharedStyleContex duration: duration as f64, delay: delay as f64, iteration_state: iteration_state, - paused: paused, + running_state: running_state, direction: animation_direction, current_direction: initial_direction, + expired: false, })).unwrap(); had_animations = true; } @@ -375,8 +471,8 @@ pub fn update_style_for_animation_frame(mut new_style: &mut A true } -/// Updates a single animation and associated style based on the current time. If `damage` is -/// provided, inserts the appropriate restyle damage. +/// Updates a single animation and associated style based on the current time. +/// If `damage` is provided, inserts the appropriate restyle damage. pub fn update_style_for_animation(context: &SharedStyleContext, animation: &Animation, style: &mut Arc, @@ -384,10 +480,11 @@ pub fn update_style_for_animation(context: &SharedStyleContext { debug!("update_style_for_animation: entering"); - let now = time::precise_time_s(); match *animation { - Animation::Transition(_, start_time, ref frame) => { + Animation::Transition(_, start_time, ref frame, expired) => { + debug_assert!(!expired); debug!("update_style_for_animation: transition found"); + let now = time::precise_time_s(); let mut new_style = (*style).clone(); let updated_style = update_style_for_animation_frame(&mut new_style, now, start_time, @@ -401,11 +498,16 @@ where Impl: SelectorImplExt, } } Animation::Keyframes(_, ref name, ref state) => { - debug!("update_style_for_animation: animation found {:?}", name); - debug_assert!(!state.paused); + debug_assert!(!state.expired); + debug!("update_style_for_animation: animation found: \"{}\", {:?}", name, state); let duration = state.duration; let started_at = state.started_at; + let now = match state.running_state { + KeyframesRunningState::Running => time::precise_time_s(), + KeyframesRunningState::Paused(progress) => started_at + duration * progress, + }; + let animation = match context.stylist.animations().get(name) { None => { warn!("update_style_for_animation: Animation {:?} not found", name); @@ -456,7 +558,7 @@ where Impl: SelectorImplExt, last_keyframe_position = target_keyframe_position.and_then(|pos| { if pos != 0 { Some(pos - 1) } else { None } - }); + }).unwrap_or(0); } AnimationDirection::reverse => { target_keyframe_position = @@ -466,7 +568,7 @@ where Impl: SelectorImplExt, last_keyframe_position = target_keyframe_position.and_then(|pos| { if pos != animation.steps.len() - 1 { Some(pos + 1) } else { None } - }); + }).unwrap_or(animation.steps.len() - 1); } _ => unreachable!(), } @@ -485,14 +587,7 @@ where Impl: SelectorImplExt, } }; - let last_keyframe = match last_keyframe_position { - Some(last) => &animation.steps[last], - None => { - warn!("update_style_for_animation: No last keyframe found for animation \"{}\" at progress {}", - name, total_progress); - return; - } - }; + let last_keyframe = &animation.steps[last_keyframe_position]; let relative_timespan = (target_keyframe.start_percentage.0 - last_keyframe.start_percentage.0).abs(); let relative_duration = relative_timespan as f64 * duration; @@ -524,7 +619,6 @@ where Impl: SelectorImplExt, &from_style); let mut new_style = (*style).clone(); - let mut style_changed = false; for transition_property in &animation.properties_changed { debug!("update_style_for_animation: scanning prop {:?} for animation \"{}\"", @@ -538,7 +632,6 @@ where Impl: SelectorImplExt, debug!("update_style_for_animation: got property animation for prop {:?}", transition_property); debug!("update_style_for_animation: {:?}", property_animation); property_animation.update(Arc::make_mut(&mut new_style), relative_progress); - style_changed = true; } None => { debug!("update_style_for_animation: property animation {:?} not animating", @@ -547,14 +640,12 @@ where Impl: SelectorImplExt, } } - if style_changed { - debug!("update_style_for_animation: got style change in animation \"{}\"", name); - if let Some(damage) = damage { - *damage = *damage | Damage::compute(Some(style), &new_style); - } - - *style = new_style; + debug!("update_style_for_animation: got style change in animation \"{}\"", name); + if let Some(damage) = damage { + *damage = *damage | Damage::compute(Some(style), &new_style); } + + *style = new_style; } } } diff --git a/components/style/matching.rs b/components/style/matching.rs index 3d6d2bd147e..7b13e34c1bc 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -469,8 +469,9 @@ trait PrivateMatchMethods: TNode had_animations_to_expire = animations_to_expire.is_some(); if let Some(ref animations) = animations_to_expire { for animation in *animations { - // TODO: revisit this code for keyframes - if let Animation::Transition(_, _, ref frame) = *animation { + // NB: Expiring a keyframes animation is the same as not + // applying the keyframes style to it, so we're safe. + if let Animation::Transition(_, _, ref frame, _) = *animation { frame.property_animation.update(Arc::make_mut(style), 1.0); } } @@ -489,11 +490,11 @@ trait PrivateMatchMethods: TNode .is_some(); if had_running_animations { let mut all_running_animations = context.running_animations.write().unwrap(); - for running_animation in all_running_animations.get(&this_opaque).unwrap() { + for mut running_animation in all_running_animations.get_mut(&this_opaque).unwrap() { animation::update_style_for_animation::::Impl>(context, running_animation, style, None); + running_animation.mark_as_expired(); } - all_running_animations.remove(&this_opaque); } had_animations_to_expire || had_running_animations diff --git a/components/style/parallel.rs b/components/style/parallel.rs index e967c3839fd..55f15f1d80f 100644 --- a/components/style/parallel.rs +++ b/components/style/parallel.rs @@ -58,7 +58,7 @@ fn top_down_dom(unsafe_nodes: UnsafeNodeList, where N: TNode, C: DomTraversalContext { let context = C::new(proxy.user_data(), unsafe_nodes.1); - let mut discovered_child_nodes = Vec::new(); + let mut discovered_child_nodes = vec![]; for unsafe_node in *unsafe_nodes.0 { // Get a real layout node. let node = unsafe { N::from_unsafe(&unsafe_node) }; From f149d4d57748db205772a4873533884cccca4dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 25 Jun 2016 15:46:55 +0200 Subject: [PATCH 20/28] style: Don't calculate animations just on style changes. Calculate it also on the first restyle. --- components/style/matching.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/components/style/matching.rs b/components/style/matching.rs index 7b13e34c1bc..aed3958a36e 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -416,25 +416,26 @@ trait PrivateMatchMethods: TNode } }; - // Trigger transitions if necessary. This will reset `this_style` back to its old value if - // it did trigger a transition. if animate_properties { + let this_opaque = self.opaque(); + // Trigger any present animations if necessary. + let mut animations_started = animation::maybe_start_animations::<::Impl>( + &context, + this_opaque, + &this_style); + + // Trigger transitions if necessary. This will reset `this_style` back + // to its old value if it did trigger a transition. if let Some(ref style) = style { - let mut animations_started = + animations_started |= animation::start_transitions_if_applicable::( &context.new_animations_sender, - self.opaque(), + this_opaque, &**style, &mut this_style); - - // TODO: Take into account animation-play-state - animations_started |= animation::maybe_start_animations::<::Impl>( - &context, - self.opaque(), - &mut this_style); - - cacheable = cacheable && !animations_started } + + cacheable = cacheable && !animations_started } // Calculate style difference. From f54583884efa810c186600edbc75c563915b9c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sat, 25 Jun 2016 22:35:27 -0700 Subject: [PATCH 21/28] layout: animation: hoist down precise_time_s() call to avoid calling it unnecessarily This was here before, but I moved it up presumably in one of my attempts and never forgot to restore that. --- components/layout/animation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index 18e185aca84..30f622b32f0 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -24,7 +24,6 @@ pub fn update_animation_state(constellation_chan: &IpcSender, expired_animations: &mut HashMap>, new_animations_receiver: &Receiver, pipeline_id: PipelineId) { - let now = time::precise_time_s(); let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { let should_push = match animation { @@ -69,6 +68,7 @@ pub fn update_animation_state(constellation_chan: &IpcSender, return } + let now = time::precise_time_s(); // Expire old running animations. let mut keys_to_remove = vec![]; for (key, running_animations) in running_animations.iter_mut() { From 46eec458867adaa956110784b6ae230e68e9a4e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Sun, 26 Jun 2016 12:41:09 -0700 Subject: [PATCH 22/28] layout: Simplify logic in layout/animation.rs Self-review is helpful it seems. --- components/layout/animation.rs | 43 ++++++++++++++-------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index 30f622b32f0..c28a4c33d8e 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -26,36 +26,25 @@ pub fn update_animation_state(constellation_chan: &IpcSender, pipeline_id: PipelineId) { let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { - let should_push = match animation { - Animation::Transition(..) => true, - Animation::Keyframes(ref node, ref name, ref state) => { - // If the animation was already present in the list for the - // node, just update its state, else push the new animation to - // run. - if let Some(ref mut animations) = running_animations.get_mut(node) { - // TODO: This being linear is probably not optimal. - // Also, we should move this logic somehow. - match animations.iter_mut().find(|anim| match **anim { - Animation::Keyframes(_, ref anim_name, _) => *name == *anim_name, - Animation::Transition(..) => false, - }) { - Some(mut anim) => { + let mut should_push = true; + if let Animation::Keyframes(ref node, ref name, ref state) = animation { + // If the animation was already present in the list for the + // node, just update its state, else push the new animation to + // run. + if let Some(ref mut animations) = running_animations.get_mut(node) { + // TODO: This being linear is probably not optimal. + for mut anim in animations.iter_mut() { + if let Animation::Keyframes(_, ref anim_name, ref mut anim_state) = *anim { + if *name == *anim_name { debug!("update_animation_state: Found other animation {}", name); - match *anim { - Animation::Keyframes(_, _, ref mut anim_state) => { - anim_state.update_from_other(&state); - false - } - _ => unreachable!(), - } + anim_state.update_from_other(&state); + should_push = false; + break; } - None => true, } - } else { - true } } - }; + } if should_push { new_running_animations.push(animation); @@ -70,6 +59,10 @@ pub fn update_animation_state(constellation_chan: &IpcSender, let now = time::precise_time_s(); // Expire old running animations. + // + // TODO: Do not expunge Keyframes animations, since we need that state if + // the animation gets re-triggered. Probably worth splitting in two + // different maps, or at least using a linked list? let mut keys_to_remove = vec![]; for (key, running_animations) in running_animations.iter_mut() { let mut animations_still_running = vec![]; From 2d566ef0ef15b8339de904d9e179f3988ece0ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Mon, 27 Jun 2016 08:52:33 -0700 Subject: [PATCH 23/28] style: Fix parsing and add generated keyframes --- components/layout/animation.rs | 17 ++- components/layout_thread/lib.rs | 3 +- components/style/animation.rs | 96 +++++++++----- components/style/context.rs | 6 +- components/style/keyframes.rs | 124 +++++++++++++----- components/style/matching.rs | 7 +- .../style/properties/properties.mako.rs | 11 +- components/style/selector_impl.rs | 5 +- components/style/servo.rs | 3 +- components/style/stylesheets.rs | 11 +- 10 files changed, 189 insertions(+), 94 deletions(-) diff --git a/components/layout/animation.rs b/components/layout/animation.rs index c28a4c33d8e..8f0ac53bd45 100644 --- a/components/layout/animation.rs +++ b/components/layout/animation.rs @@ -14,16 +14,17 @@ use script_traits::{AnimationState, LayoutMsg as ConstellationMsg}; use std::collections::HashMap; use std::sync::mpsc::Receiver; use style::animation::{Animation, update_style_for_animation}; +use style::selector_impl::{SelectorImplExt, ServoSelectorImpl}; use time; /// Processes any new animations that were discovered after style recalculation. /// Also expire any old animations that have completed, inserting them into /// `expired_animations`. -pub fn update_animation_state(constellation_chan: &IpcSender, - running_animations: &mut HashMap>, - expired_animations: &mut HashMap>, - new_animations_receiver: &Receiver, - pipeline_id: PipelineId) { +pub fn update_animation_state(constellation_chan: &IpcSender, + running_animations: &mut HashMap>>, + expired_animations: &mut HashMap>>, + new_animations_receiver: &Receiver>, + pipeline_id: PipelineId) { let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { let mut should_push = true; @@ -118,9 +119,13 @@ pub fn update_animation_state(constellation_chan: &IpcSender, /// Recalculates style for a set of animations. This does *not* run with the DOM /// lock held. +// NB: This is specific for ServoSelectorImpl, since the layout context and the +// flows are ServoSelectorImpl specific too. If that goes away at some point, +// this should be made generic. pub fn recalc_style_for_animations(context: &SharedLayoutContext, flow: &mut Flow, - animations: &HashMap>) { + animations: &HashMap>>) { let mut damage = RestyleDamage::empty(); flow.mutate_fragments(&mut |fragment| { if let Some(ref animations) = animations.get(&fragment.node) { diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 0f5e558305a..e47670f99c1 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -98,7 +98,6 @@ use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::{Arc, Mutex, MutexGuard, RwLock}; -use style::animation::Animation; use style::computed_values::{filter, mix_blend_mode}; use style::context::ReflowGoal; use style::dom::{TDocument, TElement, TNode}; @@ -109,7 +108,7 @@ use style::parallel::WorkQueueData; use style::properties::ComputedValues; use style::refcell::RefCell; use style::selector_matching::USER_OR_USER_AGENT_STYLESHEETS; -use style::servo::{SharedStyleContext, Stylesheet, Stylist}; +use style::servo::{Animation, SharedStyleContext, Stylesheet, Stylist}; use style::stylesheets::CSSRuleIteratorExt; use url::Url; use util::geometry::MAX_RECT; diff --git a/components/style/animation.rs b/components/style/animation.rs index 28dfb2b7a92..4c8bfdc9bf4 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -4,12 +4,11 @@ //! CSS transitions and animations. -use app_units::Au; use bezier::Bezier; use context::SharedStyleContext; use dom::{OpaqueNode, TRestyleDamage}; use euclid::point::Point2D; -use keyframes::KeyframesStep; +use keyframes::{KeyframesStep, KeyframesStepValue}; use properties::animated_properties::{AnimatedProperty, TransitionProperty}; use properties::longhands::animation_direction::computed_value::AnimationDirection; use properties::longhands::animation_iteration_count::computed_value::AnimationIterationCount; @@ -54,7 +53,7 @@ pub enum KeyframesRunningState { /// playing or paused). // TODO: unify the use of f32/f64 in this file. #[derive(Debug, Clone)] -pub struct KeyframesAnimationState { +pub struct KeyframesAnimationState { /// The time this animation started at. pub started_at: f64, /// The duration of this animation. @@ -71,9 +70,12 @@ pub struct KeyframesAnimationState { pub current_direction: AnimationDirection, /// Werther this keyframe animation is outdated due to a restyle. pub expired: bool, + /// The original cascade style, needed to compute the generated keyframes of + /// the animation. + pub cascade_style: Arc, } -impl KeyframesAnimationState { +impl KeyframesAnimationState { /// Performs a tick in the animation state, i.e., increments the counter of /// the current iteration count, updates times and then toggles the /// direction if appropriate. @@ -121,7 +123,8 @@ impl KeyframesAnimationState { /// /// There are some bits of state we can't just replace, over all taking in /// account times, so here's that logic. - pub fn update_from_other(&mut self, other: &Self) { + pub fn update_from_other(&mut self, other: &Self) + where Self: Clone { use self::KeyframesRunningState::*; debug!("KeyframesAnimationState::update_from_other({:?}, {:?})", self, other); @@ -167,7 +170,7 @@ impl KeyframesAnimationState { /// State relating to an animation. #[derive(Clone, Debug)] -pub enum Animation { +pub enum Animation { /// A transition is just a single frame triggered at a time, with a reflow. /// /// the f64 field is the start time as returned by `time::precise_time_s()`. @@ -176,10 +179,10 @@ pub enum Animation { Transition(OpaqueNode, f64, AnimationFrame, bool), /// A keyframes animation is identified by a name, and can have a /// node-dependent state (i.e. iteration count, etc.). - Keyframes(OpaqueNode, Atom, KeyframesAnimationState), + Keyframes(OpaqueNode, Atom, KeyframesAnimationState), } -impl Animation { +impl Animation { #[inline] pub fn mark_as_expired(&mut self) { debug_assert!(!self.is_expired()); @@ -344,18 +347,20 @@ impl GetMod for Vec { // // TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a // cloneable part and a non-cloneable part.. -pub fn start_transitions_if_applicable(new_animations_sender: &Mutex>, - node: OpaqueNode, - old_style: &C, - new_style: &mut C) - -> bool { +pub fn start_transitions_if_applicable(new_animations_sender: &Mutex>>, + node: OpaqueNode, + old_style: &Impl::ComputedValues, + new_style: &mut Arc) + -> bool { let mut had_animations = false; for i in 0..new_style.get_box().transition_count() { // Create any property animations, if applicable. - let property_animations = PropertyAnimation::from_transition(i, old_style, new_style); + let property_animations = PropertyAnimation::from_transition(i, old_style, Arc::make_mut(new_style)); for property_animation in property_animations { // Set the property to the initial value. - property_animation.update(new_style, 0.0); + // NB: get_mut is guaranteed to succeed since we called make_mut() + // above. + property_animation.update(Arc::get_mut(new_style).unwrap(), 0.0); // Kick off the animation. let now = time::precise_time_s(); @@ -378,24 +383,33 @@ pub fn start_transitions_if_applicable(new_animations_sender: fn compute_style_for_animation_step(context: &SharedStyleContext, step: &KeyframesStep, - old_style: &Impl::ComputedValues) -> Impl::ComputedValues { - let declaration_block = DeclarationBlock { - declarations: step.declarations.clone(), - source_order: 0, - specificity: ::std::u32::MAX, - }; - let (computed, _) = properties::cascade(context.viewport_size, - &[declaration_block], - false, - Some(old_style), - None, - context.error_reporter.clone()); - computed + previous_style: &Impl::ComputedValues, + style_from_cascade: &Impl::ComputedValues) + -> Impl::ComputedValues { + match step.value { + // TODO: avoiding this spurious clone might involve having to create + // an Arc in the below (more common case). + KeyframesStepValue::ComputedValues => style_from_cascade.clone(), + KeyframesStepValue::Declarations(ref declarations) => { + let declaration_block = DeclarationBlock { + declarations: declarations.clone(), + source_order: 0, + specificity: ::std::u32::MAX, + }; + let (computed, _) = properties::cascade(context.viewport_size, + &[declaration_block], + false, + Some(previous_style), + None, + context.error_reporter.clone()); + computed + } + } } pub fn maybe_start_animations(context: &SharedStyleContext, node: OpaqueNode, - new_style: &Impl::ComputedValues) -> bool + new_style: &Arc) -> bool { let mut had_animations = false; @@ -407,8 +421,17 @@ pub fn maybe_start_animations(context: &SharedStyleContex continue } - if context.stylist.animations().get(&name).is_some() { + if let Some(ref anim) = context.stylist.animations().get(&name) { debug!("maybe_start_animations: animation {} found", name); + + // If this animation doesn't have any keyframe, we can just continue + // without submitting it to the compositor, since both the first and + // the second keyframes would be synthetised from the computed + // values. + if anim.steps.is_empty() { + continue; + } + let delay = box_style.animation_delay.0.get_mod(i).seconds(); let now = time::precise_time_s(); let animation_start = now + delay as f64; @@ -444,6 +467,7 @@ pub fn maybe_start_animations(context: &SharedStyleContex direction: animation_direction, current_direction: initial_direction, expired: false, + cascade_style: new_style.clone(), })).unwrap(); had_animations = true; } @@ -474,7 +498,7 @@ pub fn update_style_for_animation_frame(mut new_style: &mut A /// Updates a single animation and associated style based on the current time. /// If `damage` is provided, inserts the appropriate restyle damage. pub fn update_style_for_animation(context: &SharedStyleContext, - animation: &Animation, + animation: &Animation, style: &mut Arc, damage: Option<&mut Damage>) where Impl: SelectorImplExt, @@ -516,6 +540,8 @@ where Impl: SelectorImplExt, Some(animation) => animation, }; + debug_assert!(!animation.steps.is_empty()); + let maybe_index = style.as_servo() .get_box().animation_name.0.iter() .position(|animation_name| name == animation_name); @@ -579,8 +605,6 @@ where Impl: SelectorImplExt, let target_keyframe = match target_keyframe_position { Some(target) => &animation.steps[target], None => { - // TODO: The 0. case falls here, maybe we should just resort - // to the first keyframe instead. warn!("update_style_for_animation: No current keyframe found for animation \"{}\" at progress {}", name, total_progress); return; @@ -605,7 +629,8 @@ where Impl: SelectorImplExt, // TODO: How could we optimise it? Is it such a big deal? let from_style = compute_style_for_animation_step(context, last_keyframe, - &**style); + &**style, + &state.cascade_style); // NB: The spec says that the timing function can be overwritten // from the keyframe style. @@ -616,7 +641,8 @@ where Impl: SelectorImplExt, let target_style = compute_style_for_animation_step(context, target_keyframe, - &from_style); + &from_style, + &state.cascade_style); let mut new_style = (*style).clone(); diff --git a/components/style/context.rs b/components/style/context.rs index 839d7b81a2e..041b52d2a17 100644 --- a/components/style/context.rs +++ b/components/style/context.rs @@ -34,16 +34,16 @@ pub struct SharedStyleContext { /// A channel on which new animations that have been triggered by style recalculation can be /// sent. - pub new_animations_sender: Mutex>, + pub new_animations_sender: Mutex>>, /// Why is this reflow occurring pub goal: ReflowGoal, /// The animations that are currently running. - pub running_animations: Arc>>>, + pub running_animations: Arc>>>>, /// The list of animations that have expired since the last style recalculation. - pub expired_animations: Arc>>>, + pub expired_animations: Arc>>>>, ///The CSS error reporter for all CSS loaded in this layout thread pub error_reporter: Box, diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 07e504a5988..cbfa35b78c4 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -2,33 +2,12 @@ * 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 cssparser::{AtRuleParser, Delimiter, Parser, QualifiedRuleParser, RuleListParser}; +use parser::{ParserContext, log_css_error}; use properties::animated_properties::TransitionProperty; use properties::{PropertyDeclaration, parse_property_declaration_list}; use std::sync::Arc; -/// 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))); - } - - if keyframes.len() < 2 { - return Err(()) - } - - Ok(keyframes) -} - /// A number from 1 to 100, indicating the percentage of the animation where /// this keyframe should run. #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, HeapSizeOf)] @@ -57,7 +36,11 @@ impl KeyframePercentage { } else if input.try(|input| input.expect_ident_matching("to")).is_ok() { KeyframePercentage::new(1.) } else { - KeyframePercentage::new(try!(input.expect_percentage())) + let percentage = try!(input.expect_percentage()); + if percentage > 1. || percentage < 0. { + return Err(()); + } + KeyframePercentage::new(percentage) }; Ok(percentage) @@ -100,8 +83,7 @@ impl Keyframe { Ok(parse_property_declaration_list(context, input)) }).unwrap(); - // NB: Other browsers seem to ignore important declarations in keyframe - // animations too. + // NB: Important declarations are explicitely ignored in the spec. Ok(Keyframe { selector: selector, declarations: declarations.normal, @@ -109,22 +91,33 @@ impl Keyframe { } } +/// A keyframes step value. This can be a synthetised keyframes animation, that +/// is, one autogenerated from the current computed values, or a list of +/// declarations to apply. +// TODO: Find a better name for this? +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub enum KeyframesStepValue { + Declarations(Arc>), + ComputedValues, +} + /// A single step from a keyframe animation. #[derive(Debug, Clone, PartialEq, HeapSizeOf)] pub struct KeyframesStep { /// The percentage of the animation duration when this step starts. pub start_percentage: KeyframePercentage, - /// Declarations that will determine the final style during the step. - pub declarations: Arc>, + /// Declarations that will determine the final style during the step, or + /// `ComputedValues` if this is an autogenerated step. + pub value: KeyframesStepValue, } impl KeyframesStep { #[inline] fn new(percentage: KeyframePercentage, - declarations: Arc>) -> Self { + value: KeyframesStepValue) -> Self { KeyframesStep { start_percentage: percentage, - declarations: declarations, + value: value, } } } @@ -161,24 +154,34 @@ fn get_animated_properties(keyframe: &Keyframe) -> Vec { impl KeyframesAnimation { pub fn from_keyframes(keyframes: &[Keyframe]) -> Option { - debug_assert!(keyframes.len() > 1); - let mut steps = vec![]; - let animated_properties = get_animated_properties(&keyframes[0]); - if animated_properties.is_empty() { + if keyframes.is_empty() || animated_properties.is_empty() { return None; } + let mut steps = vec![]; + for keyframe in keyframes { for percentage in keyframe.selector.0.iter() { steps.push(KeyframesStep::new(*percentage, - keyframe.declarations.clone())); + KeyframesStepValue::Declarations(keyframe.declarations.clone()))); } } // Sort by the start percentage, so we can easily find a frame. steps.sort_by_key(|step| step.start_percentage); + // Prepend autogenerated keyframes if appropriate. + if steps[0].start_percentage.0 != 0. { + steps.insert(0, KeyframesStep::new(KeyframePercentage::new(0.), + KeyframesStepValue::ComputedValues)); + } + + if steps.last().unwrap().start_percentage.0 != 1. { + steps.push(KeyframesStep::new(KeyframePercentage::new(0.), + KeyframesStepValue::ComputedValues)); + } + Some(KeyframesAnimation { steps: steps, properties_changed: animated_properties, @@ -186,3 +189,54 @@ impl KeyframesAnimation { } } +/// Parses a keyframes list, like: +/// 0%, 50% { +/// width: 50%; +/// } +/// +/// 40%, 60%, 100% { +/// width: 100%; +/// } +struct KeyframeListParser<'a> { + context: &'a ParserContext<'a>, +} + +pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser) -> Vec { + RuleListParser::new_for_nested_rule(input, KeyframeListParser { context: context }) + .filter_map(Result::ok) + .collect() +} + +enum Void {} +impl<'a> AtRuleParser for KeyframeListParser<'a> { + type Prelude = Void; + type AtRule = Keyframe; +} + +impl<'a> QualifiedRuleParser for KeyframeListParser<'a> { + type Prelude = KeyframeSelector; + type QualifiedRule = Keyframe; + + fn parse_prelude(&self, input: &mut Parser) -> Result { + let start = input.position(); + match input.parse_comma_separated(|input| KeyframePercentage::parse(input)) { + Ok(percentages) => Ok(KeyframeSelector(percentages)), + Err(()) => { + let message = format!("Invalid keyframe rule: '{}'", input.slice_from(start)); + log_css_error(input, start, &message, self.context); + Err(()) + } + } + } + + fn parse_block(&self, prelude: Self::Prelude, input: &mut Parser) + -> Result { + Ok(Keyframe { + selector: prelude, + // FIXME: needs parsing different from parse_property_declaration_list: + // https://drafts.csswg.org/css-animations/#keyframes + // Paragraph "The inside of ..." + declarations: parse_property_declaration_list(self.context, input).normal, + }) + } +} diff --git a/components/style/matching.rs b/components/style/matching.rs index aed3958a36e..1f85effdb33 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -386,7 +386,7 @@ trait PrivateMatchMethods: TNode cacheable = !self.update_animations_for_cascade(context, &mut style) && cacheable; } - let mut this_style; + let this_style; match parent_style { Some(ref parent_style) => { let cache_entry = applicable_declarations_cache.find(applicable_declarations); @@ -416,6 +416,8 @@ trait PrivateMatchMethods: TNode } }; + let mut this_style = Arc::new(this_style); + if animate_properties { let this_opaque = self.opaque(); // Trigger any present animations if necessary. @@ -428,7 +430,7 @@ trait PrivateMatchMethods: TNode // to its old value if it did trigger a transition. if let Some(ref style) = style { animations_started |= - animation::start_transitions_if_applicable::( + animation::start_transitions_if_applicable::<::Impl>( &context.new_animations_sender, this_opaque, &**style, @@ -439,7 +441,6 @@ trait PrivateMatchMethods: TNode } // Calculate style difference. - let this_style = Arc::new(this_style); let damage = Self::ConcreteRestyleDamage::compute(style.map(|s| &*s), &*this_style); // Cache the resolved style if it was cacheable. diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index e1a21572a85..c42f973205c 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -14,7 +14,7 @@ use std::ascii::AsciiExt; use std::boxed::Box as StdBox; use std::collections::HashSet; use std::fmt; -use std::fmt::Write; +use std::fmt::{Debug, Write}; use std::sync::Arc; use app_units::Au; @@ -1069,9 +1069,10 @@ impl PropertyDeclaration { pub mod style_struct_traits { use super::longhands; + use std::fmt::Debug; % for style_struct in data.active_style_structs(): - pub trait ${style_struct.trait_name}: Clone { + pub trait ${style_struct.trait_name}: Debug + Clone { % for longhand in style_struct.longhands: #[allow(non_snake_case)] fn set_${longhand.ident}(&mut self, v: longhands::${longhand.ident}::computed_value::T); @@ -1100,7 +1101,7 @@ pub mod style_structs { #[derive(Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] % else: - #[derive(PartialEq, Clone)] + #[derive(PartialEq, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] % endif pub struct ${style_struct.servo_struct_name} { @@ -1249,7 +1250,7 @@ pub mod style_structs { % endfor } -pub trait ComputedValues : Clone + Send + Sync + 'static { +pub trait ComputedValues : Debug + Clone + Send + Sync + 'static { % for style_struct in data.active_style_structs(): type Concrete${style_struct.trait_name}: style_struct_traits::${style_struct.trait_name}; % endfor @@ -1292,7 +1293,7 @@ pub trait ComputedValues : Clone + Send + Sync + 'static { fn is_multicol(&self) -> bool; } -#[derive(Clone)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct ServoComputedValues { % for style_struct in data.active_style_structs(): diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index a394ef987cc..7f80cb2ea38 100644 --- a/components/style/selector_impl.rs +++ b/components/style/selector_impl.rs @@ -9,6 +9,7 @@ use properties::{self, ServoComputedValues}; use selector_matching::{USER_OR_USER_AGENT_STYLESHEETS, QUIRKS_MODE_STYLESHEET}; use selectors::Element; use selectors::parser::{ParserContext, SelectorImpl}; +use std::fmt::Debug; use stylesheets::Stylesheet; /// This function determines if a pseudo-element is eagerly cascaded or not. @@ -62,7 +63,9 @@ pub trait ElementExt: Element { fn is_link(&self) -> bool; } -pub trait SelectorImplExt : SelectorImpl + Sized { +// NB: The `Clone` trait is here for convenience due to: +// https://github.com/rust-lang/rust/issues/26925 +pub trait SelectorImplExt : SelectorImpl + Clone + Debug + Sized { type ComputedValues: properties::ComputedValues; fn pseudo_element_cascade_type(pseudo: &Self::PseudoElement) -> PseudoElementCascadeType; diff --git a/components/style/servo.rs b/components/style/servo.rs index 8278713d26f..b592104832a 100644 --- a/components/style/servo.rs +++ b/components/style/servo.rs @@ -1,9 +1,9 @@ /* 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/. */ - //! Concrete types for servo Style implementation +use animation; use context; use data; use properties::ServoComputedValues; @@ -15,3 +15,4 @@ pub type Stylesheet = stylesheets::Stylesheet; pub type PrivateStyleData = data::PrivateStyleData; pub type Stylist = selector_matching::Stylist; pub type SharedStyleContext = context::SharedStyleContext; +pub type Animation = animation::Animation; diff --git a/components/style/stylesheets.rs b/components/style/stylesheets.rs index 726db0251cf..98864868233 100644 --- a/components/style/stylesheets.rs +++ b/components/style/stylesheets.rs @@ -5,7 +5,7 @@ //! Style sheets and their CSS rules. use cssparser::{AtRuleParser, Parser, QualifiedRuleParser, decode_stylesheet_bytes}; -use cssparser::{AtRuleType, RuleListParser}; +use cssparser::{AtRuleType, RuleListParser, Token}; use encoding::EncodingRef; use error_reporting::ParseErrorReporter; use font_face::{FontFaceRule, parse_font_face_block}; @@ -506,7 +506,12 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleParser for NestedRuleParser<'a, 'b, Impl> } }, "keyframes" => { - let name = try!(input.expect_ident()); + 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)))) }, _ => Err(()) @@ -530,7 +535,7 @@ impl<'a, 'b, Impl: SelectorImpl> AtRuleParser for NestedRuleParser<'a, 'b, Impl> AtRulePrelude::Keyframes(name) => { Ok(CSSRule::Keyframes(KeyframesRule { name: name, - keyframes: try!(parse_keyframe_list(&self.context, input)), + keyframes: parse_keyframe_list(&self.context, input), })) } } From e015e876973ce3ec2b0fba6c17f811f68fe04067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Mon, 27 Jun 2016 23:20:52 +0000 Subject: [PATCH 24/28] geckolib: fix geckolib build by implementing display on Atoms. --- ports/geckolib/data.rs | 5 +++-- ports/geckolib/properties.mako.rs | 2 +- ports/geckolib/selector_impl.rs | 1 + ports/geckolib/string_cache/lib.rs | 10 ++++++++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ports/geckolib/data.rs b/ports/geckolib/data.rs index dd0fa4dc18d..0f07ef83c57 100644 --- a/ports/geckolib/data.rs +++ b/ports/geckolib/data.rs @@ -6,12 +6,11 @@ use euclid::Size2D; use euclid::size::TypedSize2D; use gecko_bindings::bindings::RawServoStyleSet; use num_cpus; -use selector_impl::{Stylist, Stylesheet, SharedStyleContext}; +use selector_impl::{GeckoSelectorImpl, Stylist, Stylesheet, SharedStyleContext}; use std::cmp; use std::collections::HashMap; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::{Arc, RwLock}; -use style::animation::Animation; use style::dom::OpaqueNode; use style::media_queries::{Device, MediaType}; use style::parallel::WorkQueueData; @@ -19,6 +18,8 @@ use util::geometry::ViewportPx; use util::thread_state; use util::workqueue::WorkQueue; +pub type Animation = ::style::animation::Animation; + pub struct PerDocumentStyleData { /// Rule processor. pub stylist: Arc, diff --git a/ports/geckolib/properties.mako.rs b/ports/geckolib/properties.mako.rs index 79b200d0f1b..6493a399ea9 100644 --- a/ports/geckolib/properties.mako.rs +++ b/ports/geckolib/properties.mako.rs @@ -39,7 +39,7 @@ use values::{StyleCoordHelpers, ToGeckoStyleCoord, convert_nscolor_to_rgba}; use values::{convert_rgba_to_nscolor, debug_assert_unit_is_safe_to_copy}; use values::round_border_to_device_pixels; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct GeckoComputedValues { % for style_struct in data.style_structs: ${style_struct.ident}: Arc<${style_struct.gecko_struct_name}>, diff --git a/ports/geckolib/selector_impl.rs b/ports/geckolib/selector_impl.rs index dda81ecec06..5c5e1731b3b 100644 --- a/ports/geckolib/selector_impl.rs +++ b/ports/geckolib/selector_impl.rs @@ -16,6 +16,7 @@ pub type PrivateStyleData = style::data::PrivateStyleData fmt::Result { + for c in char::decode_utf16(self.as_slice().iter().cloned()) { + try!(write!(w, "{}", c.unwrap_or(char::REPLACEMENT_CHARACTER))) + } + Ok(()) + } +} + impl<'a> From<&'a str> for Atom { #[inline] fn from(string: &str) -> Atom { From 173f489d7b13673ea321054722a19a26e3c73483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Mon, 27 Jun 2016 23:27:29 +0000 Subject: [PATCH 25/28] style: Add documentation lost in a rebase, plus implement box-shadow animations. --- components/style/properties/data.py | 2 +- .../helpers/animated_properties.mako.rs | 120 +++++++++++++++++- 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index e778baf095b..1add6d93148 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -58,7 +58,7 @@ def is_known_animatable_property(name): def is_not_supported_animatable_property(name): return name in [ "flex-basis", "column-width", "column-height", "column-count", - "column-gap", "box-shadow", "clip", "filter", "transform-origin", + "column-gap", "clip", "filter", "transform-origin", "perspective-origin", "font-stretch", "letter-spacing", "word-spacing", "text-decoration" ] diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index ffd89b52244..da6887e4abd 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -15,6 +15,8 @@ use properties::longhands::font_weight::computed_value::T as FontWeight; use properties::longhands::line_height::computed_value::T as LineHeight; use properties::longhands::text_shadow::computed_value::T as TextShadowList; use properties::longhands::text_shadow::computed_value::TextShadow; +use properties::longhands::box_shadow::computed_value::T as BoxShadowList; +use properties::longhands::box_shadow::computed_value::BoxShadow; use properties::longhands::transform::computed_value::ComputedMatrix; use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; use properties::longhands::transform::computed_value::T as TransformList; @@ -22,7 +24,7 @@ use properties::longhands::vertical_align::computed_value::T as VerticalAlign; use properties::longhands::visibility::computed_value::T as Visibility; use properties::longhands::z_index::computed_value::T as ZIndex; use properties::style_struct_traits::*; -use std::cmp::Ordering; +use std::cmp::{self, Ordering}; use std::fmt; use std::iter::repeat; use super::ComputedValues; @@ -148,10 +150,14 @@ impl AnimatedProperty { } } +/// A trait used to implement [interpolation][interpolated-types]. +/// +/// [interpolated-types]: https://drafts.csswg.org/css-transitions/#interpolated-types pub trait Interpolate: Sized { fn interpolate(&self, other: &Self, time: f64) -> Option; } +/// https://drafts.csswg.org/css-transitions/#animtype-number impl Interpolate for Au { #[inline] fn interpolate(&self, other: &Au, time: f64) -> Option { @@ -173,6 +179,7 @@ impl Interpolate for Option where T: Interpolate { } } +/// https://drafts.csswg.org/css-transitions/#animtype-number impl Interpolate for f32 { #[inline] fn interpolate(&self, other: &f32, time: f64) -> Option { @@ -180,6 +187,7 @@ impl Interpolate for f32 { } } +/// https://drafts.csswg.org/css-transitions/#animtype-number impl Interpolate for f64 { #[inline] fn interpolate(&self, other: &f64, time: f64) -> Option { @@ -187,6 +195,7 @@ impl Interpolate for f64 { } } +/// https://drafts.csswg.org/css-transitions/#animtype-number impl Interpolate for i32 { #[inline] fn interpolate(&self, other: &i32, time: f64) -> Option { @@ -196,6 +205,7 @@ impl Interpolate for i32 { } } +/// https://drafts.csswg.org/css-transitions/#animtype-number impl Interpolate for Angle { #[inline] fn interpolate(&self, other: &Angle, time: f64) -> Option { @@ -203,6 +213,7 @@ impl Interpolate for Angle { } } +/// https://drafts.csswg.org/css-transitions/#animtype-visibility impl Interpolate for Visibility { #[inline] fn interpolate(&self, other: &Visibility, time: f64) @@ -222,6 +233,7 @@ impl Interpolate for Visibility { } } +/// https://drafts.csswg.org/css-transitions/#animtype-integer impl Interpolate for ZIndex { #[inline] fn interpolate(&self, other: &ZIndex, time: f64) @@ -278,6 +290,7 @@ impl Interpolate for BorderRadiusSize { } } +/// https://drafts.csswg.org/css-transitions/#animtype-length impl Interpolate for VerticalAlign { #[inline] fn interpolate(&self, other: &VerticalAlign, time: f64) @@ -294,6 +307,7 @@ impl Interpolate for VerticalAlign { } } +/// https://drafts.csswg.org/css-transitions/#animtype-simple-list impl Interpolate for BorderSpacing { #[inline] fn interpolate(&self, other: &BorderSpacing, time: f64) @@ -306,6 +320,7 @@ impl Interpolate for BorderSpacing { } } +/// https://drafts.csswg.org/css-transitions/#animtype-color impl Interpolate for RGBA { #[inline] fn interpolate(&self, other: &RGBA, time: f64) -> Option { @@ -321,6 +336,7 @@ impl Interpolate for RGBA { } } +/// https://drafts.csswg.org/css-transitions/#animtype-color impl Interpolate for CSSParserColor { #[inline] fn interpolate(&self, other: &Self, time: f64) -> Option { @@ -335,6 +351,7 @@ impl Interpolate for CSSParserColor { } } +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc impl Interpolate for CalcLengthOrPercentage { #[inline] fn interpolate(&self, other: &CalcLengthOrPercentage, time: f64) @@ -346,6 +363,7 @@ impl Interpolate for CalcLengthOrPercentage { } } +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc impl Interpolate for LengthOrPercentage { #[inline] fn interpolate(&self, other: &LengthOrPercentage, time: f64) @@ -374,6 +392,7 @@ impl Interpolate for LengthOrPercentage { } } +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc impl Interpolate for LengthOrPercentageOrAuto { #[inline] fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64) @@ -405,6 +424,7 @@ impl Interpolate for LengthOrPercentageOrAuto { } } +/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc impl Interpolate for LengthOrPercentageOrNone { #[inline] fn interpolate(&self, other: &LengthOrPercentageOrNone, time: f64) @@ -430,6 +450,8 @@ impl Interpolate for LengthOrPercentageOrNone { } } +/// https://drafts.csswg.org/css-transitions/#animtype-number +/// https://drafts.csswg.org/css-transitions/#animtype-length impl Interpolate for LineHeight { #[inline] fn interpolate(&self, other: &LineHeight, time: f64) @@ -485,6 +507,7 @@ impl Interpolate for FontWeight { } } +/// https://drafts.csswg.org/css-transitions/#animtype-rect impl Interpolate for ClipRect { #[inline] fn interpolate(&self, other: &ClipRect, time: f64) @@ -501,6 +524,7 @@ impl Interpolate for ClipRect { } } +/// https://drafts.csswg.org/css-transitions/#animtype-simple-list impl Interpolate for BackgroundPosition { #[inline] fn interpolate(&self, other: &BackgroundPosition, time: f64) @@ -532,6 +556,7 @@ impl Interpolate for BackgroundSize { } } +/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list impl Interpolate for TextShadow { #[inline] fn interpolate(&self, other: &TextShadow, time: f64) @@ -543,11 +568,12 @@ impl Interpolate for TextShadow { (Some(offset_x), Some(offset_y), Some(blur_radius), Some(color)) => { Some(TextShadow { offset_x: offset_x, offset_y: offset_y, blur_radius: blur_radius, color: color }) }, - (_, _, _, _) => None, + _ => None, } } } +/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list impl Interpolate for TextShadowList { #[inline] fn interpolate(&self, other: &TextShadowList, time: f64) @@ -696,6 +722,95 @@ fn build_identity_transform_list(list: &[TransformOperation]) -> Vec Option { + // The inset value must change + let mut zero = BoxShadow { + offset_x: Au(0), + offset_y: Au(0), + spread_radius: Au(0), + blur_radius: Au(0), + color: CSSParserColor::RGBA(RGBA { + red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0 + }), + inset: false, + }; + + let max_len = cmp::max(self.0.len(), other.0.len()); + let mut result = Vec::with_capacity(max_len); + + for i in 0..max_len { + let shadow = match (self.0.get(i), other.0.get(i)) { + (Some(shadow), Some(other)) => { + match shadow.interpolate(other, time) { + Some(shadow) => shadow, + None => return None, + } + } + (Some(shadow), None) => { + zero.inset = shadow.inset; + shadow.interpolate(&zero, time).unwrap() + } + (None, Some(shadow)) => { + zero.inset = shadow.inset; + zero.interpolate(&shadow, time).unwrap() + } + (None, None) => unreachable!(), + }; + result.push(shadow); + } + + Some(BoxShadowList(result)) + } +} + +/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list +impl Interpolate for BoxShadow { + #[inline] + fn interpolate(&self, other: &Self, time: f64) + -> Option { + if self.inset != other.inset { + return None; + } + + let x = match self.offset_x.interpolate(&other.offset_x, time) { + Some(x) => x, + None => return None, + }; + + let y = match self.offset_y.interpolate(&other.offset_y, time) { + Some(y) => y, + None => return None, + }; + + let color = match self.color.interpolate(&other.color, time) { + Some(c) => c, + None => return None, + }; + + let spread = match self.spread_radius.interpolate(&other.spread_radius, time) { + Some(s) => s, + None => return None, + }; + + let blur = match self.blur_radius.interpolate(&other.blur_radius, time) { + Some(r) => r, + None => return None, + }; + + Some(BoxShadow { + offset_x: x, + offset_y: y, + blur_radius: blur, + spread_radius: spread, + color: color, + inset: self.inset, + }) + } +} + impl Interpolate for LengthOrNone { fn interpolate(&self, other: &Self, time: f64) -> Option { match (*self, *other) { @@ -706,6 +821,7 @@ impl Interpolate for LengthOrNone { } } +/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms impl Interpolate for TransformList { #[inline] fn interpolate(&self, other: &TransformList, time: f64) -> Option { From 793de6dff270ce111773e7ce2e6637c4cbe10658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 28 Jun 2016 10:29:12 +0000 Subject: [PATCH 26/28] style: Remove an unsound where clause, and don't reset the iteration count on restyle. --- components/style/animation.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/components/style/animation.rs b/components/style/animation.rs index 4c8bfdc9bf4..b2a64a5fa5e 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -123,8 +123,7 @@ impl KeyframesAnimationState { /// /// There are some bits of state we can't just replace, over all taking in /// account times, so here's that logic. - pub fn update_from_other(&mut self, other: &Self) - where Self: Clone { + pub fn update_from_other(&mut self, other: &Self) { use self::KeyframesRunningState::*; debug!("KeyframesAnimationState::update_from_other({:?}, {:?})", self, other); @@ -135,6 +134,7 @@ impl KeyframesAnimationState { let old_duration = self.duration; let old_direction = self.current_direction; let old_running_state = self.running_state.clone(); + let old_iteration_state = self.iteration_state.clone(); *self = other.clone(); let mut new_started_at = old_started_at; @@ -155,6 +155,15 @@ impl KeyframesAnimationState { _ => {}, } + // Don't update the iteration count, just the iteration limit. + // TODO: see how changing the limit affects rendering in other browsers. + // We might need to keep the iteration count even when it's infinite. + match (&mut self.iteration_state, old_iteration_state) { + (&mut KeyframesIterationState::Finite(ref mut iters, _), KeyframesIterationState::Finite(old_iters, _)) + => *iters = old_iters, + _ => {} + } + self.current_direction = old_direction; self.started_at = new_started_at; } From faed3df59404711fb7273fc3547f5f3ace5f96c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 28 Jun 2016 14:14:35 +0000 Subject: [PATCH 27/28] style: Take down the global animatable props list and use a kwarg instead --- components/style/properties/data.py | 62 ++---------- .../helpers/animated_properties.mako.rs | 26 +++++ .../properties/longhand/background.mako.rs | 29 ++++-- .../style/properties/longhand/border.mako.rs | 22 +++-- .../style/properties/longhand/box.mako.rs | 98 ++++++++++++------- .../style/properties/longhand/color.mako.rs | 2 +- .../style/properties/longhand/column.mako.rs | 9 +- .../properties/longhand/counters.mako.rs | 6 +- .../style/properties/longhand/effects.mako.rs | 43 +++++--- .../style/properties/longhand/font.mako.rs | 27 +++-- .../properties/longhand/inherited_box.mako.rs | 22 +++-- .../properties/longhand/inherited_svg.mako.rs | 44 ++++++--- .../longhand/inherited_table.mako.rs | 14 ++- .../longhand/inherited_text.mako.rs | 49 ++++++---- .../style/properties/longhand/list.mako.rs | 9 +- .../style/properties/longhand/margin.mako.rs | 3 +- .../style/properties/longhand/outline.mako.rs | 13 ++- .../style/properties/longhand/padding.mako.rs | 3 +- .../properties/longhand/pointing.mako.rs | 22 +++-- .../properties/longhand/position.mako.rs | 63 ++++++++---- .../style/properties/longhand/svg.mako.rs | 26 +++-- .../style/properties/longhand/table.mako.rs | 3 +- .../style/properties/longhand/text.mako.rs | 16 ++- .../style/properties/longhand/ui.mako.rs | 9 +- .../style/properties/longhand/xul.mako.rs | 11 ++- 25 files changed, 393 insertions(+), 238 deletions(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 1add6d93148..04da7ad225b 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -16,54 +16,6 @@ def to_camel_case(ident): return re.sub("(^|_|-)([a-z])", lambda m: m.group(2).upper(), ident.strip("_").strip("-")) -# https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties -def is_known_animatable_property(name): - return name in [ - "-moz-outline-radius", "-moz-outline-radius-bottomleft", - "-moz-outline-radius-bottomright", "-moz-outline-radius-topleft", - "-moz-outline-radius-topright", "-webkit-text-fill-color", - "-webkit-text-stroke", "-webkit-text-stroke-color", - "-webkit-touch-callout", "all", "backdrop-filter", "background", - "background-color", "background-position", "background-size", "border", - "border-bottom", "border-bottom-color", "border-bottom-left-radius", - "border-bottom-right-radius", "border-bottom-width", "border-color", - "border-left", "border-left-color", "border-left-width", "border-radius", - "border-right", "border-right-color", "border-right-width", "border-top", - "border-top-color", "border-top-left-radius", "border-top-right-radius", - "border-top-width", "border-width", "bottom", "box-shadow", "clip", - "clip-path", "color", "column-count", "column-gap", "column-rule", - "column-rule-color", "column-rule-width", "column-width", "columns", - "filter", "flex", "flex-basis", "flex-grow", "flex-shrink", "font", - "font-size", "font-size-adjust", "font-stretch", "font-weight", - "grid-column-gap", "grid-gap", "grid-row-gap", "height", "left", - "letter-spacing", "line-height", "margin", "margin-bottom", - "margin-left", "margin-right", "margin-top", "mask", "mask-position", - "mask-size", "max-height", "max-width", "min-height", "min-width", - "motion-offset", "motion-rotation", "object-position", "opacity", - "order", "outline", "outline-color", "outline-offset", "outline-width", - "padding", "padding-bottom", "padding-left", "padding-right", - "padding-top", "perspective", "perspective-origin", "right", - "scroll-snap-coordinate", "scroll-snap-destination", - "shape-image-threshold", "shape-margin", "shape-outside", - "text-decoration", "text-decoration-color", "text-emphasis", - "text-emphasis-color", "text-indent", "text-shadow", "top", "transform", - "transform-origin", "vertical-align", "visibility", "width", - "word-spacing", "z-index" - ] - - -# FIXME: Servo doesn't support some animatable properties yet,those are in the -# following list, and can be implemented removing it from the list and -# implementing the Interpolate trait in helpers/animated_properties.mako.rs -def is_not_supported_animatable_property(name): - return name in [ - "flex-basis", "column-width", "column-height", "column-count", - "column-gap", "clip", "filter", "transform-origin", - "perspective-origin", "font-stretch", "letter-spacing", "word-spacing", - "text-decoration" - ] - - class Keyword(object): def __init__(self, name, values, gecko_constant_prefix=None, extra_gecko_values=None, extra_servo_values=None): @@ -93,9 +45,9 @@ class Keyword(object): class Longhand(object): - def __init__(self, style_struct, name, derived_from=None, keyword=None, + def __init__(self, style_struct, name, animatable=None, derived_from=None, keyword=None, predefined_type=None, custom_cascade=False, experimental=False, internal=False, - need_clone=False, gecko_ffi_name=None, animatable=None): + need_clone=False, gecko_ffi_name=None): self.name = name self.keyword = keyword self.predefined_type = predefined_type @@ -108,10 +60,16 @@ class Longhand(object): self.need_clone = need_clone self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case self.derived_from = (derived_from or "").split() - if animatable is not None: + + # This is done like this since just a plain bool argument seemed like + # really random. + if animatable is None: + raise TypeError("animatable should be specified for " + name + ")") + if isinstance(animatable, bool): self.animatable = animatable else: - self.animatable = is_known_animatable_property(name) and not is_not_supported_animatable_property(name) + assert animatable == "True" or animatable == "False" + self.animatable = animatable == "True" class Shorthand(object): diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index da6887e4abd..3b75b4ee07b 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -20,6 +20,7 @@ use properties::longhands::box_shadow::computed_value::BoxShadow; use properties::longhands::transform::computed_value::ComputedMatrix; use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; use properties::longhands::transform::computed_value::T as TransformList; +use properties::longhands::transform_origin::computed_value::T as TransformOrigin; use properties::longhands::vertical_align::computed_value::T as VerticalAlign; use properties::longhands::visibility::computed_value::T as Visibility; use properties::longhands::z_index::computed_value::T as ZIndex; @@ -821,6 +822,31 @@ impl Interpolate for LengthOrNone { } } +impl Interpolate for TransformOrigin { + fn interpolate(&self, other: &Self, time: f64) -> Option { + let horizontal = match self.horizontal.interpolate(&other.horizontal, time) { + Some(h) => h, + None => return None, + }; + + let vertical = match self.vertical.interpolate(&other.vertical, time) { + Some(v) => v, + None => return None, + }; + + let depth = match self.depth.interpolate(&other.depth, time) { + Some(d) => d, + None => return None, + }; + + Some(TransformOrigin { + horizontal: horizontal, + vertical: vertical, + depth: depth, + }) + } +} + /// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms impl Interpolate for TransformList { #[inline] diff --git a/components/style/properties/longhand/background.mako.rs b/components/style/properties/longhand/background.mako.rs index b31a27d7ed8..c2ed7d3da7e 100644 --- a/components/style/properties/longhand/background.mako.rs +++ b/components/style/properties/longhand/background.mako.rs @@ -5,11 +5,12 @@ <%namespace name="helpers" file="/helpers.mako.rs" /> <% data.new_style_struct("Background", inherited=False) %> -${helpers.predefined_type( - "background-color", "CSSColor", - "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */")} -<%helpers:longhand name="background-image"> +${helpers.predefined_type("background-color", "CSSColor", + "::cssparser::Color::RGBA(::cssparser::RGBA { red: 0., green: 0., blue: 0., alpha: 0. }) /* transparent */", + animatable=True)} + +<%helpers:longhand name="background-image" animatable="False"> use cssparser::ToCss; use std::fmt; use values::specified::Image; @@ -71,7 +72,7 @@ ${helpers.predefined_type( } -<%helpers:longhand name="background-position"> +<%helpers:longhand name="background-position" animatable="True"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -186,15 +187,23 @@ ${helpers.predefined_type( } -${helpers.single_keyword("background-repeat", "repeat repeat-x repeat-y no-repeat")} +${helpers.single_keyword("background-repeat", + "repeat repeat-x repeat-y no-repeat", + animatable=False)} -${helpers.single_keyword("background-attachment", "scroll fixed" + (" local" if product == "gecko" else ""))} +${helpers.single_keyword("background-attachment", + "scroll fixed" + (" local" if product == "gecko" else ""), + animatable=False)} -${helpers.single_keyword("background-clip", "border-box padding-box content-box")} +${helpers.single_keyword("background-clip", + "border-box padding-box content-box", + animatable=False)} -${helpers.single_keyword("background-origin", "padding-box border-box content-box")} +${helpers.single_keyword("background-origin", + "padding-box border-box content-box", + animatable=False)} -<%helpers:longhand name="background-size"> +<%helpers:longhand name="background-size" animatable="True"> use cssparser::{ToCss, Token}; use std::ascii::AsciiExt; use std::fmt; diff --git a/components/style/properties/longhand/border.mako.rs b/components/style/properties/longhand/border.mako.rs index 96656861583..57bd1880233 100644 --- a/components/style/properties/longhand/border.mako.rs +++ b/components/style/properties/longhand/border.mako.rs @@ -10,15 +10,19 @@ "bool") for side in ["top", "right", "bottom", "left"]]) %> % for side in ["top", "right", "bottom", "left"]: - ${helpers.predefined_type("border-%s-color" % side, "CSSColor", "::cssparser::Color::CurrentColor")} + ${helpers.predefined_type("border-%s-color" % side, "CSSColor", + "::cssparser::Color::CurrentColor", + animatable=True)} % endfor % for side in ["top", "right", "bottom", "left"]: - ${helpers.predefined_type("border-%s-style" % side, "BorderStyle", "specified::BorderStyle::none", need_clone=True)} + ${helpers.predefined_type("border-%s-style" % side, "BorderStyle", + "specified::BorderStyle::none", + need_clone=True, animatable=False)} % endfor % for side in ["top", "right", "bottom", "left"]: - <%helpers:longhand name="border-${side}-width"> + <%helpers:longhand name="border-${side}-width" animatable="True"> use app_units::Au; use cssparser::ToCss; use std::fmt; @@ -60,13 +64,15 @@ % for corner in ["top-left", "top-right", "bottom-right", "bottom-left"]: ${helpers.predefined_type("border-" + corner + "-radius", "BorderRadiusSize", "computed::BorderRadiusSize::zero()", - "parse")} + "parse", + animatable=True)} % endfor -${helpers.single_keyword("box-decoration-break", "slice clone", products="gecko")} +${helpers.single_keyword("box-decoration-break", "slice clone", + products="gecko", animatable=False)} -${helpers.single_keyword("-moz-float-edge", - "content-box margin-box", +${helpers.single_keyword("-moz-float-edge", "content-box margin-box", gecko_ffi_name="mFloatEdge", gecko_constant_prefix="NS_STYLE_FLOAT_EDGE", - products="gecko")} + products="gecko", + animatable=False)} diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 4be64fd50aa..f9f2a4be97c 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -11,7 +11,10 @@ additional_methods=[Method("transition_count", "usize")]) %> // TODO(SimonSapin): don't parse `inline-table`, since we don't support it -<%helpers:longhand name="display" need_clone="True" custom_cascade="${product == 'servo'}"> +<%helpers:longhand name="display" + need_clone="True" + animatable="False" + custom_cascade="${product == 'servo'}"> <% values = """inline block inline-block table inline-table table-row-group table-header-group table-footer-group @@ -86,9 +89,14 @@ -${helpers.single_keyword("position", "static absolute relative fixed", need_clone=True, extra_gecko_values="sticky")} +${helpers.single_keyword("position", "static absolute relative fixed", + need_clone=True, extra_gecko_values="sticky", animatable=False)} -<%helpers:single_keyword_computed name="float" values="none left right" need_clone="True" gecko_ffi_name="mFloats"> +<%helpers:single_keyword_computed name="float" + values="none left right" + animatable="False" + need_clone="True" + gecko_ffi_name="mFloats"> impl ToComputedValue for SpecifiedValue { type ComputedValue = computed_value::T; @@ -107,9 +115,13 @@ ${helpers.single_keyword("position", "static absolute relative fixed", need_clon -${helpers.single_keyword("clear", "none left right both", gecko_ffi_name="mBreakType")} +${helpers.single_keyword("clear", "none left right both", + animatable=False, gecko_ffi_name="mBreakType")} -<%helpers:longhand name="-servo-display-for-hypothetical-box" derived_from="display" products="servo"> +<%helpers:longhand name="-servo-display-for-hypothetical-box" + animatable="False" + derived_from="display" + products="servo"> pub use super::display::{SpecifiedValue, get_initial_value}; pub use super::display::{parse}; @@ -125,7 +137,8 @@ ${helpers.single_keyword("clear", "none left right both", gecko_ffi_name="mBreak -<%helpers:longhand name="vertical-align"> +<%helpers:longhand name="vertical-align" + animatable="True"> use cssparser::ToCss; use std::fmt; @@ -219,18 +232,21 @@ ${helpers.single_keyword("clear", "none left right both", gecko_ffi_name="mBreak // CSS 2.1, Section 11 - Visual effects // Non-standard, see https://developer.mozilla.org/en-US/docs/Web/CSS/overflow-clip-box#Specifications -${helpers.single_keyword("-servo-overflow-clip-box", "padding-box content-box", products="servo", - internal=True)} +${helpers.single_keyword("-servo-overflow-clip-box", "padding-box content-box", + products="servo", animatable=False, internal=True)} -${helpers.single_keyword("overflow-clip-box", "padding-box content-box", products="gecko", - internal=True)} +${helpers.single_keyword("overflow-clip-box", "padding-box content-box", + products="gecko", animatable=False, internal=True)} // FIXME(pcwalton, #2742): Implement scrolling for `scroll` and `auto`. -${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone=True, - gecko_constant_prefix="NS_STYLE_OVERFLOW")} +${helpers.single_keyword("overflow-x", "visible hidden scroll auto", + need_clone=True, animatable=False, + gecko_constant_prefix="NS_STYLE_OVERFLOW")} // FIXME(pcwalton, #2742): Implement scrolling for `scroll` and `auto`. -<%helpers:longhand name="overflow-y" need_clone="True"> +<%helpers:longhand name="overflow-y" + need_clone="True" + animatable="False"> use super::overflow_x; use cssparser::ToCss; @@ -269,7 +285,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= // TODO(pcwalton): Multiple transitions. -<%helpers:longhand name="transition-duration"> +<%helpers:longhand name="transition-duration" animatable="False"> use values::computed::ComputedValueAsSpecified; use values::specified::Time; @@ -327,7 +343,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= // TODO(pcwalton): Lots more timing functions. // TODO(pcwalton): Multiple transitions. -<%helpers:longhand name="transition-timing-function"> +<%helpers:longhand name="transition-timing-function" animatable="False"> use self::computed_value::{StartEnd, TransitionTimingFunction}; use euclid::point::Point2D; @@ -525,7 +541,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } -<%helpers:longhand name="transition-property"> +<%helpers:longhand name="transition-property" animatable="False"> pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue; pub use self::computed_value::T as SpecifiedValue; @@ -576,14 +592,14 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } -<%helpers:longhand name="transition-delay"> +<%helpers:longhand name="transition-delay" animatable="False"> pub use properties::longhands::transition_duration::{SingleSpecifiedValue, SpecifiedValue}; pub use properties::longhands::transition_duration::{computed_value}; pub use properties::longhands::transition_duration::{get_initial_single_value}; pub use properties::longhands::transition_duration::{get_initial_value, parse, parse_one}; -<%helpers:longhand name="animation-name"> +<%helpers:longhand name="animation-name" animatable="False"> use values::computed::ComputedValueAsSpecified; pub mod computed_value { @@ -629,19 +645,19 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= impl ComputedValueAsSpecified for SpecifiedValue {} -<%helpers:longhand name="animation-duration"> +<%helpers:longhand name="animation-duration" animatable="False"> pub use super::transition_duration::computed_value; pub use super::transition_duration::{parse, get_initial_value}; pub use super::transition_duration::SpecifiedValue; -<%helpers:longhand name="animation-timing-function"> +<%helpers:longhand name="animation-timing-function" animatable="False"> pub use super::transition_timing_function::computed_value; pub use super::transition_timing_function::{parse, get_initial_value}; pub use super::transition_timing_function::SpecifiedValue; -<%helpers:longhand name="animation-iteration-count"> +<%helpers:longhand name="animation-iteration-count" animatable="False"> use values::computed::ComputedValueAsSpecified; pub mod computed_value { @@ -711,16 +727,19 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= ${helpers.keyword_list("animation-direction", - "normal reverse alternate alternate-reverse")} + "normal reverse alternate alternate-reverse", + animatable=False)} ${helpers.keyword_list("animation-play-state", "running paused", - need_clone=True)} + need_clone=True, + animatable=False)} ${helpers.keyword_list("animation-fill-mode", - "none forwards backwards both")} + "none forwards backwards both", + animatable=False)} -<%helpers:longhand name="animation-delay"> +<%helpers:longhand name="animation-delay" animatable="False"> pub use super::transition_duration::computed_value; pub use super::transition_duration::{parse, get_initial_value}; pub use super::transition_duration::SpecifiedValue; @@ -730,43 +749,51 @@ ${helpers.keyword_list("animation-fill-mode", // https://www.w3.org/TR/cssom-view-1/ ${helpers.single_keyword("scroll-behavior", "auto smooth", - products="gecko")} + products="gecko", + animatable=False)} // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type-x ${helpers.single_keyword("scroll-snap-type-x", "none mandatory proximity", products="gecko", - gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE")} + gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE", + animatable=False)} // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type-y ${helpers.single_keyword("scroll-snap-type-y", "none mandatory proximity", products="gecko", - gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE")} + gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE", + animatable=False)} // Compositing and Blending Level 1 // http://www.w3.org/TR/compositing-1/ ${helpers.single_keyword("isolation", "auto isolate", - products="gecko")} + products="gecko", + animatable=False)} ${helpers.single_keyword("page-break-after", "auto always avoid left right", - products="gecko")} + products="gecko", + animatable=False)} ${helpers.single_keyword("page-break-before", "auto always avoid left right", - products="gecko")} + products="gecko", + animatable=False)} ${helpers.single_keyword("page-break-inside", "auto avoid", products="gecko", gecko_ffi_name="mBreakInside", - gecko_constant_prefix="NS_STYLE_PAGE_BREAK")} + gecko_constant_prefix="NS_STYLE_PAGE_BREAK", + animatable=False)} // CSS Basic User Interface Module Level 3 // http://dev.w3.org/csswg/css-ui/ ${helpers.single_keyword("resize", "none both horizontal vertical", - products="gecko")} + products="gecko", + animatable=False)} // Non-standard ${helpers.single_keyword("-moz-appearance", @@ -793,10 +820,11 @@ ${helpers.single_keyword("-moz-appearance", """, gecko_ffi_name="mAppearance", gecko_constant_prefix="NS_THEME", - products="gecko")} + products="gecko", + animatable=False)} // Non-standard: https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-binding -<%helpers:longhand name="-moz-binding" products="gecko"> +<%helpers:longhand name="-moz-binding" products="gecko" animatable="False"> use cssparser::{CssStringWriter, ToCss}; use gecko_bindings::ptr::{GeckoArcPrincipal, GeckoArcURI}; use std::fmt::{self, Write}; diff --git a/components/style/properties/longhand/color.mako.rs b/components/style/properties/longhand/color.mako.rs index fa19eab0726..31368dbe9ee 100644 --- a/components/style/properties/longhand/color.mako.rs +++ b/components/style/properties/longhand/color.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("Color", inherited=True) %> -<%helpers:raw_longhand name="color" need_clone="True"> +<%helpers:raw_longhand name="color" need_clone="True" animatable="True"> use cssparser::Color as CSSParserColor; use cssparser::RGBA; use values::specified::{CSSColor, CSSRGBA}; diff --git a/components/style/properties/longhand/column.mako.rs b/components/style/properties/longhand/column.mako.rs index 6e8975639af..91e1fc8fc3a 100644 --- a/components/style/properties/longhand/column.mako.rs +++ b/components/style/properties/longhand/column.mako.rs @@ -6,7 +6,8 @@ <% data.new_style_struct("Column", inherited=False) %> -<%helpers:longhand name="column-width" experimental="True"> +// FIXME: This prop should be animatable. +<%helpers:longhand name="column-width" experimental="True" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -70,7 +71,8 @@ } -<%helpers:longhand name="column-count" experimental="True"> +// FIXME: This prop should be animatable. +<%helpers:longhand name="column-count" experimental="True" animatable="False"> use cssparser::ToCss; use std::fmt; @@ -137,7 +139,8 @@ } -<%helpers:longhand name="column-gap" experimental="True"> +// FIXME: This prop should be animatable. +<%helpers:longhand name="column-gap" experimental="True" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; diff --git a/components/style/properties/longhand/counters.mako.rs b/components/style/properties/longhand/counters.mako.rs index 6dd97da6032..93fdb4bdc2c 100644 --- a/components/style/properties/longhand/counters.mako.rs +++ b/components/style/properties/longhand/counters.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("Counters", inherited=False, gecko_name="Content") %> -<%helpers:longhand name="content"> +<%helpers:longhand name="content" animatable="False"> use cssparser::Token; use std::ascii::AsciiExt; use values::computed::ComputedValueAsSpecified; @@ -171,7 +171,7 @@ } -<%helpers:longhand name="counter-increment"> +<%helpers:longhand name="counter-increment" animatable="False"> use std::fmt; use super::content; use values::computed::ComputedValueAsSpecified; @@ -241,7 +241,7 @@ } -<%helpers:longhand name="counter-reset"> +<%helpers:longhand name="counter-reset" animatable="False"> pub use super::counter_increment::{SpecifiedValue, computed_value, get_initial_value}; use super::counter_increment::{parse_common}; diff --git a/components/style/properties/longhand/effects.mako.rs b/components/style/properties/longhand/effects.mako.rs index 8781a4b553b..4fa881c93fd 100644 --- a/components/style/properties/longhand/effects.mako.rs +++ b/components/style/properties/longhand/effects.mako.rs @@ -9,9 +9,10 @@ ${helpers.predefined_type("opacity", "Opacity", - "1.0")} + "1.0", + animatable=True)} -<%helpers:longhand name="box-shadow"> +<%helpers:longhand name="box-shadow" animatable="True"> use cssparser::{self, ToCss}; use std::fmt; use values::LocalToCss; @@ -223,7 +224,8 @@ ${helpers.predefined_type("opacity", } -<%helpers:longhand name="clip"> +// FIXME: This prop should be animatable +<%helpers:longhand name="clip" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -394,7 +396,8 @@ ${helpers.predefined_type("opacity", } -<%helpers:longhand name="filter"> +// FIXME: This prop should be animatable +<%helpers:longhand name="filter" animatable="False"> //pub use self::computed_value::T as SpecifiedValue; use cssparser::ToCss; use std::fmt; @@ -630,7 +633,7 @@ ${helpers.predefined_type("opacity", } -<%helpers:longhand name="transform"> +<%helpers:longhand name="transform" animatable="True"> use app_units::Au; use values::CSSFloat; @@ -1174,13 +1177,20 @@ pub fn parse_origin(_: &ParserContext, input: &mut Parser) -> Result +<%helpers:longhand name="transform-origin" animatable="True"> use app_units::Au; use values::LocalToCss; use values::specified::{Length, LengthOrPercentage, Percentage}; @@ -1261,10 +1271,12 @@ ${helpers.single_keyword("transform-style", "auto flat preserve-3d")} ${helpers.predefined_type("perspective", - "LengthOrNone", - "computed::LengthOrNone::None")} + "LengthOrNone", + "computed::LengthOrNone::None", + animatable=True)} -<%helpers:longhand name="perspective-origin"> +// FIXME: This prop should be animatable +<%helpers:longhand name="perspective-origin" animatable="False"> use values::specified::{LengthOrPercentage, Percentage}; use cssparser::ToCss; @@ -1337,6 +1349,7 @@ ${helpers.predefined_type("perspective", ${helpers.single_keyword("mix-blend-mode", - """normal multiply screen overlay darken lighten color-dodge - color-burn hard-light soft-light difference exclusion hue - saturation color luminosity""", gecko_constant_prefix="NS_STYLE_BLEND")} + """normal multiply screen overlay darken lighten color-dodge + color-burn hard-light soft-light difference exclusion hue + saturation color luminosity""", gecko_constant_prefix="NS_STYLE_BLEND", + animatable=False)} diff --git a/components/style/properties/longhand/font.mako.rs b/components/style/properties/longhand/font.mako.rs index cf63c578adb..601097a604c 100644 --- a/components/style/properties/longhand/font.mako.rs +++ b/components/style/properties/longhand/font.mako.rs @@ -8,7 +8,7 @@ <% data.new_style_struct("Font", inherited=True, additional_methods=[Method("compute_font_hash", is_mut=True)]) %> -<%helpers:longhand name="font-family"> +<%helpers:longhand name="font-family" animatable="False"> use self::computed_value::FontFamily; use values::computed::ComputedValueAsSpecified; pub use self::computed_value::T as SpecifiedValue; @@ -117,10 +117,15 @@ -${helpers.single_keyword("font-style", "normal italic oblique", gecko_constant_prefix="NS_FONT_STYLE")} -${helpers.single_keyword("font-variant", "normal small-caps")} +${helpers.single_keyword("font-style", + "normal italic oblique", + gecko_constant_prefix="NS_FONT_STYLE", + animatable=False)} +${helpers.single_keyword("font-variant", + "normal small-caps", + animatable=False)} -<%helpers:longhand name="font-weight" need_clone="True"> +<%helpers:longhand name="font-weight" need_clone="True" animatable="True"> use cssparser::ToCss; use std::fmt; @@ -241,7 +246,7 @@ ${helpers.single_keyword("font-variant", "normal small-caps")} } -<%helpers:longhand name="font-size" need_clone="True"> +<%helpers:longhand name="font-size" need_clone="True" animatable="True"> use app_units::Au; use cssparser::ToCss; use std::fmt; @@ -307,8 +312,14 @@ ${helpers.single_keyword("font-variant", "normal small-caps")} } +// FIXME: This prop should be animatable ${helpers.single_keyword("font-stretch", - "normal ultra-condensed extra-condensed condensed semi-condensed semi-expanded \ - expanded extra-expanded ultra-expanded")} + "normal ultra-condensed extra-condensed condensed \ + semi-condensed semi-expanded expanded extra-expanded \ + ultra-expanded", + animatable=False)} -${helpers.single_keyword("font-kerning", "auto none normal", products="gecko")} +${helpers.single_keyword("font-kerning", + "auto none normal", + products="gecko", + animatable=False)} diff --git a/components/style/properties/longhand/inherited_box.mako.rs b/components/style/properties/longhand/inherited_box.mako.rs index 482780f3a6e..65fac030ec2 100644 --- a/components/style/properties/longhand/inherited_box.mako.rs +++ b/components/style/properties/longhand/inherited_box.mako.rs @@ -6,20 +6,22 @@ <% data.new_style_struct("InheritedBox", inherited=True, gecko_name="Visibility") %> -${helpers.single_keyword("direction", "ltr rtl", need_clone=True)} +${helpers.single_keyword("direction", "ltr rtl", need_clone=True, animatable=False)} // TODO: collapse. Well, do tables first. ${helpers.single_keyword("visibility", "visible hidden", extra_gecko_values="collapse", - gecko_ffi_name="mVisible")} + gecko_ffi_name="mVisible", + animatable=True)} // CSS Writing Modes Level 3 // http://dev.w3.org/csswg/css-writing-modes/ ${helpers.single_keyword("writing-mode", "horizontal-tb vertical-rl vertical-lr", experimental=True, - need_clone=True)} + need_clone=True, + animatable=False)} // FIXME(SimonSapin): Add 'mixed' and 'upright' (needs vertical text support) // FIXME(SimonSapin): initial (first) value should be 'mixed', when that's implemented @@ -29,13 +31,16 @@ ${helpers.single_keyword("text-orientation", experimental=True, need_clone=True, extra_gecko_values="mixed upright", - extra_servo_values="sideways-right sideways-left")} + extra_servo_values="sideways-right sideways-left", + animatable=False)} // CSS Color Module Level 4 // https://drafts.csswg.org/css-color/ -${helpers.single_keyword("color-adjust", "economy exact", products="gecko")} +${helpers.single_keyword("color-adjust", + "economy exact", products="gecko", + animatable=False)} -<%helpers:longhand name="image-rendering"> +<%helpers:longhand name="image-rendering" animatable="False"> pub mod computed_value { use cssparser::ToCss; use std::fmt; @@ -92,7 +97,10 @@ ${helpers.single_keyword("color-adjust", "economy exact", products="gecko")} // Used in the bottom-up flow construction traversal to avoid constructing flows for // descendants of nodes with `display: none`. -<%helpers:longhand name="-servo-under-display-none" derived_from="display" products="servo"> +<%helpers:longhand name="-servo-under-display-none" + derived_from="display" + products="servo" + animatable="False"> use cssparser::ToCss; use std::fmt; use values::computed::ComputedValueAsSpecified; diff --git a/components/style/properties/longhand/inherited_svg.mako.rs b/components/style/properties/longhand/inherited_svg.mako.rs index ca0c959a3ca..59e21e60ce5 100644 --- a/components/style/properties/longhand/inherited_svg.mako.rs +++ b/components/style/properties/longhand/inherited_svg.mako.rs @@ -10,36 +10,52 @@ inherited=True, gecko_name="SVG") %> +// TODO(emilio): Should some of these types be animatable? + // Section 10 - Text -${helpers.single_keyword("text-anchor", "start middle end", products="gecko")} +${helpers.single_keyword("text-anchor", + "start middle end", + products="gecko", + animatable=False)} // Section 11 - Painting: Filling, Stroking and Marker Symbols -${helpers.single_keyword("color-interpolation", "auto sRGB linearRGB", products="gecko")} - -${helpers.single_keyword("color-interpolation-filters", +${helpers.single_keyword("color-interpolation", "auto sRGB linearRGB", products="gecko", - gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION")} + animatable=False)} -${helpers.predefined_type("fill-opacity", "Opacity", "1.0", products="gecko")} +${helpers.single_keyword("color-interpolation-filters", "auto sRGB linearRGB", + products="gecko", + gecko_constant_prefix="NS_STYLE_COLOR_INTERPOLATION", + animatable=False)} -${helpers.single_keyword("fill-rule", "nonzero evenodd", products="gecko")} +${helpers.predefined_type("fill-opacity", "Opacity", "1.0", + products="gecko", animatable=False)} + +${helpers.single_keyword("fill-rule", "nonzero evenodd", + products="gecko", animatable=False)} ${helpers.single_keyword("shape-rendering", "auto optimizeSpeed crispEdges geometricPrecision", - products="gecko")} + products="gecko", + animatable=False)} -${helpers.single_keyword("stroke-linecap", "butt round square", products="gecko")} +${helpers.single_keyword("stroke-linecap", "butt round square", + products="gecko", animatable=False)} -${helpers.single_keyword("stroke-linejoin", "miter round bevel", products="gecko")} +${helpers.single_keyword("stroke-linejoin", "miter round bevel", + products="gecko", animatable=False)} -${helpers.predefined_type("stroke-miterlimit", "Number", "4.0", "parse_at_least_one", - products="gecko")} +${helpers.predefined_type("stroke-miterlimit", "Number", "4.0", + "parse_at_least_one", products="gecko", + animatable=False)} -${helpers.predefined_type("stroke-opacity", "Opacity", "1.0", products="gecko")} +${helpers.predefined_type("stroke-opacity", "Opacity", "1.0", + products="gecko", animatable=False)} // Section 14 - Clipping, Masking and Compositing ${helpers.single_keyword("clip-rule", "nonzero evenodd", products="gecko", - gecko_constant_prefix="NS_STYLE_FILL_RULE")} + gecko_constant_prefix="NS_STYLE_FILL_RULE", + animatable=False)} diff --git a/components/style/properties/longhand/inherited_table.mako.rs b/components/style/properties/longhand/inherited_table.mako.rs index 7a11caae0b4..dd9b468ee1f 100644 --- a/components/style/properties/longhand/inherited_table.mako.rs +++ b/components/style/properties/longhand/inherited_table.mako.rs @@ -6,11 +6,17 @@ <% data.new_style_struct("InheritedTable", inherited=True, gecko_name="TableBorder") %> -${helpers.single_keyword("border-collapse", "separate collapse", gecko_constant_prefix="NS_STYLE_BORDER")} -${helpers.single_keyword("empty-cells", "show hide", gecko_constant_prefix="NS_STYLE_TABLE_EMPTY_CELLS")} -${helpers.single_keyword("caption-side", "top bottom", extra_gecko_values="right left top-outside bottom-outside")} +${helpers.single_keyword("border-collapse", "separate collapse", + gecko_constant_prefix="NS_STYLE_BORDER", + animatable=False)} +${helpers.single_keyword("empty-cells", "show hide", + gecko_constant_prefix="NS_STYLE_TABLE_EMPTY_CELLS", + animatable=False)} +${helpers.single_keyword("caption-side", "top bottom", + extra_gecko_values="right left top-outside bottom-outside", + animatable=False)} -<%helpers:longhand name="border-spacing"> +<%helpers:longhand name="border-spacing" animatable="False"> use app_units::Au; use values::LocalToCss; diff --git a/components/style/properties/longhand/inherited_text.mako.rs b/components/style/properties/longhand/inherited_text.mako.rs index b088e9bf63d..d902b62739a 100644 --- a/components/style/properties/longhand/inherited_text.mako.rs +++ b/components/style/properties/longhand/inherited_text.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("InheritedText", inherited=True, gecko_name="Text") %> -<%helpers:longhand name="line-height"> +<%helpers:longhand name="line-height" animatable="True"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -120,7 +120,7 @@ } -<%helpers:longhand name="text-align"> +<%helpers:longhand name="text-align" animatable="False"> pub use self::computed_value::T as SpecifiedValue; use values::computed::ComputedValueAsSpecified; impl ComputedValueAsSpecified for SpecifiedValue {} @@ -179,7 +179,8 @@ } -<%helpers:longhand name="letter-spacing"> +// FIXME: This prop should be animatable. +<%helpers:longhand name="letter-spacing" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -243,7 +244,7 @@ } -<%helpers:longhand name="word-spacing"> +<%helpers:longhand name="word-spacing" animatable="False"> use cssparser::ToCss; use std::fmt; use values::LocalToCss; @@ -309,27 +310,33 @@ ${helpers.predefined_type("text-indent", "LengthOrPercentage", - "computed::LengthOrPercentage::Length(Au(0))")} + "computed::LengthOrPercentage::Length(Au(0))", + animatable=True)} // Also known as "word-wrap" (which is more popular because of IE), but this is the preferred // name per CSS-TEXT 6.2. ${helpers.single_keyword("overflow-wrap", "normal break-word", - gecko_constant_prefix="NS_STYLE_OVERFLOWWRAP")} + gecko_constant_prefix="NS_STYLE_OVERFLOWWRAP", + animatable=False)} // TODO(pcwalton): Support `word-break: keep-all` once we have better CJK support. ${helpers.single_keyword("word-break", "normal break-all", extra_gecko_values="keep-all", - gecko_constant_prefix="NS_STYLE_WORDBREAK")} + gecko_constant_prefix="NS_STYLE_WORDBREAK", + animatable=False)} // TODO(pcwalton): Support `text-justify: distribute`. ${helpers.single_keyword("text-justify", "auto none inter-word", - products="servo")} + products="servo", + animatable=False)} <%helpers:longhand name="-servo-text-decorations-in-effect" - derived_from="display text-decoration" need_clone="True" products="servo"> + derived_from="display text-decoration" + need_clone="True" products="servo" + animatable="False"> use cssparser::{RGBA, ToCss}; use std::fmt; @@ -410,8 +417,10 @@ ${helpers.single_keyword("text-justify", } -<%helpers:single_keyword_computed name="white-space" values="normal pre nowrap pre-wrap pre-line", - gecko_constant_prefix="NS_STYLE_WHITESPACE"> +<%helpers:single_keyword_computed name="white-space" + values="normal pre nowrap pre-wrap pre-line" + gecko_constant_prefix="NS_STYLE_WHITESPACE" + animatable="False"> use values::computed::ComputedValueAsSpecified; impl ComputedValueAsSpecified for SpecifiedValue {} @@ -448,7 +457,7 @@ ${helpers.single_keyword("text-justify", } -<%helpers:longhand name="text-shadow"> +<%helpers:longhand name="text-shadow" animatable="True"> use cssparser::{self, ToCss}; use std::fmt; use values::LocalToCss; @@ -635,16 +644,22 @@ ${helpers.single_keyword("text-justify", // TODO(pcwalton): `full-width` ${helpers.single_keyword("text-transform", "none capitalize uppercase lowercase", - extra_gecko_values="full-width")} + extra_gecko_values="full-width", + animatable=False)} -${helpers.single_keyword("text-rendering", "auto optimizespeed optimizelegibility geometricprecision")} +${helpers.single_keyword("text-rendering", + "auto optimizespeed optimizelegibility geometricprecision", + animatable=False)} // CSS Text Module Level 3 // https://www.w3.org/TR/css-text-3/ -${helpers.single_keyword("hyphens", "none manual auto", products="gecko")} +${helpers.single_keyword("hyphens", "none manual auto", + products="gecko", animatable=False)} // CSS Ruby Layout Module Level 1 // https://www.w3.org/TR/css-ruby-1/ -${helpers.single_keyword("ruby-align", "start center space-between space-around", products="gecko")} +${helpers.single_keyword("ruby-align", "start center space-between space-around", + products="gecko", animatable=False)} -${helpers.single_keyword("ruby-position", "over under", products="gecko")} +${helpers.single_keyword("ruby-position", "over under", + products="gecko", animatable=False)} diff --git a/components/style/properties/longhand/list.mako.rs b/components/style/properties/longhand/list.mako.rs index 298f387cf13..c5d5c6be5b0 100644 --- a/components/style/properties/longhand/list.mako.rs +++ b/components/style/properties/longhand/list.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("List", inherited=True) %> -${helpers.single_keyword("list-style-position", "outside inside")} +${helpers.single_keyword("list-style-position", "outside inside", animatable=False)} // TODO(pcwalton): Implement the full set of counter styles per CSS-COUNTER-STYLES [1] 6.1: // @@ -23,9 +23,10 @@ ${helpers.single_keyword("list-style-type", """ myanmar oriya persian telugu thai tibetan cjk-earthly-branch cjk-heavenly-stem lower-greek hiragana hiragana-iroha katakana katakana-iroha""", - gecko_constant_prefix="NS_STYLE_LIST_STYLE")} + gecko_constant_prefix="NS_STYLE_LIST_STYLE", + animatable=False)} -<%helpers:longhand name="list-style-image"> +<%helpers:longhand name="list-style-image" animatable="False"> use cssparser::{ToCss, Token}; use std::fmt; use url::Url; @@ -92,7 +93,7 @@ ${helpers.single_keyword("list-style-type", """ } -<%helpers:longhand name="quotes"> +<%helpers:longhand name="quotes" animatable="False"> use std::borrow::Cow; use std::fmt; use values::computed::ComputedValueAsSpecified; diff --git a/components/style/properties/longhand/margin.mako.rs b/components/style/properties/longhand/margin.mako.rs index a9e4e477bbd..32f5243d745 100644 --- a/components/style/properties/longhand/margin.mako.rs +++ b/components/style/properties/longhand/margin.mako.rs @@ -8,5 +8,6 @@ % for side in ["top", "right", "bottom", "left"]: ${helpers.predefined_type("margin-" + side, "LengthOrPercentageOrAuto", - "computed::LengthOrPercentageOrAuto::Length(Au(0))")} + "computed::LengthOrPercentageOrAuto::Length(Au(0))", + animatable=True)} % endfor diff --git a/components/style/properties/longhand/outline.mako.rs b/components/style/properties/longhand/outline.mako.rs index cfe24868d29..0869df7a657 100644 --- a/components/style/properties/longhand/outline.mako.rs +++ b/components/style/properties/longhand/outline.mako.rs @@ -10,9 +10,10 @@ additional_methods=[Method("outline_has_nonzero_width", "bool")]) %> // TODO(pcwalton): `invert` -${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::CurrentColor")} +${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::CurrentColor", + animatable=True)} -<%helpers:longhand name="outline-style" need_clone="True"> +<%helpers:longhand name="outline-style" need_clone="True" animatable="False"> pub use values::specified::BorderStyle as SpecifiedValue; pub fn get_initial_value() -> SpecifiedValue { SpecifiedValue::none } pub mod computed_value { @@ -26,7 +27,7 @@ ${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::Curr } -<%helpers:longhand name="outline-width"> +<%helpers:longhand name="outline-width" animatable="True"> use app_units::Au; use cssparser::ToCss; use std::fmt; @@ -60,10 +61,12 @@ ${helpers.predefined_type("outline-color", "CSSColor", "::cssparser::Color::Curr // The -moz-outline-radius-* properties are non-standard and not on a standards track. +// TODO: Should they animate? % for corner in ["topleft", "topright", "bottomright", "bottomleft"]: ${helpers.predefined_type("-moz-outline-radius-" + corner, "BorderRadiusSize", "computed::BorderRadiusSize::zero()", - "parse", products="gecko")} + "parse", products="gecko", + animatable=False)} % endfor -${helpers.predefined_type("outline-offset", "Length", "Au(0)")} +${helpers.predefined_type("outline-offset", "Length", "Au(0)", animatable=True)} diff --git a/components/style/properties/longhand/padding.mako.rs b/components/style/properties/longhand/padding.mako.rs index f5448e393de..f022ba58878 100644 --- a/components/style/properties/longhand/padding.mako.rs +++ b/components/style/properties/longhand/padding.mako.rs @@ -9,5 +9,6 @@ % for side in ["top", "right", "bottom", "left"]: ${helpers.predefined_type("padding-" + side, "LengthOrPercentage", "computed::LengthOrPercentage::Length(Au(0))", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} % endfor diff --git a/components/style/properties/longhand/pointing.mako.rs b/components/style/properties/longhand/pointing.mako.rs index c2ad1502a9e..2394e2f3bee 100644 --- a/components/style/properties/longhand/pointing.mako.rs +++ b/components/style/properties/longhand/pointing.mako.rs @@ -6,7 +6,7 @@ <% data.new_style_struct("Pointing", inherited=True, gecko_name="UserInterface") %> -<%helpers:longhand name="cursor"> +<%helpers:longhand name="cursor" animatable="False"> pub use self::computed_value::T as SpecifiedValue; use values::computed::ComputedValueAsSpecified; @@ -54,16 +54,20 @@ // NB: `pointer-events: auto` (and use of `pointer-events` in anything that isn't SVG, in fact) // is nonstandard, slated for CSS4-UI. // TODO(pcwalton): SVG-only values. -${helpers.single_keyword("pointer-events", "auto none")} +${helpers.single_keyword("pointer-events", "auto none", animatable=False)} -${helpers.single_keyword("-moz-user-input", "none enabled disabled", products="gecko", - gecko_ffi_name="mUserInput", gecko_constant_prefix="NS_STYLE_USER_INPUT")} +${helpers.single_keyword("-moz-user-input", "none enabled disabled", + products="gecko", gecko_ffi_name="mUserInput", + gecko_constant_prefix="NS_STYLE_USER_INPUT", + animatable=False)} -${helpers.single_keyword("-moz-user-modify", "read-only read-write write-only", products="gecko", - gecko_ffi_name="mUserModify", gecko_constant_prefix="NS_STYLE_USER_MODIFY")} +${helpers.single_keyword("-moz-user-modify", "read-only read-write write-only", + products="gecko", gecko_ffi_name="mUserModify", + gecko_constant_prefix="NS_STYLE_USER_MODIFY", + animatable=False)} ${helpers.single_keyword("-moz-user-focus", "ignore normal select-after select-before select-menu select-same select-all none", - products="gecko", - gecko_ffi_name="mUserFocus", - gecko_constant_prefix="NS_STYLE_USER_FOCUS")} + products="gecko", gecko_ffi_name="mUserFocus", + gecko_constant_prefix="NS_STYLE_USER_FOCUS", + animatable=False)} diff --git a/components/style/properties/longhand/position.mako.rs b/components/style/properties/longhand/position.mako.rs index 5ce0b83f87d..a143dab44c5 100644 --- a/components/style/properties/longhand/position.mako.rs +++ b/components/style/properties/longhand/position.mako.rs @@ -8,10 +8,11 @@ % for side in ["top", "right", "bottom", "left"]: ${helpers.predefined_type(side, "LengthOrPercentageOrAuto", - "computed::LengthOrPercentageOrAuto::Auto")} + "computed::LengthOrPercentageOrAuto::Auto", + animatable=True)} % endfor -<%helpers:longhand name="z-index"> +<%helpers:longhand name="z-index" animatable="True"> use values::computed::ComputedValueAsSpecified; impl ComputedValueAsSpecified for SpecifiedValue {} @@ -62,39 +63,49 @@ // http://www.w3.org/TR/css3-flexbox/ // Flex container properties -${helpers.single_keyword("flex-direction", "row row-reverse column column-reverse", experimental=True)} +${helpers.single_keyword("flex-direction", "row row-reverse column column-reverse", + experimental=True, animatable=False)} -${helpers.single_keyword("flex-wrap", "nowrap wrap wrap-reverse", experimental=True)} +${helpers.single_keyword("flex-wrap", "nowrap wrap wrap-reverse", + experimental=True, animatable=False)} // FIXME(stshine): The type of 'justify-content' and 'align-content' is uint16_t in gecko // FIXME(stshine): Its higher bytes are used to store fallback value. Disable them in geckolib for now ${helpers.single_keyword("justify-content", "flex-start flex-end center space-between space-around", experimental=True, gecko_constant_prefix="NS_STYLE_JUSTIFY", - products="servo")} + products="servo", + animatable=False)} ${helpers.single_keyword("align-items", "stretch flex-start flex-end center baseline", experimental=True, need_clone=True, - gecko_constant_prefix="NS_STYLE_ALIGN")} + gecko_constant_prefix="NS_STYLE_ALIGN", + animatable=False)} ${helpers.single_keyword("align-content", "stretch flex-start flex-end center space-between space-around", experimental=True, gecko_constant_prefix="NS_STYLE_ALIGN", - products="servo")} + products="servo", + animatable=False)} // Flex item properties -${helpers.predefined_type("flex-grow", "Number", "0.0", "parse_non_negative", experimental=True)} +${helpers.predefined_type("flex-grow", "Number", + "0.0", "parse_non_negative", + experimental=True, animatable=True)} -${helpers.predefined_type("flex-shrink", "Number", "1.0", "parse_non_negative", experimental=True)} +${helpers.predefined_type("flex-shrink", "Number", + "1.0", "parse_non_negative", + experimental=True, animatable=True)} ${helpers.single_keyword("align-self", "auto stretch flex-start flex-end center baseline", experimental=True, need_clone=True, - gecko_constant_prefix="NS_STYLE_ALIGN")} + gecko_constant_prefix="NS_STYLE_ALIGN", + animatable=False)} // https://drafts.csswg.org/css-flexbox/#propdef-order -<%helpers:longhand name="order"> +<%helpers:longhand name="order" animatable="True"> use values::computed::ComputedValueAsSpecified; impl ComputedValueAsSpecified for SpecifiedValue {} @@ -115,41 +126,53 @@ ${helpers.single_keyword("align-self", "auto stretch flex-start flex-end center } +// FIXME: This property should be animatable. ${helpers.predefined_type("flex-basis", "LengthOrPercentageOrAutoOrContent", - "computed::LengthOrPercentageOrAutoOrContent::Auto")} + "computed::LengthOrPercentageOrAutoOrContent::Auto", + animatable=False)} ${helpers.predefined_type("width", "LengthOrPercentageOrAuto", "computed::LengthOrPercentageOrAuto::Auto", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} ${helpers.predefined_type("height", "LengthOrPercentageOrAuto", "computed::LengthOrPercentageOrAuto::Auto", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} ${helpers.predefined_type("min-width", "LengthOrPercentage", "computed::LengthOrPercentage::Length(Au(0))", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} + ${helpers.predefined_type("max-width", "LengthOrPercentageOrNone", "computed::LengthOrPercentageOrNone::None", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} ${helpers.predefined_type("min-height", "LengthOrPercentage", "computed::LengthOrPercentage::Length(Au(0))", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} + ${helpers.predefined_type("max-height", "LengthOrPercentageOrNone", "computed::LengthOrPercentageOrNone::None", - "parse_non_negative")} + "parse_non_negative", + animatable=True)} ${helpers.single_keyword("box-sizing", - "content-box border-box")} + "content-box border-box", + animatable=False)} // CSS Image Values and Replaced Content Module Level 3 // https://drafts.csswg.org/css-images-3/ -${helpers.single_keyword("object-fit", "fill contain cover none scale-down", products="gecko")} +${helpers.single_keyword("object-fit", "fill contain cover none scale-down", + products="gecko", animatable=False)} diff --git a/components/style/properties/longhand/svg.mako.rs b/components/style/properties/longhand/svg.mako.rs index a9921e46d7d..49dd2bdbdac 100644 --- a/components/style/properties/longhand/svg.mako.rs +++ b/components/style/properties/longhand/svg.mako.rs @@ -6,36 +6,46 @@ <% data.new_style_struct("SVG", inherited=False, gecko_name="SVGReset") %> +// TODO: Which of these should be animatable properties? ${helpers.single_keyword("dominant-baseline", """auto use-script no-change reset-size ideographic alphabetic hanging mathematical central middle text-after-edge text-before-edge""", - products="gecko")} + products="gecko", + animatable=False)} -${helpers.single_keyword("vector-effect", "none non-scaling-stroke", products="gecko")} +${helpers.single_keyword("vector-effect", "none non-scaling-stroke", + products="gecko", animatable=False)} // Section 13 - Gradients and Patterns ${helpers.predefined_type( "stop-color", "CSSColor", "CSSParserColor::RGBA(RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 })", - products="gecko")} + products="gecko", + animatable=False)} -${helpers.predefined_type("stop-opacity", "Opacity", "1.0", products="gecko")} +${helpers.predefined_type("stop-opacity", "Opacity", "1.0", + products="gecko", + animatable=False)} // Section 15 - Filter Effects ${helpers.predefined_type( "flood-color", "CSSColor", "CSSParserColor::RGBA(RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 })", - products="gecko")} + products="gecko", + animatable=False)} -${helpers.predefined_type("flood-opacity", "Opacity", "1.0", products="gecko")} +${helpers.predefined_type("flood-opacity", "Opacity", + "1.0", products="gecko", animatable=False)} ${helpers.predefined_type( "lighting-color", "CSSColor", "CSSParserColor::RGBA(RGBA { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 })", - products="gecko")} + products="gecko", + animatable=False)} // CSS Masking Module Level 1 // https://www.w3.org/TR/css-masking-1/ -${helpers.single_keyword("mask-type", "luminance alpha", products="gecko")} +${helpers.single_keyword("mask-type", "luminance alpha", + products="gecko", animatable=False)} diff --git a/components/style/properties/longhand/table.mako.rs b/components/style/properties/longhand/table.mako.rs index cfb3050c06d..a83d25c7c29 100644 --- a/components/style/properties/longhand/table.mako.rs +++ b/components/style/properties/longhand/table.mako.rs @@ -6,4 +6,5 @@ <% data.new_style_struct("Table", inherited=False) %> -${helpers.single_keyword("table-layout", "auto fixed", gecko_ffi_name="mLayoutStrategy")} +${helpers.single_keyword("table-layout", "auto fixed", + gecko_ffi_name="mLayoutStrategy", animatable=False)} diff --git a/components/style/properties/longhand/text.mako.rs b/components/style/properties/longhand/text.mako.rs index 2e34a0c6243..50a4ef3476c 100644 --- a/components/style/properties/longhand/text.mako.rs +++ b/components/style/properties/longhand/text.mako.rs @@ -12,12 +12,16 @@ Method("has_overline", "bool"), Method("has_line_through", "bool")]) %> -${helpers.single_keyword("text-overflow", "clip ellipsis")} +${helpers.single_keyword("text-overflow", "clip ellipsis", animatable=False)} -${helpers.single_keyword("unicode-bidi", "normal embed isolate bidi-override isolate-override plaintext")} +${helpers.single_keyword("unicode-bidi", + "normal embed isolate bidi-override isolate-override plaintext", + animatable=False)} +// FIXME: This prop should be animatable. <%helpers:longhand name="${'text-decoration' if product == 'servo' else 'text-decoration-line'}" - custom_cascade="${product == 'servo'}"> + custom_cascade="${product == 'servo'}" + animatable="False"> use cssparser::ToCss; use std::fmt; use values::computed::ComputedValueAsSpecified; @@ -116,9 +120,11 @@ ${helpers.single_keyword("unicode-bidi", "normal embed isolate bidi-override iso ${helpers.single_keyword("text-decoration-style", "solid double dotted dashed wavy -moz-none", - products="gecko")} + products="gecko", + animatable=False)} ${helpers.predefined_type( "text-decoration-color", "CSSColor", "CSSParserColor::RGBA(RGBA { red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0 })", - products="gecko")} + products="gecko", + animatable=True)} diff --git a/components/style/properties/longhand/ui.mako.rs b/components/style/properties/longhand/ui.mako.rs index 6e2fdb345fc..b78222ab560 100644 --- a/components/style/properties/longhand/ui.mako.rs +++ b/components/style/properties/longhand/ui.mako.rs @@ -9,8 +9,11 @@ // https://drafts.csswg.org/css-ui-3/ <% data.new_style_struct("UI", inherited=False, gecko_name="UIReset") %> -${helpers.single_keyword("ime-mode", "normal auto active disabled inactive", products="gecko", - gecko_ffi_name="mIMEMode")} +${helpers.single_keyword("ime-mode", "normal auto active disabled inactive", + products="gecko", gecko_ffi_name="mIMEMode", + animatable=False)} ${helpers.single_keyword("-moz-user-select", "auto text none all", products="gecko", - gecko_ffi_name="mUserSelect", gecko_constant_prefix="NS_STYLE_USER_SELECT")} + gecko_ffi_name="mUserSelect", + gecko_constant_prefix="NS_STYLE_USER_SELECT", + animatable=False)} diff --git a/components/style/properties/longhand/xul.mako.rs b/components/style/properties/longhand/xul.mako.rs index cb5db00717c..06af2c2b1a4 100644 --- a/components/style/properties/longhand/xul.mako.rs +++ b/components/style/properties/longhand/xul.mako.rs @@ -8,8 +8,11 @@ // Non-standard properties that Gecko uses for XUL elements. <% data.new_style_struct("XUL", inherited=False) %> -${helpers.single_keyword("-moz-box-align", "stretch start center baseline end", products="gecko", - gecko_ffi_name="mBoxAlign", gecko_constant_prefix="NS_STYLE_BOX_ALIGN")} +${helpers.single_keyword("-moz-box-align", "stretch start center baseline end", + products="gecko", gecko_ffi_name="mBoxAlign", + gecko_constant_prefix="NS_STYLE_BOX_ALIGN", + animatable=False)} -${helpers.predefined_type("-moz-box-flex", "Number", "0.0", "parse_non_negative", products="gecko", - gecko_ffi_name="mBoxFlex")} +${helpers.predefined_type("-moz-box-flex", "Number", "0.0", "parse_non_negative", + products="gecko", gecko_ffi_name="mBoxFlex", + animatable=False)} From 392f243ca7dd08a34da4ca15dfc5596f69adf4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Tue, 28 Jun 2016 15:07:14 +0000 Subject: [PATCH 28/28] style: Shadow declarations of no-op animations Although we don't generate a "computed" keyframe animation, we're required to shadow others. --- components/style/selector_matching.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index fc9c2eb6600..35a38d5ed14 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -258,6 +258,11 @@ impl Stylist { debug!("Found valid keyframe animation: {:?}", animation); self.animations.insert(keyframes_rule.name.clone(), animation); + } else { + // If there's a valid keyframes rule, even if it doesn't + // produce an animation, should shadow other animations + // with the same name. + self.animations.remove(&keyframes_rule.name); } } // We don't care about any other rule.