Bug 1445570 - Remove EnsureEventualAfterPaint timer. r=tnikkel
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Thu, 06 Sep 2018 02:21:39 +0000
changeset 435027 6fefaf842b05bea156591d27cbd64095617dbf27
parent 435026 7a928140b9339e4f0592e6bdbcd29499bb5a4056
child 435028 1ff28f2b711f6744237f909bf3ae9ed450c88817
push id34587
push userebalazs@mozilla.com
push dateThu, 06 Sep 2018 09:51:47 +0000
treeherdermozilla-central@cc51965a00a9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstnikkel
bugs1445570
milestone64.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 1445570 - Remove EnsureEventualAfterPaint timer. r=tnikkel MozReview-Commit-ID: C7WICJ5Q0ES Differential Revision: https://phabricator.services.mozilla.com/D5005
dom/base/nsDOMWindowUtils.cpp
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
layout/reftests/text/reftest.list
layout/style/test/test_restyles_in_smil_animation.html
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -387,16 +387,17 @@ nsDOMWindowUtils::UpdateLayerTree()
     // Don't flush throttled animations since it might fire MozAfterPaint event
     // (in WebRender it constantly does), thus the reftest harness can't take
     // any snapshot until the throttled animations finished.
     presShell->FlushPendingNotifications(
       ChangesToFlush(FlushType::Display, false /* flush animations */));
     RefPtr<nsViewManager> vm = presShell->GetViewManager();
     nsView* view = vm->GetRootView();
     if (view) {
+      nsAutoScriptBlocker scriptBlocker;
       presShell->Paint(view, view->GetBounds(),
           nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_SYNC_DECODE_IMAGES);
       presShell->GetLayerManager()->WaitOnTransactionProcessed();
     }
   }
   return NS_OK;
 }
 
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -133,16 +133,17 @@ nsPresContext::MakeColorPref(const nsStr
 }
 
 bool
 nsPresContext::IsDOMPaintEventPending()
 {
   if (!mTransactions.IsEmpty()) {
     return true;
   }
+
   nsRootPresContext* drpc = GetRootPresContext();
   if (drpc && drpc->mRefreshDriver->ViewManagerFlushIsPending()) {
     // Since we're promising that there will be a MozAfterPaint event
     // fired, we record an empty invalidation in case display list
     // invalidation doesn't invalidate anything further.
     NotifyInvalidation(drpc->mRefreshDriver->LastTransactionId().Next(), nsRect(0, 0, 0, 0));
     return true;
   }
@@ -337,19 +338,16 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPresContext)
 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsPresContext, LastRelease())
 
 void
 nsPresContext::LastRelease()
 {
-  if (IsRoot()) {
-    static_cast<nsRootPresContext*>(this)->CancelAllDidPaintTimers();
-  }
   if (mMissingFonts) {
     mMissingFonts->Clear();
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsPresContext)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsPresContext)
@@ -958,19 +956,16 @@ nsPresContext::DetachShell()
   }
 
   if (IsRoot()) {
     nsRootPresContext* thisRoot = static_cast<nsRootPresContext*>(this);
 
     // Have to cancel our plugin geometry timer, because the
     // callback for that depends on a non-null presshell.
     thisRoot->CancelApplyPluginGeometryTimer();
-
-    // The did-paint timer also depends on a non-null pres shell.
-    thisRoot->CancelAllDidPaintTimers();
   }
 }
 
 void
 nsPresContext::DoChangeCharSet(NotNull<const Encoding*> aCharSet)
 {
   UpdateCharSet(aCharSet);
   mDeviceContext->FlushFontCache();
@@ -2429,22 +2424,16 @@ nsPresContext::NotifyInvalidation(Transa
     TransactionInvalidations* transaction = pc->GetInvalidations(aTransactionId);
     if (transaction) {
       break;
     } else {
       transaction = pc->mTransactions.AppendElement();
       transaction->mTransactionId = aTransactionId;
     }
   }
-  if (!pc) {
-    nsRootPresContext* rpc = GetRootPresContext();
-    if (rpc) {
-      rpc->EnsureEventualDidPaintEvent(aTransactionId);
-    }
-  }
 
   TransactionInvalidations* transaction = GetInvalidations(aTransactionId);
   MOZ_ASSERT(transaction);
   transaction->mInvalidations.AppendElement(aRect);
 }
 
 /* static */ void
 nsPresContext::NotifySubDocInvalidation(ContainerLayer* aContainer,
@@ -2503,16 +2492,28 @@ nsPresContext::NotifyDidPaintSubdocument
   nsPresContext* pc = aDocument->GetPresContext();
   if (pc) {
     pc->NotifyDidPaintForSubtree(closure->mTransactionId,
                                  closure->mTimeStamp);
   }
   return true;
 }
 
+/* static */ bool
+nsPresContext::NotifyRevokingDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData)
+{
+  NotifyDidPaintSubdocumentCallbackClosure* closure =
+    static_cast<NotifyDidPaintSubdocumentCallbackClosure*>(aData);
+  nsPresContext* pc = aDocument->GetPresContext();
+  if (pc) {
+    pc->NotifyRevokingDidPaint(closure->mTransactionId);
+  }
+  return true;
+}
+
 class DelayedFireDOMPaintEvent : public Runnable {
 public:
   DelayedFireDOMPaintEvent(
     nsPresContext* aPresContext,
     nsTArray<nsRect>* aList,
     TransactionId aTransactionId,
     const mozilla::TimeStamp& aTimeStamp = mozilla::TimeStamp())
     : mozilla::Runnable("DelayedFireDOMPaintEvent")
@@ -2536,25 +2537,61 @@ public:
 
   RefPtr<nsPresContext> mPresContext;
   TransactionId mTransactionId;
   const mozilla::TimeStamp mTimeStamp;
   nsTArray<nsRect> mList;
 };
 
 void
+nsPresContext::NotifyRevokingDidPaint(TransactionId aTransactionId)
+ {
+   if ((IsRoot() || !PresShell()->IsVisible()) && mTransactions.IsEmpty()) {
+     return;
+   }
+
+  TransactionInvalidations* transaction = nullptr;
+  for (auto& t : mTransactions) {
+    if (t.mTransactionId == aTransactionId) {
+      transaction = &t;
+      break;
+    }
+  }
+  // If there are no transaction invalidations (which imply callers waiting
+  // on the event) for this revoked id, then we don't need to fire a
+  // MozAfterPaint.
+  if (!transaction) {
+    return;
+  }
+
+  // If there are queued transactions with an earlier id, we can't send
+  // our event now since it will arrive out of order. Set the waiting for
+  // previous transaction flag to true, and we'll send the event when
+  // the others are completed.
+  // If this is the only transaction, then we can send it immediately.
+  if (mTransactions.Length() == 1) {
+    nsCOMPtr<nsIRunnable> ev =
+      new DelayedFireDOMPaintEvent(this, &transaction->mInvalidations,
+                                   transaction->mTransactionId, mozilla::TimeStamp());
+    nsContentUtils::AddScriptRunner(ev);
+    mTransactions.RemoveElementAt(0);
+  } else {
+    transaction->mIsWaitingForPreviousTransaction = true;
+  }
+
+  NotifyDidPaintSubdocumentCallbackClosure closure = { aTransactionId, mozilla::TimeStamp() };
+  mDocument->EnumerateSubDocuments(nsPresContext::NotifyRevokingDidPaintSubdocumentCallback, &closure);
+}
+
+void
 nsPresContext::NotifyDidPaintForSubtree(TransactionId aTransactionId,
                                         const mozilla::TimeStamp& aTimeStamp)
 {
-  if (IsRoot()) {
-    static_cast<nsRootPresContext*>(this)->CancelDidPaintTimers(aTransactionId);
-
-    if (mTransactions.IsEmpty()) {
-      return;
-    }
+  if (IsRoot() && mTransactions.IsEmpty()) {
+    return;
   }
 
   if (!PresShell()->IsVisible() && mTransactions.IsEmpty()) {
     return;
   }
 
   // Non-root prescontexts fire MozAfterPaint to all their descendants
   // unconditionally, even if no invalidations have been collected. This is
@@ -2570,16 +2607,27 @@ nsPresContext::NotifyDidPaintForSubtree(
         nsCOMPtr<nsIRunnable> ev =
           new DelayedFireDOMPaintEvent(this, &mTransactions[i].mInvalidations,
                                        mTransactions[i].mTransactionId, aTimeStamp);
         nsContentUtils::AddScriptRunner(ev);
         sent = true;
       }
       mTransactions.RemoveElementAt(i);
     } else {
+      // If there are transaction which is waiting for this transaction,
+      // we should fire a MozAfterPaint immediately.
+      if (sent && mTransactions[i].mIsWaitingForPreviousTransaction) {
+        nsCOMPtr<nsIRunnable> ev =
+          new DelayedFireDOMPaintEvent(this, &mTransactions[i].mInvalidations,
+                                       mTransactions[i].mTransactionId, aTimeStamp);
+        nsContentUtils::AddScriptRunner(ev);
+        sent = true;
+        mTransactions.RemoveElementAt(i);
+        continue;
+      }
       i++;
     }
   }
 
   if (!sent) {
     nsTArray<nsRect> dummy;
     nsCOMPtr<nsIRunnable> ev =
       new DelayedFireDOMPaintEvent(this, &dummy,
@@ -2951,24 +2999,22 @@ nsRootPresContext::nsRootPresContext(nsI
   : nsPresContext(aDocument, aType)
 {
 }
 
 nsRootPresContext::~nsRootPresContext()
 {
   NS_ASSERTION(mRegisteredPlugins.Count() == 0,
                "All plugins should have been unregistered");
-  CancelAllDidPaintTimers();
   CancelApplyPluginGeometryTimer();
 }
 
 /* virtual */ void
 nsRootPresContext::Detach()
 {
-  CancelAllDidPaintTimers();
   // XXXmats maybe also CancelApplyPluginGeometryTimer(); ?
   nsPresContext::Detach();
 }
 
 void
 nsRootPresContext::RegisterPluginForGeometryUpdates(nsIContent* aPlugin)
 {
   mRegisteredPlugins.PutEntry(aPlugin);
@@ -3214,65 +3260,16 @@ nsRootPresContext::CollectPluginGeometry
   if (clm) {
     clm->StorePluginWidgetConfigurations(configurations);
   }
   PluginDidSetGeometry(mRegisteredPlugins);
 #endif  // #ifndef XP_MACOSX
 }
 
 void
-nsRootPresContext::EnsureEventualDidPaintEvent(TransactionId aTransactionId)
-{
-  for (NotifyDidPaintTimer& t : mNotifyDidPaintTimers) {
-    if (t.mTransactionId == aTransactionId) {
-      return;
-    }
-  }
-
-  nsCOMPtr<nsITimer> timer;
-  RefPtr<nsRootPresContext> self = this;
-  nsresult rv = NS_NewTimerWithCallback(
-    getter_AddRefs(timer),
-    NewNamedTimerCallback([self, aTransactionId](){
-      nsAutoScriptBlocker blockScripts;
-      self->NotifyDidPaintForSubtree(aTransactionId);
-     }, "NotifyDidPaintForSubtree"), 100, nsITimer::TYPE_ONE_SHOT,
-    Document()->EventTargetFor(TaskCategory::Other));
-
-  if (NS_SUCCEEDED(rv)) {
-    NotifyDidPaintTimer* t = mNotifyDidPaintTimers.AppendElement();
-    t->mTransactionId = aTransactionId;
-    t->mTimer = timer;
-  }
-}
-
-void
-nsRootPresContext::CancelDidPaintTimers(TransactionId aTransactionId)
-{
-  uint32_t i = 0;
-  while (i < mNotifyDidPaintTimers.Length()) {
-    if (mNotifyDidPaintTimers[i].mTransactionId <= aTransactionId) {
-      mNotifyDidPaintTimers[i].mTimer->Cancel();
-      mNotifyDidPaintTimers.RemoveElementAt(i);
-    } else {
-      i++;
-    }
-  }
-}
-
-void
-nsRootPresContext::CancelAllDidPaintTimers()
-{
-  for (uint32_t i = 0; i < mNotifyDidPaintTimers.Length(); i++) {
-    mNotifyDidPaintTimers[i].mTimer->Cancel();
-  }
-  mNotifyDidPaintTimers.Clear();
-}
-
-void
 nsRootPresContext::AddWillPaintObserver(nsIRunnable* aRunnable)
 {
   if (!mWillPaintFallbackEvent.IsPending()) {
     mWillPaintFallbackEvent = new RunWillPaintObservers(this);
     Document()->Dispatch(TaskCategory::Other,
                          do_AddRef(mWillPaintFallbackEvent));
   }
   mWillPaintObservers.AppendElement(aRunnable);
@@ -3294,13 +3291,12 @@ nsRootPresContext::FlushWillPaintObserve
 
 size_t
 nsRootPresContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return nsPresContext::SizeOfExcludingThis(aMallocSizeOf);
 
   // Measurement of the following members may be added later if DMD finds it is
   // worthwhile:
-  // - mNotifyDidPaintTimer
   // - mRegisteredPlugins
   // - mWillPaintObservers
   // - mWillPaintFallbackEvent
 }
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -1012,16 +1012,17 @@ public:
   // by nsRefreshDriver::GetTransactionId).
   // Invalidated regions will be dispatched to MozAfterPaint events when
   // NotifyDidPaintForSubtree is called for the transaction id (or any higher id).
   void NotifyInvalidation(TransactionId aTransactionId, const nsRect& aRect);
   // aRect is in device pixels
   void NotifyInvalidation(TransactionId aTransactionId, const nsIntRect& aRect);
   void NotifyDidPaintForSubtree(TransactionId aTransactionId = TransactionId{0},
                                 const mozilla::TimeStamp& aTimeStamp = mozilla::TimeStamp());
+  void NotifyRevokingDidPaint(TransactionId aTransactionId);
   void FireDOMPaintEvent(nsTArray<nsRect>* aList,
                          TransactionId aTransactionId,
                          mozilla::TimeStamp aTimeStamp = mozilla::TimeStamp());
 
   // Callback for catching invalidations in ContainerLayers
   // Passed to LayerProperties::ComputeDifference
   static void NotifySubDocInvalidation(mozilla::layers::ContainerLayer* aContainer,
                                        const nsIntRegion* aRegion);
@@ -1210,16 +1211,18 @@ protected:
   {
     nsAtom* lang = aLanguage ? aLanguage : mLanguage.get();
     return StaticPresData::Get()->GetFontPrefsForLangHelper(lang, &mLangGroupFontPrefs, aNeedsToCache);
   }
 
   void UpdateCharSet(NotNull<const Encoding*> aCharSet);
 
   static bool NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData);
+  static bool NotifyRevokingDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData);
+
 
 public:
   // Used by the PresShell to force a reflow when some aspect of font info
   // has been updated, potentially affecting font selection and layout.
   void ForceReflowForFontInfoUpdate();
 
   void DoChangeCharSet(NotNull<const Encoding*> aCharSet);
 
@@ -1249,16 +1252,17 @@ protected:
   // Returns a refcounted pointer to the timer (or nullptr on failure).
   already_AddRefed<nsITimer> CreateTimer(nsTimerCallbackFunc aCallback,
                                          const char* aName,
                                          uint32_t aDelay);
 
   struct TransactionInvalidations {
     TransactionId mTransactionId;
     nsTArray<nsRect> mInvalidations;
+    bool mIsWaitingForPreviousTransaction = false;
   };
   TransactionInvalidations* GetInvalidations(TransactionId aTransactionId);
 
   // IMPORTANT: The ownership implicit in the following member variables
   // has been explicitly checked.  If you add any members to this class,
   // please make the ownership explicit (pinkerton, scc).
 
   nsPresContextType     mType;
@@ -1496,33 +1500,16 @@ public:
 
 class nsRootPresContext final : public nsPresContext {
 public:
   nsRootPresContext(nsIDocument* aDocument, nsPresContextType aType);
   virtual ~nsRootPresContext();
   virtual void Detach() override;
 
   /**
-   * Ensure that NotifyDidPaintForSubtree is eventually called on this
-   * object after a timeout.
-   */
-  void EnsureEventualDidPaintEvent(TransactionId aTransactionId);
-
-  /**
-   * Cancels any pending eventual did paint timer for transaction
-   * ids up to and including aTransactionId.
-   */
-  void CancelDidPaintTimers(TransactionId aTransactionId);
-
-  /**
-   * Cancel all pending eventual did paint timers.
-   */
-  void CancelAllDidPaintTimers();
-
-  /**
    * Registers a plugin to receive geometry updates (position and clip
    * region) so it can update its widget.
    * Callers must call UnregisterPluginForGeometryUpdates before
    * the aPlugin frame is destroyed.
    */
   void RegisterPluginForGeometryUpdates(nsIContent* aPlugin);
   /**
    * Stops a plugin receiving geometry updates (position and clip
@@ -1603,22 +1590,16 @@ protected:
       return NS_OK;
     }
     // The lifetime of this reference is handled by an nsRevocableEventPtr
     nsRootPresContext* MOZ_NON_OWNING_REF mPresContext;
   };
 
   friend class nsPresContext;
 
-  struct NotifyDidPaintTimer {
-    TransactionId mTransactionId;
-    nsCOMPtr<nsITimer> mTimer;
-  };
-  AutoTArray<NotifyDidPaintTimer, 4> mNotifyDidPaintTimers;
-
   nsCOMPtr<nsITimer> mApplyPluginGeometryTimer;
   nsTHashtable<nsRefPtrHashKey<nsIContent> > mRegisteredPlugins;
   nsTArray<nsCOMPtr<nsIRunnable> > mWillPaintObservers;
   nsRevocableEventPtr<RunWillPaintObservers> mWillPaintFallbackEvent;
 };
 
 #ifdef MOZ_REFLOW_PERF
 
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -1105,17 +1105,18 @@ nsRefreshDriver::ChooseTimer() const
   }
   return sRegularRateTimer;
 }
 
 nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
   : mActiveTimer(nullptr),
     mPresContext(aPresContext),
     mRootRefresh(nullptr),
-    mPendingTransaction{0},
+    mNextTransactionId{0},
+    mOutstandingTransactionId{0},
     mCompletedTransaction{0},
     mFreezeCount(0),
     mThrottledFrameRequestInterval(TimeDuration::FromMilliseconds(
                                      GetThrottledTimerInterval())),
     mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),
     mThrottled(false),
     mNeedToRecomputeVisibility(false),
     mTestControllingRefreshes(false),
@@ -1182,17 +1183,17 @@ nsRefreshDriver::AdvanceTimeAndRefresh(i
   DoTick();
 }
 
 void
 nsRefreshDriver::RestoreNormalRefresh()
 {
   mTestControllingRefreshes = false;
   EnsureTimerStarted(eAllowTimeToGoBackwards);
-  mCompletedTransaction = mPendingTransaction;
+  mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId;
 }
 
 TimeStamp
 nsRefreshDriver::MostRecentRefresh() const
 {
   // In case of stylo traversal, we have already activated the refresh driver in
   // RestyleManager::ProcessPendingRestyles().
   if (!ServoStyleSet::IsInServoTraversal()) {
@@ -2150,79 +2151,95 @@ nsRefreshDriver::FinishedWaitingForTrans
   mWaitingForTransaction = false;
   mSkippedPaints = false;
   mWarningThreshold = 1;
 }
 
 mozilla::layers::TransactionId
 nsRefreshDriver::GetTransactionId(bool aThrottle)
 {
-  mPendingTransaction = mPendingTransaction.Next();
+  mOutstandingTransactionId = mOutstandingTransactionId.Next();
+  mNextTransactionId = mNextTransactionId.Next();
 
   if (aThrottle &&
-      mPendingTransaction - mCompletedTransaction >= 2 &&
+      mOutstandingTransactionId - mCompletedTransaction >= 2 &&
       !mWaitingForTransaction &&
       !mTestControllingRefreshes) {
     mWaitingForTransaction = true;
     mSkippedPaints = false;
     mWarningThreshold = 1;
   }
 
-  return mPendingTransaction;
+  return mNextTransactionId;
 }
 
 mozilla::layers::TransactionId
 nsRefreshDriver::LastTransactionId() const
 {
-  return mPendingTransaction;
+  return mNextTransactionId;
 }
 
 void
 nsRefreshDriver::RevokeTransactionId(mozilla::layers::TransactionId aTransactionId)
 {
-  MOZ_ASSERT(aTransactionId == mPendingTransaction);
-  if (mPendingTransaction - mCompletedTransaction == 2 &&
+  MOZ_ASSERT(aTransactionId == mNextTransactionId);
+  if (mOutstandingTransactionId - mCompletedTransaction == 2 &&
       mWaitingForTransaction) {
     MOZ_ASSERT(!mSkippedPaints, "How did we skip a paint when we're in the middle of one?");
     FinishedWaitingForTransaction();
   }
-  mPendingTransaction = mPendingTransaction.Prev();
+
+  // Notify the pres context so that it can deliver MozAfterPaint for this
+  // id if any caller was expecting it.
+  nsPresContext* pc = GetPresContext();
+  if (pc) {
+    pc->NotifyRevokingDidPaint(aTransactionId);
+  }
+  // Revert the outstanding transaction since we're no longer waiting on it to be
+  // completed, but don't revert mNextTransactionId since we can't use the id
+  // again.
+  mOutstandingTransactionId = mOutstandingTransactionId.Prev();
 }
 
 void
 nsRefreshDriver::ClearPendingTransactions()
 {
-  mCompletedTransaction = mPendingTransaction;
+  mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId;
   mWaitingForTransaction = false;
 }
 
 void
 nsRefreshDriver::ResetInitialTransactionId(mozilla::layers::TransactionId aTransactionId)
 {
-  mCompletedTransaction = mPendingTransaction = aTransactionId;
+  mCompletedTransaction = mOutstandingTransactionId = mNextTransactionId = aTransactionId;
 }
 
 mozilla::TimeStamp
 nsRefreshDriver::GetTransactionStart()
 {
   return mTickStart;
 }
 
 void
 nsRefreshDriver::NotifyTransactionCompleted(mozilla::layers::TransactionId aTransactionId)
 {
   if (aTransactionId > mCompletedTransaction) {
-    if (mPendingTransaction - mCompletedTransaction > 1 &&
+    if (mOutstandingTransactionId - mCompletedTransaction > 1 &&
         mWaitingForTransaction) {
       mCompletedTransaction = aTransactionId;
       FinishedWaitingForTransaction();
     } else {
       mCompletedTransaction = aTransactionId;
     }
   }
+
+  // If completed transaction id get ahead of outstanding id, reset to distance id.
+  if (mCompletedTransaction > mOutstandingTransactionId) {
+    mOutstandingTransactionId = mCompletedTransaction;
+  }
 }
 
 void
 nsRefreshDriver::WillRefresh(mozilla::TimeStamp aTime)
 {
   mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
   mRootRefresh = nullptr;
   if (mSkippedPaints) {
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -460,17 +460,22 @@ private:
   mozilla::RefreshDriverTimer* mActiveTimer;
 
   // nsPresContext passed in constructor and unset in Disconnect.
   mozilla::WeakPtr<nsPresContext> mPresContext;
 
   RefPtr<nsRefreshDriver> mRootRefresh;
 
   // The most recently allocated transaction id.
-  TransactionId mPendingTransaction;
+  TransactionId mNextTransactionId;
+  // This number is mCompletedTransaction + (pending transaction count).
+  // When we revoke a transaction id, we revert this number (since it's
+  // no longer outstanding), but not mNextTransactionId (since we don't
+  // want to reuse the number).
+  TransactionId mOutstandingTransactionId;
   // The most recently completed transaction id.
   TransactionId mCompletedTransaction;
 
   uint32_t mFreezeCount;
 
   // How long we wait between ticks for throttled (which generally means
   // non-visible) documents registered with a non-throttled refresh driver.
   const mozilla::TimeDuration mThrottledFrameRequestInterval;
--- a/layout/reftests/text/reftest.list
+++ b/layout/reftests/text/reftest.list
@@ -316,17 +316,17 @@ pref(gfx.font_rendering.graphite.enabled
 == auto-hyphenation-uk-1.html auto-hyphenation-uk-1-ref.html
 
 == auto-hyphenation-transformed-1.html auto-hyphenation-transformed-1-ref.html
 
 == hyphenation-control-1.html hyphenation-control-1-ref.html
 == hyphenation-control-2.html hyphenation-control-2-ref.html
 == hyphenation-control-3.html hyphenation-control-3-ref.html
 == hyphenation-control-4.html hyphenation-control-4-ref.html
-fuzzy-if(winWidget,0-47,0-6) == hyphenation-control-5.html hyphenation-control-5-ref.html
+fuzzy-if(winWidget,0-56,0-6) == hyphenation-control-5.html hyphenation-control-5-ref.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) == hyphenation-control-6.html hyphenation-control-6-ref.html # Bug 1392106
 == hyphenation-control-7.html hyphenation-control-7-ref.html
 
 # osx-font-smoothing - with and without subpixel AA, only under OSX
 fails-if(!cocoaWidget) != osx-font-smoothing.html osx-font-smoothing-ref.html
 fails-if(!cocoaWidget) != osx-font-smoothing-2.html osx-font-smoothing-2-notref.html
 == osx-font-smoothing-2.html osx-font-smoothing-2-ref.html
 
--- a/layout/style/test/test_restyles_in_smil_animation.html
+++ b/layout/style/test/test_restyles_in_smil_animation.html
@@ -72,17 +72,17 @@ add_task(async function smil_is_in_displ
     document.createElementNS("http://www.w3.org/2000/svg", "animate");
   animate.setAttribute("attributeType", "XML");
   animate.setAttribute("attributeName", "fill");
   animate.setAttribute("values", "red;lime");
   animate.setAttribute("dur", "1s");
   animate.setAttribute("repeatCount", "indefinite");
   document.getElementById("svg-rect").appendChild(animate);
 
-  await waitForPaintFlushed();
+  await waitForAnimationFrames(2);
 
   var displayMarkers = await observeStyling(5);
   // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
   // element is newly associated with an nsIFrame.
   ok(displayMarkers.length == 5 || displayMarkers.length == 4,
      `should restyle in most frames (got ${displayMarkers.length} restyles`
      + ' over five frames, expected 4~5)');
 
@@ -92,17 +92,17 @@ add_task(async function smil_is_in_displ
   getComputedStyle(div).display;
   await waitForPaintFlushed();
 
   var displayNoneMarkers = await observeStyling(5);
   is(displayNoneMarkers.length, 0, "should never restyle if display:none");
 
   div.style.display = "";
   getComputedStyle(div).display;
-  await waitForPaintFlushed();
+  await waitForAnimationFrames(2);
 
   var displayAgainMarkers = await observeStyling(5);
   // FIXME: Bug 866411: SMIL animations sometimes skip restyles when the target
   // element is newly associated with an nsIFrame.
   ok(displayMarkers.length == 5 || displayMarkers.length == 4,
      `should restyle again (got ${displayMarkers.length} restyles over`
      + ' five frames, expected 4~5)');