diff --git a/components/style/properties/shorthand/box.mako.rs b/components/style/properties/shorthand/box.mako.rs index f28e3b0371c..1120c8eac5a 100644 --- a/components/style/properties/shorthand/box.mako.rs +++ b/components/style/properties/shorthand/box.mako.rs @@ -308,28 +308,55 @@ macro_rules! try_parse_one { impl<'a> LonghandsToSerialize<'a> { fn to_css_declared(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(self.animation_duration.to_css(dest)); - try!(write!(dest, " ")); + fn extract_value(x: &DeclaredValue) -> Option< &T> { + match *x { + DeclaredValue::Value(ref val) => Some(val), + _ => None, + } + } - try!(self.animation_timing_function.to_css(dest)); - try!(write!(dest, " ")); + // TODO: When the lengths are different, shorthand shouldn't be serialized + // at all. + use std::cmp; + let mut len = 0; + % for name in "duration timing_function delay direction fill_mode iteration_count play_state name".split(): + len = cmp::max(len, extract_value(self.animation_${name}).map(|i| i.0.len()) + .unwrap_or(0)); + % endfor - try!(self.animation_delay.to_css(dest)); - try!(write!(dest, " ")); + // There should be at least one declared value + if len == 0 { + return dest.write_str("") + } - try!(self.animation_direction.to_css(dest)); - try!(write!(dest, " ")); + let mut first = true; + for i in 0..len { + % for name in "duration timing_function delay direction fill_mode iteration_count play_state name".split(): + let ${name} = if let DeclaredValue::Value(ref arr) = *self.animation_${name} { + arr.0.get(i % arr.0.len()) + } else { + None + }; + % endfor - try!(self.animation_fill_mode.to_css(dest)); - try!(write!(dest, " ")); + if first { + first = false; + } else { + try!(write!(dest, ", ")); + } - try!(self.animation_iteration_count.to_css(dest)); - try!(write!(dest, " ")); + % for name in "duration timing_function delay direction fill_mode iteration_count play_state".split(): + if let Some(${name}) = ${name} { + try!(${name}.to_css(dest)); + try!(write!(dest, " ")); + } + % endfor - try!(self.animation_play_state.to_css(dest)); - try!(write!(dest, " ")); - - self.animation_name.to_css(dest) + if let Some(name) = name { + try!(name.to_css(dest)); + } + } + Ok(()) } } diff --git a/tests/unit/style/properties/serialization.rs b/tests/unit/style/properties/serialization.rs index ab255450d0f..e54ef253059 100644 --- a/tests/unit/style/properties/serialization.rs +++ b/tests/unit/style/properties/serialization.rs @@ -1136,6 +1136,99 @@ mod shorthand_serialization { assert_eq!(try_serialize.is_ok(), true); assert_eq!(s, "none"); } + } + mod animation { + pub use super::*; + use cssparser::Parser; + use media_queries::CSSErrorReporterTest; + use servo_url::ServoUrl; + use style::parser::ParserContext; + use style::properties::{parse_property_declaration_list, PropertyDeclarationBlock}; + use style::stylesheets::Origin; + + fn property_declaration_block(css_properties: &str) -> PropertyDeclarationBlock { + let url = ServoUrl::parse("http://localhost").unwrap(); + let context = ParserContext::new(Origin::Author, &url, Box::new(CSSErrorReporterTest)); + let mut parser = Parser::new(css_properties); + parse_property_declaration_list(&context, &mut parser) + } + + #[test] + fn serialize_single_animation() { + let block = property_declaration_block("\ + animation-name: bounce;\ + animation-duration: 1s;\ + animation-timing-function: ease-in;\ + animation-delay: 0s;\ + animation-direction: normal;\ + animation-fill-mode: forwards;\ + animation-iteration-count: infinite;\ + animation-play-state: paused;"); + + let serialization = block.to_css_string(); + + assert_eq!(serialization, "animation: 1s ease-in 0s normal forwards infinite paused bounce;") + } + + #[test] + fn serialize_multiple_animations() { + let block = property_declaration_block("\ + animation-name: bounce, roll;\ + animation-duration: 1s, 0.2s;\ + animation-timing-function: ease-in, linear;\ + animation-delay: 0s, 1s;\ + animation-direction: normal, reverse;\ + animation-fill-mode: forwards, backwards;\ + animation-iteration-count: infinite, 2;\ + animation-play-state: paused, running;"); + + let serialization = block.to_css_string(); + + assert_eq!(serialization, + "animation: 1s ease-in 0s normal forwards infinite paused bounce, \ + 0.2s linear 1s reverse backwards 2 running roll;"); + } + + #[test] + fn serialize_multiple_animations_unequal_property_lists() { + // Currently the implementation cycles values if the lists are + // uneven. This is incorrect, in that we should serialize only + // when the lists have the same length (both here and for background + // and transition. See https://github.com/servo/servo/issues/15398 ) + let block = property_declaration_block("\ + animation-name: bounce, roll, flip, jump;\ + animation-duration: 1s, 0.2s;\ + animation-timing-function: ease-in, linear;\ + animation-delay: 0s, 1s, 0.5s;\ + animation-direction: normal;\ + animation-fill-mode: forwards, backwards;\ + animation-iteration-count: infinite, 2;\ + animation-play-state: paused, running;"); + + let serialization = block.to_css_string(); + + assert_eq!(serialization, "animation: \ + 1s ease-in 0s normal forwards infinite paused bounce, \ + 0.2s linear 1s normal backwards 2 running roll, \ + 1s ease-in 0.5s normal forwards infinite paused flip, \ + 0.2s linear 0s normal backwards 2 running jump;") + } + + #[test] + fn serialize_multiple_without_all_properties_returns_longhand() { + // timing function and direction are missing, so no shorthand is returned. + let block_text = "animation-name: bounce, roll; \ + animation-duration: 1s, 0.2s; \ + animation-delay: 0s, 1s; \ + animation-fill-mode: forwards, backwards; \ + animation-iteration-count: infinite, 2; \ + animation-play-state: paused, running;"; + let block = property_declaration_block(block_text); + + let serialization = block.to_css_string(); + + assert_eq!(serialization, block_text); + } } }