Bug 1472076 - Update animations when the refresh driver's time changed due to the active timer changes. r=birtles a=lizzard
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Tue, 03 Jul 2018 10:59:55 +0900
changeset 477864 76f1a92b24ca9b2a06d27da38eaf837dd762cfba
parent 477863 ae6851be89984fe2a74d35862ad25c59d20193aa
child 477865 33216e31fc3a81ea67e7cdde095ea93a58d5ec6b
push id9448
push userarchaeopteryx@coole-files.de
push dateMon, 09 Jul 2018 17:22:41 +0000
treeherdermozilla-beta@32cde6cdb297 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles, lizzard
bugs1472076
milestone62.0
Bug 1472076 - Update animations when the refresh driver's time changed due to the active timer changes. r=birtles a=lizzard 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;