style: Fix parsing and add generated keyframes

This commit is contained in:
Emilio Cobos Álvarez 2016-06-27 08:52:33 -07:00
parent 46eec45886
commit 2d566ef0ef
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
10 changed files with 189 additions and 94 deletions

View file

@ -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<ConstellationMsg>,
running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>,
new_animations_receiver: &Receiver<Animation>,
pipeline_id: PipelineId) {
pub fn update_animation_state<Impl: SelectorImplExt>(constellation_chan: &IpcSender<ConstellationMsg>,
running_animations: &mut HashMap<OpaqueNode, Vec<Animation<Impl>>>,
expired_animations: &mut HashMap<OpaqueNode, Vec<Animation<Impl>>>,
new_animations_receiver: &Receiver<Animation<Impl>>,
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<ConstellationMsg>,
/// 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<OpaqueNode, Vec<Animation>>) {
animations: &HashMap<OpaqueNode,
Vec<Animation<ServoSelectorImpl>>>) {
let mut damage = RestyleDamage::empty();
flow.mutate_fragments(&mut |fragment| {
if let Some(ref animations) = animations.get(&fragment.node) {

View file

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

View file

@ -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<Impl: SelectorImplExt> {
/// 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::ComputedValues>,
}
impl KeyframesAnimationState {
impl<Impl: SelectorImplExt> KeyframesAnimationState<Impl> {
/// 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<Impl: SelectorImplExt> {
/// 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>),
}
impl Animation {
impl<Impl: SelectorImplExt> Animation<Impl> {
#[inline]
pub fn mark_as_expired(&mut self) {
debug_assert!(!self.is_expired());
@ -344,18 +347,20 @@ impl<T> GetMod for Vec<T> {
//
// TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a
// cloneable part and a non-cloneable part..
pub fn start_transitions_if_applicable<C: ComputedValues>(new_animations_sender: &Mutex<Sender<Animation>>,
node: OpaqueNode,
old_style: &C,
new_style: &mut C)
-> bool {
pub fn start_transitions_if_applicable<Impl: SelectorImplExt>(new_animations_sender: &Mutex<Sender<Animation<Impl>>>,
node: OpaqueNode,
old_style: &Impl::ComputedValues,
new_style: &mut Arc<Impl::ComputedValues>)
-> 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<C: ComputedValues>(new_animations_sender:
fn compute_style_for_animation_step<Impl: SelectorImplExt>(context: &SharedStyleContext<Impl>,
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<Impl: SelectorImplExt>(context: &SharedStyleContext<Impl>,
node: OpaqueNode,
new_style: &Impl::ComputedValues) -> bool
new_style: &Arc<Impl::ComputedValues>) -> bool
{
let mut had_animations = false;
@ -407,8 +421,17 @@ pub fn maybe_start_animations<Impl: SelectorImplExt>(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<Impl: SelectorImplExt>(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<C: ComputedValues>(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<Damage, Impl>(context: &SharedStyleContext<Impl>,
animation: &Animation,
animation: &Animation<Impl>,
style: &mut Arc<Damage::ConcreteComputedValues>,
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();

View file

@ -34,16 +34,16 @@ pub struct SharedStyleContext<Impl: SelectorImplExt> {
/// A channel on which new animations that have been triggered by style recalculation can be
/// sent.
pub new_animations_sender: Mutex<Sender<Animation>>,
pub new_animations_sender: Mutex<Sender<Animation<Impl>>>,
/// Why is this reflow occurring
pub goal: ReflowGoal,
/// The animations that are currently running.
pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation<Impl>>>>>,
/// The list of animations that have expired since the last style recalculation.
pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation<Impl>>>>>,
///The CSS error reporter for all CSS loaded in this layout thread
pub error_reporter: Box<ParseErrorReporter + Sync>,

View file

@ -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<Vec<Keyframe>, ()> {
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<Vec<PropertyDeclaration>>),
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<Vec<PropertyDeclaration>>,
/// 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<Vec<PropertyDeclaration>>) -> Self {
value: KeyframesStepValue) -> Self {
KeyframesStep {
start_percentage: percentage,
declarations: declarations,
value: value,
}
}
}
@ -161,24 +154,34 @@ fn get_animated_properties(keyframe: &Keyframe) -> Vec<TransitionProperty> {
impl KeyframesAnimation {
pub fn from_keyframes(keyframes: &[Keyframe]) -> Option<Self> {
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<Keyframe> {
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<Self::Prelude, ()> {
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<Self::QualifiedRule, ()> {
Ok(Keyframe {
selector: prelude,
// FIXME: needs parsing different from parse_property_declaration_list:
// https://drafts.csswg.org/css-animations/#keyframes
// Paragraph "The <declaration-list> inside of <keyframe-block> ..."
declarations: parse_property_declaration_list(self.context, input).normal,
})
}
}

View file

@ -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::<Self::ConcreteComputedValues>(
animation::start_transitions_if_applicable::<<Self::ConcreteElement as Element>::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.

View file

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

View file

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

View file

@ -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<ServoSelectorImpl>;
pub type PrivateStyleData = data::PrivateStyleData<ServoSelectorImpl, ServoComputedValues>;
pub type Stylist = selector_matching::Stylist<ServoSelectorImpl>;
pub type SharedStyleContext = context::SharedStyleContext<ServoSelectorImpl>;
pub type Animation = animation::Animation<ServoSelectorImpl>;

View file

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