Implement and test animation of css3-animations. (Bug 435442, patch 11) r=bzbarsky
authorL. David Baron <dbaron@dbaron.org>
Mon, 11 Apr 2011 23:18:44 -0700
changeset 67985 1c17ed72040cc82505821c7267ac8e683bd9d33d
parent 67984 6ab8e5df08ec2ed73b65c62a210014e8dea48d77
child 67986 3a3c77941d26fd6e4f77c04234948870c9a440f6
push id19461
push userdbaron@mozilla.com
push dateTue, 12 Apr 2011 06:21:43 +0000
treeherdermozilla-central@b48ebf9695bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs435442
milestone2.2a1pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Implement and test animation of css3-animations. (Bug 435442, patch 11) r=bzbarsky
content/base/src/nsGenericElement.cpp
content/base/src/nsGkAtomList.h
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/base/nsPresShell.cpp
layout/style/Makefile.in
layout/style/nsAnimationManager.cpp
layout/style/nsAnimationManager.h
layout/style/nsStyleSet.cpp
layout/style/nsStyleSet.h
layout/style/test/Makefile.in
layout/style/test/animation_utils.js
layout/style/test/test_animations.html
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -3031,16 +3031,19 @@ nsGenericElement::UnbindFromTree(PRBool 
   // Ensure that CSS transitions don't continue on an element at a
   // different place in the tree (even if reinserted before next
   // animation refresh).
   // FIXME (Bug 522599): Need a test for this.
   if (HasFlag(NODE_HAS_PROPERTIES)) {
     DeleteProperty(nsGkAtoms::transitionsOfBeforeProperty);
     DeleteProperty(nsGkAtoms::transitionsOfAfterProperty);
     DeleteProperty(nsGkAtoms::transitionsProperty);
+    DeleteProperty(nsGkAtoms::animationsOfBeforeProperty);
+    DeleteProperty(nsGkAtoms::animationsOfAfterProperty);
+    DeleteProperty(nsGkAtoms::animationsProperty);
   }
 
   // Unset this since that's what the old code effectively did.
   UnsetFlags(NODE_FORCE_XBL_BINDINGS);
   
 #ifdef MOZ_XUL
   nsXULElement* xulElem = nsXULElement::FromContent(this);
   if (xulElem) {
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1824,16 +1824,19 @@ GK_ATOM(ended, "ended")
 GK_ATOM(canplay, "canplay")
 GK_ATOM(canplaythrough, "canplaythrough")
 GK_ATOM(ratechange, "ratechange")
 GK_ATOM(durationchange, "durationchange")
 GK_ATOM(volumechange, "volumechange")
 #endif
 
 // Content property names
+GK_ATOM(animationsProperty, "AnimationsProperty")        // FrameAnimations*
+GK_ATOM(animationsOfBeforeProperty, "AnimationsOfBeforeProperty") // FrameAnimations*
+GK_ATOM(animationsOfAfterProperty, "AnimationsOfAfterProperty") // FrameAnimations*
 GK_ATOM(transitionsProperty, "TransitionsProperty")        // FrameTransitions*
 GK_ATOM(transitionsOfBeforeProperty, "TransitionsOfBeforeProperty") // FrameTransitions*
 GK_ATOM(transitionsOfAfterProperty, "TransitionsOfAfterProperty") // FrameTransitions*
 GK_ATOM(genConInitializerProperty, "QuoteNodeProperty")
 GK_ATOM(labelMouseDownPtProperty, "LabelMouseDownPtProperty")
 
 // Languages for lang-specific transforms
 GK_ATOM(Japanese, "ja")
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -90,16 +90,17 @@
 #include "nsIEventListenerManager.h"
 #include "nsStyleStructInlines.h"
 #include "nsIAppShell.h"
 #include "prenv.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsIDOMEventTarget.h"
 #include "nsObjectFrame.h"
 #include "nsTransitionManager.h"
+#include "nsAnimationManager.h"
 #include "mozilla/dom/Element.h"
 #include "nsIFrameMessageManager.h"
 #include "FrameLayerBuilder.h"
 
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #endif // MOZ_SMIL
 
@@ -909,16 +910,20 @@ nsPresContext::Init(nsIDeviceContext* aD
     return NS_ERROR_OUT_OF_MEMORY;
 
   NS_ADDREF(mEventManager);
 
   mTransitionManager = new nsTransitionManager(this);
   if (!mTransitionManager)
     return NS_ERROR_OUT_OF_MEMORY;
 
+  mAnimationManager = new nsAnimationManager(this);
+  if (!mAnimationManager)
+    return NS_ERROR_OUT_OF_MEMORY;
+
   if (mDocument->GetDisplayDocument()) {
     NS_ASSERTION(mDocument->GetDisplayDocument()->GetShell() &&
                  mDocument->GetDisplayDocument()->GetShell()->GetPresContext(),
                  "Why are we being initialized?");
     mRefreshDriver = mDocument->GetDisplayDocument()->GetShell()->
       GetPresContext()->RefreshDriver();
   } else {
     nsIDocument* parent = mDocument->GetParentDocument();
@@ -1071,16 +1076,20 @@ nsPresContext::SetShell(nsIPresShell* aS
       mImageLoaders[i].Enumerate(destroy_loads, nsnull);
       mImageLoaders[i].Clear();
     }
 
     if (mTransitionManager) {
       mTransitionManager->Disconnect();
       mTransitionManager = nsnull;
     }
+    if (mAnimationManager) {
+      mAnimationManager->Disconnect();
+      mAnimationManager = nsnull;
+    }
   }
 }
 
 void
 nsPresContext::UpdateCharSet(const nsAFlatCString& aCharSet)
 {
   if (mLangService) {
     NS_IF_RELEASE(mLanguage);
@@ -1620,16 +1629,17 @@ void
 nsPresContext::RebuildAllStyleData(nsChangeHint aExtraHint)
 {
   if (!mShell) {
     // We must have been torn down. Nothing to do here.
     return;
   }
 
   RebuildUserFontSet();
+  AnimationManager()->KeyframesListIsDirty();
 
   mShell->FrameConstructor()->RebuildAllStyleData(aExtraHint);
 }
 
 void
 nsPresContext::PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint)
 {
   if (!mShell) {
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -97,16 +97,17 @@ class nsIAtom;
 struct nsStyleBackground;
 struct nsStyleBorder;
 class nsIRunnable;
 class gfxUserFontSet;
 class nsUserFontSet;
 struct nsFontFaceRuleContainer;
 class nsObjectFrame;
 class nsTransitionManager;
+class nsAnimationManager;
 class nsRefreshDriver;
 class imgIContainer;
 
 #ifdef MOZ_REFLOW_PERF
 class nsIRenderingContext;
 #endif
 
 enum nsWidgetType {
@@ -233,16 +234,17 @@ public:
 
 #ifdef _IMPL_NS_LAYOUT
   nsStyleSet* StyleSet() { return GetPresShell()->StyleSet(); }
 
   nsFrameManager* FrameManager()
     { return GetPresShell()->FrameManager(); } 
 
   nsTransitionManager* TransitionManager() { return mTransitionManager; }
+  nsAnimationManager* AnimationManager() { return mAnimationManager; }
 
   nsRefreshDriver* RefreshDriver() { return mRefreshDriver; }
 #endif
 
   /**
    * Rebuilds all style data by throwing out the old rule tree and
    * building a new one, and additionally applying aExtraHint (which
    * must not contain nsChangeHint_ReconstructFrame) to the root frame.
@@ -1056,16 +1058,17 @@ protected:
                                         // better safe than sorry.
                                         // Cannot reintroduce cycles
                                         // since there is no dependency
                                         // from gfx back to layout.
   nsIEventStateManager* mEventManager;  // [STRONG]
   nsILookAndFeel*       mLookAndFeel;   // [STRONG]
   nsRefPtr<nsRefreshDriver> mRefreshDriver;
   nsRefPtr<nsTransitionManager> mTransitionManager;
+  nsRefPtr<nsAnimationManager> mAnimationManager;
   nsIAtom*              mMedium;        // initialized by subclass ctors;
                                         // weak pointer to static atom
 
   nsILinkHandler*       mLinkHandler;   // [WEAK]
 
   // Formerly mLangGroup; moving from charset-oriented langGroup to
   // maintaining actual language settings everywhere (see bug 524107).
   // This may in fact hold a langGroup such as x-western rather than
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -61,16 +61,17 @@
 #include "nsIContent.h"
 #include "mozilla/dom/Element.h"
 #include "nsIDocument.h"
 #include "nsIDOMXULDocument.h"
 #include "nsStubDocumentObserver.h"
 #include "nsStyleSet.h"
 #include "nsCSSStyleSheet.h" // XXX for UA sheet loading hack, can this go away please?
 #include "nsIDOMCSSStyleSheet.h"  // for Pref-related rule management (bugs 22963,20760,31816)
+#include "nsAnimationManager.h"
 #include "nsINameSpaceManager.h"  // for Pref-related rule management (bugs 22963,20760,31816)
 #include "nsIServiceManager.h"
 #include "nsFrame.h"
 #include "nsIViewManager.h"
 #include "nsCRT.h"
 #include "nsCRTGlue.h"
 #include "prlog.h"
 #include "prmem.h"
@@ -5100,16 +5101,17 @@ nsIPresShell::ReconstructStyleDataIntern
 
   if (mIsDestroying) {
     // We don't want to mess with restyles at this point
     return;
   }
 
   if (mPresContext) {
     mPresContext->RebuildUserFontSet();
+    mPresContext->AnimationManager()->KeyframesListIsDirty();
   }
 
   Element* root = mDocument->GetRootElement();
   if (!mDidInitialReflow) {
     // Nothing to do here, since we have no frames yet
     return;
   }
 
--- a/layout/style/Makefile.in
+++ b/layout/style/Makefile.in
@@ -107,16 +107,17 @@ EXPORTS_mozilla/css = \
 
 CPPSRCS		= \
 		AnimationCommon.cpp \
 		nsCSSAnonBoxes.cpp \
 		nsCSSDataBlock.cpp \
 		Declaration.cpp \
 		nsCSSKeywords.cpp \
 		Loader.cpp \
+		nsAnimationManager.cpp \
 		nsCSSParser.cpp \
 		nsCSSProps.cpp \
 		nsCSSPseudoClasses.cpp \
 		nsCSSPseudoElements.cpp \
 		nsCSSRuleProcessor.cpp \
 		nsCSSRules.cpp \
 		nsCSSScanner.cpp \
 		nsCSSStyleSheet.cpp \
new file mode 100644
--- /dev/null
+++ b/layout/style/nsAnimationManager.cpp
@@ -0,0 +1,810 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is nsAnimationManager, an implementation of part
+ * of css3-animations.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsAnimationManager.h"
+#include "nsPresContext.h"
+#include "nsRuleProcessorData.h"
+#include "nsStyleSet.h"
+#include "nsCSSRules.h"
+#include "mozilla/TimeStamp.h"
+#include "nsStyleAnimation.h"
+#include "nsSMILKeySpline.h"
+
+using namespace mozilla;
+
+struct AnimationSegmentProperty
+{
+  nsCSSProperty mProperty;
+  nsStyleAnimation::Value mFromValue, mToValue;
+};
+
+struct AnimationSegment
+{
+  float mFromKey, mToKey;
+  css::ComputedTimingFunction mTimingFunction;
+  InfallibleTArray<AnimationSegmentProperty> mProperties;
+};
+
+/**
+ * Data about one animation (i.e., one of the values of
+ * 'animation-name') running on an element.
+ */
+struct ElementAnimation
+{
+  nsString mName; // empty string for 'none'
+  float mIterationCount; // NS_IEEEPositiveInfinity() means infinite
+  PRUint8 mDirection;
+  PRUint8 mFillMode;
+  PRUint8 mPlayState;
+
+  bool FillsForwards() const {
+    return mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BOTH ||
+           mFillMode == NS_STYLE_ANIMATION_FILL_MODE_FORWARDS;
+  }
+  bool FillsBackwards() const {
+    return mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BOTH ||
+           mFillMode == NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS;
+  }
+
+  bool IsPaused() const {
+    return mPlayState == NS_STYLE_ANIMATION_PLAY_STATE_PAUSED;
+  }
+
+  TimeStamp mStartTime; // with delay taken into account
+  TimeStamp mPauseStart;
+  TimeDuration mIterationDuration;
+
+  InfallibleTArray<AnimationSegment> mSegments;
+};
+
+/**
+ * Data about all of the animations running on an element.
+ */
+struct ElementAnimations : public mozilla::css::CommonElementAnimationData
+{
+  ElementAnimations(dom::Element *aElement, nsIAtom *aElementProperty,
+                     nsAnimationManager *aAnimationManager)
+    : CommonElementAnimationData(aElement, aElementProperty,
+                                 aAnimationManager),
+      mNeedsRefreshes(true)
+  {
+  }
+
+  void EnsureStyleRuleFor(TimeStamp aRefreshTime);
+
+  void PostRestyleForAnimation(nsPresContext *aPresContext) {
+    nsRestyleHint hint =
+      (mElementProperty == nsGkAtoms::animationsProperty)
+        ? eRestyle_Self : eRestyle_Subtree;
+    aPresContext->PresShell()->RestyleForAnimation(mElement, hint);
+  }
+
+  // This style rule contains the style data for currently animating
+  // values.  It only matches when styling with animation.  When we
+  // style without animation, we need to not use it so that we can
+  // detect any new changes; if necessary we restyle immediately
+  // afterwards with animation.
+  // NOTE: If we don't need to apply any styles, mStyleRule will be
+  // null, but mStyleRuleRefreshTime will still be valid.
+  nsRefPtr<css::AnimValuesStyleRule> mStyleRule;
+  // The refresh time associated with mStyleRule.
+  TimeStamp mStyleRuleRefreshTime;
+
+  // False when we know that our current style rule is valid
+  // indefinitely into the future (because all of our animations are
+  // either completed or paused).  May be invalidated by a style change.
+  bool mNeedsRefreshes;
+
+  InfallibleTArray<ElementAnimation> mAnimations;
+};
+
+static void
+ElementAnimationsPropertyDtor(void           *aObject,
+                              nsIAtom        *aPropertyName,
+                              void           *aPropertyValue,
+                              void           *aData)
+{
+  ElementAnimations *ea = static_cast<ElementAnimations*>(aPropertyValue);
+  delete ea;
+}
+
+void
+ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime)
+{
+  if (!mNeedsRefreshes) {
+    // All of our animations are paused or completed.
+    mStyleRuleRefreshTime = aRefreshTime;
+    return;
+  }
+
+  mNeedsRefreshes = false;
+
+  // mStyleRule may be null and valid, if we have no style to apply.
+  if (mStyleRuleRefreshTime.IsNull() ||
+      mStyleRuleRefreshTime != aRefreshTime) {
+    mStyleRuleRefreshTime = aRefreshTime;
+    mStyleRule = nsnull;
+
+    // FIXME(spec): assume that properties in higher animations override
+    // those in lower ones (and that our |HasProperty| check in
+    // |BuildSegment| matches the definition of when they should do so.
+    // Therefore, we iterate from last animation to first.
+    nsCSSPropertySet properties;
+
+    for (PRUint32 i = mAnimations.Length(); i-- != 0; ) {
+      const ElementAnimation &anim = mAnimations[i];
+
+      if (anim.mSegments.Length() == 0 ||
+          anim.mIterationDuration.ToMilliseconds() <= 0.0) {
+        // No animation data.
+        continue;
+      }
+
+      TimeDuration currentTimeDuration;
+      if (anim.IsPaused()) {
+        // FIXME: avoid recalculating every time
+        currentTimeDuration = anim.mPauseStart - anim.mStartTime;
+      } else {
+        currentTimeDuration = aRefreshTime - anim.mStartTime;
+      }
+
+      // Set |currentIterationCount| to the (fractional) number of
+      // iterations we've completed up to the current position.
+      double currentIterationCount =
+        currentTimeDuration / anim.mIterationDuration;
+      if (currentIterationCount >= double(anim.mIterationCount)) {
+        if (!anim.FillsForwards()) {
+          // No animation data.
+          continue;
+        }
+        currentIterationCount = double(anim.mIterationCount);
+      } else {
+        if (!anim.IsPaused()) {
+          mNeedsRefreshes = true;
+        }
+        if (currentIterationCount < 0.0) {
+          if (!anim.FillsBackwards()) {
+            // No animation data.
+            continue;
+          }
+          currentIterationCount = 0.0;
+        }
+      }
+
+      // Set |positionInIteration| to the position from 0% to 100% along
+      // the keyframes.
+      NS_ABORT_IF_FALSE(currentIterationCount >= 0.0, "must be positive");
+      PRUint32 whichIteration = int(currentIterationCount);
+      if (whichIteration == anim.mIterationCount) {
+        // When the animation's iteration count is an integer (as it
+        // normally is), we need to end at 100% of its last iteration
+        // rather than 0% of the next one.
+        --whichIteration;
+      }
+      double positionInIteration =
+        currentIterationCount - double(whichIteration);
+      if (anim.mDirection == NS_STYLE_ANIMATION_DIRECTION_ALTERNATE &&
+          (whichIteration & 1) == 1) {
+        positionInIteration = 1.0 - positionInIteration;
+      }
+
+      NS_ABORT_IF_FALSE(0.0 <= positionInIteration &&
+                          positionInIteration <= 1.0,
+                        "position should be in [0-1]");
+
+      NS_ABORT_IF_FALSE(anim.mSegments[0].mFromKey == 0.0,
+                        "incorrect first from key");
+      NS_ABORT_IF_FALSE(anim.mSegments[anim.mSegments.Length() - 1].mToKey
+                          == 1.0,
+                        "incorrect last to key");
+
+      // FIXME: Maybe cache the current segment?
+      const AnimationSegment *segment = anim.mSegments.Elements();
+      while (segment->mToKey < positionInIteration) {
+        NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
+                          "incorrect keys");
+        ++segment;
+        NS_ABORT_IF_FALSE(segment->mFromKey == (segment-1)->mToKey,
+                          "incorrect keys");
+      }
+      NS_ABORT_IF_FALSE(segment->mFromKey < segment->mToKey,
+                        "incorrect keys");
+      NS_ABORT_IF_FALSE(segment - anim.mSegments.Elements() <
+                          anim.mSegments.Length(),
+                        "ran off end");
+
+      if (segment->mProperties.IsEmpty()) {
+        // No animation data.
+        continue;
+      }
+
+      if (!mStyleRule) {
+        // Allocate the style rule now that we know we have animation data.
+        mStyleRule = new css::AnimValuesStyleRule();
+      }
+
+      double positionInSegment = (positionInIteration - segment->mFromKey) /
+                                 (segment->mToKey - segment->mFromKey);
+      double valuePosition =
+        segment->mTimingFunction.GetValue(positionInSegment);
+
+      for (PRUint32 j = 0, j_end = segment->mProperties.Length();
+           j != j_end; ++j) {
+        const AnimationSegmentProperty &prop = segment->mProperties[j];
+        if (properties.HasProperty(prop.mProperty)) {
+          // A later animation already set this property.
+          continue;
+        }
+        properties.AddProperty(prop.mProperty);
+
+        nsStyleAnimation::Value *val =
+          mStyleRule->AddEmptyValue(prop.mProperty);
+
+#ifdef DEBUG
+        PRBool result =
+#endif
+          nsStyleAnimation::Interpolate(prop.mProperty,
+                                        prop.mFromValue, prop.mToValue,
+                                        valuePosition, *val);
+        NS_ABORT_IF_FALSE(result, "interpolate must succeed now");
+      }
+    }
+  }
+}
+
+ElementAnimations*
+nsAnimationManager::GetElementAnimations(dom::Element *aElement,
+                                         nsCSSPseudoElements::Type aPseudoType,
+                                         PRBool aCreateIfNeeded)
+{
+  if (!aCreateIfNeeded && PR_CLIST_IS_EMPTY(&mElementData)) {
+    // Early return for the most common case.
+    return nsnull;
+  }
+
+  nsIAtom *propName;
+  if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
+    propName = nsGkAtoms::animationsProperty;
+  } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before) {
+    propName = nsGkAtoms::animationsOfBeforeProperty;
+  } else if (aPseudoType == nsCSSPseudoElements::ePseudo_after) {
+    propName = nsGkAtoms::animationsOfAfterProperty;
+  } else {
+    NS_ASSERTION(!aCreateIfNeeded,
+                 "should never try to create transitions for pseudo "
+                 "other than :before or :after");
+    return nsnull;
+  }
+  ElementAnimations *ea = static_cast<ElementAnimations*>(
+                             aElement->GetProperty(propName));
+  if (!ea && aCreateIfNeeded) {
+    // FIXME: Consider arena-allocating?
+    ea = new ElementAnimations(aElement, propName, this);
+    if (!ea) {
+      NS_WARNING("out of memory");
+      return nsnull;
+    }
+    nsresult rv = aElement->SetProperty(propName, ea,
+                                        ElementAnimationsPropertyDtor, nsnull);
+    if (NS_FAILED(rv)) {
+      NS_WARNING("SetProperty failed");
+      delete ea;
+      return nsnull;
+    }
+
+    AddElementData(ea);
+  }
+
+  return ea;
+}
+
+/* virtual */ void
+nsAnimationManager::RulesMatching(ElementRuleProcessorData* aData)
+{
+  NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
+                    "pres context mismatch");
+  nsIStyleRule *rule =
+    GetAnimationRule(aData->mElement,
+                     nsCSSPseudoElements::ePseudo_NotPseudoElement);
+  if (rule) {
+    aData->mRuleWalker->Forward(rule);
+  }
+}
+
+/* virtual */ void
+nsAnimationManager::RulesMatching(PseudoElementRuleProcessorData* aData)
+{
+  NS_ABORT_IF_FALSE(aData->mPresContext == mPresContext,
+                    "pres context mismatch");
+  if (aData->mPseudoType != nsCSSPseudoElements::ePseudo_before &&
+      aData->mPseudoType != nsCSSPseudoElements::ePseudo_after) {
+    return;
+  }
+
+  // FIXME: Do we really want to be the only thing keeping a
+  // pseudo-element alive?  I *think* the non-animation restyle should
+  // handle that, but should add a test.
+  nsIStyleRule *rule = GetAnimationRule(aData->mElement, aData->mPseudoType);
+  if (rule) {
+    aData->mRuleWalker->Forward(rule);
+  }
+}
+
+/* virtual */ void
+nsAnimationManager::RulesMatching(AnonBoxRuleProcessorData* aData)
+{
+}
+
+#ifdef MOZ_XUL
+/* virtual */ void
+nsAnimationManager::RulesMatching(XULTreeRuleProcessorData* aData)
+{
+}
+#endif
+
+nsIStyleRule*
+nsAnimationManager::CheckAnimationRule(nsStyleContext* aStyleContext,
+                                       mozilla::dom::Element* aElement)
+{
+  if (!mPresContext->IsProcessingAnimationStyleChange()) {
+    // Everything that causes our animation data to change triggers a
+    // style change, which in turn triggers a non-animation restyle.
+    // Likewise, when we initially construct frames, we're not in a
+    // style change, but also not in an animation restyle.
+
+    const nsStyleDisplay *disp = aStyleContext->GetStyleDisplay();
+    ElementAnimations *ea =
+      GetElementAnimations(aElement, aStyleContext->GetPseudoType(), PR_FALSE);
+    if (!ea &&
+        disp->mAnimations.Length() == 1 &&
+        disp->mAnimations[0].GetName().IsEmpty()) {
+      return nsnull;
+    }
+
+    // build the animations list
+    InfallibleTArray<ElementAnimation> newAnimations;
+    BuildAnimations(aStyleContext, newAnimations);
+
+    if (newAnimations.IsEmpty()) {
+      if (ea) {
+        ea->Destroy();
+      }
+      return nsnull;
+    }
+
+    TimeStamp refreshTime = mPresContext->RefreshDriver()->MostRecentRefresh();
+
+    if (ea) {
+      // The cached style rule is invalid.
+      ea->mStyleRule = nsnull;
+      ea->mStyleRuleRefreshTime = TimeStamp();
+
+      // Copy over the start times and (if still paused) pause starts
+      // for each animation (matching on name only) that was also in the
+      // old list of animations.
+      // This means that we honor dynamic changes, which isn't what the
+      // spec says to do, but WebKit seems to honor at least some of
+      // them.  See
+      // http://lists.w3.org/Archives/Public/www-style/2011Apr/0079.html
+      // In order to honor what the spec said, we'd copy more data over
+      // (or potentially optimize BuildAnimations to avoid rebuilding it
+      // in the first place).
+      if (!ea->mAnimations.IsEmpty()) {
+        for (PRUint32 i = 0, i_end = newAnimations.Length();
+             i != i_end; ++i) {
+          ElementAnimation *newAnim = &newAnimations[i];
+
+          // Find the matching animation with this name in the old list
+          // of animations.  Because of this code, they must all have
+          // the same start time, though they might differ in pause
+          // state.  So if a page uses multiple copies of the same
+          // animation in one element's animation list, and gives them
+          // different pause states, they, well, get what they deserve.
+          // We'll use the last one since it's more likely to be the one
+          // doing something.
+          const ElementAnimation *oldAnim = nsnull;
+          for (PRUint32 j = ea->mAnimations.Length(); j-- != 0; ) {
+            const ElementAnimation *a = &ea->mAnimations[j];
+            if (a->mName == newAnim->mName) {
+              oldAnim = a;
+              break;
+            }
+          }
+          if (!oldAnim) {
+            continue;
+          }
+
+          newAnim->mStartTime = oldAnim->mStartTime;
+
+          if (oldAnim->IsPaused()) {
+            if (newAnim->IsPaused()) {
+              // Copy pause start just like start time.
+              newAnim->mPauseStart = oldAnim->mPauseStart;
+            } else {
+              // Handle change in pause state by adjusting start
+              // time to unpause.
+              newAnim->mStartTime += refreshTime - oldAnim->mPauseStart;
+            }
+          }
+        }
+      }
+    } else {
+      ea = GetElementAnimations(aElement, aStyleContext->GetPseudoType(),
+                                PR_TRUE);
+    }
+    ea->mAnimations.SwapElements(newAnimations);
+    ea->mNeedsRefreshes = true;
+
+    ea->EnsureStyleRuleFor(refreshTime);
+  }
+
+  return GetAnimationRule(aElement, aStyleContext->GetPseudoType());
+}
+
+class PercentageHashKey : public PLDHashEntryHdr
+{
+public:
+  typedef const float& KeyType;
+  typedef const float* KeyTypePointer;
+
+  PercentageHashKey(KeyTypePointer aKey) : mValue(*aKey) { }
+  PercentageHashKey(const PercentageHashKey& toCopy) : mValue(toCopy.mValue) { }
+  ~PercentageHashKey() { }
+
+  KeyType GetKey() const { return mValue; }
+  PRBool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
+
+  static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+  static PLDHashNumber HashKey(KeyTypePointer aKey) {
+    PR_STATIC_ASSERT(sizeof(PLDHashNumber) == sizeof(PRUint32));
+    PR_STATIC_ASSERT(PLDHashNumber(-1) > PLDHashNumber(0));
+    float key = *aKey;
+    NS_ABORT_IF_FALSE(0.0f <= key && key <= 1.0f, "out of range");
+    return PLDHashNumber(key * PR_UINT32_MAX);
+  }
+  enum { ALLOW_MEMMOVE = PR_TRUE };
+
+private:
+  const float mValue;
+};
+
+struct KeyframeData {
+  float mKey;
+  nsCSSKeyframeRule *mRule;
+};
+
+typedef InfallibleTArray<KeyframeData> KeyframeDataArray;
+
+static PLDHashOperator
+AppendKeyframeData(const float &aKey, nsCSSKeyframeRule *aRule, void *aData)
+{
+  KeyframeDataArray *array = static_cast<KeyframeDataArray*>(aData);
+  KeyframeData *data = array->AppendElement();
+  data->mKey = aKey;
+  data->mRule = aRule;
+  return PL_DHASH_NEXT;
+}
+
+struct KeyframeDataComparator {
+  PRBool Equals(const KeyframeData& A, const KeyframeData& B) const {
+    return A.mKey == B.mKey;
+  }
+  PRBool LessThan(const KeyframeData& A, const KeyframeData& B) const {
+    return A.mKey < B.mKey;
+  }
+};
+
+class ResolvedStyleCache {
+public:
+  ResolvedStyleCache() {
+    mCache.Init(16); // FIXME: make infallible!
+  }
+  nsStyleContext* Get(nsPresContext *aPresContext,
+                      nsStyleContext *aParentStyleContext,
+                      nsCSSKeyframeRule *aKeyframe);
+
+private:
+  nsRefPtrHashtable<nsPtrHashKey<nsCSSKeyframeRule>, nsStyleContext> mCache;
+};
+
+nsStyleContext*
+ResolvedStyleCache::Get(nsPresContext *aPresContext,
+                        nsStyleContext *aParentStyleContext,
+                        nsCSSKeyframeRule *aKeyframe)
+{
+  // FIXME (spec):  The css3-animations spec isn't very clear about how
+  // properties are resolved when they have values that depend on other
+  // properties (e.g., values in 'em').  I presume that they're resolved
+  // relative to the other styles of the element.  The question is
+  // whether they are resolved relative to other animations:  I assume
+  // that they're not, since that would prevent us from caching a lot of
+  // data that we'd really like to cache (in particular, the
+  // nsStyleAnimation::Value values in AnimationSegmentProperty).
+  nsStyleContext *result = mCache.GetWeak(aKeyframe);
+  if (!result) {
+    nsCOMArray<nsIStyleRule> rules;
+    rules.AppendObject(aKeyframe);
+    nsRefPtr<nsStyleContext> resultStrong = aPresContext->StyleSet()->
+      ResolveStyleByAddingRules(aParentStyleContext, rules);
+    mCache.Put(aKeyframe, resultStrong);
+    result = resultStrong;
+  }
+  return result;
+}
+
+void
+nsAnimationManager::BuildAnimations(nsStyleContext* aStyleContext,
+                                    InfallibleTArray<ElementAnimation>& aAnimations)
+{
+  NS_ABORT_IF_FALSE(aAnimations.IsEmpty(), "expect empty array");
+
+  ResolvedStyleCache resolvedStyles;
+
+  const nsStyleDisplay *disp = aStyleContext->GetStyleDisplay();
+  TimeStamp now = mPresContext->RefreshDriver()->MostRecentRefresh();
+  for (PRUint32 i = 0, i_end = disp->mAnimations.Length(); i != i_end; ++i) {
+    const nsAnimation& aSrc = disp->mAnimations[i];
+    ElementAnimation& aDest = *aAnimations.AppendElement();
+
+    aDest.mName = aSrc.GetName();
+    aDest.mIterationCount = aSrc.GetIterationCount();
+    aDest.mDirection = aSrc.GetDirection();
+    aDest.mFillMode = aSrc.GetFillMode();
+    aDest.mPlayState = aSrc.GetPlayState();
+
+    aDest.mStartTime = now + TimeDuration::FromMilliseconds(aSrc.GetDelay());
+    if (aDest.IsPaused()) {
+      aDest.mPauseStart = now;
+    } else {
+      aDest.mPauseStart = TimeStamp();
+    }
+
+    aDest.mIterationDuration = TimeDuration::FromMilliseconds(aSrc.GetDuration());
+
+    nsCSSKeyframesRule *rule = KeyframesRuleFor(aDest.mName);
+    if (!rule) {
+      // no segments
+      continue;
+    }
+
+    // Build the set of unique keyframes in the @keyframes rule.  Per
+    // css3-animations, later keyframes with the same key replace
+    // earlier ones (no cascading).
+    nsDataHashtable<PercentageHashKey, nsCSSKeyframeRule*> keyframes;
+    keyframes.Init(16); // FIXME: make infallible!
+    for (PRUint32 j = 0, j_end = rule->StyleRuleCount(); j != j_end; ++j) {
+      nsICSSRule* cssRule = rule->GetStyleRuleAt(j);
+      NS_ABORT_IF_FALSE(cssRule, "must have rule");
+      NS_ABORT_IF_FALSE(cssRule->GetType() == nsICSSRule::KEYFRAME_RULE,
+                        "must be keyframe rule");
+      nsCSSKeyframeRule *kfRule = static_cast<nsCSSKeyframeRule*>(cssRule);
+
+      const nsTArray<float> &keys = kfRule->GetKeys();
+      for (PRUint32 k = 0, k_end = keys.Length(); k != k_end; ++k) {
+        float key = keys[k];
+        // FIXME (spec):  The spec doesn't say what to do with
+        // out-of-range keyframes.  We'll ignore them.
+        // (And PercentageHashKey currently assumes we either ignore or
+        // clamp them.)
+        if (0.0f <= key && key <= 1.0f) {
+          keyframes.Put(key, kfRule);
+        }
+      }
+    }
+
+    KeyframeDataArray sortedKeyframes;
+    keyframes.EnumerateRead(AppendKeyframeData, &sortedKeyframes);
+    sortedKeyframes.Sort(KeyframeDataComparator());
+
+    if (sortedKeyframes.Length() == 0) {
+      // no segments
+      continue;
+    }
+
+    KeyframeData fromKeyframe = sortedKeyframes[0];
+    nsRefPtr<nsStyleContext> fromContext =
+      resolvedStyles.Get(mPresContext, aStyleContext,
+                         fromKeyframe.mRule);
+
+    // If there's no rule for 0%, there's implicitly an empty rule.
+    if (fromKeyframe.mKey != 0.0f) {
+      BuildSegment(aDest.mSegments, aSrc,
+                   0.0f, aStyleContext, nsnull,
+                   fromKeyframe.mKey, fromContext,
+                     fromKeyframe.mRule->Declaration());
+    }
+
+    for (PRUint32 j = 1, j_end = sortedKeyframes.Length(); j != j_end; ++j) {
+      KeyframeData toKeyframe = sortedKeyframes[j];
+      nsRefPtr<nsStyleContext> toContext =
+        resolvedStyles.Get(mPresContext, aStyleContext, toKeyframe.mRule);
+
+      BuildSegment(aDest.mSegments, aSrc,
+                   fromKeyframe.mKey, fromContext,
+                   fromKeyframe.mRule->Declaration(),
+                   toKeyframe.mKey, toContext,
+                   toKeyframe.mRule->Declaration());
+
+      fromContext = toContext;
+      fromKeyframe = toKeyframe;
+    }
+
+    // If there's no rule for 100%, there's implicitly an empty rule.
+    if (fromKeyframe.mKey != 1.0f) {
+      BuildSegment(aDest.mSegments, aSrc,
+                   fromKeyframe.mKey, fromContext,
+                   fromKeyframe.mRule->Declaration(),
+                   1.0f, aStyleContext, nsnull);
+    }
+  }
+}
+
+void
+nsAnimationManager::BuildSegment(InfallibleTArray<AnimationSegment>& aSegments,
+                                 const nsAnimation& aAnimation,
+                                 float aFromKey, nsStyleContext* aFromContext,
+                                 mozilla::css::Declaration* aFromDeclaration,
+                                 float aToKey, nsStyleContext* aToContext,
+                                 mozilla::css::Declaration* aToDeclaration)
+{
+  AnimationSegment &segment = *aSegments.AppendElement();
+
+  segment.mFromKey = aFromKey;
+  segment.mToKey = aToKey;
+  const nsTimingFunction *tf;
+  if (aFromDeclaration &&
+      aFromDeclaration->HasProperty(eCSSProperty_animation_timing_function)) {
+    tf = &aFromContext->GetStyleDisplay()->mAnimations[0].GetTimingFunction();
+  } else {
+    tf = &aAnimation.GetTimingFunction();
+  }
+  segment.mTimingFunction.Init(*tf);
+
+  for (nsCSSProperty prop = nsCSSProperty(0);
+       prop < eCSSProperty_COUNT_no_shorthands;
+       prop = nsCSSProperty(prop + 1)) {
+    if (nsCSSProps::kAnimTypeTable[prop] == eStyleAnimType_None) {
+      continue;
+    }
+
+    if (!(aFromDeclaration && aFromDeclaration->HasProperty(prop)) &&
+        !(aToDeclaration && aToDeclaration->HasProperty(prop))) {
+      // Don't store an animation if neither declaration has the property.
+      continue;
+    }
+
+    nsStyleAnimation::Value fromValue, toValue, dummyValue;
+    if (ExtractComputedValueForTransition(prop, aFromContext, fromValue) &&
+        ExtractComputedValueForTransition(prop, aToContext, toValue) &&
+        // Check that we can interpolate between these values
+        // (If this is ever a performance problem, we could add a
+        // CanInterpolate method, but it seems fine for now.)
+        nsStyleAnimation::Interpolate(prop, fromValue, toValue,
+                                      0.5, dummyValue)) {
+      AnimationSegmentProperty &p = *segment.mProperties.AppendElement();
+      p.mProperty = prop;
+      p.mFromValue = fromValue;
+      p.mToValue = toValue;
+    }
+  }
+}
+
+nsIStyleRule*
+nsAnimationManager::GetAnimationRule(mozilla::dom::Element* aElement,
+                                     nsCSSPseudoElements::Type aPseudoType)
+{
+  NS_ABORT_IF_FALSE(
+    aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ||
+    aPseudoType == nsCSSPseudoElements::ePseudo_before ||
+    aPseudoType == nsCSSPseudoElements::ePseudo_after,
+    "forbidden pseudo type");
+
+  ElementAnimations *ea =
+    GetElementAnimations(aElement, aPseudoType, PR_FALSE);
+  if (!ea) {
+    return nsnull;
+  }
+
+  NS_ABORT_IF_FALSE(ea->mStyleRuleRefreshTime ==
+                      mPresContext->RefreshDriver()->MostRecentRefresh(),
+                    "should already have refreshed style rule");
+
+  if (mPresContext->IsProcessingRestyles() &&
+      !mPresContext->IsProcessingAnimationStyleChange()) {
+    // During the non-animation part of processing restyles, we don't
+    // add the animation rule.
+
+    if (ea->mStyleRule) {
+      ea->PostRestyleForAnimation(mPresContext);
+    }
+
+    return nsnull;
+  }
+
+  return ea->mStyleRule;
+}
+
+/* virtual */ void
+nsAnimationManager::WillRefresh(mozilla::TimeStamp aTime)
+{
+  NS_ABORT_IF_FALSE(mPresContext,
+                    "refresh driver should not notify additional observers "
+                    "after pres context has been destroyed");
+  if (!mPresContext->GetPresShell()) {
+    // Someone might be keeping mPresContext alive past the point
+    // where it has been torn down; don't bother doing anything in
+    // this case.  But do get rid of all our transitions so we stop
+    // triggering refreshes.
+    RemoveAllElementData();
+    return;
+  }
+
+  // FIXME: check that there's at least one style rule that's not
+  // in its "done" state, and if there isn't, remove ourselves from
+  // the refresh driver (but leave the animations!).
+  for (PRCList *l = PR_LIST_HEAD(&mElementData); l != &mElementData;
+       l = PR_NEXT_LINK(l)) {
+    ElementAnimations *ea = static_cast<ElementAnimations*>(l);
+    nsRefPtr<css::AnimValuesStyleRule> oldStyleRule = ea->mStyleRule;
+    ea->EnsureStyleRuleFor(mPresContext->RefreshDriver()->MostRecentRefresh());
+    if (oldStyleRule != ea->mStyleRule) {
+      ea->PostRestyleForAnimation(mPresContext);
+    }
+  }
+}
+
+nsCSSKeyframesRule*
+nsAnimationManager::KeyframesRuleFor(const nsSubstring& aName)
+{
+  if (mKeyframesListIsDirty) {
+    mKeyframesListIsDirty = false;
+
+    nsTArray<nsCSSKeyframesRule*> rules;
+    mPresContext->StyleSet()->AppendKeyframesRules(mPresContext, rules);
+
+    // Per css3-animations, the last @keyframes rule specified wins.
+    mKeyframesRules.Clear();
+    for (PRUint32 i = 0, i_end = rules.Length(); i != i_end; ++i) {
+      nsCSSKeyframesRule *rule = rules[i];
+      mKeyframesRules.Put(rule->GetName(), rule);
+    }
+  }
+
+  return mKeyframesRules.Get(aName);
+}
+
new file mode 100644
--- /dev/null
+++ b/layout/style/nsAnimationManager.h
@@ -0,0 +1,117 @@
+/* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is nsAnimationManager, an implementation of part
+ * of css3-animations.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   L. David Baron <dbaron@dbaron.org>, Mozilla Corporation (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+#ifndef nsAnimationManager_h_
+#define nsAnimationManager_h_
+
+#include "AnimationCommon.h"
+#include "nsCSSPseudoElements.h"
+#include "nsStyleContext.h"
+#include "nsTHashtable.h"
+
+class nsCSSKeyframesRule;
+struct AnimationSegment;
+struct ElementAnimation;
+struct ElementAnimations;
+
+namespace mozilla {
+namespace css {
+class Declaration;
+}
+}
+
+class nsAnimationManager : public mozilla::css::CommonAnimationManager
+{
+public:
+  nsAnimationManager(nsPresContext *aPresContext)
+    : mozilla::css::CommonAnimationManager(aPresContext),
+      mKeyframesListIsDirty(true)
+  {
+    mKeyframesRules.Init(16); // FIXME: make infallible!
+  }
+
+  // nsIStyleRuleProcessor (parts)
+  virtual void RulesMatching(ElementRuleProcessorData* aData);
+  virtual void RulesMatching(PseudoElementRuleProcessorData* aData);
+  virtual void RulesMatching(AnonBoxRuleProcessorData* aData);
+#ifdef MOZ_XUL
+  virtual void RulesMatching(XULTreeRuleProcessorData* aData);
+#endif
+
+  // nsARefreshObserver
+  virtual void WillRefresh(mozilla::TimeStamp aTime);
+
+  /**
+   * Return the style rule that RulesMatching should add for
+   * aStyleContext.  This might be different from what RulesMatching
+   * actually added during aStyleContext's construction because the
+   * element's animation-name may have changed.  (However, this does
+   * return null during the non-animation restyling phase, as
+   * RulesMatching does.)
+   *
+   * aStyleContext may be a style context for aElement or for its
+   * :before or :after pseudo-element.
+   */
+  nsIStyleRule* CheckAnimationRule(nsStyleContext* aStyleContext,
+                                   mozilla::dom::Element* aElement);
+
+  void KeyframesListIsDirty() {
+    mKeyframesListIsDirty = PR_TRUE;
+  }
+
+private:
+  ElementAnimations* GetElementAnimations(mozilla::dom::Element *aElement,
+                                          nsCSSPseudoElements::Type aPseudoType,
+                                          PRBool aCreateIfNeeded);
+  void BuildAnimations(nsStyleContext* aStyleContext,
+                       InfallibleTArray<ElementAnimation>& aAnimations);
+  void BuildSegment(InfallibleTArray<AnimationSegment>& aSegments,
+                    const nsAnimation& aAnimation,
+                    float aFromKey, nsStyleContext* aFromContext,
+                    mozilla::css::Declaration* aFromDeclaration,
+                    float aToKey, nsStyleContext* aToContext,
+                    mozilla::css::Declaration* aToDeclaration);
+  nsIStyleRule* GetAnimationRule(mozilla::dom::Element* aElement,
+                                 nsCSSPseudoElements::Type aPseudoType);
+
+  nsCSSKeyframesRule* KeyframesRuleFor(const nsSubstring& aName);
+
+  bool mKeyframesListIsDirty;
+  nsDataHashtable<nsStringHashKey, nsCSSKeyframesRule*> mKeyframesRules;
+};
+
+#endif /* !defined(nsAnimationManager_h_) */
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -54,16 +54,17 @@
 #include "nsCSSAnonBoxes.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSRuleProcessor.h"
 #include "nsIContent.h"
 #include "nsIFrame.h"
 #include "nsContentUtils.h"
 #include "nsRuleProcessorData.h"
 #include "nsTransitionManager.h"
+#include "nsAnimationManager.h"
 #include "nsIEventStateManager.h"
 #include "mozilla/dom/Element.h"
 
 using namespace mozilla::dom;
 
 NS_IMPL_ISUPPORTS1(nsEmptyStyleRule, nsIStyleRule)
 
 /* virtual */ void
@@ -111,16 +112,17 @@ nsStyleSet::Init(nsPresContext *aPresCon
   }
 
   mRuleTree = nsRuleNode::CreateRootNode(aPresContext);
   if (!mRuleTree) {
     mDefaultStyleData.Destroy(0, aPresContext);
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  GatherRuleProcessors(eAnimationSheet);
   GatherRuleProcessors(eTransitionSheet);
 
   return NS_OK;
 }
 
 nsresult
 nsStyleSet::BeginReconstruct()
 {
@@ -191,16 +193,23 @@ nsStyleSet::GatherRuleProcessors(sheetTy
 {
   mRuleProcessors[aType] = nsnull;
   if (mAuthorStyleDisabled && (aType == eDocSheet || 
                                aType == ePresHintSheet ||
                                aType == eStyleAttrSheet)) {
     //don't regather if this level is disabled
     return NS_OK;
   }
+  if (aType == eAnimationSheet) {
+    // We have no sheet for the animations level; just a rule
+    // processor.  (XXX: We should probably do this for the other
+    // non-CSS levels too!)
+    mRuleProcessors[aType] = PresContext()->AnimationManager();
+    return NS_OK;
+  }
   if (aType == eTransitionSheet) {
     // We have no sheet for the transitions level; just a rule
     // processor.  (XXX: We should probably do this for the other
     // non-CSS levels too!)
     mRuleProcessors[aType] = PresContext()->TransitionManager();
     return NS_OK;
   }
   if (mSheets[aType].Count()) {
@@ -420,17 +429,19 @@ nsStyleSet::GetContext(nsStyleContext* a
                        // it means that we don't need to force creation
                        // of a StyleIfVisited.  (But if we make one
                        // because aParentContext has one, then aRuleNode
                        // should be used.)
                        nsRuleNode* aVisitedRuleNode,
                        PRBool aIsLink,
                        PRBool aIsVisitedLink,
                        nsIAtom* aPseudoTag,
-                       nsCSSPseudoElements::Type aPseudoType)
+                       nsCSSPseudoElements::Type aPseudoType,
+                       PRBool aDoAnimations,
+                       Element* aElementForAnimation)
 {
   NS_PRECONDITION((!aPseudoTag &&
                    aPseudoType ==
                      nsCSSPseudoElements::ePseudo_NotPseudoElement) ||
                   (aPseudoTag &&
                    nsCSSPseudoElements::GetPseudoType(aPseudoTag) ==
                      aPseudoType),
                   "Pseudo mismatch");
@@ -503,16 +514,61 @@ nsStyleSet::GetContext(nsStyleContext* a
     if (!aParentContext)
       mRoots.AppendElement(result);
   }
   else {
     NS_ASSERTION(result->GetPseudoType() == aPseudoType, "Unexpected type");
     NS_ASSERTION(result->GetPseudo() == aPseudoTag, "Unexpected pseudo");
   }
 
+  if (aDoAnimations) {
+    // Normally the animation manager has already added the correct
+    // style rule.  However, if the animation-name just changed, it
+    // might have been wrong.  So ask it to double-check based on the
+    // resulting style context.
+    nsIStyleRule *animRule = PresContext()->AnimationManager()->
+      CheckAnimationRule(result, aElementForAnimation);
+    bool rerun;
+    NS_ABORT_IF_FALSE(result->GetRuleNode() == aRuleNode,
+                      "unexpected rule node");
+    NS_ABORT_IF_FALSE(!result->GetStyleIfVisited() == !aVisitedRuleNode,
+                      "unexpected visited rule node");
+    NS_ABORT_IF_FALSE(!aVisitedRuleNode ||
+                      result->GetStyleIfVisited()->GetRuleNode() ==
+                        aVisitedRuleNode,
+                      "unexpected visited rule node");
+    if (animRule) {
+      rerun = aRuleNode->GetRule() != animRule;
+    } else {
+      rerun = !aRuleNode->IsRoot() && aRuleNode->GetLevel() == eAnimationSheet;
+    }
+    if (rerun) {
+      nsRuleNode *ruleNode = (aRuleNode->GetLevel() == eAnimationSheet)
+                               ? aRuleNode->GetParent() : aRuleNode;
+      if (animRule) {
+        ruleNode = ruleNode->Transition(animRule, eAnimationSheet, PR_FALSE);
+      }
+
+      nsRuleNode *visitedRuleNode;
+      if (aVisitedRuleNode) {
+        visitedRuleNode = (aVisitedRuleNode->GetLevel() == eAnimationSheet)
+                            ? aVisitedRuleNode->GetParent() : aVisitedRuleNode;
+        if (animRule) {
+          ruleNode = visitedRuleNode->Transition(animRule, eAnimationSheet,
+                                                 PR_FALSE);
+        }
+      } else {
+        visitedRuleNode = nsnull;
+      }
+      result = GetContext(aParentContext, ruleNode, visitedRuleNode,
+                          aIsLink, aIsVisitedLink,
+                          aPseudoTag, aPseudoType, PR_FALSE, nsnull);
+    }
+  }
+
   return result.forget();
 }
 
 void
 nsStyleSet::AddImportantRules(nsRuleNode* aCurrLevelNode,
                               nsRuleNode* aLastPrevLevelNode,
                               nsRuleWalker* aRuleWalker)
 {
@@ -677,16 +733,19 @@ nsStyleSet::FileRules(nsIStyleRuleProces
   }
 #endif
 
 #ifdef DEBUG
   nsRuleNode *lastImportantRN = aRuleWalker->CurrentNode();
 #endif
   aRuleWalker->SetLevel(eTransitionSheet, PR_FALSE, PR_FALSE);
   (*aCollectorFunc)(mRuleProcessors[eTransitionSheet], aData);
+  // GetContext() depends on the animation rules being *last*
+  aRuleWalker->SetLevel(eAnimationSheet, PR_FALSE, PR_FALSE);
+  (*aCollectorFunc)(mRuleProcessors[eAnimationSheet], aData);
 #ifdef DEBUG
   AssertNoCSSRules(aRuleWalker->CurrentNode(), lastImportantRN);
   AssertNoImportantRules(aRuleWalker->CurrentNode(), lastImportantRN);
 #endif
 
 }
 
 // Enumerate all the rules in a way that doesn't care about the order
@@ -718,16 +777,18 @@ nsStyleSet::WalkRuleProcessors(nsIStyleR
   if (!skipUserStyles && !cutOffInheritance &&
       mRuleProcessors[eDocSheet]) // NOTE: different
     (*aFunc)(mRuleProcessors[eDocSheet], aData);
   if (mRuleProcessors[eStyleAttrSheet])
     (*aFunc)(mRuleProcessors[eStyleAttrSheet], aData);
   if (mRuleProcessors[eOverrideSheet])
     (*aFunc)(mRuleProcessors[eOverrideSheet], aData);
   (*aFunc)(mRuleProcessors[eTransitionSheet], aData);
+  // GetContext depends on the animation rule being *last*
+  (*aFunc)(mRuleProcessors[eAnimationSheet], aData);
 }
 
 PRBool nsStyleSet::BuildDefaultStyleData(nsPresContext* aPresContext)
 {
   NS_ASSERTION(!mDefaultStyleData.mResetData &&
                !mDefaultStyleData.mInheritedData,
                "leaking default style data");
   mDefaultStyleData.mResetData = new (aPresContext) nsResetStyleData;
@@ -792,17 +853,18 @@ nsStyleSet::ResolveStyleFor(Element* aEl
               &ruleWalker);
     visitedRuleNode = ruleWalker.CurrentNode();
   }
 
   return GetContext(aParentContext, ruleNode, visitedRuleNode,
                     nsCSSRuleProcessor::IsLink(aElement),
                     nsCSSRuleProcessor::GetContentState(aElement).
                       HasState(NS_EVENT_STATE_VISITED),
-                    nsnull, nsCSSPseudoElements::ePseudo_NotPseudoElement);
+                    nsnull, nsCSSPseudoElements::ePseudo_NotPseudoElement,
+                    PR_TRUE, aElement);
 }
 
 already_AddRefed<nsStyleContext>
 nsStyleSet::ResolveStyleForRules(nsStyleContext* aParentContext,
                                  const nsCOMArray<nsIStyleRule> &aRules)
 {
   NS_ENSURE_FALSE(mInShutdown, nsnull);
 
@@ -811,17 +873,18 @@ nsStyleSet::ResolveStyleForRules(nsStyle
   // matter.
   ruleWalker.SetLevel(eDocSheet, PR_FALSE, PR_FALSE);
   for (PRInt32 i = 0; i < aRules.Count(); i++) {
     ruleWalker.Forward(aRules.ObjectAt(i));
   }
 
   return GetContext(aParentContext, ruleWalker.CurrentNode(), nsnull,
                     PR_FALSE, PR_FALSE,
-                    nsnull, nsCSSPseudoElements::ePseudo_NotPseudoElement);
+                    nsnull, nsCSSPseudoElements::ePseudo_NotPseudoElement,
+                    PR_FALSE, nsnull);
 }
 
 already_AddRefed<nsStyleContext>
 nsStyleSet::ResolveStyleByAddingRules(nsStyleContext* aBaseContext,
                                       const nsCOMArray<nsIStyleRule> &aRules)
 {
   NS_ENSURE_FALSE(mInShutdown, nsnull);
 
@@ -844,26 +907,27 @@ nsStyleSet::ResolveStyleByAddingRules(ns
     }
     visitedRuleNode = ruleWalker.CurrentNode();
   }
 
   return GetContext(aBaseContext->GetParent(), ruleNode, visitedRuleNode,
                     aBaseContext->IsLinkContext(),
                     aBaseContext->RelevantLinkVisited(),
                     aBaseContext->GetPseudo(),
-                    aBaseContext->GetPseudoType());
+                    aBaseContext->GetPseudoType(),
+                    PR_FALSE, nsnull);
 }
 
 already_AddRefed<nsStyleContext>
 nsStyleSet::ResolveStyleForNonElement(nsStyleContext* aParentContext)
 {
   return GetContext(aParentContext, mRuleTree, nsnull,
                     PR_FALSE, PR_FALSE,
                     nsCSSAnonBoxes::mozNonElement,
-                    nsCSSPseudoElements::ePseudo_AnonBox);
+                    nsCSSPseudoElements::ePseudo_AnonBox, PR_FALSE, nsnull);
 }
 
 void
 nsStyleSet::WalkRestrictionRule(nsCSSPseudoElements::Type aPseudoType,
                                 nsRuleWalker* aRuleWalker)
 {
   // This needs to match GetPseudoRestriction in nsRuleNode.cpp.
   aRuleWalker->SetLevel(eAgentSheet, PR_FALSE, PR_FALSE);
@@ -903,17 +967,20 @@ nsStyleSet::ResolvePseudoElementStyle(El
               aParentElement, &ruleWalker);
     visitedRuleNode = ruleWalker.CurrentNode();
   }
 
   return GetContext(aParentContext, ruleNode, visitedRuleNode,
                     // For pseudos, |data.IsLink()| being true means that
                     // our parent node is a link.
                     PR_FALSE, PR_FALSE,
-                    nsCSSPseudoElements::GetPseudoAtom(aType), aType);
+                    nsCSSPseudoElements::GetPseudoAtom(aType), aType,
+                    aType == nsCSSPseudoElements::ePseudo_before ||
+                    aType == nsCSSPseudoElements::ePseudo_after,
+                    aParentElement);
 }
 
 already_AddRefed<nsStyleContext>
 nsStyleSet::ProbePseudoElementStyle(Element* aParentElement,
                                     nsCSSPseudoElements::Type aType,
                                     nsStyleContext* aParentContext)
 {
   TreeMatchContext treeContext(PR_TRUE, nsRuleWalker::eRelevantLinkUnvisited,
@@ -960,17 +1027,20 @@ nsStyleSet::ProbePseudoElementStyle(Elem
     visitedRuleNode = ruleWalker.CurrentNode();
   }
 
   nsRefPtr<nsStyleContext> result =
     GetContext(aParentContext, ruleNode, visitedRuleNode,
                // For pseudos, |data.IsLink()| being true means that
                // our parent node is a link.
                PR_FALSE, PR_FALSE,
-               pseudoTag, aType);
+               pseudoTag, aType,
+               aType == nsCSSPseudoElements::ePseudo_before ||
+               aType == nsCSSPseudoElements::ePseudo_after,
+               aParentElement);
 
   // For :before and :after pseudo-elements, having display: none or no
   // 'content' property is equivalent to not having the pseudo-element
   // at all.
   if (result &&
       (pseudoTag == nsCSSPseudoElements::before ||
        pseudoTag == nsCSSPseudoElements::after)) {
     const nsStyleDisplay *display = result->GetStyleDisplay();
@@ -1002,17 +1072,18 @@ nsStyleSet::ResolveAnonymousBoxStyle(nsI
 
   nsRuleWalker ruleWalker(mRuleTree);
   AnonBoxRuleProcessorData data(PresContext(), aPseudoTag, &ruleWalker);
   FileRules(EnumRulesMatching<AnonBoxRuleProcessorData>, &data, nsnull,
             &ruleWalker);
 
   return GetContext(aParentContext, ruleWalker.CurrentNode(), nsnull,
                     PR_FALSE, PR_FALSE,
-                    aPseudoTag, nsCSSPseudoElements::ePseudo_AnonBox);
+                    aPseudoTag, nsCSSPseudoElements::ePseudo_AnonBox,
+                    PR_FALSE, nsnull);
 }
 
 #ifdef MOZ_XUL
 already_AddRefed<nsStyleContext>
 nsStyleSet::ResolveXULTreePseudoStyle(Element* aParentElement,
                                       nsIAtom* aPseudoTag,
                                       nsStyleContext* aParentContext,
                                       nsICSSPseudoComparator* aComparator)
@@ -1041,17 +1112,18 @@ nsStyleSet::ResolveXULTreePseudoStyle(El
               aParentElement, &ruleWalker);
     visitedRuleNode = ruleWalker.CurrentNode();
   }
 
   return GetContext(aParentContext, ruleNode, visitedRuleNode,
                     // For pseudos, |data.IsLink()| being true means that
                     // our parent node is a link.
                     PR_FALSE, PR_FALSE,
-                    aPseudoTag, nsCSSPseudoElements::ePseudo_XULTree);
+                    aPseudoTag, nsCSSPseudoElements::ePseudo_XULTree,
+                    PR_FALSE, nsnull);
 }
 #endif
 
 PRBool
 nsStyleSet::AppendFontFaceRules(nsPresContext* aPresContext,
                                 nsTArray<nsFontFaceRuleContainer>& aArray)
 {
   NS_ENSURE_FALSE(mInShutdown, PR_FALSE);
@@ -1200,16 +1272,17 @@ nsStyleSet::ReparentStyleContext(nsStyle
   nsCSSPseudoElements::Type pseudoType = aStyleContext->GetPseudoType();
   nsRuleNode* ruleNode = aStyleContext->GetRuleNode();
 
   // Skip transition rules as needed just like
   // nsTransitionManager::WalkTransitionRule would.
   PRBool skipTransitionRules = PresContext()->IsProcessingRestyles() &&
     !PresContext()->IsProcessingAnimationStyleChange();
   if (skipTransitionRules) {
+    // FIXME do something here for animations?
     // Make sure that we're not using transition rules for our new style
     // context.  If we need them, an animation restyle will provide.
     ruleNode =
       SkipTransitionRules(ruleNode, aElement,
                           pseudoType !=
                             nsCSSPseudoElements::ePseudo_NotPseudoElement);
   }
 
@@ -1218,27 +1291,32 @@ nsStyleSet::ReparentStyleContext(nsStyle
   // Reparenting a style context just changes where we inherit from,
   // not what rules we match or what our DOM looks like.  In
   // particular, it doesn't change whether this is a style context for
   // a link.
   if (visitedContext) {
      visitedRuleNode = visitedContext->GetRuleNode();
      // Again, skip transition rules as needed
      if (skipTransitionRules) {
+      // FIXME do something here for animations?
        visitedRuleNode =
          SkipTransitionRules(visitedRuleNode, aElement,
                              pseudoType !=
                                nsCSSPseudoElements::ePseudo_NotPseudoElement);
      }
   }
 
   return GetContext(aNewParentContext, ruleNode, visitedRuleNode,
                     aStyleContext->IsLinkContext(),
                     aStyleContext->RelevantLinkVisited(),
-                    pseudoTag, pseudoType);
+                    pseudoTag, pseudoType,
+                    pseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement ||
+                    pseudoType == nsCSSPseudoElements::ePseudo_before ||
+                    pseudoType == nsCSSPseudoElements::ePseudo_after,
+                    aElement);
 }
 
 struct StatefulData : public StateRuleProcessorData {
   StatefulData(nsPresContext* aPresContext, Element* aElement,
                nsEventStates aStateMask, TreeMatchContext& aTreeMatchContext)
     : StateRuleProcessorData(aPresContext, aElement, aStateMask,
                              aTreeMatchContext),
       mHint(nsRestyleHint(0))
--- a/layout/style/nsStyleSet.h
+++ b/layout/style/nsStyleSet.h
@@ -235,16 +235,17 @@ class nsStyleSet
   enum sheetType {
     eAgentSheet, // CSS
     eUserSheet, // CSS
     ePresHintSheet,
     eDocSheet, // CSS
     eStyleAttrSheet,
     eOverrideSheet, // CSS
     eTransitionSheet,
+    eAnimationSheet,
     eSheetTypeCount
     // be sure to keep the number of bits in |mDirty| below and in
     // NS_RULE_NODE_LEVEL_MASK updated when changing the number of sheet
     // types
   };
 
   // APIs to manipulate the style sheet lists.  The sheets in each
   // list are stored with the most significant sheet last.
@@ -359,17 +360,19 @@ class nsStyleSet
 
   already_AddRefed<nsStyleContext>
   GetContext(nsStyleContext* aParentContext,
              nsRuleNode* aRuleNode,
              nsRuleNode* aVisitedRuleNode,
              PRBool aIsLink,
              PRBool aIsVisitedLink,
              nsIAtom* aPseudoTag,
-             nsCSSPseudoElements::Type aPseudoType);
+             nsCSSPseudoElements::Type aPseudoType,
+             PRBool aDoAnimation,
+             mozilla::dom::Element* aElementForAnimation);
 
   nsPresContext* PresContext() { return mRuleTree->GetPresContext(); }
 
   // The sheets in each array in mSheets are stored with the most significant
   // sheet last.
   nsCOMArray<nsIStyleSheet> mSheets[eSheetTypeCount];
 
   nsCOMPtr<nsIStyleRuleProcessor> mRuleProcessors[eSheetTypeCount];
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -90,16 +90,17 @@ css_properties.js: host_ListCSSPropertie
 	$(RM) $@
 	./host_ListCSSProperties$(HOST_BIN_SUFFIX) > $@
 	cat $(srcdir)/css_properties_like_longhand.js >> $@
 
 GARBAGE += css_properties.js
 
 
 _TEST_FILES =	test_acid3_test46.html \
+		test_animations.html \
 		test_any_dynamic.html \
 		test_at_rule_parse_serialize.html \
 		test_bug73586.html \
 		test_bug74880.html \
 		test_bug98997.html \
 		test_bug160403.html \
 		test_bug221428.html \
 		test_bug229915.html \
--- a/layout/style/test/animation_utils.js
+++ b/layout/style/test/animation_utils.js
@@ -27,8 +27,36 @@ function bezier(x1, y1, x2, y2) {
         return (mint + maxt) / 2;
     }
     return function bezier_closure(x) {
         if (x == 0) return 0;
         if (x == 1) return 1;
         return y_for_t(t_for_x(x));
     }
 }
+
+function step_end(nsteps) {
+    return function step_end_closure(x) {
+        return Math.floor(x * nsteps) / nsteps;
+    }
+}
+
+function step_start(nsteps) {
+    var stepend = step_end(nsteps);
+    return function step_start_closure(x) {
+        return 1.0 - stepend(1.0 - x);
+    }
+}
+
+var gTF = {
+  "ease": bezier(0.25, 0.1, 0.25, 1),
+  "linear": function(x) { return x; },
+  "ease_in": bezier(0.42, 0, 1, 1),
+  "ease_out": bezier(0, 0, 0.58, 1),
+  "ease_in_out": bezier(0.42, 0, 0.58, 1),
+  "step_start": step_start(1),
+  "step_end": step_end(1),
+};
+
+function is_approx(float1, float2, error, desc) {
+  ok(Math.abs(float1 - float2) < error,
+     desc + ": " + float1 + " and " + float2 + " should be within " + error);
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_animations.html
@@ -0,0 +1,999 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435442
+-->
+<head>
+  <title>Test for css3-animations (Bug 435442)</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="animation_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style type="text/css">
+  @-moz-keyframes anim1 {
+     0% { margin-left: 0px }
+     50% { margin-left: 80px }
+     100% { margin-left: 100px }
+  }
+  @-moz-keyframes anim2 {
+    from { margin-right: 0 } to { margin-right: 100px }
+  }
+  @-moz-keyframes anim3 {
+    from { margin-top: 0 } to { margin-top: 100px }
+  }
+  @-moz-keyframes anim4 {
+    from { margin-bottom: 0 } to { margin-bottom: 100px }
+  }
+  @-moz-keyframes anim5 {
+    from { margin-left: 0 } to { margin-left: 100px }
+  }
+
+  @-moz-keyframes kf1 {
+    50% { margin-top: 50px }
+    to { margin-top: 150px }
+  }
+  @-moz-keyframes kf2 {
+    from { margin-top: 150px }
+    50% { margin-top: 50px }
+  }
+  @-moz-keyframes kf3 {
+    25% { margin-top: 100px }
+  }
+  @-moz-keyframes kf4 {
+    to, from { display: inline; margin-top: 37px }
+  }
+  @-moz-keyframes kf_cascade1 {
+    from { padding-top: 50px }
+    50%, from { padding-top: 30px }      /* wins: 0% */
+    75%, 85%, 50% { padding-top: 20px }  /* wins: 75%, 50% */
+    100%, 85% { padding-top: 70px }      /* wins: 100% */
+    85.1% { padding-top: 60px }          /* wins: 85.1% */
+    85% { padding-top: 30px }            /* wins: 85% */
+  }
+  @-moz-keyframes kf_cascade2 { from, to { margin-top: 100px } }
+  @-moz-keyframes kf_cascade2 { from, to { margin-left: 200px } }
+  @-moz-keyframes kf_cascade2 { from, to { margin-left: 300px } }
+  @-moz-keyframes kf_tf1 {
+    0%   { padding-bottom: 20px; -moz-animation-timing-function: ease }
+    25%  { padding-bottom: 60px; }
+    50%  { padding-bottom: 160px; -moz-animation-timing-function: steps(5) }
+    75%  { padding-bottom: 120px; -moz-animation-timing-function: linear }
+    100% { padding-bottom: 20px; -moz-animation-timing-function: ease-out }
+  }
+  </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435442">Mozilla Bug 435442</a>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+/** Test for css3-animations (Bug 435442) **/
+
+function advance_clock(milliseconds) {
+  SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
+}
+
+var display = document.getElementById("display");
+var div = null;
+var cs = null;
+function new_div(style) {
+  if (div != null || cs != null) {
+    ok(false, "test author forgot to call done_div");
+  }
+  if (typeof(style) != "string") {
+    ok(false, "test author forgot to pass argument");
+  }
+  div = document.createElement("div");
+  div.setAttribute("style", style);
+  display.appendChild(div);
+  cs = getComputedStyle(div, "");
+}
+function done_div() {
+  display.removeChild(div);
+  div = null;
+  cs = null;
+}
+
+// take over the refresh driver right from the start.
+advance_clock(0);
+
+/*
+ * css3-animations:  2. Animations
+ * http://dev.w3.org/csswg/css3-animations/#animations
+ */
+
+// Test that animations don't affect the computed value before the
+// start of the animation or after its end.  Test without
+// animation-fill-mode, but then repeat the test with all the values of
+// animation-fill-mode.
+function test_fill_mode(fill_mode, fills_backwards, fills_forwards)
+{
+  var style = "margin-left: 30px; -moz-animation: 10s 3s anim1 linear";
+  var desc;
+  if (fill_mode.length > 0) {
+    style += " " + fill_mode;
+    desc = "fill mode " + fill_mode + ": ";
+  } else {
+    desc = "default fill mode: ";
+  }
+  new_div(style);
+  if (fills_backwards)
+    is(cs.marginLeft, "0px", desc + "does affect value during delay (0s)");
+  else
+    is(cs.marginLeft, "30px", desc + "doesn't affect value during delay (0s)");
+  advance_clock(2000);
+  if (fills_backwards)
+    is(cs.marginLeft, "0px", desc + "does affect value during delay (2s)");
+  else
+    is(cs.marginLeft, "30px", desc + "doesn't affect value during delay (2s)");
+  advance_clock(1000);
+  if (fills_backwards)
+    is(cs.marginLeft, "0px", desc + "affects value at start of animation");
+  advance_clock(125);
+  is(cs.marginLeft, "2px", desc + "affects value during animation");
+  advance_clock(2375);
+  is(cs.marginLeft, "40px", desc + "affects value during animation");
+  advance_clock(2500);
+  is(cs.marginLeft, "80px", desc + "affects value during animation");
+  advance_clock(2500);
+  is(cs.marginLeft, "90px", desc + "affects value during animation");
+  advance_clock(2375);
+  is(cs.marginLeft, "99.5px", desc + "affects value during animation");
+  advance_clock(125);
+  if (fills_forwards)
+    is(cs.marginLeft, "100px", desc + "affects value at end of animation");
+  advance_clock(10);
+  if (fills_forwards)
+    is(cs.marginLeft, "100px", desc + "does affect value after animation");
+  else
+    is(cs.marginLeft, "30px", desc + "does not affect value after animation");
+  done_div();
+}
+test_fill_mode("", false, false);
+test_fill_mode("none", false, false);
+test_fill_mode("forwards", false, true);
+test_fill_mode("backwards", true, false);
+test_fill_mode("both", true, true);
+
+// Test that animations continue running when the animation name
+// list is changed.
+new_div("-moz-animation: anim1 linear 10s");
+  is(cs.getPropertyValue("margin-top"), "0px",
+     "just anim1, margin-top at start");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "just anim1, margin-right at start");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "just anim1, margin-bottom at start");
+  is(cs.getPropertyValue("margin-left"), "0px",
+     "just anim1, margin-left at start");
+advance_clock(1000);
+  is(cs.getPropertyValue("margin-top"), "0px",
+     "just anim1, margin-top at 1s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "just anim1, margin-right at 1s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "just anim1, margin-bottom at 1s");
+  is(cs.getPropertyValue("margin-left"), "16px",
+     "just anim1, margin-left at 1s");
+// append anim2
+div.style.MozAnimation = "anim1 linear 10s, anim2 linear 10s";
+  is(cs.getPropertyValue("margin-top"), "0px",
+     "anim1 + anim2, margin-top at 1s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim1 + anim2, margin-right at 1s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim1 + anim2, margin-bottom at 1s");
+  is(cs.getPropertyValue("margin-left"), "16px",
+     "anim1 + anim2, margin-left at 1s");
+advance_clock(1000);
+  is(cs.getPropertyValue("margin-top"), "0px",
+     "anim1 + anim2, margin-top at 2s");
+  is(cs.getPropertyValue("margin-right"), "10px",
+     "anim1 + anim2, margin-right at 2s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim1 + anim2, margin-bottom at 2s");
+  is(cs.getPropertyValue("margin-left"), "32px",
+     "anim1 + anim2, margin-left at 2s");
+// prepend anim3
+div.style.MozAnimation = "anim3 linear 10s, anim1 linear 10s, anim2 linear 10s";
+  is(cs.getPropertyValue("margin-top"), "0px",
+     "anim3 + anim1 + anim2, margin-top at 2s");
+  is(cs.getPropertyValue("margin-right"), "10px",
+     "anim3 + anim1 + anim2, margin-right at 2s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim3 + anim1 + anim2, margin-bottom at 2s");
+  is(cs.getPropertyValue("margin-left"), "32px",
+     "anim3 + anim1 + anim2, margin-left at 2s");
+advance_clock(1000);
+  is(cs.getPropertyValue("margin-top"), "10px",
+     "anim3 + anim1 + anim2, margin-top at 3s");
+  is(cs.getPropertyValue("margin-right"), "20px",
+     "anim3 + anim1 + anim2, margin-right at 3s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim3 + anim1 + anim2, margin-bottom at 3s");
+  is(cs.getPropertyValue("margin-left"), "48px",
+     "anim3 + anim1 + anim2, margin-left at 3s");
+// remove anim2 from end
+div.style.MozAnimation = "anim3 linear 10s, anim1 linear 10s";
+  is(cs.getPropertyValue("margin-top"), "10px",
+     "anim3 + anim1, margin-top at 3s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim3 + anim1, margin-right at 3s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim3 + anim1, margin-bottom at 3s");
+  is(cs.getPropertyValue("margin-left"), "48px",
+     "anim3 + anim1, margin-left at 3s");
+advance_clock(1000);
+  is(cs.getPropertyValue("margin-top"), "20px",
+     "anim3 + anim1, margin-top at 4s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim3 + anim1, margin-right at 4s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim3 + anim1, margin-bottom at 4s");
+  is(cs.getPropertyValue("margin-left"), "64px",
+     "anim3 + anim1, margin-left at 4s");
+// swap anim1 and anim3, change duration of anim3
+div.style.MozAnimation = "anim1 linear 10s, anim3 linear 5s";
+  is(cs.getPropertyValue("margin-top"), "40px",
+     "anim1 + anim3, margin-top at 4s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim1 + anim3, margin-right at 4s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim1 + anim3, margin-bottom at 4s");
+  is(cs.getPropertyValue("margin-left"), "64px",
+     "anim1 + anim3, margin-left at 4s");
+advance_clock(1000);
+  is(cs.getPropertyValue("margin-top"), "60px",
+     "anim1 + anim3, margin-top at 5s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim1 + anim3, margin-right at 5s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim1 + anim3, margin-bottom at 5s");
+  is(cs.getPropertyValue("margin-left"), "80px",
+     "anim1 + anim3, margin-left at 5s");
+// list anim1 twice, last duration wins, original start time still applies
+div.style.MozAnimation = "anim1 linear 10s, anim3 linear 5s, anim1 linear 20s";
+  is(cs.getPropertyValue("margin-top"), "60px",
+     "anim1 + anim3 + anim1, margin-top at 5s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim1 + anim3 + anim1, margin-right at 5s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim1 + anim3 + anim1, margin-bottom at 5s");
+  is(cs.getPropertyValue("margin-left"), "40px",
+     "anim1 + anim3 + anim1, margin-left at 5s");
+// drop one of the anim1, and list anim5 as well, which animates
+// the same property as anim1
+div.style.MozAnimation = "anim3 linear 5s, anim1 linear 20s, anim5 linear 10s";
+  is(cs.getPropertyValue("margin-top"), "60px",
+     "anim3 + anim1 + anim5, margin-top at 5s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim3 + anim1 + anim5, margin-right at 5s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim3 + anim1 + anim5, margin-bottom at 5s");
+  is(cs.getPropertyValue("margin-left"), "0px",
+     "anim3 + anim1 + anim5, margin-left at 5s");
+advance_clock(1000);
+  is(cs.getPropertyValue("margin-top"), "80px",
+     "anim3 + anim1 + anim5, margin-top at 6s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim3 + anim1 + anim5, margin-right at 6s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim3 + anim1 + anim5, margin-bottom at 6s");
+  is(cs.getPropertyValue("margin-left"), "10px",
+     "anim3 + anim1 + anim5, margin-left at 6s");
+// now swap the anim5 and anim1 order
+div.style.MozAnimation = "anim3 linear 5s, anim5 linear 10s, anim1 linear 20s";
+  is(cs.getPropertyValue("margin-top"), "80px",
+     "anim3 + anim1 + anim5, margin-top at 6s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim3 + anim1 + anim5, margin-right at 6s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim3 + anim1 + anim5, margin-bottom at 6s");
+  is(cs.getPropertyValue("margin-left"), "48px",
+     "anim3 + anim1 + anim5, margin-left at 6s");
+advance_clock(1000);
+  is(cs.getPropertyValue("margin-top"), "0px",
+     "anim3 + anim1 + anim5, margin-top at 7s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim3 + anim1 + anim5, margin-right at 7s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim3 + anim1 + anim5, margin-bottom at 7s");
+  is(cs.getPropertyValue("margin-left"), "56px",
+     "anim3 + anim1 + anim5, margin-left at 7s");
+// swap anim1 and anim5 back
+div.style.MozAnimation = "anim3 linear 5s, anim1 linear 20s, anim5 linear 10s";
+  is(cs.getPropertyValue("margin-top"), "0px",
+     "anim3 + anim1 + anim5, margin-top at 7s");
+  is(cs.getPropertyValue("margin-right"), "0px",
+     "anim3 + anim1 + anim5, margin-right at 7s");
+  is(cs.getPropertyValue("margin-bottom"), "0px",
+     "anim3 + anim1 + anim5, margin-bottom at 7s");
+  is(cs.getPropertyValue("margin-left"), "20px",
+     "anim3 + anim1 + anim5, margin-left at 7s");
+advance_clock(100);
+  is(cs.getPropertyValue("margin-top"), "0px",
+     "anim3 + anim1 + anim5, margin-top at 7.1s");
+// Change the animation fill mode on the completed animation.
+div.style.MozAnimation = "anim3 linear 5s forwards, anim1 linear 20s, anim5 linear 10s";
+  is(cs.getPropertyValue("margin-top"), "100px",
+     "anim3 + anim1 + anim5, margin-top at 7.1s, with fill mode");
+advance_clock(900);
+  is(cs.getPropertyValue("margin-top"), "100px",
+     "anim3 + anim1 + anim5, margin-top at 8s, with fill mode");
+// Change the animation duration on the completed animation, so it is
+// no longer completed.
+div.style.MozAnimation = "anim3 linear 10s, anim1 linear 20s, anim5 linear 10s";
+  is(cs.getPropertyValue("margin-top"), "60px",
+     "anim3 + anim1 + anim5, margin-top at 8s, with fill mode");
+  is(cs.getPropertyValue("margin-left"), "30px",
+     "anim3 + anim1 + anim5, margin-left at 8s");
+done_div();
+
+/*
+ * css3-animations:  3. Keyframes
+ * http://dev.w3.org/csswg/css3-animations/#keyframes
+ *
+ * Also see test_keyframes_rules.html .
+ */
+
+// Test the rules on keyframes that lack a 0% or 100% rule:
+// (simultaneously, test that reverse animations have their keyframes
+// run backwards)
+
+// 100px at 0%, 50px at 50%, 150px at 100%
+new_div("margin-top: 100px; -moz-animation: kf1 ease 1s alternate infinite");
+is(cs.marginTop, "100px", "no-0% at 0.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.2), 0.01,
+          "no-0% at 0.1s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.6), 0.01,
+          "no-0% at 0.3s");
+advance_clock(200);
+is(cs.marginTop, "50px", "no-0% at 0.5s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.4), 0.01,
+          "no-0% at 0.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.8), 0.01,
+          "no-0% at 0.9s");
+advance_clock(100);
+is(cs.marginTop, "150px", "no-0% at 1.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.8), 0.01,
+          "no-0% at 1.1s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 50 + 100 * gTF.ease(0.2), 0.01,
+          "no-0% at 1.4s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.6), 0.01,
+          "no-0% at 1.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease(0.2), 0.01,
+          "no-0% at 1.9s");
+advance_clock(100);
+is(cs.marginTop, "100px", "no-0% at 2.0s");
+done_div();
+
+// 150px at 0%, 50px at 50%, 100px at 100%
+new_div("margin-top: 100px; -moz-animation: kf2 ease-in 1s alternate infinite");
+is(cs.marginTop, "150px", "no-100% at 0.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.2), 0.01,
+          "no-100% at 0.1s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.6), 0.01,
+          "no-100% at 0.3s");
+advance_clock(200);
+is(cs.marginTop, "50px", "no-100% at 0.5s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.4), 0.01,
+          "no-100% at 0.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.8), 0.01,
+          "no-100% at 0.9s");
+advance_clock(100);
+is(cs.marginTop, "100px", "no-100% at 1.0s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.8), 0.01,
+          "no-100% at 1.1s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_in(0.2), 0.01,
+          "no-100% at 1.4s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.6), 0.01,
+          "no-100% at 1.7s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 150 - 100 * gTF.ease_in(0.2), 0.01,
+          "no-100% at 1.9s");
+advance_clock(100);
+is(cs.marginTop, "150px", "no-100% at 2.0s");
+done_div();
+
+
+// 50px at 0%, 100px at 25%, 50px at 100%
+new_div("margin-top: 50px; -moz-animation: kf3 ease-out 1s alternate infinite");
+is(cs.marginTop, "50px", "no-0%-no-100% at 0.0s");
+advance_clock(50);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.2), 0.01,
+          "no-0%-no-100% at 0.05s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.6), 0.01,
+          "no-0%-no-100% at 0.15s");
+advance_clock(100);
+is(cs.marginTop, "100px", "no-0%-no-100% at 0.25s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.4), 0.01,
+          "no-0%-no-100% at 0.55s");
+advance_clock(300);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.8), 0.01,
+          "no-0%-no-100% at 0.85s");
+advance_clock(150);
+is(cs.marginTop, "50px", "no-0%-no-100% at 1.0s");
+advance_clock(150);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.8), 0.01,
+          "no-0%-no-100% at 1.15s");
+advance_clock(450);
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_out(0.2), 0.01,
+          "no-0%-no-100% at 1.6s");
+advance_clock(250);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.6), 0.01,
+          "no-0%-no-100% at 1.85s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginTop), 50 + 50 * gTF.ease_out(0.2), 0.01,
+          "no-0%-no-100% at 1.95s");
+advance_clock(50);
+is(cs.marginTop, "50px", "no-0%-no-100% at 2.0s");
+done_div();
+
+// Test that non-animatable properties are ignored.
+// Simultaneously, test that the block is still honored, and that
+// we still override the value when two consecutive keyframes have
+// the same value.
+new_div("-moz-animation: kf4 ease 10s");
+is(cs.display, "block",
+   "non-animatable properties should be ignored (linear, 0s)");
+is(cs.marginTop, "37px",
+   "animatable properties should still apply (linear, 0s)");
+advance_clock(1000);
+is(cs.display, "block",
+   "non-animatable properties should be ignored (linear, 1s)");
+is(cs.marginTop, "37px",
+   "animatable properties should still apply (linear, 1s)");
+done_div();
+new_div("-moz-animation: kf4 step-start 10s");
+is(cs.display, "block",
+   "non-animatable properties should be ignored (step-start, 0s)");
+is(cs.marginTop, "37px",
+   "animatable properties should still apply (step-start, 0s)");
+advance_clock(1000);
+is(cs.display, "block",
+   "non-animatable properties should be ignored (step-start, 1s)");
+is(cs.marginTop, "37px",
+   "animatable properties should still apply (step-start, 1s)");
+done_div();
+
+// Test cascading of the keyframes within an @keyframes rule.
+new_div("-moz-animation: kf_cascade1 linear 10s");
+//   0%: 30px
+//  50%: 20px
+//  75%: 20px
+//  85%: 30px
+//  85.1%: 60px
+// 100%: 70px
+is(cs.paddingTop, "30px", "kf_cascade1 at 0s");
+advance_clock(2500);
+is(cs.paddingTop, "25px", "kf_cascade1 at 2.5s");
+advance_clock(2500);
+is(cs.paddingTop, "20px", "kf_cascade1 at 5s");
+advance_clock(2000);
+is(cs.paddingTop, "20px", "kf_cascade1 at 7s");
+advance_clock(500);
+is(cs.paddingTop, "20px", "kf_cascade1 at 7.5s");
+advance_clock(500);
+is(cs.paddingTop, "25px", "kf_cascade1 at 8s");
+advance_clock(500);
+is(cs.paddingTop, "30px", "kf_cascade1 at 8.5s");
+advance_clock(10);
+is(cs.paddingTop, "60px", "kf_cascade1 at 8.51s");
+advance_clock(745);
+is(cs.paddingTop, "65px", "kf_cascade1 at 9.2505s");
+done_div();
+
+// Test cascading of the @keyframes rules themselves.
+new_div("-moz-animation: kf_cascade2 linear 10s");
+is(cs.marginTop, "0px", "@keyframes rule with margin-top should be ignored");
+is(cs.marginLeft, "300px", "last @keyframes rule with margin-left should win");
+done_div();
+
+/*
+ * css3-animations:  3.1. Timing functions for keyframes
+ * http://dev.w3.org/csswg/css3-animations/#timing-functions-for-keyframes-
+ */
+new_div("-moz-animation: kf_tf1 ease-in 10s alternate infinite");
+is(cs.paddingBottom, "20px",
+   "keyframe timing functions test at 0s (test needed for flush)");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
+          "keyframe timing functions test at 1s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.8), 0.01,
+          "keyframe timing functions test at 2s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01,
+          "keyframe timing functions test at 3s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
+          "keyframe timing functions test at 4s");
+advance_clock(1000);
+is(cs.paddingBottom, "160px",
+   "keyframe timing functions test at 5s");
+advance_clock(1010); // avoid floating point error
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
+          "keyframe timing functions test at 6s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01,
+          "keyframe timing functions test at 7s");
+advance_clock(990);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
+          "keyframe timing functions test at 8s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.6), 0.01,
+          "keyframe timing functions test at 9s");
+advance_clock(1000);
+is(cs.paddingBottom, "20px",
+   "keyframe timing functions test at 10s");
+advance_clock(20000);
+is(cs.paddingBottom, "20px",
+   "keyframe timing functions test at 30s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.6), 0.01,
+          "keyframe timing functions test at 31s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
+          "keyframe timing functions test at 32s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.8), 0.01,
+          "keyframe timing functions test at 33s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
+          "keyframe timing functions test at 34s");
+advance_clock(1000);
+is(cs.paddingBottom, "160px",
+   "keyframe timing functions test at 35s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
+          "keyframe timing functions test at 36s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.2), 0.01,
+          "keyframe timing functions test at 37s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.8), 0.01,
+          "keyframe timing functions test at 38s");
+advance_clock(1000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
+          "keyframe timing functions test at 39s");
+advance_clock(1000);
+is(cs.paddingBottom, "20px",
+   "keyframe timing functions test at 40s");
+done_div();
+
+// spot-check the same thing without alternate
+new_div("-moz-animation: kf_tf1 ease-in 10s infinite");
+is(cs.paddingBottom, "20px",
+   "keyframe timing functions test at 0s (test needed for flush)");
+advance_clock(11000);
+is_approx(px_to_num(cs.paddingBottom), 20 + 40 * gTF.ease(0.4), 0.01,
+          "keyframe timing functions test at 11s");
+advance_clock(3000);
+is_approx(px_to_num(cs.paddingBottom), 60 + 100 * gTF.ease_in(0.6), 0.01,
+          "keyframe timing functions test at 14s");
+advance_clock(2000);
+is_approx(px_to_num(cs.paddingBottom), 160 - 40 * step_end(5)(0.4), 0.01,
+          "keyframe timing functions test at 16s");
+advance_clock(2000);
+is_approx(px_to_num(cs.paddingBottom), 120 - 100 * gTF.linear(0.2), 0.01,
+          "keyframe timing functions test at 18s");
+done_div();
+
+/*
+ * css3-animations:  3.2. The 'animation-name' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-name-property-
+ */
+
+// animation-name is reasonably well-tested up in the tests for Section
+// 2, particularly the tests that "Test that animations continue running
+// when the animation name list is changed."
+
+// Test that 'animation-name: none' steps the animation, and setting
+// it again starts a new one.
+
+new_div("");
+div.style.MozAnimation = "anim2 ease-in-out 10s";
+is(cs.marginRight, "0px", "after setting animation-name to anim2");
+advance_clock(1000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in_out(0.1), 0.01,
+          "before changing animation-name to none");
+div.style.MozAnimationName = "none";
+is(cs.marginRight, "0px", "after changing animation-name to none");
+advance_clock(1000);
+is(cs.marginRight, "0px", "after changing animation-name to none plus 1s");
+div.style.MozAnimationName = "anim2";
+is(cs.marginRight, "0px", "after changing animation-name to anim2");
+advance_clock(1000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in_out(0.1), 0.01,
+          "at 1s in animation when animation-name no longer none again");
+div.style.MozAnimationName = "none";
+is(cs.marginRight, "0px", "after changing animation-name to none");
+advance_clock(1000);
+is(cs.marginRight, "0px", "after changing animation-name to none plus 1s");
+done_div();
+
+/*
+ * css3-animations:  3.3. The 'animation-duration' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-duration-property-
+ */
+
+// FIXME: test animation-duration of 0 (quite a bit, including interaction
+// with fill-mode, count, and reversing), once I know what the right
+// behavior is.
+
+/*
+ * css3-animations:  3.4. The 'animation-timing-function' Property
+ * http://dev.w3.org/csswg/css3-animations/#animation-timing-function_tag
+ */
+
+// tested in tests for section 3.1
+
+/*
+ * css3-animations:  3.5. The 'animation-iteration-count' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-iteration-count-property-
+ */
+new_div("-moz-animation: anim2 ease-in 10s 0.3 forwards");
+is(cs.marginRight, "0px", "animation-iteration-count test 1 at 0s");
+advance_clock(2000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+          "animation-iteration-count test 1 at 2s");
+advance_clock(900);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.29), 0.01,
+          "animation-iteration-count test 1 at 2.9s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+          "animation-iteration-count test 1 at 3s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+          "animation-iteration-count test 1 at 3.1s");
+advance_clock(5000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.3), 0.01,
+          "animation-iteration-count test 1 at 8.1s");
+done_div();
+
+new_div("-moz-animation: anim2 ease-in 10s 0.3, anim3 ease-out 20s 1.2 alternate forwards, anim4 ease-in-out 5s 1.6 forwards");
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 0s");
+is(cs.marginTop, "0px", "animation-iteration-count test 3 at 0s");
+is(cs.marginBottom, "0px", "animation-iteration-count test 4 at 0s");
+advance_clock(2000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.2), 0.01,
+          "animation-iteration-count test 2 at 2s");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.1), 0.01,
+          "animation-iteration-count test 3 at 2s");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.4), 0.01,
+          "animation-iteration-count test 4 at 2s");
+advance_clock(900);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.29), 0.01,
+          "animation-iteration-count test 2 at 2.9s");
+advance_clock(200);
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 3.1s");
+advance_clock(1800);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.98), 0.01,
+          "animation-iteration-count test 4 at 4.9s");
+advance_clock(200);
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 5.1s");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.02), 0.01,
+          "animation-iteration-count test 4 at 5.1s");
+advance_clock(2800);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.58), 0.01,
+          "animation-iteration-count test 4 at 7.9s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+          "animation-iteration-count test 4 at 8s");
+advance_clock(100);
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+          "animation-iteration-count test 4 at 8.1s");
+advance_clock(11700);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.99), 0.01,
+          "animation-iteration-count test 3 at 19.8s");
+advance_clock(200);
+is(cs.marginTop, "100px", "animation-iteration-count test 3 at 20s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.99), 0.01,
+          "animation-iteration-count test 3 at 20.2s");
+advance_clock(3600);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.81), 0.01,
+          "animation-iteration-count test 3 at 23.8s");
+advance_clock(200);
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.8), 0.01,
+          "animation-iteration-count test 3 at 24s");
+advance_clock(200);
+is(cs.marginRight, "0px", "animation-iteration-count test 2 at 25s");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_out(0.8), 0.01,
+          "animation-iteration-count test 3 at 25s");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+          "animation-iteration-count test 4 at 25s");
+done_div();
+
+/*
+ * css3-animations:  3.6. The 'animation-direction' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-direction-property-
+ */
+
+// Tested in tests for sections 3.1 and 3.5.
+
+/*
+ * css3-animations:  3.7. The 'animation-play-state' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-play-state-property-
+ */
+
+// simple test with just one animation
+new_div("");
+div.style.MozAnimationTimingFunction = "ease";
+div.style.MozAnimationName = "anim1";
+div.style.MozAnimationDuration = "1s";
+div.style.MozAnimationDirection = "alternate";
+div.style.MozAnimationIterationCount = "2";
+is(cs.marginLeft, "0px", "animation-play-state test 1, at 0s");
+advance_clock(250);
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 at 250ms");
+div.style.MozAnimationPlayState = "paused";
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 at 250ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 still at 500ms");
+div.style.MozAnimationPlayState = "running";
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 still at 500ms");
+advance_clock(500);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 at 1000ms");
+advance_clock(250);
+is(cs.marginLeft, "100px", "animation-play-state test 1 at 1250ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 at 1500ms");
+div.style.MozAnimationPlayState = "paused";
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 at 1500ms");
+advance_clock(2000);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 at 3500ms");
+advance_clock(500);
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 at 4000ms");
+div.style.MozAnimationPlayState = "";
+is_approx(px_to_num(cs.marginLeft), 80 + 20 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 at 4000ms");
+advance_clock(500);
+is_approx(px_to_num(cs.marginLeft), 80 * gTF.ease(0.5), 0.01,
+          "animation-play-state test 1 at 4500ms");
+advance_clock(250);
+is(cs.marginLeft, "0px", "animation-play-state test 1, at 4750ms");
+advance_clock(250);
+is(cs.marginLeft, "0px", "animation-play-state test 1, at 5000ms");
+done_div();
+
+// more complicated test with multiple animations (and different directions
+// and iteration counts)
+new_div("");
+div.style.MozAnimationTimingFunction = "ease-out, ease-in, ease-in-out";
+div.style.MozAnimationName = "anim2, anim3, anim4";
+div.style.MozAnimationDuration = "1s, 2s, 1s";
+div.style.MozAnimationDirection = "alternate, normal, normal";
+div.style.MozAnimationIterationCount = "4, 2, infinite";
+is(cs.marginRight, "0px", "animation-play-state test 2, at 0s");
+is(cs.marginTop, "0px", "animation-play-state test 3, at 0s");
+is(cs.marginBottom, "0px", "animation-play-state test 4, at 0s");
+advance_clock(250);
+div.style.MozAnimationPlayState = "paused, running"; // pause 1 and 3
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+          "animation-play-state test 2 at 250ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.125), 0.01,
+          "animation-play-state test 3 at 250ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01,
+          "animation-play-state test 4 at 250ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+          "animation-play-state test 2 at 500ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.25), 0.01,
+          "animation-play-state test 3 at 500ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01,
+          "animation-play-state test 4 at 500ms");
+div.style.MozAnimationPlayState = "paused, running, running"; // unpause 3
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+          "animation-play-state test 2 at 500ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.25), 0.01,
+          "animation-play-state test 3 at 500ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.25), 0.01,
+          "animation-play-state test 4 at 500ms");
+advance_clock(250);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.25), 0.01,
+          "animation-play-state test 2 at 750ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+          "animation-play-state test 3 at 750ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.5), 0.01,
+          "animation-play-state test 4 at 750ms");
+div.style.MozAnimationPlayState = "running, paused"; // unpause 1, pause 2
+advance_clock(0); // notify refresh observers
+advance_clock(250);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.5), 0.01,
+          "animation-play-state test 2 at 1000ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+          "animation-play-state test 3 at 1000ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.75), 0.01,
+          "animation-play-state test 4 at 1000ms");
+div.style.MozAnimationPlayState = "paused"; // pause all
+advance_clock(0); // notify refresh observers
+advance_clock(3000);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.5), 0.01,
+          "animation-play-state test 2 at 4000ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+          "animation-play-state test 3 at 4000ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.75), 0.01,
+          "animation-play-state test 4 at 4000ms");
+div.style.MozAnimationPlayState = "running, paused"; // pause 2
+advance_clock(0); // notify refresh observers
+advance_clock(850);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.65), 0.01,
+          "animation-play-state test 2 at 4850ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+          "animation-play-state test 3 at 4850ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.6), 0.01,
+          "animation-play-state test 4 at 4850ms");
+advance_clock(300);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.35), 0.01,
+          "animation-play-state test 2 at 5150ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+          "animation-play-state test 3 at 5150ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.9), 0.01,
+          "animation-play-state test 4 at 5150ms");
+advance_clock(2300);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.05), 0.01,
+          "animation-play-state test 2 at 7450ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+          "animation-play-state test 3 at 7450ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.2), 0.01,
+          "animation-play-state test 4 at 7450ms");
+advance_clock(100);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 7550ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.375), 0.01,
+          "animation-play-state test 3 at 7550ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01,
+          "animation-play-state test 4 at 7550ms");
+div.style.MozAnimationPlayState = "running"; // unpause 2
+advance_clock(0); // notify refresh observers
+advance_clock(1000);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 7550ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.875), 0.01,
+          "animation-play-state test 3 at 7550ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01,
+          "animation-play-state test 4 at 7550ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 8050ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.125), 0.01,
+          "animation-play-state test 3 at 8050ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01,
+          "animation-play-state test 4 at 8050ms");
+advance_clock(1000);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 9050ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.625), 0.01,
+          "animation-play-state test 3 at 9050ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01,
+          "animation-play-state test 4 at 9050ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 9550ms");
+is_approx(px_to_num(cs.marginTop), 100 * gTF.ease_in(0.875), 0.01,
+          "animation-play-state test 3 at 9550ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.3), 0.01,
+          "animation-play-state test 4 at 9550ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "animation-play-state test 2 at 10050ms");
+is(cs.marginTop, "0px", "animation-play-state test 3 at 10050ms");
+is_approx(px_to_num(cs.marginBottom), 100 * gTF.ease_in_out(0.8), 0.01,
+          "animation-play-state test 4 at 10050ms");
+done_div();
+
+/*
+ * css3-animations:  3.8. The 'animation-delay' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-delay-property-
+ */
+
+// test positive delay
+new_div("-moz-animation: anim2 1s 0.5s ease-out");
+is(cs.marginRight, "0px", "positive delay test at 0ms");
+advance_clock(400);
+is(cs.marginRight, "0px", "positive delay test at 400ms");
+advance_clock(100);
+is(cs.marginRight, "0px", "positive delay test at 500ms");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+          "positive delay test at 500ms");
+done_div();
+
+// test dynamic changes to delay (i.e., that we only check delay once,
+// at the very start of the animation)
+new_div("-moz-animation: anim2 1s 0.5s ease-out");
+is(cs.marginRight, "0px", "dynamic delay delay test at 0ms");
+advance_clock(400);
+is(cs.marginRight, "0px", "dynamic delay delay test at 400ms");
+div.style.MozAnimationDelay = "0.2s";
+advance_clock(0);
+advance_clock(100);
+is(cs.marginRight, "0px", "dynamic delay delay test at 500ms");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+          "dynamic delay delay test at 500ms");
+div.style.MozAnimationDelay = "1s";
+advance_clock(0);
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.2), 0.01,
+          "dynamic delay delay test at 500ms");
+done_div();
+
+// test delay and play-state interaction
+new_div("-moz-animation: anim2 1s 0.5s ease-out");
+is(cs.marginRight, "0px", "delay and play-state delay test at 0ms");
+advance_clock(400);
+is(cs.marginRight, "0px", "delay and play-state delay test at 400ms");
+div.style.MozAnimationPlayState = "paused";
+advance_clock(0);
+advance_clock(100);
+is(cs.marginRight, "0px", "delay and play-state delay test at 500ms");
+advance_clock(500);
+is(cs.marginRight, "0px", "delay and play-state delay test at 1000ms");
+div.style.MozAnimationPlayState = "running";
+advance_clock(0);
+advance_clock(100);
+is(cs.marginRight, "0px", "delay and play-state delay test at 1100ms");
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+          "delay and play-state delay test at 1200ms");
+div.style.MozAnimationPlayState = "paused";
+advance_clock(0);
+advance_clock(100);
+is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_out(0.1), 0.01,
+          "delay and play-state delay test at 1300ms");
+done_div();
+
+// test negative delay and implicit starting values
+new_div("margin-top: 1000px");
+advance_clock(300);
+div.style.marginTop = "100px";
+div.style.MozAnimation = "kf1 1s -0.1s ease-in";
+is_approx(px_to_num(cs.marginTop), 100 - 50 * gTF.ease_in(0.2), 0.01,
+          "delay and implicit starting values test");
+done_div();
+
+/*
+ * css3-animations:  3.9. The 'animation-fill-mode' Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-fill-mode-property-
+ */
+
+// animation-fill-mode is tested in the tests for section (2).
+
+/*
+ * css3-animations:  3.10. The 'animation' Shorthand Property
+ * http://dev.w3.org/csswg/css3-animations/#the-animation-shorthand-property-
+ */
+
+// shorthand vs. longhand is adequately tested by the
+// property_database.js-based tests.
+
+SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+
+</script>
+</pre>
+</body>
+</html>