Bug 1320753 - Make TabGroup event target be a ThrottledEventQueue for timers, workers (r=bkelly)
authorBill McCloskey <billm@mozilla.com>
Sun, 27 Nov 2016 11:24:34 -0800
changeset 453590 4e9f3f0d6d8e2c7e8aa0037decd8405d5024bfa0
parent 453589 f980cbff80529d0c9d19d257e32f2af416c559b2
child 453591 7ab404ffffc6b813dc7491cf076c8c40e91abbec
push id39711
push userdmitchell@mozilla.com
push dateFri, 23 Dec 2016 21:59:47 +0000
reviewersbkelly
bugs1320753
milestone53.0a1
Bug 1320753 - Make TabGroup event target be a ThrottledEventQueue for timers, workers (r=bkelly) MozReview-Commit-ID: FCfYz02r8yI
dom/base/Dispatcher.h
dom/base/TabGroup.cpp
dom/base/TabGroup.h
dom/base/TimeoutManager.cpp
dom/base/TimeoutManager.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsPIDOMWindow.h
dom/workers/WorkerPrivate.cpp
xpcom/threads/ThrottledEventQueue.cpp
xpcom/threads/ThrottledEventQueue.h
--- a/dom/base/Dispatcher.h
+++ b/dom/base/Dispatcher.h
@@ -24,16 +24,19 @@ enum class TaskCategory {
   UI,
 
   // Data from the network
   Network,
 
   // setTimeout, setInterval
   Timer,
 
+  // Runnables posted from a worker to the main thread
+  Worker,
+
   // requestIdleCallback
   IdleCallback,
 
   // Vsync notifications
   RefreshDriver,
 
   // Most DOM events (postMessage, media, plugins)
   Other,
--- a/dom/base/TabGroup.cpp
+++ b/dom/base/TabGroup.cpp
@@ -57,23 +57,27 @@ void
 TabGroup::EnsureThrottledEventQueues()
 {
   if (mThrottledQueuesInitialized) {
     return;
   }
 
   mThrottledQueuesInitialized = true;
 
-  nsCOMPtr<nsIThread> mainThread;
-  NS_GetMainThread(getter_AddRefs(mainThread));
-  MOZ_DIAGNOSTIC_ASSERT(mainThread);
-
-  // This may return nullptr during xpcom shutdown.  This is ok as we
-  // do not guarantee a ThrottledEventQueue will be present.
-  mThrottledEventQueue = ThrottledEventQueue::Create(mainThread);
+  for (size_t i = 0; i < size_t(TaskCategory::Count); i++) {
+    TaskCategory category = static_cast<TaskCategory>(i);
+    if (category == TaskCategory::Worker || category == TaskCategory::Timer) {
+      nsCOMPtr<nsIEventTarget> target = ThrottledEventQueue::Create(mEventTargets[i]);
+      if (target) {
+        // This may return nullptr during xpcom shutdown.  This is ok as we
+        // do not guarantee a ThrottledEventQueue will be present.
+        mEventTargets[i] = target;
+      }
+    }
+  }
 }
 
 TabGroup*
 TabGroup::GetChromeTabGroup()
 {
   if (!sChromeTabGroup) {
     sChromeTabGroup = new TabGroup(true /* chrome tab group */);
     ClearOnShutdown(&sChromeTabGroup);
@@ -218,24 +222,16 @@ TabGroup::GetTopLevelWindows()
         !outerWindow->GetScriptableParentOrNull()) {
       array.AppendElement(outerWindow);
     }
   }
 
   return array;
 }
 
-ThrottledEventQueue*
-TabGroup::GetThrottledEventQueue() const
-{
-  MOZ_RELEASE_ASSERT(mThrottledQueuesInitialized || this == GetChromeTabGroup());
-  MOZ_RELEASE_ASSERT(!mLastWindowLeft);
-  return mThrottledEventQueue;
-}
-
 NS_IMPL_ISUPPORTS(TabGroup, nsISupports)
 
 TabGroup::HashEntry::HashEntry(const nsACString* aKey)
   : nsCStringHashKey(aKey), mDocGroup(nullptr)
 {}
 
 nsresult
 TabGroup::Dispatch(const char* aName,
@@ -253,14 +249,18 @@ TabGroup::Dispatch(const char* aName,
   } else {
     return NS_DispatchToMainThread(runnable.forget());
   }
 }
 
 nsIEventTarget*
 TabGroup::EventTargetFor(TaskCategory aCategory) const
 {
+  if (aCategory == TaskCategory::Worker || aCategory == TaskCategory::Timer) {
+    MOZ_RELEASE_ASSERT(mThrottledQueuesInitialized || this == sChromeTabGroup);
+  }
+
   MOZ_RELEASE_ASSERT(!mLastWindowLeft);
   return mEventTargets[size_t(aCategory)];
 }
 
 }
 }
--- a/dom/base/TabGroup.h
+++ b/dom/base/TabGroup.h
@@ -105,21 +105,16 @@ public:
   nsresult
   FindItemWithName(const nsAString& aName,
                    nsIDocShellTreeItem* aRequestor,
                    nsIDocShellTreeItem* aOriginalRequestor,
                    nsIDocShellTreeItem** aFoundItem);
 
   nsTArray<nsPIDOMWindowOuter*> GetTopLevelWindows();
 
-  // Get the event queue that associated windows can use to issue runnables to
-  // the main thread.  This may return nullptr during browser shutdown.
-  ThrottledEventQueue*
-  GetThrottledEventQueue() const;
-
   // This method is always safe to call off the main thread.
   virtual nsresult Dispatch(const char* aName,
                             TaskCategory aCategory,
                             already_AddRefed<nsIRunnable>&& aRunnable) override;
 
   // This method is always safe to call off the main thread. The nsIEventTarget
   // can always be used off the main thread.
   virtual nsIEventTarget* EventTargetFor(TaskCategory aCategory) const override;
@@ -128,17 +123,16 @@ public:
 
 private:
   void EnsureThrottledEventQueues();
 
   ~TabGroup();
   DocGroupMap mDocGroups;
   Atomic<bool> mLastWindowLeft;
   nsTArray<nsPIDOMWindowOuter*> mWindows;
-  bool mThrottledQueuesInitialized;
-  RefPtr<ThrottledEventQueue> mThrottledEventQueue;
+  Atomic<bool> mThrottledQueuesInitialized;
   nsCOMPtr<nsIEventTarget> mEventTargets[size_t(TaskCategory::Count)];
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // defined(TabGroup_h)
--- a/dom/base/TimeoutManager.cpp
+++ b/dom/base/TimeoutManager.cpp
@@ -199,17 +199,18 @@ TimeoutManager::SetTimeout(nsITimeoutHan
     nsresult rv;
     timeout->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     RefPtr<Timeout> copy = timeout;
 
-    rv = timeout->InitTimer(mWindow.GetThrottledEventQueue(), realInterval);
+    rv = timeout->InitTimer(mWindow.EventTargetFor(TaskCategory::Timer),
+                            realInterval);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // The timeout is now also held in the timer's closure.
     Unused << copy.forget();
   }
 
@@ -580,17 +581,18 @@ TimeoutManager::MaybeApplyBackPressure()
 
   // If we are already in back pressure then we don't need to apply back
   // pressure again.  We also shouldn't need to apply back pressure while
   // the window is suspended.
   if (mBackPressureDelayMS > 0 || mWindow.IsSuspended()) {
     return;
   }
 
-  RefPtr<ThrottledEventQueue> queue = mWindow.TabGroup()->GetThrottledEventQueue();
+  RefPtr<ThrottledEventQueue> queue =
+    do_QueryObject(mWindow.TabGroup()->EventTargetFor(TaskCategory::Timer));
   if (!queue) {
     return;
   }
 
   // Only begin back pressure if the window has greatly fallen behind the main
   // thread.  This is a somewhat arbitrary threshold chosen such that it should
   // rarely fire under normaly circumstances.  Its low enough, though,
   // that we should have time to slow new runnables from being added before an
@@ -616,17 +618,18 @@ TimeoutManager::MaybeApplyBackPressure()
 void
 TimeoutManager::CancelOrUpdateBackPressure(nsGlobalWindow* aWindow)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aWindow == &mWindow);
   MOZ_ASSERT(mBackPressureDelayMS > 0);
 
   // First, re-calculate the back pressure delay.
-  RefPtr<ThrottledEventQueue> queue = mWindow.TabGroup()->GetThrottledEventQueue();
+  RefPtr<ThrottledEventQueue> queue =
+    do_QueryObject(mWindow.TabGroup()->EventTargetFor(TaskCategory::Timer));
   int32_t newBackPressureDelayMS =
     CalculateNewBackPressureDelayMS(queue ? queue->Length() : 0);
 
   // If the delay has increased, then simply apply it.  Increasing the delay
   // does not risk re-ordering timers with similar parameters.  We want to
   // extra careful not to re-order sequential calls to setTimeout(func, 0),
   // for example.
   if (newBackPressureDelayMS > mBackPressureDelayMS) {
@@ -720,17 +723,17 @@ TimeoutManager::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(mWindow.GetThrottledEventQueue(),
+  nsresult rv = aTimeout->InitTimer(mWindow.EventTargetFor(TaskCategory::Timer),
                                     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
@@ -764,35 +767,36 @@ TimeoutManager::ResetTimersForThrottleRe
   if (mWindow.IsFrozen() || mWindow.IsSuspended()) {
     return NS_OK;
   }
 
   auto minTimeout = DOMMinTimeoutValue();
   Timeouts::SortBy sortBy = mWindow.IsFrozen() ? Timeouts::SortBy::TimeRemaining
                                                : Timeouts::SortBy::TimeWhen;
 
+  nsCOMPtr<nsIEventTarget> queue = mWindow.EventTargetFor(TaskCategory::Timer);
   nsresult rv = mNormalTimeouts.ResetTimersForThrottleReduction(aPreviousThrottleDelayMS,
                                                                 minTimeout,
                                                                 sortBy,
-                                                                mWindow.GetThrottledEventQueue());
+                                                                queue);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mTrackingTimeouts.ResetTimersForThrottleReduction(aPreviousThrottleDelayMS,
                                                          minTimeout,
                                                          sortBy,
-                                                         mWindow.GetThrottledEventQueue());
+                                                         queue);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 TimeoutManager::Timeouts::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS,
                                                           int32_t aMinTimeoutValueMS,
                                                           SortBy aSortBy,
-                                                          ThrottledEventQueue* aQueue)
+                                                          nsIEventTarget* aQueue)
 {
   TimeStamp now = TimeStamp::Now();
 
   // If insertion point is non-null, we're in the middle of firing timers and
   // the timers we're planning to fire all come before insertion point;
   // insertion point itself is a dummy timeout with an mWhen that may be
   // semi-bogus.  In that case, we don't need to do anything with insertion
   // point or anything before it, so should start at the timer after insertion
@@ -1028,17 +1032,18 @@ TimeoutManager::Resume()
     uint32_t delay = std::max(remaining, DOMMinTimeoutValue());
 
     aTimeout->mTimer = do_CreateInstance("@mozilla.org/timer;1");
     if (!aTimeout->mTimer) {
       aTimeout->remove();
       return;
     }
 
-    nsresult rv = aTimeout->InitTimer(mWindow.GetThrottledEventQueue(), delay);
+    nsresult rv = aTimeout->InitTimer(mWindow.EventTargetFor(TaskCategory::Timer),
+                                      delay);
     if (NS_FAILED(rv)) {
       aTimeout->mTimer = nullptr;
       aTimeout->remove();
       return;
     }
 
     // Add a reference for the new timer's closure.
     aTimeout->AddRef();
--- a/dom/base/TimeoutManager.h
+++ b/dom/base/TimeoutManager.h
@@ -4,23 +4,21 @@
  * 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/. */
 
 #ifndef mozilla_dom_TimeoutManager_h__
 #define mozilla_dom_TimeoutManager_h__
 
 #include "mozilla/dom/Timeout.h"
 
+class nsIEventTarget;
 class nsITimeoutHandler;
 class nsGlobalWindow;
 
 namespace mozilla {
-
-class ThrottledEventQueue;
-
 namespace dom {
 
 class OrderedTimeoutIterator;
 
 // This class manages the timeouts in a Window's setTimeout/setInterval pool.
 class TimeoutManager final
 {
 public:
@@ -128,17 +126,17 @@ private:
     {
       TimeRemaining,
       TimeWhen
     };
     void Insert(mozilla::dom::Timeout* aTimeout, SortBy aSortBy);
     nsresult ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS,
                                              int32_t aMinTimeoutValueMS,
                                              SortBy aSortBy,
-                                             mozilla::ThrottledEventQueue* aQueue);
+                                             nsIEventTarget* aQueue);
 
     const Timeout* GetFirst() const { return mTimeoutList.getFirst(); }
     Timeout* GetFirst() { return mTimeoutList.getFirst(); }
     const Timeout* GetLast() const { return mTimeoutList.getLast(); }
     Timeout* GetLast() { return mTimeoutList.getLast(); }
     bool IsEmpty() const { return mTimeoutList.isEmpty(); }
     void InsertFront(Timeout* aTimeout) { mTimeoutList.insertFront(aTimeout); }
     void Clear() { mTimeoutList.clear(); }
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -9560,28 +9560,16 @@ nsGlobalWindow::UpdateCommands(const nsA
       nsContentUtils::AddScriptRunner(new CommandDispatcher(xulCommandDispatcher,
                                                             anAction));
     }
   }
 
   return NS_OK;
 }
 
-ThrottledEventQueue*
-nsGlobalWindow::GetThrottledEventQueue()
-{
-  // We must have an outer to access the TabGroup.
-  nsGlobalWindow* outer = GetOuterWindowInternal();
-  if (!outer) {
-    return nullptr;
-  }
-
-  return TabGroup()->GetThrottledEventQueue();
-}
-
 Selection*
 nsGlobalWindow::GetSelectionOuter()
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
     return nullptr;
   }
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1179,18 +1179,16 @@ public:
              const mozilla::dom::Sequence<JS::Value>& aExtraArgument,
              mozilla::ErrorResult& aError);
   nsresult OpenDialog(const nsAString& aUrl, const nsAString& aName,
                       const nsAString& aOptions,
                       nsISupports* aExtraArgument,
                       nsPIDOMWindowOuter** _retval) override;
   nsresult UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason) override;
 
-  mozilla::ThrottledEventQueue* GetThrottledEventQueue() override;
-
   already_AddRefed<nsPIDOMWindowOuter>
   GetContentInternal(mozilla::ErrorResult& aError,
                      mozilla::dom::CallerType aCallerType);
   void GetContentOuter(JSContext* aCx,
                        JS::MutableHandle<JSObject*> aRetval,
                        mozilla::dom::CallerType aCallerType,
                        mozilla::ErrorResult& aError);
   void GetContent(JSContext* aCx,
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -7,16 +7,17 @@
 #ifndef nsPIDOMWindow_h__
 #define nsPIDOMWindow_h__
 
 #include "nsIDOMWindow.h"
 #include "mozIDOMWindow.h"
 
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
+#include "mozilla/dom/Dispatcher.h"
 #include "mozilla/dom/EventTarget.h"
 #include "js/TypeDecls.h"
 #include "nsRefPtrHashtable.h"
 
 // Only fired for inner windows.
 #define DOM_WINDOW_DESTROYED_TOPIC "dom-window-destroyed"
 #define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
 #define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
@@ -574,17 +575,18 @@ public:
 
   virtual nsresult MoveBy(int32_t aXDif, int32_t aYDif) = 0;
   virtual nsresult UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason) = 0;
 
   mozilla::dom::TabGroup* TabGroup();
 
   mozilla::dom::DocGroup* GetDocGroup() const;
 
-  virtual mozilla::ThrottledEventQueue* GetThrottledEventQueue() = 0;
+  virtual nsIEventTarget*
+  EventTargetFor(mozilla::dom::TaskCategory aCategory) const = 0;
 
 protected:
   // The nsPIDOMWindow constructor. The aOuterWindow argument should
   // be null if and only if the created window itself is an outer
   // window. In all other cases aOuterWindow should be the outer
   // window for the inner window that is being created.
   explicit nsPIDOMWindow<T>(nsPIDOMWindowOuter *aOuterWindow);
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4044,17 +4044,17 @@ WorkerPrivate::WorkerPrivate(WorkerPriva
   // moment.
   if (aParent) {
     mMainThreadThrottledEventQueue = aParent->mMainThreadThrottledEventQueue;
     mMainThreadEventTarget = aParent->mMainThreadEventTarget;
     return;
   }
 
   MOZ_ASSERT(NS_IsMainThread());
-  target = GetWindow() ? GetWindow()->GetThrottledEventQueue() : nullptr;
+  target = GetWindow() ? GetWindow()->EventTargetFor(TaskCategory::Worker) : nullptr;
 
   if (!target) {
     nsCOMPtr<nsIThread> mainThread;
     NS_GetMainThread(getter_AddRefs(mainThread));
     MOZ_DIAGNOSTIC_ASSERT(mainThread);
     target = mainThread;
   }
 
--- a/xpcom/threads/ThrottledEventQueue.cpp
+++ b/xpcom/threads/ThrottledEventQueue.cpp
@@ -359,17 +359,17 @@ public:
     return NS_OK;
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
 };
 
 NS_IMPL_ISUPPORTS(ThrottledEventQueue::Inner, nsIObserver);
 
-NS_IMPL_ISUPPORTS(ThrottledEventQueue, nsIEventTarget);
+NS_IMPL_ISUPPORTS(ThrottledEventQueue, ThrottledEventQueue, nsIEventTarget);
 
 ThrottledEventQueue::ThrottledEventQueue(already_AddRefed<Inner> aInner)
   : mInner(aInner)
 {
   MOZ_ASSERT(mInner);
 }
 
 ThrottledEventQueue::~ThrottledEventQueue()
--- a/xpcom/threads/ThrottledEventQueue.h
+++ b/xpcom/threads/ThrottledEventQueue.h
@@ -6,16 +6,20 @@
 
 // nsIEventTarget wrapper for throttling event dispatch.
 
 #ifndef mozilla_ThrottledEventQueue_h
 #define mozilla_ThrottledEventQueue_h
 
 #include "nsIEventTarget.h"
 
+#define NS_THROTTLEDEVENTQUEUE_IID \
+{ 0x8f3cf7dc, 0xfc14, 0x4ad5, \
+  { 0x9f, 0xd5, 0xdb, 0x79, 0xbc, 0xe6, 0xd5, 0x08 } }
+
 namespace mozilla {
 
 // A ThrottledEventQueue is an event target that can be used to throttle
 // events being dispatched to another base target.  It maintains its
 // own queue of events and only dispatches one at a time to the wrapped
 // target.  This can be used to avoid flooding the base target.
 //
 // Flooding is avoided via a very simply principal.  Runnables dispatched
@@ -82,13 +86,17 @@ public:
   uint32_t Length() const;
 
   // Block the current thread until the queue is empty.  This may not
   // be called on the main thread or the base target.
   void AwaitIdle() const;
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIEVENTTARGET
+
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_THROTTLEDEVENTQUEUE_IID);
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(ThrottledEventQueue, NS_THROTTLEDEVENTQUEUE_IID);
+
 } // namespace mozilla
 
 #endif // mozilla_ThrottledEventQueue_h