diff options
10 files changed, 249 insertions, 59 deletions
diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index 02864d39a77..22044bcf17f 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -331,6 +331,7 @@ partial interface CSSStyleDeclaration { [SetterThrows, TreatNullAs=EmptyString] attribute DOMString alignSelf; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString align-self; + [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-name; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animationName; [SetterThrows, TreatNullAs=EmptyString] attribute DOMString animation-duration; diff --git a/components/style/animation.rs b/components/style/animation.rs index 87184928eb8..1f0cbcaefde 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -626,7 +626,9 @@ where Impl: SelectorImplExt, // NB: The spec says that the timing function can be overwritten // from the keyframe style. let mut timing_function = style.get_box().animation_timing_function_mod(index); - if from_style.get_box().animation_timing_function_count() != 0 { + if last_keyframe.declared_timing_function { + // NB: animation_timing_function can never be empty, always has + // at least the default value (`ease`). timing_function = from_style.get_box().animation_timing_function_at(0); } diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 82dc4c9200f..a6e667e9282 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -114,15 +114,33 @@ pub struct KeyframesStep { /// Declarations that will determine the final style during the step, or /// `ComputedValues` if this is an autogenerated step. pub value: KeyframesStepValue, + /// Wether a animation-timing-function declaration exists in the list of + /// declarations. + /// + /// This is used to know when to override the keyframe animation style. + pub declared_timing_function: bool, } impl KeyframesStep { #[inline] fn new(percentage: KeyframePercentage, value: KeyframesStepValue) -> Self { + let declared_timing_function = match value { + KeyframesStepValue::Declarations(ref declarations) => { + declarations.iter().any(|prop_decl| { + match *prop_decl { + PropertyDeclaration::AnimationTimingFunction(..) => true, + _ => false, + } + }) + } + _ => false, + }; + KeyframesStep { start_percentage: percentage, value: value, + declared_timing_function: declared_timing_function, } } } diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index 81dff023605..dae60af0860 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -235,11 +235,21 @@ } } + pub use self::computed_value::${to_camel_case(name)} as SingleSpecifiedValue; + + #[inline] + pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue, ()> { + SingleSpecifiedValue::parse(input) + } + #[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])} - ]) + computed_value::T(vec![get_initial_single_value()]) + } + + #[inline] + pub fn get_initial_single_value() -> SingleSpecifiedValue { + SingleSpecifiedValue::${to_rust_ident(values.split()[0])} } #[inline] diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 381ca4df1d9..3a02b6c656d 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -490,8 +490,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> { if let Ok(function_name) = input.try(|input| input.expect_function()) { - return match_ignore_ascii_case! { - function_name, + return match_ignore_ascii_case! { function_name, "cubic-bezier" => { let (mut p1x, mut p1y, mut p2x, mut p2y) = (0.0, 0.0, 0.0, 0.0); try!(input.parse_nested_block(|input| { @@ -508,16 +507,17 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", Ok(TransitionTimingFunction::CubicBezier(p1, p2)) }, "steps" => { - let (mut step_count, mut start_end) = (0, computed_value::StartEnd::Start); + let (mut step_count, mut start_end) = (0, computed_value::StartEnd::End); try!(input.parse_nested_block(|input| { step_count = try!(specified::parse_integer(input)); - try!(input.expect_comma()); - start_end = try!(match_ignore_ascii_case! { - try!(input.expect_ident()), - "start" => Ok(computed_value::StartEnd::Start), - "end" => Ok(computed_value::StartEnd::End), - _ => Err(()) - }); + if input.try(|input| input.expect_comma()).is_ok() { + start_end = try!(match_ignore_ascii_case! { + try!(input.expect_ident()), + "start" => Ok(computed_value::StartEnd::Start), + "end" => Ok(computed_value::StartEnd::End), + _ => Err(()) + }); + } Ok(()) })); Ok(TransitionTimingFunction::Steps(step_count as u32, start_end)) @@ -582,6 +582,12 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", computed_value::T(Vec::new()) } + + #[inline] + pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue, ()> { + SingleSpecifiedValue::parse(input) + } + pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> { Ok(SpecifiedValue(try!(input.parse_comma_separated(SingleSpecifiedValue::parse)))) } @@ -640,6 +646,18 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", } pub use self::computed_value::T as SpecifiedValue; + pub use string_cache::Atom as SingleSpecifiedValue; + + #[inline] + pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue, ()> { + use cssparser::Token; + + Ok(match input.next() { + Ok(Token::Ident(ref value)) if value != "none" => Atom::from(&**value), + Ok(Token::QuotedString(value)) => Atom::from(&*value), + _ => return Err(()), + }) + } #[inline] pub fn get_initial_value() -> computed_value::T { @@ -648,9 +666,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> { use std::borrow::Cow; - Ok(SpecifiedValue(try!(input.parse_comma_separated(|input| { - input.expect_ident().map(Atom::from) - })))) + Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one)))) } impl ComputedValueAsSpecified for SpecifiedValue {} @@ -660,16 +676,20 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_index="True" animatable="False"> pub use super::transition_duration::computed_value; - pub use super::transition_duration::{parse, get_initial_value}; + pub use super::transition_duration::{get_initial_value, get_initial_single_value}; + pub use super::transition_duration::{parse, parse_one}; pub use super::transition_duration::SpecifiedValue; + pub use super::transition_duration::SingleSpecifiedValue; </%helpers:longhand> <%helpers:longhand name="animation-timing-function" need_index="True" 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::{get_initial_value, get_initial_single_value}; + pub use super::transition_timing_function::{parse, parse_one}; pub use super::transition_timing_function::SpecifiedValue; + pub use super::transition_timing_function::SingleSpecifiedValue; </%helpers:longhand> <%helpers:longhand name="animation-iteration-count" @@ -720,8 +740,14 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", } pub use self::computed_value::AnimationIterationCount; + pub use self::computed_value::AnimationIterationCount as SingleSpecifiedValue; pub use self::computed_value::T as SpecifiedValue; + #[inline] + pub fn get_initial_single_value() -> AnimationIterationCount { + AnimationIterationCount::Number(1) + } + pub fn parse_one(input: &mut Parser) -> Result<AnimationIterationCount, ()> { if input.try(|input| input.expect_ident_matching("infinite")).is_ok() { Ok(AnimationIterationCount::Infinite) @@ -740,8 +766,9 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one)))) } + #[inline] pub fn get_initial_value() -> computed_value::T { - computed_value::T(vec![AnimationIterationCount::Number(1)]) + computed_value::T(vec![get_initial_single_value()]) } impl ComputedValueAsSpecified for SpecifiedValue {} @@ -767,8 +794,10 @@ ${helpers.keyword_list("animation-fill-mode", need_index="True" animatable="False"> pub use super::transition_duration::computed_value; - pub use super::transition_duration::{parse, get_initial_value}; + pub use super::transition_duration::{get_initial_value, get_initial_single_value}; + pub use super::transition_duration::{parse, parse_one}; pub use super::transition_duration::SpecifiedValue; + pub use super::transition_duration::SingleSpecifiedValue; </%helpers:longhand> // CSSOM View Module diff --git a/components/style/properties/longhand/color.mako.rs b/components/style/properties/longhand/color.mako.rs index 31368dbe9ee..1f3831517ab 100644 --- a/components/style/properties/longhand/color.mako.rs +++ b/components/style/properties/longhand/color.mako.rs @@ -25,7 +25,8 @@ use cssparser; pub type T = cssparser::RGBA; } - #[inline] pub fn get_initial_value() -> computed_value::T { + #[inline] + pub fn get_initial_value() -> computed_value::T { RGBA { red: 0., green: 0., blue: 0., alpha: 1. } /* black */ } pub fn parse_specified(_context: &ParserContext, input: &mut Parser) diff --git a/components/style/properties/shorthand/box.mako.rs b/components/style/properties/shorthand/box.mako.rs index 27e923ba1aa..416923ced64 100644 --- a/components/style/properties/shorthand/box.mako.rs +++ b/components/style/properties/shorthand/box.mako.rs @@ -14,8 +14,20 @@ }) </%helpers:shorthand> +macro_rules! try_parse_one { + ($input: expr, $var: ident, $prop_module: ident) => { + if $var.is_none() { + if let Ok(value) = $input.try($prop_module::parse_one) { + $var = Some(value); + continue; + } + } + } +} + <%helpers:shorthand name="transition" - sub_properties="transition-property transition-duration transition-timing-function + sub_properties="transition-property transition-duration + transition-timing-function transition-delay"> use properties::longhands::{transition_delay, transition_duration, transition_property}; use properties::longhands::{transition_timing_function}; @@ -31,35 +43,10 @@ let (mut property, mut duration) = (None, None); let (mut timing_function, mut delay) = (None, None); loop { - if property.is_none() { - if let Ok(value) = input.try(transition_property::SingleSpecifiedValue::parse) { - property = Some(value); - continue - } - } - - if duration.is_none() { - if let Ok(value) = input.try(|input| transition_duration::parse_one(input)) { - duration = Some(value); - continue - } - } - - if timing_function.is_none() { - if let Ok(value) = input.try(|input| { - transition_timing_function::parse_one(input) - }) { - timing_function = Some(value); - continue - } - } - - if delay.is_none() { - if let Ok(value) = input.try(|input| transition_delay::parse_one(input)) { - delay = Some(value); - continue; - } - } + try_parse_one!(input, property, transition_property); + try_parse_one!(input, duration, transition_duration); + try_parse_one!(input, timing_function, transition_timing_function); + try_parse_one!(input, delay, transition_delay); break } @@ -68,12 +55,12 @@ Ok(SingleTransition { transition_property: property, transition_duration: - duration.unwrap_or(transition_duration::get_initial_single_value()), + duration.unwrap_or_else(transition_duration::get_initial_single_value), transition_timing_function: - timing_function.unwrap_or( - transition_timing_function::get_initial_single_value()), + timing_function.unwrap_or_else( + transition_timing_function::get_initial_single_value), transition_delay: - delay.unwrap_or(transition_delay::get_initial_single_value()), + delay.unwrap_or_else(transition_delay::get_initial_single_value), }) } else { Err(()) @@ -107,3 +94,121 @@ transition_delay: Some(transition_delay::SpecifiedValue(delays)), }) </%helpers:shorthand> + +<%helpers:shorthand name="animation" + sub_properties="animation-name animation-duration + animation-timing-function animation-delay + animation-iteration-count animation-direction + animation-fill-mode animation-play-state"> + use properties::longhands::{animation_name, animation_duration, animation_timing_function}; + use properties::longhands::{animation_delay, animation_iteration_count, animation_direction}; + use properties::longhands::{animation_fill_mode, animation_play_state}; + + struct SingleAnimation { + animation_name: animation_name::SingleSpecifiedValue, + animation_duration: animation_duration::SingleSpecifiedValue, + animation_timing_function: animation_timing_function::SingleSpecifiedValue, + animation_delay: animation_delay::SingleSpecifiedValue, + animation_iteration_count: animation_iteration_count::SingleSpecifiedValue, + animation_direction: animation_direction::SingleSpecifiedValue, + animation_fill_mode: animation_fill_mode::SingleSpecifiedValue, + animation_play_state: animation_play_state::SingleSpecifiedValue, + } + + fn parse_one_animation(input: &mut Parser) -> Result<SingleAnimation,()> { + let mut duration = None; + let mut timing_function = None; + let mut delay = None; + let mut iteration_count = None; + let mut direction = None; + let mut fill_mode = None; + let mut play_state = None; + let mut name = None; + + // NB: Name must be the last one here so that keywords valid for other + // longhands are not interpreted as names. + // + // Also, duration must be before delay, see + // https://drafts.csswg.org/css-animations/#typedef-single-animation + loop { + try_parse_one!(input, duration, animation_duration); + try_parse_one!(input, timing_function, animation_timing_function); + try_parse_one!(input, delay, animation_delay); + try_parse_one!(input, iteration_count, animation_iteration_count); + try_parse_one!(input, direction, animation_direction); + try_parse_one!(input, fill_mode, animation_fill_mode); + try_parse_one!(input, play_state, animation_play_state); + try_parse_one!(input, name, animation_name); + + break + } + + if let Some(name) = name { + Ok(SingleAnimation { + animation_name: name, + animation_duration: + duration.unwrap_or_else(animation_duration::get_initial_single_value), + animation_timing_function: + timing_function.unwrap_or_else(animation_timing_function::get_initial_single_value), + animation_delay: + delay.unwrap_or_else(animation_delay::get_initial_single_value), + animation_iteration_count: + iteration_count.unwrap_or_else(animation_iteration_count::get_initial_single_value), + animation_direction: + direction.unwrap_or_else(animation_direction::get_initial_single_value), + animation_fill_mode: + fill_mode.unwrap_or_else(animation_fill_mode::get_initial_single_value), + animation_play_state: + play_state.unwrap_or_else(animation_play_state::get_initial_single_value), + }) + } else { + Err(()) + } + } + + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(Longhands { + animation_name: None, + animation_duration: None, + animation_timing_function: None, + animation_delay: None, + animation_iteration_count: None, + animation_direction: None, + animation_fill_mode: None, + animation_play_state: None, + }) + } + + let results = try!(input.parse_comma_separated(parse_one_animation)); + + let mut names = vec![]; + let mut durations = vec![]; + let mut timing_functions = vec![]; + let mut delays = vec![]; + let mut iteration_counts = vec![]; + let mut directions = vec![]; + let mut fill_modes = vec![]; + let mut play_states = vec![]; + + for result in results.into_iter() { + names.push(result.animation_name); + durations.push(result.animation_duration); + timing_functions.push(result.animation_timing_function); + delays.push(result.animation_delay); + iteration_counts.push(result.animation_iteration_count); + directions.push(result.animation_direction); + fill_modes.push(result.animation_fill_mode); + play_states.push(result.animation_play_state); + } + + Ok(Longhands { + animation_name: Some(animation_name::SpecifiedValue(names)), + animation_duration: Some(animation_duration::SpecifiedValue(durations)), + animation_timing_function: Some(animation_timing_function::SpecifiedValue(timing_functions)), + animation_delay: Some(animation_delay::SpecifiedValue(delays)), + animation_iteration_count: Some(animation_iteration_count::SpecifiedValue(iteration_counts)), + animation_direction: Some(animation_direction::SpecifiedValue(directions)), + animation_fill_mode: Some(animation_fill_mode::SpecifiedValue(fill_modes)), + animation_play_state: Some(animation_play_state::SpecifiedValue(play_states)), + }) +</%helpers:shorthand> diff --git a/tests/html/animation-timing-function-override-from-keyframes.html b/tests/html/animation-timing-function-override-from-keyframes.html new file mode 100644 index 00000000000..7e37c2d19cb --- /dev/null +++ b/tests/html/animation-timing-function-override-from-keyframes.html @@ -0,0 +1,23 @@ +<!doctype html> +<meta charset="utf-8"> +<style> + @keyframes foo { + from { background: white; animation-timing-function: ease; } + to { background: black; } + } + + @keyframes bar { + from { background: white } + to { background: black } + } + + div { + height: 50px; + width: 100px; + animation: foo 1s infinite steps(4, end); + } + .bar { animation-name: bar } +</style> +<p>You should see an eased animation in the first-element, and a stepped one in the second one</p> +<div></div> +<div class="bar"></div> diff --git a/tests/wpt/metadata-css/css-flexbox-1_dev/html/css-flexbox-height-animation-stretch.htm.ini b/tests/wpt/metadata-css/css-flexbox-1_dev/html/css-flexbox-height-animation-stretch.htm.ini new file mode 100644 index 00000000000..3d874530583 --- /dev/null +++ b/tests/wpt/metadata-css/css-flexbox-1_dev/html/css-flexbox-height-animation-stretch.htm.ini @@ -0,0 +1,4 @@ +[css-flexbox-height-animation-stretch.htm] + type: reftest + expected: TIMEOUT + bug: https://github.com/servo/servo/issues/12328 diff --git a/tests/wpt/metadata-css/css-transitions-1_dev/html/transition-timing-function-001.htm.ini b/tests/wpt/metadata-css/css-transitions-1_dev/html/transition-timing-function-001.htm.ini index 1d3aaf317a8..e5ec8603b1e 100644 --- a/tests/wpt/metadata-css/css-transitions-1_dev/html/transition-timing-function-001.htm.ini +++ b/tests/wpt/metadata-css/css-transitions-1_dev/html/transition-timing-function-001.htm.ini @@ -1,8 +1,5 @@ [transition-timing-function-001.htm] type: testharness - [parse 'steps(3)'] - expected: FAIL - [parse 'cubic-bezier(-0.1, -0.2, -0.3, -0.4)'] expected: FAIL |