Bug 1049975 - Part 9: Implement writable Animation effect. draft
authorBoris Chiou <boris.chiou@gmail.com>
Wed, 13 Jul 2016 18:44:19 +0800
changeset 405237 eba2506e124b88c35fd327f8f5b5d58bc0d247cf
parent 405236 5af756497d1316a293ade37a36e9d1b5c39d0680
child 405238 1411cca0001f4fc6acf2856255ff884b5d69b426
push id27442
push userbmo:boris.chiou@gmail.com
push dateThu, 25 Aug 2016 04:26:27 +0000
bugs1049975
milestone51.0a1
Bug 1049975 - Part 9: Implement writable Animation effect. MozReview-Commit-ID: 1dwHpcYJto3
dom/animation/Animation.cpp
dom/animation/AnimationEffectReadOnly.cpp
dom/animation/AnimationEffectReadOnly.h
dom/animation/KeyframeEffect.cpp
dom/animation/KeyframeEffect.h
layout/style/nsTransitionManager.cpp
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -125,30 +125,74 @@ Animation::SetId(const nsAString& aId)
 
 void
 Animation::SetEffect(AnimationEffectReadOnly* aEffect)
 {
   SetEffectNoUpdate(aEffect);
   PostUpdate();
 }
 
+// https://w3c.github.io/web-animations/#setting-the-target-effect
 void
 Animation::SetEffectNoUpdate(AnimationEffectReadOnly* aEffect)
 {
   RefPtr<Animation> kungFuDeathGrip(this);
 
   if (mEffect == aEffect) {
     return;
   }
+
   if (mEffect) {
-    mEffect->SetAnimation(nullptr);
+    if (!aEffect) {
+      // If the new effect is null, call ResetPendingTasks before clearing
+      // mEffect since ResetPendingTasks needs it to get the appropriate
+      // PendingAnimationTracker.
+      ResetPendingTasks();
+    }
+
+    // Break links with the old effect and then drop it.
+    RefPtr<AnimationEffectReadOnly> oldEffect = mEffect;
+    mEffect = nullptr;
+    oldEffect->SetAnimation(nullptr);
   }
-  mEffect = aEffect;
-  if (mEffect) {
+
+  if (aEffect) {
+    // Break links from the new effect to its previous animation, if any.
+    RefPtr<AnimationEffectReadOnly> newEffect = aEffect;
+    Animation* prevAnim = aEffect->GetAnimation();
+    if (prevAnim) {
+      prevAnim->SetEffect(nullptr);
+    }
+
+    // Create links with the new effect.
+    mEffect = newEffect;
     mEffect->SetAnimation(this);
+
+    // Reschedule pending pause or pending play tasks.
+    // If we have a pending animation, it will either be registered
+    // in the pending animation tracker and have a null pending ready time,
+    // or, after it has been painted, it will be removed from the tracker
+    // and assigned a pending ready time.
+    // After updating the effect we'll typically need to repaint so if we've
+    // already been assigned a pending ready time, we should clear it and put
+    // the animation back in the tracker.
+    if (!mPendingReadyTime.IsNull()) {
+      mPendingReadyTime.SetNull();
+
+      nsIDocument* doc = GetRenderedDocument();
+      if (doc) {
+        PendingAnimationTracker* tracker =
+          doc->GetOrCreatePendingAnimationTracker();
+        if (mPendingState == PendingState::PlayPending) {
+          tracker->AddPlayPending(*this);
+        } else {
+          tracker->AddPausePending(*this);
+        }
+      }
+    }
   }
 
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 }
 
 void
 Animation::SetTimeline(AnimationTimeline* aTimeline)
 {
@@ -1104,17 +1148,21 @@ Animation::UpdateFinishedState(SeekFlag 
   mPreviousCurrentTime = GetCurrentTime();
 }
 
 void
 Animation::UpdateEffect()
 {
   if (mEffect) {
     UpdateRelevance();
-    mEffect->NotifyAnimationTimingUpdated();
+
+    KeyframeEffectReadOnly* keyframeEffect = mEffect->AsKeyframeEffect();
+    if (keyframeEffect) {
+      keyframeEffect->NotifyAnimationTimingUpdated();
+    }
   }
 }
 
 void
 Animation::FlushStyle() const
 {
   nsIDocument* doc = GetRenderedDocument();
   if (doc) {
--- a/dom/animation/AnimationEffectReadOnly.cpp
+++ b/dom/animation/AnimationEffectReadOnly.cpp
@@ -88,19 +88,19 @@ AnimationEffectReadOnly::SetSpecifiedTim
 {
   if (mTiming->AsTimingParams() == aTiming) {
     return;
   }
   mTiming->SetTimingParams(aTiming);
   if (mAnimation) {
     mAnimation->NotifyEffectTimingUpdated();
   }
-  // NotifyEffectTimingUpdated will eventually cause
-  // NotifyAnimationTimingUpdated to be called on this object which will
-  // update our registration with the target element.
+  // For keyframe effects, NotifyEffectTimingUpdated above will eventually cause
+  // KeyframeEffectReadOnly::NotifyAnimationTimingUpdated to be called so it can
+  // update its registration with the target element as necessary.
 }
 
 ComputedTiming
 AnimationEffectReadOnly::GetComputedTimingAt(
     const Nullable<TimeDuration>& aLocalTime,
     const TimingParams& aTiming,
     double aPlaybackRate)
 {
@@ -318,23 +318,16 @@ AnimationEffectReadOnly::GetComputedTimi
   GetComputedTimingDictionary(GetComputedTimingAt(currentTime,
                                                   SpecifiedTiming(),
                                                   playbackRate),
                               currentTime,
                               SpecifiedTiming(),
                               aRetVal);
 }
 
-void
-AnimationEffectReadOnly::SetAnimation(Animation* aAnimation)
-{
-  mAnimation = aAnimation;
-  NotifyAnimationTimingUpdated();
-}
-
 AnimationEffectReadOnly::~AnimationEffectReadOnly()
 {
   // mTiming is cycle collected, so we have to do null check first even though
   // mTiming shouldn't be null during the lifetime of KeyframeEffect.
   if (mTiming) {
     mTiming->Unlink();
   }
 }
--- a/dom/animation/AnimationEffectReadOnly.h
+++ b/dom/animation/AnimationEffectReadOnly.h
@@ -54,17 +54,16 @@ public:
   bool IsInEffect() const;
 
   already_AddRefed<AnimationEffectTimingReadOnly> Timing();
   const TimingParams& SpecifiedTiming() const
   {
     return mTiming->AsTimingParams();
   }
   void SetSpecifiedTiming(const TimingParams& aTiming);
-  virtual void NotifyAnimationTimingUpdated() = 0;
 
   // 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.
   //
@@ -75,17 +74,17 @@ public:
   GetComputedTimingAt(const Nullable<TimeDuration>& aLocalTime,
                       const TimingParams& aTiming,
                       double aPlaybackRate);
   // Shortcut that gets the computed timing using the current local time as
   // calculated from the timeline time.
   ComputedTiming GetComputedTiming(const TimingParams* aTiming = nullptr) const;
   void GetComputedTimingAsDict(ComputedTimingProperties& aRetVal) const;
 
-  void SetAnimation(Animation* aAnimation);
+  virtual void SetAnimation(Animation* aAnimation) = 0;
   Animation* GetAnimation() const { return mAnimation; };
 
 protected:
   virtual ~AnimationEffectReadOnly();
 
   Nullable<TimeDuration> GetLocalTime() const;
 
 protected:
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -1215,16 +1215,42 @@ KeyframeEffectReadOnly::CalculateCumulat
                                          &equalStructs,
                                          &samePointerStructs);
 
       mCumulativeChangeHint |= changeHint;
     }
   }
 }
 
+void
+KeyframeEffectReadOnly::SetAnimation(Animation* aAnimation)
+{
+  if (mAnimation == aAnimation) {
+    return;
+  }
+
+  // Restyle for the old animation.
+  RequestRestyle(EffectCompositor::RestyleType::Layer);
+
+  mAnimation = aAnimation;
+
+  // Restyle for the new animation.
+  RequestRestyle(EffectCompositor::RestyleType::Layer);
+
+  if (mTarget) {
+    EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement,
+                                                   mTarget->mPseudoType);
+    if (effectSet) {
+      effectSet->MarkCascadeNeedsUpdate();
+    }
+  }
+
+  NotifyAnimationTimingUpdated();
+}
+
 bool
 KeyframeEffectReadOnly::CanIgnoreIfNotVisible() const
 {
   if (!AnimationUtils::IsOffscreenThrottlingEnabled()) {
     return false;
   }
 
   // FIXME: For further sophisticated optimization we need to check
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -225,17 +225,19 @@ public:
 
   IterationCompositeOperation IterationComposite() const;
   CompositeOperation Composite() const;
   void GetSpacing(nsString& aRetVal) const
   {
     mEffectOptions.GetSpacingAsString(aRetVal);
   }
 
-  void NotifyAnimationTimingUpdated() override;
+  void NotifyAnimationTimingUpdated();
+
+  void SetAnimation(Animation* aAnimation) override;
 
   void SetKeyframes(JSContext* aContext, JS::Handle<JSObject*> aKeyframes,
                     ErrorResult& aRv);
   void SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
                     nsStyleContext* aStyleContext);
   const AnimationProperty*
   GetAnimationOfProperty(nsCSSPropertyID aProperty) const;
   bool HasAnimationOfProperty(nsCSSPropertyID aProperty) const
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -793,20 +793,16 @@ nsTransitionManager::ConsiderStartingTra
 
   RefPtr<CSSTransition> animation =
     new CSSTransition(mPresContext->Document()->GetScopeObject());
   animation->SetOwningElement(
     OwningElementRef(*aElement, aNewStyleContext->GetPseudoType()));
   animation->SetTimelineNoUpdate(timeline);
   animation->SetCreationSequence(
     mPresContext->RestyleManager()->AsGecko()->GetAnimationGeneration());
-  // The order of the following two calls is important since PlayFromStyle
-  // will add the animation to the PendingAnimationTracker of its effect's
-  // document. When we come to make effect writeable (bug 1049975) we should
-  // remove this dependency.
   animation->SetEffectFromStyle(pt);
   animation->PlayFromStyle();
 
   if (!aElementTransitions) {
     bool createdCollection = false;
     aElementTransitions =
       CSSTransitionCollection::GetOrCreateAnimationCollection(
         aElement, aNewStyleContext->GetPseudoType(), &createdCollection);