Auto merge of #12118 - emilio:animation-shorthand, r=SimonSapin

style: Add support to the animation shorthand and fix parsing of animation-name

<!-- Please describe your changes on the following line: -->

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

Part of #11916.

r? @SimonSapin

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/12118)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-07-08 06:01:52 -07:00 committed by GitHub
commit 1fabfee27e
10 changed files with 249 additions and 59 deletions

View file

@ -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;

View file

@ -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);
}

View file

@ -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,
}
}
}

View file

@ -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]

View file

@ -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

View file

@ -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)

View file

@ -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>

View file

@ -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>

View file

@ -0,0 +1,4 @@
[css-flexbox-height-animation-stretch.htm]
type: reftest
expected: TIMEOUT
bug: https://github.com/servo/servo/issues/12328

View file

@ -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