Bug 1040543 part 8 - Move timing parameters from AnimationPlayer to Animation; r=bz
authorBrian Birtles <birtles@gmail.com>
Sun, 10 Aug 2014 17:06:50 +1000
changeset 198790 d6d0f91ff4c4a8c52a8c0345aa5ee58d649a1776
parent 198789 34cb6ae672bdfc6c2e490d83623e646694955992
child 198791 3a12123bef3f2628252113aa71582bc9f3894350
push id27286
push usernigelbabu@gmail.com
push dateMon, 11 Aug 2014 06:26:45 +0000
treeherdermozilla-central@8c4a1b3a2a8b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1040543
milestone34.0a1
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
Bug 1040543 part 8 - Move timing parameters from AnimationPlayer to Animation; r=bz As the second step in dividing functionality between AnimationPlayer and Animation, this patch moves the AnimationTiming member from AnimationPlayer to Animation. Most of this patch is simply moving code around. However, one significant functional difference is that Animation::GetLocalTime() uses the mParentTime member which is set when the Animation is updated by the player it is attached to. Other less significant differences are: * AnimationPlayer::GetLocalTime is renamed to GetCurrentTimeDuration In Web Animations, animation players have a (writeable) "current time" and animations have a (read-only) "local time". We would call the method simply "GetCurrentTime" (instead of "GetCurrentTimeDuration") but GetCurrentTime is the name of the method used in the content-facing API where it returns a double. * "IsCurrent" is defined on both AnimationPlayer and Animation with the version in AnimationPlayer serving mostly as a convenience shortcut to the version on Animation. * Animation::GetComputedTiming (previously on AnimationPlayer) now makes the timing parameter optional since most of the time it is not needed.
dom/animation/Animation.cpp
dom/animation/Animation.h
dom/animation/AnimationPlayer.cpp
dom/animation/AnimationPlayer.h
gfx/layers/composite/AsyncCompositionManager.cpp
layout/base/nsDisplayList.cpp
layout/style/AnimationCommon.cpp
layout/style/nsAnimationManager.cpp
layout/style/nsTransitionManager.cpp
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -1,17 +1,65 @@
 /* vim: set shiftwidth=2 tabstop=8 autoindent cindent expandtab: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Animation.h"
 #include "mozilla/dom/AnimationBinding.h"
+#include "mozilla/FloatingPoint.h"
 
 namespace mozilla {
+
+void
+ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
+{
+  mType = aFunction.mType;
+  if (mType == nsTimingFunction::Function) {
+    mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
+                         aFunction.mFunc.mX2, aFunction.mFunc.mY2);
+  } else {
+    mSteps = aFunction.mSteps;
+  }
+}
+
+static inline double
+StepEnd(uint32_t aSteps, double aPortion)
+{
+  NS_ABORT_IF_FALSE(0.0 <= aPortion && aPortion <= 1.0, "out of range");
+  uint32_t step = uint32_t(aPortion * aSteps); // floor
+  return double(step) / double(aSteps);
+}
+
+double
+ComputedTimingFunction::GetValue(double aPortion) const
+{
+  switch (mType) {
+    case nsTimingFunction::Function:
+      return mTimingFunction.GetSplineValue(aPortion);
+    case nsTimingFunction::StepStart:
+      // There are diagrams in the spec that seem to suggest this check
+      // and the bounds point should not be symmetric with StepEnd, but
+      // should actually step up at rather than immediately after the
+      // fraction points.  However, we rely on rounding negative values
+      // up to zero, so we can't do that.  And it's not clear the spec
+      // really meant it.
+      return 1.0 - StepEnd(mSteps, 1.0 - aPortion);
+    default:
+      NS_ABORT_IF_FALSE(false, "bad type");
+      // fall through
+    case nsTimingFunction::StepEnd:
+      return StepEnd(mSteps, aPortion);
+  }
+}
+
+// In the Web Animations model, the time fraction can be outside the range
+// [0.0, 1.0] but it shouldn't be Infinity.
+const double ComputedTiming::kNullTimeFraction = PositiveInfinity<double>();
+
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Animation, mDocument)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Animation, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Animation, Release)
 
 JSObject*
@@ -21,16 +69,164 @@ Animation::WrapObject(JSContext* aCx)
 }
 
 void
 Animation::SetParentTime(Nullable<TimeDuration> aParentTime)
 {
   mParentTime = aParentTime;
 }
 
+ComputedTiming
+Animation::GetComputedTimingAt(const Nullable<TimeDuration>& aLocalTime,
+                               const AnimationTiming& aTiming)
+{
+  const TimeDuration zeroDuration;
+
+  // Currently we expect negative durations to be picked up during CSS
+  // parsing but when we start receiving timing parameters from other sources
+  // we will need to clamp negative durations here.
+  // For now, if we're hitting this it probably means we're overflowing
+  // integer arithmetic in mozilla::TimeStamp.
+  MOZ_ASSERT(aTiming.mIterationDuration >= zeroDuration,
+             "Expecting iteration duration >= 0");
+
+  // Always return the same object to benefit from return-value optimization.
+  ComputedTiming result;
+
+  result.mActiveDuration = ActiveDuration(aTiming);
+
+  // The default constructor for ComputedTiming sets all other members to
+  // values consistent with an animation that has not been sampled.
+  if (aLocalTime.IsNull()) {
+    return result;
+  }
+  const TimeDuration& localTime = aLocalTime.Value();
+
+  // When we finish exactly at the end of an iteration we need to report
+  // the end of the final iteration and not the start of the next iteration
+  // so we set up a flag for that case.
+  bool isEndOfFinalIteration = false;
+
+  // Get the normalized time within the active interval.
+  TimeDuration activeTime;
+  // FIXME: The following check that the active duration is not equal to Forever
+  // is a temporary workaround to avoid overflow and should be removed once
+  // bug 1039924 is fixed.
+  if (result.mActiveDuration != TimeDuration::Forever() &&
+      localTime >= aTiming.mDelay + result.mActiveDuration) {
+    result.mPhase = ComputedTiming::AnimationPhase_After;
+    if (!aTiming.FillsForwards()) {
+      // The animation isn't active or filling at this time.
+      result.mTimeFraction = ComputedTiming::kNullTimeFraction;
+      return result;
+    }
+    activeTime = result.mActiveDuration;
+    // Note that infinity == floor(infinity) so this will also be true when we
+    // have finished an infinitely repeating animation of zero duration.
+    isEndOfFinalIteration =
+      aTiming.mIterationCount != 0.0 &&
+      aTiming.mIterationCount == floor(aTiming.mIterationCount);
+  } else if (localTime < aTiming.mDelay) {
+    result.mPhase = ComputedTiming::AnimationPhase_Before;
+    if (!aTiming.FillsBackwards()) {
+      // The animation isn't active or filling at this time.
+      result.mTimeFraction = ComputedTiming::kNullTimeFraction;
+      return result;
+    }
+    // activeTime is zero
+  } else {
+    MOZ_ASSERT(result.mActiveDuration != zeroDuration,
+               "How can we be in the middle of a zero-duration interval?");
+    result.mPhase = ComputedTiming::AnimationPhase_Active;
+    activeTime = localTime - aTiming.mDelay;
+  }
+
+  // Get the position within the current iteration.
+  TimeDuration iterationTime;
+  if (aTiming.mIterationDuration != zeroDuration) {
+    iterationTime = isEndOfFinalIteration
+                    ? aTiming.mIterationDuration
+                    : activeTime % aTiming.mIterationDuration;
+  } /* else, iterationTime is zero */
+
+  // Determine the 0-based index of the current iteration.
+  if (isEndOfFinalIteration) {
+    result.mCurrentIteration =
+      aTiming.mIterationCount == NS_IEEEPositiveInfinity()
+      ? UINT64_MAX // FIXME: When we return this via the API we'll need
+                   // to make sure it ends up being infinity.
+      : static_cast<uint64_t>(aTiming.mIterationCount) - 1;
+  } else if (activeTime == zeroDuration) {
+    // If the active time is zero we're either in the first iteration
+    // (including filling backwards) or we have finished an animation with an
+    // iteration duration of zero that is filling forwards (but we're not at
+    // the exact end of an iteration since we deal with that above).
+    result.mCurrentIteration =
+      result.mPhase == ComputedTiming::AnimationPhase_After
+      ? static_cast<uint64_t>(aTiming.mIterationCount) // floor
+      : 0;
+  } else {
+    result.mCurrentIteration =
+      static_cast<uint64_t>(activeTime / aTiming.mIterationDuration); // floor
+  }
+
+  // Normalize the iteration time into a fraction of the iteration duration.
+  if (result.mPhase == ComputedTiming::AnimationPhase_Before) {
+    result.mTimeFraction = 0.0;
+  } else if (result.mPhase == ComputedTiming::AnimationPhase_After) {
+    result.mTimeFraction = isEndOfFinalIteration
+                         ? 1.0
+                         : fmod(aTiming.mIterationCount, 1.0f);
+  } else {
+    // We are in the active phase so the iteration duration can't be zero.
+    MOZ_ASSERT(aTiming.mIterationDuration != zeroDuration,
+               "In the active phase of a zero-duration animation?");
+    result.mTimeFraction =
+      aTiming.mIterationDuration == TimeDuration::Forever()
+      ? 0.0
+      : iterationTime / aTiming.mIterationDuration;
+  }
+
+  bool thisIterationReverse = false;
+  switch (aTiming.mDirection) {
+    case NS_STYLE_ANIMATION_DIRECTION_NORMAL:
+      thisIterationReverse = false;
+      break;
+    case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
+      thisIterationReverse = true;
+      break;
+    case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE:
+      thisIterationReverse = (result.mCurrentIteration & 1) == 1;
+      break;
+    case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
+      thisIterationReverse = (result.mCurrentIteration & 1) == 0;
+      break;
+  }
+  if (thisIterationReverse) {
+    result.mTimeFraction = 1.0 - result.mTimeFraction;
+  }
+
+  return result;
+}
+
+TimeDuration
+Animation::ActiveDuration(const AnimationTiming& aTiming)
+{
+  if (aTiming.mIterationCount == mozilla::PositiveInfinity<float>()) {
+    // An animation that repeats forever has an infinite active duration
+    // unless its iteration duration is zero, in which case it has a zero
+    // active duration.
+    const TimeDuration zeroDuration;
+    return aTiming.mIterationDuration == zeroDuration
+           ? zeroDuration
+           : TimeDuration::Forever();
+  }
+  return aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount);
+}
+
 bool
 Animation::HasAnimationOfProperty(nsCSSProperty aProperty) const
 {
   for (size_t propIdx = 0, propEnd = mProperties.Length();
        propIdx != propEnd; ++propIdx) {
     if (aProperty == mProperties[propIdx].mProperty) {
       return true;
     }
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -16,16 +16,79 @@
 #include "mozilla/dom/Nullable.h"
 #include "nsSMILKeySpline.h"
 #include "nsStyleStruct.h" // for nsTimingFunction
 
 struct JSContext;
 
 namespace mozilla {
 
+/**
+ * Input timing parameters.
+ *
+ * Eventually this will represent all the input timing parameters specified
+ * by content but for now it encapsulates just the subset of those
+ * parameters passed to GetPositionInIteration.
+ */
+struct AnimationTiming
+{
+  TimeDuration mIterationDuration;
+  TimeDuration mDelay;
+  float mIterationCount; // mozilla::PositiveInfinity<float>() means infinite
+  uint8_t mDirection;
+  uint8_t mFillMode;
+
+  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;
+  }
+};
+
+/**
+ * Stores the results of calculating the timing properties of an animation
+ * at a given sample time.
+ */
+struct ComputedTiming
+{
+  ComputedTiming()
+    : mTimeFraction(kNullTimeFraction)
+    , mCurrentIteration(0)
+    , mPhase(AnimationPhase_Null)
+  { }
+
+  static const double kNullTimeFraction;
+
+  // The total duration of the animation including all iterations.
+  // Will equal TimeDuration::Forever() if the animation repeats indefinitely.
+  TimeDuration mActiveDuration;
+
+  // Will be kNullTimeFraction if the animation is neither animating nor
+  // filling at the sampled time.
+  double mTimeFraction;
+
+  // Zero-based iteration index (meaningless if mTimeFraction is
+  // kNullTimeFraction).
+  uint64_t mCurrentIteration;
+
+  enum {
+    // Not sampled (null sample time)
+    AnimationPhase_Null,
+    // Sampled prior to the start of the active interval
+    AnimationPhase_Before,
+    // Sampled within the active interval
+    AnimationPhase_Active,
+    // Sampled after (or at) the end of the active interval
+    AnimationPhase_After
+  } mPhase;
+};
+
 class ComputedTimingFunction
 {
 public:
   typedef nsTimingFunction::Type Type;
   void Init(const nsTimingFunction &aFunction);
   double GetValue(double aPortion) const;
   const nsSMILKeySpline* GetFunction() const {
     NS_ASSERTION(mType == nsTimingFunction::Function, "Type mismatch");
@@ -53,45 +116,98 @@ struct AnimationProperty
   InfallibleTArray<AnimationPropertySegment> mSegments;
 };
 
 namespace dom {
 
 class Animation MOZ_FINAL : public nsWrapperCache
 {
 public:
-  explicit Animation(nsIDocument* aDocument)
+  Animation(nsIDocument* aDocument, const AnimationTiming &aTiming)
     : mDocument(aDocument)
+    , mTiming(aTiming)
   {
     SetIsDOMBinding();
   }
 
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Animation)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(Animation)
 
   nsIDocument* GetParentObject() const { return mDocument; }
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   void SetParentTime(Nullable<TimeDuration> aParentTime);
 
+  const AnimationTiming& Timing() const {
+    return mTiming;
+  }
+  AnimationTiming& Timing() {
+    return mTiming;
+  }
+
+  // Return the duration from the start the active interval to the point where
+  // the animation begins playback. This is zero unless the animation has
+  // a negative delay in which case it is the absolute value of the delay.
+  // This is used for setting the elapsedTime member of CSS AnimationEvents.
+  TimeDuration InitialAdvance() const {
+    return std::max(TimeDuration(), mTiming.mDelay * -1);
+  }
+
+  Nullable<TimeDuration> GetLocalTime() const {
+    // Since the *animation* start time is currently always zero, the local
+    // time is equal to the parent time.
+    return mParentTime;
+  }
+
+  // This function takes as input the timing parameters of an animation and
+  // returns the computed timing at the specified local time.
+  //
+  // The local time may be null in which case only static parameters such as the
+  // active duration are calculated. All other members of the returned object
+  // are given a null/initial value.
+  //
+  // This function returns ComputedTiming::kNullTimeFraction for the
+  // mTimeFraction member of the return value if the animation should not be
+  // run (because it is not currently active and is not filling at this time).
+  static ComputedTiming
+  GetComputedTimingAt(const Nullable<TimeDuration>& aLocalTime,
+                      const AnimationTiming& aTiming);
+
+  // Shortcut for that gets the computed timing using the current local time as
+  // calculated from the timeline time.
+  ComputedTiming GetComputedTiming(const AnimationTiming* aTiming
+                                     = nullptr) const {
+    return GetComputedTimingAt(GetLocalTime(), aTiming ? *aTiming : mTiming);
+  }
+
+  // Return the duration of the active interval for the given timing parameters.
+  static TimeDuration ActiveDuration(const AnimationTiming& aTiming);
+
+  bool IsCurrent() const {
+    ComputedTiming computedTiming = GetComputedTiming();
+    return computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
+           computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
+  }
+
   bool HasAnimationOfProperty(nsCSSProperty aProperty) const;
   const InfallibleTArray<AnimationProperty>& Properties() const {
     return mProperties;
   }
   InfallibleTArray<AnimationProperty>& Properties() {
     return mProperties;
   }
 
 protected:
   virtual ~Animation() { }
 
   // We use a document for a parent object since the other likely candidate,
   // the target element, can be empty.
   nsRefPtr<nsIDocument> mDocument;
   Nullable<TimeDuration> mParentTime;
 
+  AnimationTiming mTiming;
   InfallibleTArray<AnimationProperty> mProperties;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Animation_h
--- a/dom/animation/AnimationPlayer.cpp
+++ b/dom/animation/AnimationPlayer.cpp
@@ -2,63 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AnimationPlayer.h"
 #include "mozilla/dom/AnimationPlayerBinding.h"
 
 namespace mozilla {
-
-void
-ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
-{
-  mType = aFunction.mType;
-  if (mType == nsTimingFunction::Function) {
-    mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
-                         aFunction.mFunc.mX2, aFunction.mFunc.mY2);
-  } else {
-    mSteps = aFunction.mSteps;
-  }
-}
-
-static inline double
-StepEnd(uint32_t aSteps, double aPortion)
-{
-  NS_ABORT_IF_FALSE(0.0 <= aPortion && aPortion <= 1.0, "out of range");
-  uint32_t step = uint32_t(aPortion * aSteps); // floor
-  return double(step) / double(aSteps);
-}
-
-double
-ComputedTimingFunction::GetValue(double aPortion) const
-{
-  switch (mType) {
-    case nsTimingFunction::Function:
-      return mTimingFunction.GetSplineValue(aPortion);
-    case nsTimingFunction::StepStart:
-      // There are diagrams in the spec that seem to suggest this check
-      // and the bounds point should not be symmetric with StepEnd, but
-      // should actually step up at rather than immediately after the
-      // fraction points.  However, we rely on rounding negative values
-      // up to zero, so we can't do that.  And it's not clear the spec
-      // really meant it.
-      return 1.0 - StepEnd(mSteps, 1.0 - aPortion);
-    default:
-      NS_ABORT_IF_FALSE(false, "bad type");
-      // fall through
-    case nsTimingFunction::StepEnd:
-      return StepEnd(mSteps, aPortion);
-  }
-}
-
-// In the Web Animations model, the time fraction can be outside the range
-// [0.0, 1.0] but it shouldn't be Infinity.
-const double ComputedTiming::kNullTimeFraction = PositiveInfinity<double>();
-
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AnimationPlayer, mTimeline, mSource)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AnimationPlayer, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AnimationPlayer, Release)
 
 JSObject*
@@ -72,21 +25,17 @@ AnimationPlayer::StartTime() const
 {
   Nullable<double> startTime = mTimeline->ToTimelineTime(mStartTime);
   return startTime.IsNull() ? 0.0 : startTime.Value();
 }
 
 double
 AnimationPlayer::CurrentTime() const
 {
-  // In Web Animations, AnimationPlayers have a *current* time and Animations
-  // have a *local* time. However, since we have a 1:1 correspondence between
-  // AnimationPlayers and Animations, and since the startTime of *Animations*
-  // (but not AnimationPlayers) is always 0, these are currently identical.
-  Nullable<TimeDuration> currentTime = GetLocalTime();
+  Nullable<TimeDuration> currentTime = GetCurrentTimeDuration();
 
   // The current time is currently only going to be null when don't have a
   // refresh driver (e.g. because we are in a display:none iframe).
   //
   // Web Animations says that in this case we should use a timeline time of
   // 0 (the "effective timeline time") and calculate the current time from that.
   // Doing that, however, requires storing the start time as an offset rather
   // than a timestamp so for now we just return 0.
@@ -104,193 +53,39 @@ AnimationPlayer::CurrentTime() const
 void
 AnimationPlayer::SetSource(Animation* aSource)
 {
   if (mSource) {
     mSource->SetParentTime(Nullable<TimeDuration>());
   }
   mSource = aSource;
   if (mSource) {
-    mSource->SetParentTime(GetLocalTime());
+    mSource->SetParentTime(GetCurrentTimeDuration());
   }
 }
 
 void
 AnimationPlayer::Tick()
 {
   if (mSource) {
-    mSource->SetParentTime(GetLocalTime());
+    mSource->SetParentTime(GetCurrentTimeDuration());
   }
 }
 
 bool
 AnimationPlayer::IsRunning() const
 {
-  if (IsPaused() || IsFinishedTransition()) {
+  if (IsPaused() || !GetSource() || IsFinishedTransition()) {
     return false;
   }
 
-  ComputedTiming computedTiming = GetComputedTiming(mTiming);
+  ComputedTiming computedTiming = GetSource()->GetComputedTiming();
   return computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
 }
 
 bool
 AnimationPlayer::IsCurrent() const
 {
-  if (IsFinishedTransition()) {
-    return false;
-  }
-
-  ComputedTiming computedTiming = GetComputedTiming(mTiming);
-  return computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
-         computedTiming.mPhase == ComputedTiming::AnimationPhase_Active;
-}
-
-ComputedTiming
-AnimationPlayer::GetComputedTimingAt(const Nullable<TimeDuration>& aLocalTime,
-                                      const AnimationTiming& aTiming)
-{
-  const TimeDuration zeroDuration;
-
-  // Currently we expect negative durations to be picked up during CSS
-  // parsing but when we start receiving timing parameters from other sources
-  // we will need to clamp negative durations here.
-  // For now, if we're hitting this it probably means we're overflowing
-  // integer arithmetic in mozilla::TimeStamp.
-  MOZ_ASSERT(aTiming.mIterationDuration >= zeroDuration,
-             "Expecting iteration duration >= 0");
-
-  // Always return the same object to benefit from return-value optimization.
-  ComputedTiming result;
-
-  result.mActiveDuration = ActiveDuration(aTiming);
-
-  // The default constructor for ComputedTiming sets all other members to
-  // values consistent with an animation that has not been sampled.
-  if (aLocalTime.IsNull()) {
-    return result;
-  }
-  const TimeDuration& localTime = aLocalTime.Value();
-
-  // When we finish exactly at the end of an iteration we need to report
-  // the end of the final iteration and not the start of the next iteration
-  // so we set up a flag for that case.
-  bool isEndOfFinalIteration = false;
-
-  // Get the normalized time within the active interval.
-  TimeDuration activeTime;
-  // FIXME: The following check that the active duration is not equal to Forever
-  // is a temporary workaround to avoid overflow and should be removed once
-  // bug 1039924 is fixed.
-  if (result.mActiveDuration != TimeDuration::Forever() &&
-      localTime >= aTiming.mDelay + result.mActiveDuration) {
-    result.mPhase = ComputedTiming::AnimationPhase_After;
-    if (!aTiming.FillsForwards()) {
-      // The animation isn't active or filling at this time.
-      result.mTimeFraction = ComputedTiming::kNullTimeFraction;
-      return result;
-    }
-    activeTime = result.mActiveDuration;
-    // Note that infinity == floor(infinity) so this will also be true when we
-    // have finished an infinitely repeating animation of zero duration.
-    isEndOfFinalIteration =
-      aTiming.mIterationCount != 0.0 &&
-      aTiming.mIterationCount == floor(aTiming.mIterationCount);
-  } else if (localTime < aTiming.mDelay) {
-    result.mPhase = ComputedTiming::AnimationPhase_Before;
-    if (!aTiming.FillsBackwards()) {
-      // The animation isn't active or filling at this time.
-      result.mTimeFraction = ComputedTiming::kNullTimeFraction;
-      return result;
-    }
-    // activeTime is zero
-  } else {
-    MOZ_ASSERT(result.mActiveDuration != zeroDuration,
-               "How can we be in the middle of a zero-duration interval?");
-    result.mPhase = ComputedTiming::AnimationPhase_Active;
-    activeTime = localTime - aTiming.mDelay;
-  }
-
-  // Get the position within the current iteration.
-  TimeDuration iterationTime;
-  if (aTiming.mIterationDuration != zeroDuration) {
-    iterationTime = isEndOfFinalIteration
-                    ? aTiming.mIterationDuration
-                    : activeTime % aTiming.mIterationDuration;
-  } /* else, iterationTime is zero */
-
-  // Determine the 0-based index of the current iteration.
-  if (isEndOfFinalIteration) {
-    result.mCurrentIteration =
-      aTiming.mIterationCount == NS_IEEEPositiveInfinity()
-      ? UINT64_MAX // FIXME: When we return this via the API we'll need
-                   // to make sure it ends up being infinity.
-      : static_cast<uint64_t>(aTiming.mIterationCount) - 1;
-  } else if (activeTime == zeroDuration) {
-    // If the active time is zero we're either in the first iteration
-    // (including filling backwards) or we have finished an animation with an
-    // iteration duration of zero that is filling forwards (but we're not at
-    // the exact end of an iteration since we deal with that above).
-    result.mCurrentIteration =
-      result.mPhase == ComputedTiming::AnimationPhase_After
-      ? static_cast<uint64_t>(aTiming.mIterationCount) // floor
-      : 0;
-  } else {
-    result.mCurrentIteration =
-      static_cast<uint64_t>(activeTime / aTiming.mIterationDuration); // floor
-  }
-
-  // Normalize the iteration time into a fraction of the iteration duration.
-  if (result.mPhase == ComputedTiming::AnimationPhase_Before) {
-    result.mTimeFraction = 0.0;
-  } else if (result.mPhase == ComputedTiming::AnimationPhase_After) {
-    result.mTimeFraction = isEndOfFinalIteration
-                         ? 1.0
-                         : fmod(aTiming.mIterationCount, 1.0f);
-  } else {
-    // We are in the active phase so the iteration duration can't be zero.
-    MOZ_ASSERT(aTiming.mIterationDuration != zeroDuration,
-               "In the active phase of a zero-duration animation?");
-    result.mTimeFraction =
-      aTiming.mIterationDuration == TimeDuration::Forever()
-      ? 0.0
-      : iterationTime / aTiming.mIterationDuration;
-  }
-
-  bool thisIterationReverse = false;
-  switch (aTiming.mDirection) {
-    case NS_STYLE_ANIMATION_DIRECTION_NORMAL:
-      thisIterationReverse = false;
-      break;
-    case NS_STYLE_ANIMATION_DIRECTION_REVERSE:
-      thisIterationReverse = true;
-      break;
-    case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE:
-      thisIterationReverse = (result.mCurrentIteration & 1) == 1;
-      break;
-    case NS_STYLE_ANIMATION_DIRECTION_ALTERNATE_REVERSE:
-      thisIterationReverse = (result.mCurrentIteration & 1) == 0;
-      break;
-  }
-  if (thisIterationReverse) {
-    result.mTimeFraction = 1.0 - result.mTimeFraction;
-  }
-
-  return result;
-}
-
-TimeDuration
-AnimationPlayer::ActiveDuration(const AnimationTiming& aTiming)
-{
-  if (aTiming.mIterationCount == mozilla::PositiveInfinity<float>()) {
-    // An animation that repeats forever has an infinite active duration
-    // unless its iteration duration is zero, in which case it has a zero
-    // active duration.
-    const TimeDuration zeroDuration;
-    return aTiming.mIterationDuration == zeroDuration
-           ? zeroDuration
-           : TimeDuration::Forever();
-  }
-  return aTiming.mIterationDuration.MultDouble(aTiming.mIterationCount);
+  return GetSource() && !IsFinishedTransition() && GetSource()->IsCurrent();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/AnimationPlayer.h
+++ b/dom/animation/AnimationPlayer.h
@@ -21,79 +21,16 @@
 #endif
 
 struct JSContext;
 
 namespace mozilla {
 
 struct ElementPropertyTransition;
 
-/**
- * Input timing parameters.
- *
- * Eventually this will represent all the input timing parameters specified
- * by content but for now it encapsulates just the subset of those
- * parameters passed to GetPositionInIteration.
- */
-struct AnimationTiming
-{
-  TimeDuration mIterationDuration;
-  TimeDuration mDelay;
-  float mIterationCount; // mozilla::PositiveInfinity<float>() means infinite
-  uint8_t mDirection;
-  uint8_t mFillMode;
-
-  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;
-  }
-};
-
-/**
- * Stores the results of calculating the timing properties of an animation
- * at a given sample time.
- */
-struct ComputedTiming
-{
-  ComputedTiming()
-  : mTimeFraction(kNullTimeFraction)
-  , mCurrentIteration(0)
-  , mPhase(AnimationPhase_Null)
-  { }
-
-  static const double kNullTimeFraction;
-
-  // The total duration of the animation including all iterations.
-  // Will equal TimeDuration::Forever() if the animation repeats indefinitely.
-  TimeDuration mActiveDuration;
-
-  // Will be kNullTimeFraction if the animation is neither animating nor
-  // filling at the sampled time.
-  double mTimeFraction;
-
-  // Zero-based iteration index (meaningless if mTimeFraction is
-  // kNullTimeFraction).
-  uint64_t mCurrentIteration;
-
-  enum {
-    // Not sampled (null sample time)
-    AnimationPhase_Null,
-    // Sampled prior to the start of the active interval
-    AnimationPhase_Before,
-    // Sampled within the active interval
-    AnimationPhase_Active,
-    // Sampled after (or at) the end of the active interval
-    AnimationPhase_After
-  } mPhase;
-};
-
 namespace dom {
 
 class AnimationPlayer : public nsWrapperCache
 {
 protected:
   virtual ~AnimationPlayer() { }
 
 public:
@@ -143,70 +80,38 @@ public:
     MOZ_ASSERT(AsTransition(),
                "Calling SetFinishedTransition but it's not a transition");
     mIsFinishedTransition = true;
   }
 
   bool IsRunning() const;
   bool IsCurrent() const;
 
-  // Return the duration since the start of the delay period, taking into
+  // Return the duration since the start time of the player, taking into
   // account the pause state.  May be negative.
   // Returns a null value if the timeline associated with this object has a
   // current timestamp that is null or if the start time of this object is
   // null.
-  Nullable<TimeDuration> GetLocalTime() const {
+  Nullable<TimeDuration> GetCurrentTimeDuration() const {
     const TimeStamp& timelineTime = mTimeline->GetCurrentTimeStamp();
     // FIXME: In order to support arbitrary timelines we will need to fix
     // the pause logic to handle the timeline time going backwards.
     MOZ_ASSERT(timelineTime.IsNull() || !IsPaused() ||
                timelineTime >= mPauseStart,
                "if paused, any non-null value of aTime must be at least"
                " mPauseStart");
 
     Nullable<TimeDuration> result; // Initializes to null
     if (!timelineTime.IsNull() && !mStartTime.IsNull()) {
       result.SetValue((IsPaused() ? mPauseStart : timelineTime) - mStartTime);
     }
     return result;
   }
 
-  // Return the duration from the start the active interval to the point where
-  // the animation begins playback. This is zero unless the animation has
-  // a negative delay in which case it is the absolute value of the delay.
-  // This is used for setting the elapsedTime member of AnimationEvents.
-  TimeDuration InitialAdvance() const {
-    return std::max(TimeDuration(), mTiming.mDelay * -1);
-  }
-
-  // This function takes as input the timing parameters of an animation and
-  // returns the computed timing at the specified local time.
-  //
-  // The local time may be null in which case only static parameters such as the
-  // active duration are calculated. All other members of the returned object
-  // are given a null/initial value.
-  //
-  // This function returns ComputedTiming::kNullTimeFraction for the
-  // mTimeFraction member of the return value if the animation should not be
-  // run (because it is not currently active and is not filling at this time).
-  static ComputedTiming
-  GetComputedTimingAt(const Nullable<TimeDuration>& aLocalTime,
-                      const AnimationTiming& aTiming);
-
-  // Shortcut for that gets the computed timing using the current local time as
-  // calculated from the timeline time.
-  ComputedTiming GetComputedTiming(const AnimationTiming& aTiming) const {
-    return GetComputedTimingAt(GetLocalTime(), aTiming);
-  }
-
-  // Return the duration of the active interval for the given timing parameters.
-  static TimeDuration ActiveDuration(const AnimationTiming& aTiming);
-
   nsString mName;
-  AnimationTiming mTiming;
   // The beginning of the delay period.
   TimeStamp mStartTime;
   TimeStamp mPauseStart;
   uint8_t mPlayState;
   bool mIsRunningOnCompositor;
   // A flag to mark transitions that have finished and are due to
   // be removed on the next throttle-able cycle.
   bool mIsFinishedTransition;
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -445,17 +445,17 @@ SampleAnimations(Layer* aLayer, TimeStam
     timing.mDirection = animation.direction();
     // Animations typically only run on the compositor during their active
     // interval but if we end up sampling them outside that range (for
     // example, while they are waiting to be removed) we currently just
     // assume that we should fill.
     timing.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BOTH;
 
     ComputedTiming computedTiming =
-      dom::AnimationPlayer::GetComputedTimingAt(
+      dom::Animation::GetComputedTimingAt(
         Nullable<TimeDuration>(elapsedDuration), timing);
 
     NS_ABORT_IF_FALSE(0.0 <= computedTiming.mTimeFraction &&
                       computedTiming.mTimeFraction <= 1.0,
                       "time fraction should be in [0-1]");
 
     int segmentIndex = 0;
     AnimationSegment* segment = animation.segments().Elements();
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -342,20 +342,21 @@ AddAnimationForProperty(nsIFrame* aFrame
   nsPresContext* presContext = aFrame->PresContext();
   nsRect bounds = nsDisplayTransform::GetFrameBoundsForTransform(aFrame);
 
   layers::Animation* animation =
     aPending ?
     aLayer->AddAnimationForNextTransaction() :
     aLayer->AddAnimation();
 
-  animation->startTime() = aPlayer->mStartTime + aPlayer->mTiming.mDelay;
-  animation->duration() = aPlayer->mTiming.mIterationDuration;
-  animation->iterationCount() = aPlayer->mTiming.mIterationCount;
-  animation->direction() = aPlayer->mTiming.mDirection;
+  const AnimationTiming& timing = aPlayer->GetSource()->Timing();
+  animation->startTime() = aPlayer->mStartTime + timing.mDelay;
+  animation->duration() = timing.mIterationDuration;
+  animation->iterationCount() = timing.mIterationCount;
+  animation->direction() = timing.mDirection;
   animation->property() = aProperty;
   animation->data() = aData;
 
   dom::Animation* anim = aPlayer->GetSource();
   for (size_t propIdx = 0;
        propIdx < anim->Properties().Length();
        propIdx++) {
     AnimationProperty& property = anim->Properties()[propIdx];
--- a/layout/style/AnimationCommon.cpp
+++ b/layout/style/AnimationCommon.cpp
@@ -482,18 +482,17 @@ AnimationPlayerCollection::EnsureStyleRu
       if (!player->GetSource() ||
           player->IsFinishedTransition() ||
           player->GetSource()->Properties().IsEmpty()) {
         continue;
       }
 
       // The GetComputedTiming() call here handles pausing.  But:
       // FIXME: avoid recalculating every time when paused.
-      ComputedTiming computedTiming =
-        player->GetComputedTiming(player->mTiming);
+      ComputedTiming computedTiming = player->GetSource()->GetComputedTiming();
 
       // XXX We shouldn't really be using mLastNotification as a general
       // indicator that the animation has finished, it should be reserved for
       // events. If we use it differently in the future this use might need
       // changing.
       if (!player->mIsRunningOnCompositor ||
           (computedTiming.mPhase == ComputedTiming::AnimationPhase_After &&
            player->mLastNotification != AnimationPlayer::LAST_NOTIFICATION_END))
@@ -519,36 +518,34 @@ AnimationPlayerCollection::EnsureStyleRu
     // FIXME(spec): assume that properties in higher animations override
     // those in lower ones.
     // Therefore, we iterate from last animation to first.
     nsCSSPropertySet properties;
 
     for (size_t playerIdx = mPlayers.Length(); playerIdx-- != 0; ) {
       AnimationPlayer* player = mPlayers[playerIdx];
 
-      if (player->IsFinishedTransition()) {
+      if (!player->GetSource() || player->IsFinishedTransition()) {
         continue;
       }
 
       // The GetComputedTiming() call here handles pausing.  But:
       // FIXME: avoid recalculating every time when paused.
-      ComputedTiming computedTiming =
-        player->GetComputedTiming(player->mTiming);
+      ComputedTiming computedTiming = player->GetSource()->GetComputedTiming();
 
       if ((computedTiming.mPhase == ComputedTiming::AnimationPhase_Before ||
            computedTiming.mPhase == ComputedTiming::AnimationPhase_Active) &&
           !player->IsPaused()) {
         mNeedsRefreshes = true;
       }
 
       // If the time fraction is null, we don't have fill data for the current
       // time so we shouldn't animate.
       // Likewise, if the player has no source content.
-      if (computedTiming.mTimeFraction == ComputedTiming::kNullTimeFraction ||
-          !player->GetSource()) {
+      if (computedTiming.mTimeFraction == ComputedTiming::kNullTimeFraction) {
         continue;
       }
 
       NS_ABORT_IF_FALSE(0.0 <= computedTiming.mTimeFraction &&
                         computedTiming.mTimeFraction <= 1.0,
                         "timing fraction should be in [0-1]");
 
       const Animation* anim = player->GetSource();
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -40,18 +40,22 @@ nsAnimationManager::UpdateStyleAndEvents
 
 void
 nsAnimationManager::GetEventsForCurrentTime(AnimationPlayerCollection*
                                               aCollection,
                                             EventArray& aEventsToDispatch)
 {
   for (size_t playerIdx = aCollection->mPlayers.Length(); playerIdx-- != 0; ) {
     AnimationPlayer* player = aCollection->mPlayers[playerIdx];
+    Animation* anim = player->GetSource();
+    if (!anim) {
+      continue;
+    }
 
-    ComputedTiming computedTiming = player->GetComputedTiming(player->mTiming);
+    ComputedTiming computedTiming = anim->GetComputedTiming();
 
     switch (computedTiming.mPhase) {
       case ComputedTiming::AnimationPhase_Null:
       case ComputedTiming::AnimationPhase_Before:
         // Do nothing
         break;
 
       case ComputedTiming::AnimationPhase_Active:
@@ -66,37 +70,37 @@ nsAnimationManager::GetEventsForCurrentT
           uint32_t message =
             player->mLastNotification ==
               AnimationPlayer::LAST_NOTIFICATION_NONE
               ? NS_ANIMATION_START
               : NS_ANIMATION_ITERATION;
 
           player->mLastNotification = computedTiming.mCurrentIteration;
           TimeDuration iterationStart =
-            player->mTiming.mIterationDuration *
+            anim->Timing().mIterationDuration *
             computedTiming.mCurrentIteration;
           TimeDuration elapsedTime =
-            std::max(iterationStart, player->InitialAdvance());
+            std::max(iterationStart, anim->InitialAdvance());
           AnimationEventInfo ei(aCollection->mElement, player->mName, message,
                                 elapsedTime, aCollection->PseudoElement());
           aEventsToDispatch.AppendElement(ei);
         }
         break;
 
       case ComputedTiming::AnimationPhase_After:
         // If we skipped the animation interval entirely, dispatch
         // 'animationstart' first
         if (player->mLastNotification ==
             AnimationPlayer::LAST_NOTIFICATION_NONE) {
           // Notifying for start of 0th iteration.
           // (This is overwritten below but we set it here to maintain
           // internal consistency.)
           player->mLastNotification = 0;
           TimeDuration elapsedTime =
-            std::min(player->InitialAdvance(), computedTiming.mActiveDuration);
+            std::min(anim->InitialAdvance(), computedTiming.mActiveDuration);
           AnimationEventInfo ei(aCollection->mElement,
                                 player->mName, NS_ANIMATION_START,
                                 elapsedTime, aCollection->PseudoElement());
           aEventsToDispatch.AppendElement(ei);
         }
         // Dispatch 'animationend' when needed.
         if (player->mLastNotification !=
             AnimationPlayer::LAST_NOTIFICATION_END) {
@@ -291,20 +295,20 @@ nsAnimationManager::CheckAnimationRule(n
             }
           }
           if (!oldPlayer) {
             continue;
           }
 
           // Update the old from the new so we can keep the original object
           // identity (and any expando properties attached to it).
-          oldPlayer->mTiming = newPlayer->mTiming;
           if (oldPlayer->GetSource() && newPlayer->GetSource()) {
             Animation* oldAnim = oldPlayer->GetSource();
             Animation* newAnim = newPlayer->GetSource();
+            oldAnim->Timing() = newAnim->Timing();
             oldAnim->Properties() = newAnim->Properties();
           }
 
           // Reset compositor state so animation will be re-synchronized.
           oldPlayer->mIsRunningOnCompositor = false;
 
           // Handle changes in play state.
           if (!oldPlayer->IsPaused() && newPlayer->IsPaused()) {
@@ -439,25 +443,26 @@ nsAnimationManager::BuildAnimations(nsSt
       continue;
     }
 
     nsRefPtr<AnimationPlayer> dest =
       *aPlayers.AppendElement(new AnimationPlayer(aTimeline));
 
     dest->mName = src.GetName();
 
-    dest->mTiming.mIterationDuration =
+    AnimationTiming timing;
+    timing.mIterationDuration =
       TimeDuration::FromMilliseconds(src.GetDuration());
-    dest->mTiming.mDelay = TimeDuration::FromMilliseconds(src.GetDelay());
-    dest->mTiming.mIterationCount = src.GetIterationCount();
-    dest->mTiming.mDirection = src.GetDirection();
-    dest->mTiming.mFillMode = src.GetFillMode();
+    timing.mDelay = TimeDuration::FromMilliseconds(src.GetDelay());
+    timing.mIterationCount = src.GetIterationCount();
+    timing.mDirection = src.GetDirection();
+    timing.mFillMode = src.GetFillMode();
 
     nsRefPtr<Animation> destAnim =
-      new Animation(mPresContext->Document());
+      new Animation(mPresContext->Document(), timing);
     dest->SetSource(destAnim);
 
     dest->mStartTime = now;
     dest->mPlayState = src.GetPlayState();
     if (dest->IsPaused()) {
       dest->mPauseStart = now;
     } else {
       dest->mPauseStart = TimeStamp();
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -41,29 +41,30 @@ using namespace mozilla::css;
 double
 ElementPropertyTransition::CurrentValuePortion() const
 {
   // It would be easy enough to handle finished transitions by using a time
   // fraction of 1 but currently we should not be called for finished
   // transitions.
   MOZ_ASSERT(!IsFinishedTransition(),
              "Getting the value portion of a finished transition");
-  MOZ_ASSERT(!GetLocalTime().IsNull(),
+  MOZ_ASSERT(!GetCurrentTimeDuration().IsNull(),
              "Getting the value portion of an animation that's not being "
              "sampled");
 
   // Transitions use a fill mode of 'backwards' so GetComputedTiming will
   // never return a null time fraction due to being *before* the animation
   // interval. However, it might be possible that we're behind on flushing
   // causing us to get called *after* the animation interval. So, just in
   // case, we override the fill mode to 'both' to ensure the time fraction
   // is never null.
-  AnimationTiming timingToUse = mTiming;
+  MOZ_ASSERT(GetSource(), "Transitions should have source content");
+  AnimationTiming timingToUse = GetSource()->Timing();
   timingToUse.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BOTH;
-  ComputedTiming computedTiming = GetComputedTiming(timingToUse);
+  ComputedTiming computedTiming = GetSource()->GetComputedTiming(&timingToUse);
 
   MOZ_ASSERT(computedTiming.mTimeFraction != ComputedTiming::kNullTimeFraction,
              "Got a null time fraction for a fill mode of 'both'");
   MOZ_ASSERT(GetSource() && GetSource()->Properties().Length() == 1,
              "Should have one animation property for a transition");
   MOZ_ASSERT(GetSource() &&
              GetSource()->Properties()[0].mSegments.Length() == 1,
              "Animation property should have one segment for a transition");
@@ -492,37 +493,40 @@ nsTransitionManager::ConsiderStartingTra
 
     duration *= valuePortion;
 
     pt->mStartForReversingTest =
       oldPT->GetSource()->Properties()[0].mSegments[0].mToValue;
     pt->mReversePortion = valuePortion;
   }
 
-  nsRefPtr<dom::Animation> anim = new dom::Animation(aElement->OwnerDoc());
-  pt->SetSource(anim);
+  AnimationTiming timing;
+  timing.mIterationDuration = TimeDuration::FromMilliseconds(duration);
+  timing.mDelay = TimeDuration::FromMilliseconds(delay);
+  timing.mIterationCount = 1;
+  timing.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL;
+  timing.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS;
+
+  nsRefPtr<dom::Animation> anim =
+    new dom::Animation(aElement->OwnerDoc(), timing);
 
   AnimationProperty& prop = *anim->Properties().AppendElement();
   prop.mProperty = aProperty;
 
   AnimationPropertySegment& segment = *prop.mSegments.AppendElement();
   segment.mFromValue = startValue;
   segment.mToValue = endValue;
   segment.mFromKey = 0;
   segment.mToKey = 1;
   segment.mTimingFunction.Init(tf);
 
   pt->mStartTime = timeline->GetCurrentTimeStamp();
-  pt->mTiming.mIterationDuration = TimeDuration::FromMilliseconds(duration);
-  pt->mTiming.mDelay = TimeDuration::FromMilliseconds(delay);
-  pt->mTiming.mIterationCount = 1;
-  pt->mTiming.mDirection = NS_STYLE_ANIMATION_DIRECTION_NORMAL;
-  pt->mTiming.mFillMode = NS_STYLE_ANIMATION_FILL_MODE_BACKWARDS;
   pt->mPlayState = NS_STYLE_ANIMATION_PLAY_STATE_RUNNING;
   pt->mPauseStart = TimeStamp();
+  pt->SetSource(anim);
 
   if (!aElementTransitions) {
     aElementTransitions =
       GetElementTransitions(aElement, aNewStyleContext->GetPseudoType(),
                             true);
     if (!aElementTransitions) {
       NS_WARNING("allocating CommonAnimationManager failed");
       return;
@@ -779,30 +783,33 @@ nsTransitionManager::FlushTransitions(Fl
           // completion. We only clear on a throttle-able cycle because that
           // means it is a regular restyle tick and thus it is safe to discard
           // the transition. If the flush is not throttle-able, we might still
           // have new transitions left to process. See comment below.
           if (aFlags == Can_Throttle) {
             collection->mPlayers.RemoveElementAt(i);
           }
         } else {
+          MOZ_ASSERT(player->GetSource(),
+                     "Transitions should have source content");
           ComputedTiming computedTiming =
-            player->GetComputedTiming(player->mTiming);
+            player->GetSource()->GetComputedTiming();
           if (computedTiming.mPhase == ComputedTiming::AnimationPhase_After) {
-            MOZ_ASSERT(player->GetSource() &&
-                       player->GetSource()->Properties().Length() == 1,
+            MOZ_ASSERT(player->GetSource()->Properties().Length() == 1,
                        "Should have one animation property for a transition");
             nsCSSProperty prop = player->GetSource()->Properties()[0].mProperty;
             if (nsCSSProps::PropHasFlags(prop, CSS_PROPERTY_REPORT_OTHER_NAME))
             {
               prop = nsCSSProps::OtherNameFor(prop);
             }
+            TimeDuration duration =
+              player->GetSource()->Timing().mIterationDuration;
             events.AppendElement(
               TransitionEventInfo(collection->mElement, prop,
-                                  player->mTiming.mIterationDuration,
+                                  duration,
                                   collection->PseudoElement()));
 
             // Leave this transition in the list for one more refresh
             // cycle, since we haven't yet processed its style change, and
             // if we also have (already, or will have from processing
             // transitionend events or other refresh driver notifications)
             // a non-animation style change that would affect it, we need
             // to know not to start a new transition for the transition