mirror of
https://github.com/servo/servo.git
synced 2025-07-22 14:53:49 +01:00
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:
commit
1fabfee27e
10 changed files with 249 additions and 59 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -0,0 +1,4 @@
|
|||
[css-flexbox-height-animation-stretch.htm]
|
||||
type: reftest
|
||||
expected: TIMEOUT
|
||||
bug: https://github.com/servo/servo/issues/12328
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue