Bug 1399707 - Make entries in TabChild::sActiveTabs and EventLoopActivation::mEventGroups unique. r=billm
authorBevis Tseng <btseng@mozilla.com>
Wed, 13 Sep 2017 11:59:35 +0800
changeset 670371 9e7267c06e4e7fb037e539809b409519ea3d63c0
parent 670370 3dd36c1c48e3b5c83808efd5dd871da731da1758
child 670372 40ffba3a6034e0cc9a1a8e4fc9e739adb07f9e04
push id81612
push userbmo:dharvey@mozilla.com
push dateTue, 26 Sep 2017 10:16:26 +0000
reviewersbillm
bugs1399707
milestone58.0a1
Bug 1399707 - Make entries in TabChild::sActiveTabs and EventLoopActivation::mEventGroups unique. r=billm
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
ipc/glue/BackgroundChildImpl.cpp
ipc/glue/BackgroundChildImpl.h
ipc/glue/MessageChannel.cpp
ipc/glue/MessageChannel.h
ipc/glue/ProtocolUtils.h
xpcom/threads/LabeledEventQueue.cpp
xpcom/threads/Scheduler.cpp
xpcom/threads/Scheduler.h
xpcom/threads/SchedulerGroup.cpp
xpcom/threads/SchedulerGroup.h
xpcom/threads/moz.build
xpcom/threads/nsILabelableRunnable.cpp
xpcom/threads/nsILabelableRunnable.h
xpcom/threads/nsThreadUtils.cpp
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -83,18 +83,20 @@
 #include "nsCDefaultURIFixup.h"
 #include "nsIWebBrowser.h"
 #include "nsIWebBrowserFocus.h"
 #include "nsIWebBrowserSetup.h"
 #include "nsIWebProgress.h"
 #include "nsIXULRuntime.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
+#include "nsPointerHashKeys.h"
 #include "nsLayoutUtils.h"
 #include "nsPrintfCString.h"
+#include "nsTHashtable.h"
 #include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 #include "nsViewManager.h"
 #include "nsWeakReference.h"
 #include "nsWindowWatcher.h"
 #include "PermissionMessageUtils.h"
 #include "PuppetWidget.h"
 #include "StructuredCloneData.h"
@@ -159,17 +161,17 @@ using mozilla::layers::GeckoContentContr
 NS_IMPL_ISUPPORTS(ContentListener, nsIDOMEventListener)
 NS_IMPL_ISUPPORTS(TabChildSHistoryListener,
                   nsISHistoryListener,
                   nsIPartialSHistoryListener,
                   nsISupportsWeakReference)
 
 static const char BEFORE_FIRST_PAINT[] = "before-first-paint";
 
-nsTArray<TabChild*>* TabChild::sActiveTabs;
+nsTHashtable<nsPtrHashKey<TabChild>>* TabChild::sActiveTabs;
 
 typedef nsDataHashtable<nsUint64HashKey, TabChild*> TabChildMap;
 static TabChildMap* sTabChildren;
 StaticMutex sTabChildrenMutex;
 
 TabChildBase::TabChildBase()
   : mTabChildGlobal(nullptr)
 {
@@ -1115,17 +1117,17 @@ TabChild::ActorDestroy(ActorDestroyReaso
   if (GetTabId() != 0) {
     NestedTabChildMap().erase(GetTabId());
   }
 }
 
 TabChild::~TabChild()
 {
   if (sActiveTabs) {
-    sActiveTabs->RemoveElement(this);
+    sActiveTabs->RemoveEntry(this);
     if (sActiveTabs->IsEmpty()) {
       delete sActiveTabs;
       sActiveTabs = nullptr;
     }
   }
 
   DestroyWindow();
 
@@ -2584,22 +2586,22 @@ TabChild::InternalSetDocShellIsActive(bo
       }
     }
 
     docShell->SetIsActive(aIsActive);
   }
 
   if (aIsActive) {
     if (!sActiveTabs) {
-      sActiveTabs = new nsTArray<TabChild*>();
+      sActiveTabs = new nsTHashtable<nsPtrHashKey<TabChild>>();
     }
-    sActiveTabs->AppendElement(this);
+    sActiveTabs->PutEntry(this);
   } else {
     if (sActiveTabs) {
-      sActiveTabs->RemoveElement(this);
+      sActiveTabs->RemoveEntry(this);
       // We don't delete sActiveTabs here when it's empty since that
       // could cause a lot of churn. Instead, we wait until ~TabChild.
     }
   }
 
   if (aIsActive) {
     MakeVisible();
 
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -43,16 +43,19 @@
 #include "mozilla/layers/GeckoContentController.h"
 #include "nsISHistoryListener.h"
 #include "nsIPartialSHistoryListener.h"
 
 class nsIDOMWindowUtils;
 class nsIHttpChannel;
 class nsISerialEventTarget;
 
+template<typename T> class nsTHashtable;
+template<typename T> class nsPtrHashKey;
+
 namespace mozilla {
 class AbstractThread;
 namespace layout {
 class RenderFrameChild;
 } // namespace layout
 
 namespace layers {
 class APZChild;
@@ -757,17 +760,17 @@ public:
     return sActiveTabs && !sActiveTabs->IsEmpty();
   }
 
   // Returns the set of TabChilds that are currently in the foreground. There
   // can be multiple foreground TabChilds if Firefox has multiple windows
   // open. There can also be zero foreground TabChilds if the foreground tab is
   // in a different content process. Note that this function should only be
   // called if HasActiveTabs() returns true.
-  static const nsTArray<TabChild*>& GetActiveTabs()
+  static const nsTHashtable<nsPtrHashKey<TabChild>>& GetActiveTabs()
   {
     MOZ_ASSERT(HasActiveTabs());
     return *sActiveTabs;
   }
 
 protected:
   virtual ~TabChild();
 
@@ -959,17 +962,17 @@ private:
   uint32_t mPendingDocShellBlockers;
 
   WindowsHandle mWidgetNativeData;
 
   // This state is used to keep track of the current active tabs (the ones in
   // the foreground). There may be more than one if there are multiple browser
   // windows open. There may be none if this process does not host any
   // foreground tabs.
-  static nsTArray<TabChild*>* sActiveTabs;
+  static nsTHashtable<nsPtrHashKey<TabChild>>* sActiveTabs;
 
   DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TabChild_h
--- a/ipc/glue/BackgroundChildImpl.cpp
+++ b/ipc/glue/BackgroundChildImpl.cpp
@@ -623,24 +623,25 @@ BackgroundChildImpl::RecvDispatchLocalSt
 
   LocalStorage::DispatchStorageEvent(aDocumentURI, aKey, aOldValue, aNewValue,
                                      principal, aIsPrivate, nullptr, true);
 
   return IPC_OK();
 }
 
 bool
-BackgroundChildImpl::GetMessageSchedulerGroups(const Message& aMsg, nsTArray<RefPtr<SchedulerGroup>>& aGroups)
+BackgroundChildImpl::GetMessageSchedulerGroups(const Message& aMsg, SchedulerGroupSet& aGroups)
 {
   if (aMsg.type() == layout::PVsync::MessageType::Msg_Notify__ID) {
     MOZ_ASSERT(NS_IsMainThread());
     aGroups.Clear();
     if (dom::TabChild::HasActiveTabs()) {
-      for (dom::TabChild* tabChild : dom::TabChild::GetActiveTabs()) {
-        aGroups.AppendElement(tabChild->TabGroup());
+      for (auto iter = dom::TabChild::GetActiveTabs().ConstIter();
+           !iter.Done(); iter.Next()) {
+        aGroups.Put(iter.Get()->GetKey()->TabGroup());
       }
     }
     return true;
   }
 
   return false;
 }
 
--- a/ipc/glue/BackgroundChildImpl.h
+++ b/ipc/glue/BackgroundChildImpl.h
@@ -219,17 +219,17 @@ protected:
   RecvDispatchLocalStorageChange(const nsString& aDocumentURI,
                                  const nsString& aKey,
                                  const nsString& aOldValue,
                                  const nsString& aNewValue,
                                  const PrincipalInfo& aPrincipalInfo,
                                  const bool& aIsPrivate) override;
 
   bool
-  GetMessageSchedulerGroups(const Message& aMsg, nsTArray<RefPtr<SchedulerGroup>>& aGroups) override;
+  GetMessageSchedulerGroups(const Message& aMsg, SchedulerGroupSet& aGroups) override;
 };
 
 class BackgroundChildImpl::ThreadLocal final
 {
   friend class nsAutoPtr<ThreadLocal>;
 
 public:
   nsAutoPtr<mozilla::dom::indexedDB::ThreadLocal> mIndexedDBThreadLocal;
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -1997,17 +1997,17 @@ MessageChannel::MessageTask::GetPriority
   default:
     MOZ_ASSERT(false);
     break;
   }
   return NS_OK;
 }
 
 bool
-MessageChannel::MessageTask::GetAffectedSchedulerGroups(nsTArray<RefPtr<SchedulerGroup>>& aGroups)
+MessageChannel::MessageTask::GetAffectedSchedulerGroups(SchedulerGroupSet& aGroups)
 {
     if (!mChannel) {
         return false;
     }
 
     mChannel->AssertWorkerThread();
     return mChannel->mListener->GetMessageSchedulerGroups(mMessage, aGroups);
 }
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -559,17 +559,17 @@ class MessageChannel : HasResultCodes, M
         void Post();
         void Clear();
 
         bool IsScheduled() const { return mScheduled; }
 
         Message& Msg() { return mMessage; }
         const Message& Msg() const { return mMessage; }
 
-        bool GetAffectedSchedulerGroups(nsTArray<RefPtr<SchedulerGroup>>& aGroups) override;
+        bool GetAffectedSchedulerGroups(SchedulerGroupSet& aGroups) override;
 
     private:
         MessageTask() = delete;
         MessageTask(const MessageTask&) = delete;
         ~MessageTask() {}
 
         MessageChannel* mChannel;
         Message mMessage;
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -24,16 +24,17 @@
 #include "mozilla/ipc/MessageLink.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/NotNull.h"
 #include "mozilla/UniquePtr.h"
 #include "MainThreadUtils.h"
+#include "nsILabelableRunnable.h"
 
 #if defined(ANDROID) && defined(DEBUG)
 #include <android/log.h>
 #endif
 
 template<typename T> class nsTHashtable;
 template<typename T> class nsPtrHashKey;
 
@@ -263,16 +264,18 @@ class IToplevelProtocol : public IProtoc
 {
     template<class PFooSide> friend class Endpoint;
 
 protected:
     explicit IToplevelProtocol(ProtocolId aProtoId, Side aSide);
     ~IToplevelProtocol();
 
 public:
+    using SchedulerGroupSet = nsILabelableRunnable::SchedulerGroupSet;
+
     void SetTransport(UniquePtr<Transport> aTrans)
     {
         mTrans = Move(aTrans);
     }
 
     Transport* GetTransport() const { return mTrans.get(); }
 
     ProtocolId GetProtocolId() const { return mProtocolId; }
@@ -383,17 +386,17 @@ public:
     virtual void ProcessRemoteNativeEventsInInterruptCall() {
     }
 
     // Override this method in top-level protocols to change the SchedulerGroups
     // that a message might affect. This should be used only as a last resort
     // when it's difficult to determine an EventTarget ahead of time. See the
     // comment in nsILabelableRunnable.h for more information.
     virtual bool
-    GetMessageSchedulerGroups(const Message& aMsg, nsTArray<RefPtr<SchedulerGroup>>& aGroups)
+    GetMessageSchedulerGroups(const Message& aMsg, SchedulerGroupSet& aGroups)
     {
         return false;
     }
 
     virtual already_AddRefed<nsIEventTarget>
     GetMessageEventTarget(const Message& aMsg);
 
     already_AddRefed<nsIEventTarget>
--- a/xpcom/threads/LabeledEventQueue.cpp
+++ b/xpcom/threads/LabeledEventQueue.cpp
@@ -64,28 +64,17 @@ IsReadyToRun(nsIRunnable* aEvent, Schedu
     return !aEventGroup->IsRunning();
   }
 
   nsCOMPtr<nsILabelableRunnable> labelable = do_QueryInterface(aEvent);
   if (!labelable) {
     return false;
   }
 
-  AutoTArray<RefPtr<SchedulerGroup>, 1> groups;
-  bool labeled = labelable->GetAffectedSchedulerGroups(groups);
-  if (!labeled) {
-    return false;
-  }
-
-  for (SchedulerGroup* group : groups) {
-    if (group->IsRunning()) {
-      return false;
-    }
-  }
-  return true;
+  return labelable->IsReadyToRun();
 }
 
 void
 LabeledEventQueue::PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
                             EventPriority aPriority,
                             const MutexAutoLock& aProofOfLock)
 {
   nsCOMPtr<nsIRunnable> event(aEvent);
@@ -176,18 +165,19 @@ LabeledEventQueue::GetEvent(EventPriorit
   if (!sCurrentSchedulerGroup) {
     return nullptr;
   }
 
   // Move active tabs to the front of the queue. The mAvoidActiveTabCount field
   // prevents us from preferentially processing events from active tabs twice in
   // a row. This scheme is designed to prevent starvation.
   if (TabChild::HasActiveTabs() && mAvoidActiveTabCount <= 0) {
-    for (TabChild* tabChild : TabChild::GetActiveTabs()) {
-      SchedulerGroup* group = tabChild->TabGroup();
+    for (auto iter = TabChild::GetActiveTabs().ConstIter();
+         !iter.Done(); iter.Next()) {
+      SchedulerGroup* group = iter.Get()->GetKey()->TabGroup();
       if (!group->isInList() || group == sCurrentSchedulerGroup) {
         continue;
       }
 
       // For each active tab we move to the front of the queue, we have to
       // process two SchedulerGroups (the active tab and another one, presumably
       // a background group) before we prioritize active tabs again.
       mAvoidActiveTabCount += 2;
--- a/xpcom/threads/Scheduler.cpp
+++ b/xpcom/threads/Scheduler.cpp
@@ -558,36 +558,28 @@ Scheduler::EventLoopActivation::~EventLo
 }
 
 /* static */ void
 SchedulerImpl::StartEvent(Scheduler::EventLoopActivation& aActivation)
 {
   MOZ_ASSERT(!sUnlabeledEventRunning);
   if (aActivation.IsLabeled()) {
     SchedulerGroup::SetValidatingAccess(SchedulerGroup::StartValidation);
-
-    for (SchedulerGroup* group : aActivation.EventGroupsAffected()) {
-      MOZ_ASSERT(!group->IsRunning());
-      group->SetIsRunning(true);
-    }
+    aActivation.EventGroupsAffected().SetIsRunning(true);
   } else {
     sUnlabeledEventRunning = true;
   }
   sNumThreadsRunning++;
 }
 
 /* static */ void
 SchedulerImpl::FinishEvent(Scheduler::EventLoopActivation& aActivation)
 {
   if (aActivation.IsLabeled()) {
-    for (SchedulerGroup* group : aActivation.EventGroupsAffected()) {
-      MOZ_ASSERT(group->IsRunning());
-      group->SetIsRunning(false);
-    }
-
+    aActivation.EventGroupsAffected().SetIsRunning(false);
     SchedulerGroup::SetValidatingAccess(SchedulerGroup::EndValidation);
   } else {
     MOZ_ASSERT(sUnlabeledEventRunning);
     sUnlabeledEventRunning = false;
   }
 
   MOZ_ASSERT(sNumThreadsRunning > 0);
   sNumThreadsRunning--;
--- a/xpcom/threads/Scheduler.h
+++ b/xpcom/threads/Scheduler.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_Scheduler_h
 #define mozilla_Scheduler_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/EventQueue.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
+#include "nsILabelableRunnable.h"
 
 // Windows silliness. winbase.h defines an empty no-argument Yield macro.
 #undef Yield
 
 class nsIIdlePeriod;
 class nsThread;
 
 namespace mozilla {
@@ -69,34 +70,35 @@ public:
   static void Yield();
 
   static bool UnlabeledEventRunning();
   static bool AnyEventRunning();
 
   class MOZ_RAII EventLoopActivation
   {
   public:
+    using EventGroups = nsILabelableRunnable::SchedulerGroupSet;
     EventLoopActivation();
     ~EventLoopActivation();
 
     static void Init();
 
     bool IsNested() const { return !!mPrev; }
 
     void SetEvent(nsIRunnable* aEvent, EventPriority aPriority);
 
     EventPriority Priority() const { return mPriority; }
     bool IsLabeled() { return mIsLabeled; }
-    const nsTArray<RefPtr<SchedulerGroup>>& EventGroupsAffected() { return mEventGroups; }
+    EventGroups& EventGroupsAffected() { return mEventGroups; }
 
   private:
     EventLoopActivation* mPrev;
     bool mProcessingEvent;
     bool mIsLabeled;
-    nsTArray<RefPtr<SchedulerGroup>> mEventGroups;
+    EventGroups mEventGroups;
     EventPriority mPriority;
 
     static MOZ_THREAD_LOCAL(EventLoopActivation*) sTopActivation;
   };
 
 private:
   friend class SchedulerImpl;
   static UniquePtr<SchedulerImpl> sScheduler;
--- a/xpcom/threads/SchedulerGroup.cpp
+++ b/xpcom/threads/SchedulerGroup.cpp
@@ -354,20 +354,20 @@ SchedulerGroup::Runnable::Runnable(alrea
                                    SchedulerGroup* aGroup)
   : mozilla::Runnable("SchedulerGroup::Runnable")
   , mRunnable(Move(aRunnable))
   , mGroup(aGroup)
 {
 }
 
 bool
-SchedulerGroup::Runnable::GetAffectedSchedulerGroups(nsTArray<RefPtr<SchedulerGroup>>& aGroups)
+SchedulerGroup::Runnable::GetAffectedSchedulerGroups(SchedulerGroupSet& aGroups)
 {
   aGroups.Clear();
-  aGroups.AppendElement(Group());
+  aGroups.Put(Group());
   return true;
 }
 
 NS_IMETHODIMP
 SchedulerGroup::Runnable::GetName(nsACString& aName)
 {
   // Try to get a name from the underlying runnable.
   nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable);
--- a/xpcom/threads/SchedulerGroup.h
+++ b/xpcom/threads/SchedulerGroup.h
@@ -108,17 +108,17 @@ public:
   class Runnable final : public mozilla::Runnable
                        , public nsIRunnablePriority
                        , public nsILabelableRunnable
   {
   public:
     Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
              SchedulerGroup* aGroup);
 
-    bool GetAffectedSchedulerGroups(nsTArray<RefPtr<SchedulerGroup>>& aGroups) override;
+    bool GetAffectedSchedulerGroups(SchedulerGroupSet& aGroups) override;
 
     SchedulerGroup* Group() const { return mGroup; }
 
     NS_IMETHOD GetName(nsACString& aName) override;
 
     bool IsBackground() const { return mGroup->IsBackground(); }
 
     NS_DECL_ISUPPORTS_INHERITED
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -82,16 +82,17 @@ UNIFIED_SOURCES += [
     'EventQueue.cpp',
     'HangAnnotations.cpp',
     'HangMonitor.cpp',
     'InputEventStatistics.cpp',
     'LabeledEventQueue.cpp',
     'LazyIdleThread.cpp',
     'MainThreadIdlePeriod.cpp',
     'nsEnvironment.cpp',
+    'nsILabelableRunnable.cpp',
     'nsMemoryPressure.cpp',
     'nsProcessCommon.cpp',
     'nsProxyRelease.cpp',
     'nsThread.cpp',
     'nsThreadManager.cpp',
     'nsThreadPool.cpp',
     'nsThreadUtils.cpp',
     'nsTimerImpl.cpp',
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/nsILabelableRunnable.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsILabelableRunnable.h"
+
+#include "mozilla/Scheduler.h"
+#include "mozilla/SchedulerGroup.h"
+
+bool
+nsILabelableRunnable::IsReadyToRun()
+{
+  MOZ_ASSERT(mozilla::Scheduler::AnyEventRunning());
+  MOZ_ASSERT(!mozilla::Scheduler::UnlabeledEventRunning());
+  SchedulerGroupSet groups;
+  if (!GetAffectedSchedulerGroups(groups)) {
+    // it can not be labeled right now.
+    return false;
+  }
+
+  if (groups.mSingle) {
+    MOZ_ASSERT(groups.mMulti.isNothing());
+    return !groups.mSingle->IsRunning();
+  }
+
+  if (groups.mMulti.isSome()) {
+    MOZ_ASSERT(!groups.mSingle);
+    for (auto iter = groups.mMulti.ref().ConstIter(); !iter.Done(); iter.Next()) {
+      if (iter.Get()->GetKey()->IsRunning()) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  // No affected groups if we are here. Then, it's ready to run.
+  return true;
+}
+
+void
+nsILabelableRunnable::SchedulerGroupSet::Put(mozilla::SchedulerGroup* aGroup)
+{
+  if (mSingle) {
+    MOZ_ASSERT(mMulti.isNothing());
+    mMulti.emplace();
+    mMulti.ref().PutEntry(mSingle);
+    mSingle = nullptr;
+    return;
+  }
+
+  if (mMulti.isSome()) {
+    MOZ_ASSERT(!mSingle);
+    mMulti.ref().PutEntry(aGroup);
+    return;
+  }
+
+  mSingle = aGroup;
+}
+
+void
+nsILabelableRunnable::SchedulerGroupSet::Clear()
+{
+  mSingle = nullptr;
+  mMulti.reset();
+}
+
+void
+nsILabelableRunnable::SchedulerGroupSet::SetIsRunning(bool aIsRunning)
+{
+  if (mSingle) {
+    MOZ_ASSERT(mMulti.isNothing());
+    mSingle->SetIsRunning(aIsRunning);
+    return;
+  }
+
+  if (mMulti.isSome()) {
+    MOZ_ASSERT(!mSingle);
+    for (auto iter = mMulti.ref().ConstIter(); !iter.Done(); iter.Next()) {
+      MOZ_ASSERT(iter.Get()->GetKey()->IsRunning() != aIsRunning);
+      iter.Get()->GetKey()->SetIsRunning(aIsRunning);
+    }
+    return;
+  }
+}
--- a/xpcom/threads/nsILabelableRunnable.h
+++ b/xpcom/threads/nsILabelableRunnable.h
@@ -2,19 +2,21 @@
 /* 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/. */
 
 #ifndef nsILabelableRunnable_h
 #define nsILabelableRunnable_h
 
-#include "mozilla/RefPtr.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "nsHashKeys.h"
 #include "nsISupports.h"
-#include "nsTArray.h"
+#include "nsTHashtable.h"
 
 namespace mozilla {
 class SchedulerGroup;
 }
 
 #define NS_ILABELABLERUNNABLE_IID \
 { 0x40da1ea1, 0x0b81, 0x4249, \
   { 0x96, 0x46, 0x61, 0x92, 0x23, 0x39, 0xc3, 0xe8 } }
@@ -32,18 +34,40 @@ class SchedulerGroup;
 //
 // To use this interface, QI a runnable to nsILabelableRunnable and then call
 // GetAffectedSchedulerGroups.
 class nsILabelableRunnable : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ILABELABLERUNNABLE_IID);
 
+  class SchedulerGroupSet
+  {
+    friend class nsILabelableRunnable;
+
+    using MultipleSchedulerGroups =
+      nsTHashtable<nsRefPtrHashKey<mozilla::SchedulerGroup>>;
+
+    RefPtr<mozilla::SchedulerGroup> mSingle;
+    mozilla::Maybe<MultipleSchedulerGroups> mMulti;
+
+  public:
+    void Put(mozilla::SchedulerGroup* aGroup);
+
+    void Clear();
+
+    void SetIsRunning(bool aIsRunning);
+  };
+
   // Returns true if the runnable can be labeled right now. In this case,
   // aGroups will contain the set of SchedulerGroups that can be affected by the
   // runnable. If this returns false, no assumptions can be made about which
   // SchedulerGroups are affected by the runnable.
-  virtual bool GetAffectedSchedulerGroups(nsTArray<RefPtr<mozilla::SchedulerGroup>>& aGroups) = 0;
+  virtual bool GetAffectedSchedulerGroups(SchedulerGroupSet& aGroups) = 0;
+
+  // Returns true if the runnable can be labeled right now and none of its
+  // affected scheduler groups is running.
+  bool IsReadyToRun();
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsILabelableRunnable, NS_ILABELABLERUNNABLE_IID);
 
 #endif // nsILabelableRunnable_h
--- a/xpcom/threads/nsThreadUtils.cpp
+++ b/xpcom/threads/nsThreadUtils.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsThreadUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Likely.h"
 #include "mozilla/TimeStamp.h"
 #include "LeakRefPtr.h"
 
+#include "nsComponentManagerUtils.h"
+
 #ifdef MOZILLA_INTERNAL_API
 # include "nsThreadManager.h"
 #else
 # include "nsXPCOMCIDInternal.h"
 # include "nsIThreadManager.h"
 # include "nsServiceManagerUtils.h"
 #endif