Bug 1415780 - Let AnimationEventDispatcher observe nsRefreshDriver. r?birtles draft
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Sat, 27 Jan 2018 21:17:27 +0900
changeset 748027 477bcbf624be4b04f47ec7837331740a95ce4041
parent 748026 5f6604e37da8e2cd671b2e87cfa1c8e396347953
child 748028 693768f9320ec12ef85b7116e0a55663f9aeb7d7
push id97048
push userhikezoe@mozilla.com
push dateSat, 27 Jan 2018 12:23:10 +0000
reviewersbirtles
bugs1415780
milestone60.0a1
Bug 1415780 - Let AnimationEventDispatcher observe nsRefreshDriver. r?birtles So that we can now ensure nsRefreshDriver ticks (i.e. nsRefreshDriver doesn't stop its timer) for all queued events. Before this patch, dispatching CSS animation/transition events relied on the fact that DocumentTimeline observes nsRefreshDriver. For this fact, animationcancel or transitioncancel event did not dispatch properly in some cases, i.e. the case where the animation was dropped from the DocumentTimeline. MozReview-Commit-ID: 7JYro0MY2U2
dom/animation/AnimationEventDispatcher.cpp
dom/animation/AnimationEventDispatcher.h
layout/base/PresShell.cpp
layout/base/nsPresContext.h
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
--- a/dom/animation/AnimationEventDispatcher.cpp
+++ b/dom/animation/AnimationEventDispatcher.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/AnimationEventDispatcher.h"
 
 #include "mozilla/EventDispatcher.h"
+#include "nsRefreshDriver.h"
 
 namespace mozilla {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(AnimationEventDispatcher)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AnimationEventDispatcher)
   tmp->ClearEventQueue();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AnimationEventDispatcher)
@@ -21,10 +22,37 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
     ImplCycleCollectionTraverse(cb, info.mAnimation,
       "mozilla::AnimationEventDispatcher.mPendingEvents.mAnimation");
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AnimationEventDispatcher, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AnimationEventDispatcher, Release)
 
+void
+AnimationEventDispatcher::Disconnect()
+{
+  if (mIsObserving) {
+    MOZ_ASSERT(mPresContext && mPresContext->RefreshDriver(),
+               "The pres context and the refresh driver should be still "
+               "alive if we haven't disassociated from the refresh driver");
+    mPresContext->RefreshDriver()->CancelPendingAnimationEvents(this);
+    mIsObserving = false;
+  }
+  mPresContext = nullptr;
+}
+
+void
+AnimationEventDispatcher::QueueEvents(nsTArray<AnimationEventInfo>&& aEvents)
+{
+  MOZ_ASSERT(mPresContext,
+             "The pres context should be valid");
+
+  mPendingEvents.AppendElements(Move(aEvents));
+  mIsSorted = false;
+  if (!mIsObserving) {
+    mPresContext->RefreshDriver()->ScheduleAnimationEventDispatch(this);
+    mIsObserving = true;
+  }
+}
+
 } // namespace mozilla
 
--- a/dom/animation/AnimationEventDispatcher.h
+++ b/dom/animation/AnimationEventDispatcher.h
@@ -12,16 +12,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/Variant.h"
 #include "nsCSSProps.h"
 #include "nsCycleCollectionParticipant.h"
 
 class nsPresContext;
+class nsRefreshDriver;
 
 namespace mozilla {
 
 struct AnimationEventInfo
 {
   RefPtr<dom::Element> mElement;
   RefPtr<dom::Animation> mAnimation;
   TimeStamp mTimeStamp;
@@ -95,36 +96,32 @@ struct AnimationEventInfo
 };
 
 class AnimationEventDispatcher final
 {
 public:
   explicit AnimationEventDispatcher(nsPresContext* aPresContext)
     : mPresContext(aPresContext)
     , mIsSorted(true)
+    , mIsObserving(false)
   {
   }
 
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AnimationEventDispatcher)
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AnimationEventDispatcher)
 
-  void Disconnect() {
-    mPresContext = nullptr;
-  }
+  void Disconnect();
 
-  void QueueEvents(nsTArray<AnimationEventInfo>&& aEvents)
-  {
-    mPendingEvents.AppendElements(Move(aEvents));
-    mIsSorted = false;
-  }
+  void QueueEvents(nsTArray<AnimationEventInfo>&& aEvents);
 
   // This will call SortEvents automatically if it has not already been
   // called.
   void DispatchEvents()
   {
+    mIsObserving = false;
     if (!mPresContext || mPendingEvents.IsEmpty()) {
       return;
     }
 
     SortEvents();
 
     EventArray events;
     mPendingEvents.SwapElements(events);
@@ -149,17 +146,26 @@ public:
   void ClearEventQueue()
   {
     mPendingEvents.Clear();
     mIsSorted = true;
   }
   bool HasQueuedEvents() const { return !mPendingEvents.IsEmpty(); }
 
 private:
+#ifndef DEBUG
   ~AnimationEventDispatcher() = default;
+#else
+  ~AnimationEventDispatcher()
+  {
+    MOZ_ASSERT(!mIsObserving,
+               "AnimationEventDispatcher should have disassociated from "
+               "nsRefreshDriver");
+  }
+#endif
 
   class AnimationEventInfoLessThan
   {
   public:
     bool operator()(const AnimationEventInfo& a, const AnimationEventInfo& b) const
     {
       if (a.mTimeStamp != b.mTimeStamp) {
         // Null timestamps sort first
@@ -190,13 +196,14 @@ private:
                      AnimationEventInfoLessThan());
     mIsSorted = true;
   }
 
   nsPresContext* mPresContext;
   typedef nsTArray<AnimationEventInfo> EventArray;
   EventArray mPendingEvents;
   bool mIsSorted;
+  bool mIsObserving;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_AnimationEventDispatcher_h
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -3,17 +3,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/. */
 
 /* a presentation of a document, part 2 */
 
 #include "mozilla/PresShell.h"
 
-#include "mozilla/AnimationEventDispatcher.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/MemoryReporting.h"
@@ -1358,17 +1357,17 @@ PresShell::Destroy()
       mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
     }
     for (DocumentTimeline* timeline : mDocument->Timelines()) {
       timeline->NotifyRefreshDriverDestroying(rd);
     }
   }
 
   if (mPresContext) {
-    mPresContext->AnimationEventDispatcher()->ClearEventQueue();
+    rd->CancelPendingAnimationEvents(mPresContext->AnimationEventDispatcher());
   }
 
   // Revoke any pending events.  We need to do this and cancel pending reflows
   // before we destroy the frame manager, since apparently frame destruction
   // sometimes spins the event queue when plug-ins are involved(!).
   if (mResizeEventPending) {
     rd->RemoveResizeEventFlushObserver(this);
   }
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -236,16 +236,17 @@ public:
 
   nsCSSFrameConstructor* FrameConstructor()
     { return PresShell()->FrameConstructor(); }
 
   mozilla::AnimationEventDispatcher* AnimationEventDispatcher()
   {
     return mAnimationEventDispatcher;
   }
+
   mozilla::EffectCompositor* EffectCompositor() { return mEffectCompositor; }
   nsTransitionManager* TransitionManager() { return mTransitionManager; }
   nsAnimationManager* AnimationManager() { return mAnimationManager; }
 
   nsRefreshDriver* RefreshDriver() { return mRefreshDriver; }
 
   mozilla::RestyleManager* RestyleManager() {
     MOZ_ASSERT(mRestyleManager);
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1424,16 +1424,17 @@ nsRefreshDriver::ObserverCount() const
   for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) {
     sum += mObservers[i].Length();
   }
 
   // Even while throttled, we need to process layout and style changes.  Style
   // changes can trigger transitions which fire events when they complete, and
   // layout changes can affect media queries on child documents, triggering
   // style changes, etc.
+  sum += mAnimationEventFlushObservers.Length();
   sum += mResizeEventFlushObservers.Length();
   sum += mStyleFlushObservers.Length();
   sum += mLayoutFlushObservers.Length();
   sum += mPendingEvents.Length();
   sum += mFrameRequestCallbackDocs.Length();
   sum += mThrottledFrameRequestCallbackDocs.Length();
   sum += mViewManagerFlushIsPending;
   sum += mEarlyRunners.Length();
@@ -1447,16 +1448,17 @@ nsRefreshDriver::HasObservers() const
     if (!mObservers[i].IsEmpty()) {
       return true;
     }
   }
 
   return mViewManagerFlushIsPending ||
          !mStyleFlushObservers.IsEmpty() ||
          !mLayoutFlushObservers.IsEmpty() ||
+         !mAnimationEventFlushObservers.IsEmpty() ||
          !mResizeEventFlushObservers.IsEmpty() ||
          !mPendingEvents.IsEmpty() ||
          !mFrameRequestCallbackDocs.IsEmpty() ||
          !mThrottledFrameRequestCallbackDocs.IsEmpty() ||
          !mEarlyRunners.IsEmpty();
 }
 
 bool
@@ -1633,32 +1635,29 @@ nsRefreshDriver::UpdateIntersectionObser
 
 void
 nsRefreshDriver::DispatchAnimationEvents()
 {
   if (!mPresContext) {
     return;
   }
 
-  AutoTArray<nsCOMPtr<nsIDocument>, 32> documents;
-  CollectDocuments(mPresContext->Document(), &documents);
+  // Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as
+  // a RefPtr<> array since each AnimationEventDispatcher might be destroyed
+  // during processing the previous dispatcher.
+  size_t len = mAnimationEventFlushObservers.Length();
+  AutoTArray<RefPtr<AnimationEventDispatcher>, 16> dispatchers;
+  RefPtr<AnimationEventDispatcher>* elems = dispatchers.AppendElements(len);
+  for (size_t i = 0; i < len; i++) {
+    elems[i] = mAnimationEventFlushObservers[i];
+  }
+  mAnimationEventFlushObservers.Clear();
 
-  for (uint32_t i = 0; i < documents.Length(); ++i) {
-    nsIDocument* doc = documents[i];
-    nsIPresShell* shell = doc->GetShell();
-    if (!shell) {
-      continue;
-    }
-
-    RefPtr<nsPresContext> context = shell->GetPresContext();
-    if (!context || context->RefreshDriver() != this) {
-      continue;
-    }
-
-    context->AnimationEventDispatcher()->DispatchEvents();
+  for (auto& dispatcher : dispatchers) {
+    dispatcher->DispatchEvents();
   }
 }
 
 void
 nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime)
 {
   // Grab all of our frame request callbacks up front.
   nsTArray<DocumentFrameCallbacks>
@@ -2407,16 +2406,24 @@ nsRefreshDriver::CancelPendingEvents(nsI
 {
   for (auto i : Reversed(IntegerRange(mPendingEvents.Length()))) {
     if (mPendingEvents[i].mTarget->OwnerDoc() == aDocument) {
       mPendingEvents.RemoveElementAt(i);
     }
   }
 }
 
+void
+nsRefreshDriver::CancelPendingAnimationEvents(AnimationEventDispatcher* aDispatcher)
+{
+  MOZ_ASSERT(aDispatcher);
+  aDispatcher->ClearEventQueue();
+  mAnimationEventFlushObservers.RemoveElement(aDispatcher);
+}
+
 /* static */ TimeStamp
 nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aDefault.IsNull());
 
   if (!sRegularRateTimer) {
     return aDefault;
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -17,16 +17,17 @@
 #include "mozilla/Vector.h"
 #include "mozilla/WeakPtr.h"
 #include "nsTObserverArray.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 #include "nsTObserverArray.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
+#include "mozilla/AnimationEventDispatcher.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/layers/TransactionIdAllocator.h"
 
 class nsPresContext;
 class nsIPresShell;
 class nsIDocument;
 class imgIRequest;
@@ -239,16 +240,34 @@ public:
 
   /**
    * Cancel all pending events scheduled by ScheduleEventDispatch which
    * targets any node in aDocument.
    */
   void CancelPendingEvents(nsIDocument* aDocument);
 
   /**
+   * Queue new animation events to dispatch in next tick.
+   */
+  void ScheduleAnimationEventDispatch(
+    mozilla::AnimationEventDispatcher* aDispatcher)
+  {
+    NS_ASSERTION(!mAnimationEventFlushObservers.Contains(aDispatcher),
+                 "Double-adding animation event flush observer");
+    mAnimationEventFlushObservers.AppendElement(aDispatcher);
+    EnsureTimerStarted();
+  }
+
+  /**
+   * Cancel all pending animation events associated with |aDispatcher|.
+   */
+  void CancelPendingAnimationEvents(
+    mozilla::AnimationEventDispatcher* aDispatcher);
+
+  /**
    * Schedule a frame visibility update "soon", subject to the heuristics and
    * throttling we apply to visibility updates.
    */
   void ScheduleFrameVisibilityUpdate() { mNeedToRecomputeVisibility = true; }
 
   /**
    * Tell the refresh driver that it is done driving refreshes and
    * should stop its timer and forget about its pres context.  This may
@@ -491,16 +510,18 @@ private:
   AutoTArray<nsIPresShell*, 16> mResizeEventFlushObservers;
   AutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
   AutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
   // nsTArray on purpose, because we want to be able to swap.
   nsTArray<nsIDocument*> mFrameRequestCallbackDocs;
   nsTArray<nsIDocument*> mThrottledFrameRequestCallbackDocs;
   nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers;
   nsTArray<PendingEvent> mPendingEvents;
+  AutoTArray<mozilla::AnimationEventDispatcher*, 16>
+    mAnimationEventFlushObservers;
 
   void BeginRefreshingImages(RequestTable& aEntries,
                              mozilla::TimeStamp aDesired);
 
   friend class mozilla::RefreshDriverTimer;
 
   static void Shutdown();