Bug 1472076 - Update animations when the refresh driver's time changed due to the active timer changes. r?birtles draft
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Tue, 03 Jul 2018 10:59:55 +0900
changeset 813402 b2ad41918bd99060562a657fc5b7acd192c20020
parent 813401 fb0a47003382ea53a4602eb76d856dfe373cb8f6
push id114881
push userhikezoe@mozilla.com
push dateTue, 03 Jul 2018 02:02:33 +0000
reviewersbirtles
bugs1472076
milestone63.0a1
Bug 1472076 - Update animations when the refresh driver's time changed due to the active timer changes. r?birtles Normally the refresh driver's time changes in nsRefreshDriver::Tick, and then DocumentTimeline::WillRefresh is called for the change. But nsRefreshDriver sometimes updates its own time when their timer changes to the active one. This patch lets DocumentTimeline update animations in response to the refresh driver's time updates. And thus this patch prevents animation state and relevant stuff inconsistencies such as an animation has been finished without proper processes, e.g. without invalidating frame for the animation. MozReview-Commit-ID: 42p5BWITQN0
dom/animation/DocumentTimeline.cpp
dom/animation/DocumentTimeline.h
--- a/dom/animation/DocumentTimeline.cpp
+++ b/dom/animation/DocumentTimeline.cpp
@@ -136,39 +136,32 @@ DocumentTimeline::NotifyAnimationUpdated
   AnimationTimeline::NotifyAnimationUpdated(aAnimation);
 
   if (!mIsObservingRefreshDriver) {
     nsRefreshDriver* refreshDriver = GetRefreshDriver();
     if (refreshDriver) {
       MOZ_ASSERT(isInList(),
                 "We should not register with the refresh driver if we are not"
                 " in the document's list of timelines");
-      refreshDriver->AddRefreshObserver(this, FlushType::Style);
-      mIsObservingRefreshDriver = true;
+
+      ObserveRefreshDriver(refreshDriver);
     }
   }
 }
 
 void
-DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
+DocumentTimeline::MostRecentRefreshTimeUpdated()
 {
   MOZ_ASSERT(mIsObservingRefreshDriver);
   MOZ_ASSERT(GetRefreshDriver(),
              "Should be able to reach refresh driver from within WillRefresh");
 
   bool needsTicks = false;
   nsTArray<Animation*> animationsToRemove(mAnimations.Count());
 
-  // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events,
-  // step2.
-  // Note that this should be done before nsAutoAnimationMutationBatch.  If
-  // PerformMicroTaskCheckpoint was called before nsAutoAnimationMutationBatch
-  // is destroyed, some mutation records might not be delivered in this
-  // checkpoint.
-  nsAutoMicroTask mt;
   nsAutoAnimationMutationBatch mb(mDocument);
 
   for (Animation* animation = mAnimationOrder.getFirst(); animation;
        animation =
          static_cast<LinkedListElement<Animation>*>(animation)->getNext()) {
     // Skip any animations that are longer need associated with this timeline.
     if (animation->GetTimeline() != this) {
       // If animation has some other timeline, it better not be also in the
@@ -201,40 +194,80 @@ DocumentTimeline::WillRefresh(mozilla::T
     // of mDocument's PresShell.
     MOZ_ASSERT(GetRefreshDriver(),
                "Refresh driver should still be valid at end of WillRefresh");
     UnregisterFromRefreshDriver();
   }
 }
 
 void
+DocumentTimeline::WillRefresh(mozilla::TimeStamp aTime)
+{
+  // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events,
+  // step2.
+  // Note that this should be done before nsAutoAnimationMutationBatch which is
+  // inside MostRecentRefreshTimeUpdated().  If PerformMicroTaskCheckpoint was
+  // called before nsAutoAnimationMutationBatch is destroyed, some mutation
+  // records might not be delivered in this checkpoint.
+  nsAutoMicroTask mt;
+  MostRecentRefreshTimeUpdated();
+}
+
+void
+DocumentTimeline::NotifyTimerAdjusted(TimeStamp aTime)
+{
+  MostRecentRefreshTimeUpdated();
+}
+
+void
+DocumentTimeline::ObserveRefreshDriver(nsRefreshDriver* aDriver)
+{
+  MOZ_ASSERT(!mIsObservingRefreshDriver);
+  // Set the mIsObservingRefreshDriver flag before calling AddRefreshObserver
+  // since it might end up calling NotifyTimerAdjusted which calls
+  // MostRecentRefreshTimeUpdated which has an assertion for
+  // mIsObserveingRefreshDriver check.
+  mIsObservingRefreshDriver = true;
+  aDriver->AddRefreshObserver(this, FlushType::Style);
+  aDriver->AddTimerAdjustmentObserver(this);
+}
+
+void
 DocumentTimeline::NotifyRefreshDriverCreated(nsRefreshDriver* aDriver)
 {
   MOZ_ASSERT(!mIsObservingRefreshDriver,
              "Timeline should not be observing the refresh driver before"
              " it is created");
 
   if (!mAnimationOrder.isEmpty()) {
     MOZ_ASSERT(isInList(),
                "We should not register with the refresh driver if we are not"
                " in the document's list of timelines");
-    aDriver->AddRefreshObserver(this, FlushType::Style);
-    mIsObservingRefreshDriver = true;
+    ObserveRefreshDriver(aDriver);
   }
 }
 
 void
+DocumentTimeline::DisconnectRefreshDriver(nsRefreshDriver* aDriver)
+{
+  MOZ_ASSERT(mIsObservingRefreshDriver);
+
+  aDriver->RemoveRefreshObserver(this, FlushType::Style);
+  aDriver->RemoveTimerAdjustmentObserver(this);
+  mIsObservingRefreshDriver = false;
+}
+
+void
 DocumentTimeline::NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver)
 {
   if (!mIsObservingRefreshDriver) {
     return;
   }
 
-  aDriver->RemoveRefreshObserver(this, FlushType::Style);
-  mIsObservingRefreshDriver = false;
+  DisconnectRefreshDriver(aDriver);
 }
 
 void
 DocumentTimeline::RemoveAnimation(Animation* aAnimation)
 {
   AnimationTimeline::RemoveAnimation(aAnimation);
 
   if (mIsObservingRefreshDriver && mAnimations.IsEmpty()) {
@@ -273,15 +306,13 @@ DocumentTimeline::UnregisterFromRefreshD
   if (!mIsObservingRefreshDriver) {
     return;
   }
 
   nsRefreshDriver* refreshDriver = GetRefreshDriver();
   if (!refreshDriver) {
     return;
   }
-
-  refreshDriver->RemoveRefreshObserver(this, FlushType::Style);
-  mIsObservingRefreshDriver = false;
+  DisconnectRefreshDriver(refreshDriver);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/DocumentTimeline.h
+++ b/dom/animation/DocumentTimeline.h
@@ -24,16 +24,17 @@ struct JSContext;
 #endif
 
 namespace mozilla {
 namespace dom {
 
 class DocumentTimeline final
   : public AnimationTimeline
   , public nsARefreshObserver
+  , public nsATimerAdjustmentObserver
   , public LinkedListElement<DocumentTimeline>
 {
 public:
   DocumentTimeline(nsIDocument* aDocument, const TimeDuration& aOriginTime)
     : AnimationTimeline(aDocument->GetParentObject())
     , mDocument(aDocument)
     , mIsObservingRefreshDriver(false)
     , mOriginTime(aOriginTime)
@@ -80,24 +81,29 @@ public:
   TimeStamp ToTimeStamp(const TimeDuration& aTimelineTime) const override;
 
   void NotifyAnimationUpdated(Animation& aAnimation) override;
 
   void RemoveAnimation(Animation* aAnimation) override;
 
   // nsARefreshObserver methods
   void WillRefresh(TimeStamp aTime) override;
+  // nsATimerAdjustmentObserver methods
+  void NotifyTimerAdjusted(TimeStamp aTime) override;
 
   void NotifyRefreshDriverCreated(nsRefreshDriver* aDriver);
   void NotifyRefreshDriverDestroying(nsRefreshDriver* aDriver);
 
 protected:
   TimeStamp GetCurrentTimeStamp() const;
   nsRefreshDriver* GetRefreshDriver() const;
   void UnregisterFromRefreshDriver();
+  void MostRecentRefreshTimeUpdated();
+  void ObserveRefreshDriver(nsRefreshDriver* aDriver);
+  void DisconnectRefreshDriver(nsRefreshDriver* aDriver);
 
   nsCOMPtr<nsIDocument> mDocument;
 
   // The most recently used refresh driver time. This is used in cases where
   // we don't have a refresh driver (e.g. because we are in a display:none
   // iframe).
   mutable TimeStamp mLastRefreshDriverTime;
   bool mIsObservingRefreshDriver;