Bug 1300659 P3 Make setTimeout() and setInterval() use the TabGroup ThrottledEventQueue. r=smaug
authorBen Kelly <ben@wanderview.com>
Mon, 07 Nov 2016 12:30:17 -0800
changeset 321573 00efae73e570689a72761918362c2e0e1e819183
parent 321572 a7b4c0d350ef04a8e33fcb98b680a8b103d453b8
child 321574 2ff33f3c59d5782f460d03d28f91a0a2dce0b8f0
push id30931
push userkwierso@gmail.com
push dateTue, 08 Nov 2016 21:58:36 +0000
treeherdermozilla-central@783356f1476e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1300659
milestone52.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 1300659 P3 Make setTimeout() and setInterval() use the TabGroup ThrottledEventQueue. r=smaug
dom/base/Timeout.cpp
dom/base/Timeout.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
--- a/dom/base/Timeout.cpp
+++ b/dom/base/Timeout.cpp
@@ -50,37 +50,58 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptHandler)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Timeout, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Timeout, Release)
 
-nsresult
-Timeout::InitTimer(uint32_t aDelay)
+namespace {
+
+void
+TimerCallback(nsITimer*, void* aClosure)
 {
-  return mTimer->InitWithNameableFuncCallback(
-    nsGlobalWindow::TimerCallback, this, aDelay,
-    nsITimer::TYPE_ONE_SHOT, Timeout::TimerNameCallback);
+  RefPtr<Timeout> timeout = (Timeout*)aClosure;
+  timeout->mWindow->RunTimeout(timeout);
 }
 
-// static
 void
-Timeout::TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf,
-                           size_t aLen)
+TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf, size_t aLen)
 {
   RefPtr<Timeout> timeout = (Timeout*)aClosure;
 
   const char* filename;
   uint32_t lineNum, column;
   timeout->mScriptHandler->GetLocation(&filename, &lineNum, &column);
   snprintf(aBuf, aLen, "[content] %s:%u:%u", filename, lineNum, column);
 }
 
+} // anonymous namespace
+
+nsresult
+Timeout::InitTimer(nsIEventTarget* aTarget, uint32_t aDelay)
+{
+  // If the given target does not match the timer's current target
+  // then we need to override it before the Init.  Note that GetTarget()
+  // will return the current thread after setting the target to nullptr.
+  // So we need to special case the nullptr target comparison.
+  nsCOMPtr<nsIEventTarget> currentTarget;
+  MOZ_ALWAYS_SUCCEEDS(mTimer->GetTarget(getter_AddRefs(currentTarget)));
+  if ((aTarget && currentTarget != aTarget) ||
+      (!aTarget && currentTarget != NS_GetCurrentThread())) {
+    // Always call Cancel() in case we are re-using a timer.  Otherwise
+    // the subsequent SetTarget() may fail.
+    MOZ_ALWAYS_SUCCEEDS(mTimer->Cancel());
+    MOZ_ALWAYS_SUCCEEDS(mTimer->SetTarget(aTarget));
+  }
+
+  return mTimer->InitWithNameableFuncCallback(
+    TimerCallback, this, aDelay, nsITimer::TYPE_ONE_SHOT, TimerNameCallback);
+}
 
 // Return true if this timeout has a refcount of 1. This is used to check
 // that dummy_timeout doesn't leak from nsGlobalWindow::RunTimeout.
 #ifdef DEBUG
 bool
 Timeout::HasRefCntOne() const
 {
   return mRefCnt.get() == 1;
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -29,23 +29,23 @@ class Timeout final
   : public LinkedListElement<Timeout>
 {
 public:
   Timeout();
 
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Timeout)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Timeout)
 
-  nsresult InitTimer(uint32_t aDelay);
+  // The target may be specified to use a particular event queue for the
+  // resulting timer runnable.  A nullptr target will result in the
+  // default main thread being used.
+  nsresult InitTimer(nsIEventTarget* aTarget, uint32_t aDelay);
 
   enum class Reason { eTimeoutOrInterval, eIdleCallbackTimeout };
 
-  static void TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf,
-                                size_t aLen);
-
 #ifdef DEBUG
   bool HasRefCntOne() const;
 #endif // DEBUG
 
   // Window for which this timeout fires
   RefPtr<nsGlobalWindow> mWindow;
 
   // The actual timer object
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -11877,17 +11877,17 @@ nsGlobalWindow::Resume()
     uint32_t delay = std::max(remaining, DOMMinTimeoutValue());
 
     t->mTimer = do_CreateInstance("@mozilla.org/timer;1");
     if (!t->mTimer) {
       t->remove();
       continue;
     }
 
-    nsresult rv = t->InitTimer(delay);
+    nsresult rv = t->InitTimer(GetThrottledEventQueue(), delay);
     if (NS_FAILED(rv)) {
       t->mTimer = nullptr;
       t->remove();
       continue;
     }
 
     // Add a reference for the new timer's closure.
     t->AddRef();
@@ -12596,17 +12596,17 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
     nsresult rv;
     timeout->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     RefPtr<Timeout> copy = timeout;
 
-    rv = timeout->InitTimer(realInterval);
+    rv = timeout->InitTimer(GetThrottledEventQueue(), realInterval);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // The timeout is now also held in the timer's closure.
     Unused << copy.forget();
   }
 
@@ -12869,17 +12869,18 @@ nsGlobalWindow::RescheduleTimeout(Timeou
     aTimeout->mTimeRemaining = delay;
     return true;
   }
 
   aTimeout->mWhen = currentNow + delay;
 
   // Reschedule the OS timer. Don't bother returning any error codes if
   // this fails since the callers of this method don't care about them.
-  nsresult rv = aTimeout->InitTimer(delay.ToMilliseconds());
+  nsresult rv = aTimeout->InitTimer(GetThrottledEventQueue(),
+                                    delay.ToMilliseconds());
 
   if (NS_FAILED(rv)) {
     NS_ERROR("Error initializing timer for DOM timeout!");
 
     // We failed to initialize the new OS timer, this timer does
     // us no good here so we just cancel it (just in case) and
     // null out the pointer to the OS timer, this will release the
     // OS timer. As we continue executing the code below we'll end
@@ -12944,16 +12945,30 @@ nsGlobalWindow::RunTimeout(Timeout* aTim
   for (Timeout* timeout = mTimeouts.getFirst();
        timeout && timeout->mWhen <= deadline;
        timeout = timeout->getNext()) {
     if (timeout->mFiringDepth == 0) {
       // Mark any timeouts that are on the list to be fired with the
       // firing depth so that we can reentrantly run timeouts
       timeout->mFiringDepth = firingDepth;
       last_expired_timeout = timeout;
+
+      // Run available timers until we see our target timer.  After
+      // that, however, stop coalescing timers so we can yield the
+      // main thread.  Further timers that are ready will get picked
+      // up by their own nsITimer runnables when they execute.
+      //
+      // For chrome windows, however, we do coalesce all timers and
+      // do not yield the main thread.  This is partly because we
+      // trust chrome windows not to misbehave and partly because a
+      // number of browser chrome tests have races that depend on this
+      // coalescing.
+      if (timeout == aTimeout && !IsChromeWindow()) {
+        break;
+      }
     }
   }
 
   // Maybe the timeout that the event was fired for has been deleted
   // and there are no others timeouts with deadlines that make them
   // eligible for execution yet. Go away.
   if (!last_expired_timeout) {
     return;
@@ -13162,17 +13177,18 @@ nsresult nsGlobalWindow::ResetTimersForN
       timeout->remove();
       // InsertTimeoutIntoList will addref |timeout| and reset
       // mFiringDepth.  Make sure to undo that after calling it.
       uint32_t firingDepth = timeout->mFiringDepth;
       InsertTimeoutIntoList(timeout);
       timeout->mFiringDepth = firingDepth;
       timeout->Release();
 
-      nsresult rv = timeout->InitTimer(delay.ToMilliseconds());
+      nsresult rv = timeout->InitTimer(GetThrottledEventQueue(),
+                                       delay.ToMilliseconds());
 
       if (NS_FAILED(rv)) {
         NS_WARNING("Error resetting non background timer for DOM timeout!");
         return rv;
       }
 
       timeout = nextTimeout;
     } else {
@@ -13251,25 +13267,16 @@ nsGlobalWindow::InsertTimeoutIntoList(Ti
 
   aTimeout->mFiringDepth = 0;
 
   // Increment the timeout's reference count since it's now held on to
   // by the list
   aTimeout->AddRef();
 }
 
-// static
-void
-nsGlobalWindow::TimerCallback(nsITimer *aTimer, void *aClosure)
-{
-  RefPtr<Timeout> timeout = (Timeout*)aClosure;
-
-  timeout->mWindow->RunTimeout(timeout);
-}
-
 //*****************************************************************************
 // nsGlobalWindow: Helper Functions
 //*****************************************************************************
 
 already_AddRefed<nsIDocShellTreeOwner>
 nsGlobalWindow::GetTreeOwner()
 {
   FORWARD_TO_OUTER(GetTreeOwner, (), nullptr);
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1493,17 +1493,16 @@ public:
   // Return true if |aTimeout| needs to be reinserted into the timeout list.
   bool RescheduleTimeout(mozilla::dom::Timeout* aTimeout, const TimeStamp& now,
                          bool aRunningPendingTimeouts);
 
   void ClearAllTimeouts();
   // Insert aTimeout into the list, before all timeouts that would
   // fire after it, but no earlier than mTimeoutInsertionPoint, if any.
   void InsertTimeoutIntoList(mozilla::dom::Timeout* aTimeout);
-  static void TimerCallback(nsITimer *aTimer, void *aClosure);
   uint32_t GetTimeoutId(mozilla::dom::Timeout::Reason aReason);
 
   // Helper Functions
   already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
   already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
   already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
   nsresult SecurityCheckURL(const char *aURL);
   bool IsPrivateBrowsing();