Bug 1675820 - Part 6: Make DocGroup cycle-collected, r=farre,smaug
authorNika Layzell <nika@thelayzells.com>
Thu, 18 Mar 2021 19:24:50 +0000
changeset 571846 e6d84dd114ed2ba77a6163e7604652c70cf06942
parent 571845 6417dd8048698c2d1d4ad90639c223fa6b7d6fc4
child 571847 f2ed6497335f2180d5d1ce6de651f278644e665b
push id38300
push userbtara@mozilla.com
push dateFri, 19 Mar 2021 09:53:39 +0000
treeherdermozilla-central@092ee6b0c9f2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfarre, smaug
bugs1675820
milestone88.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 1675820 - Part 6: Make DocGroup cycle-collected, r=farre,smaug Previously, the DocGroup type was not cycle-collected, as it needed to have references from other threads for Quantum DOM. Nowadays the only off-main-thread use of DocGroup is for dispatching runnables to the main thread which should be tracked using a performance counter for about:performance. This means we can remove the DocGroup references from these dispatching callsites, only storing the Performance Counter we're interested in, and simplify make DocGroup be cycle-collected itself. This fixes a leak caused by adding the WindowGlobalChild getter to WindowContext, by allowing cycles between the document and its BrowsingContext to be broken by the cycle-collector. Differential Revision: https://phabricator.services.mozilla.com/D108865
docshell/base/BrowsingContextGroup.cpp
dom/base/DocGroup.cpp
dom/base/DocGroup.h
dom/base/Document.cpp
dom/script/ScriptLoader.cpp
layout/style/Loader.cpp
parser/html/nsHtml5StreamParser.cpp
parser/html/nsHtml5StreamParser.h
xpcom/tests/gtest/TestThreadMetrics.cpp
xpcom/threads/SchedulerGroup.cpp
xpcom/threads/SchedulerGroup.h
xpcom/threads/nsThread.cpp
--- a/docshell/base/BrowsingContextGroup.cpp
+++ b/docshell/base/BrowsingContextGroup.cpp
@@ -446,15 +446,16 @@ void BrowsingContextGroup::GetAllGroups(
   aGroups.SetCapacity(sBrowsingContextGroups->Count());
   for (auto& group : *sBrowsingContextGroups) {
     aGroups.AppendElement(group.GetData());
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BrowsingContextGroup, mContexts,
                                       mToplevels, mHosts, mSubscribers,
-                                      mTimerEventQueue, mWorkerEventQueue)
+                                      mTimerEventQueue, mWorkerEventQueue,
+                                      mDocGroups)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContextGroup, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(BrowsingContextGroup, Release)
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/base/DocGroup.cpp
+++ b/dom/base/DocGroup.cpp
@@ -35,36 +35,32 @@ namespace {
       0xab, 0x72, 0xd2, 0x1f, 0x69, 0xee, 0xd3, 0x15 \
     }                                                \
   }
 
 // LabellingEventTarget labels all dispatches with the DocGroup that
 // created it.
 class LabellingEventTarget final : public nsISerialEventTarget,
                                    public nsIDirectTaskDispatcher {
-  // This creates a cycle with DocGroup. Therefore, when DocGroup
-  // looses its last Document, the DocGroup of the
-  // LabellingEventTarget needs to be cleared.
-  RefPtr<mozilla::dom::DocGroup> mDocGroup;
-
  public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_LABELLINGEVENTTARGET_IID)
 
-  explicit LabellingEventTarget(mozilla::dom::DocGroup* aDocGroup)
-      : mDocGroup(aDocGroup),
+  explicit LabellingEventTarget(mozilla::PerformanceCounter* aPerformanceCounter)
+      : mPerformanceCounter(aPerformanceCounter),
         mMainThread(
             static_cast<nsThread*>(mozilla::GetMainThreadSerialEventTarget())) {
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIEVENTTARGET_FULL
   NS_DECL_NSIDIRECTTASKDISPATCHER
 
  private:
   ~LabellingEventTarget() = default;
+  const RefPtr<mozilla::PerformanceCounter> mPerformanceCounter;
   const RefPtr<nsThread> mMainThread;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(LabellingEventTarget, NS_LABELLINGEVENTTARGET_IID)
 
 }  // namespace
 
 NS_IMETHODIMP
@@ -75,18 +71,18 @@ LabellingEventTarget::DispatchFromScript
 
 NS_IMETHODIMP
 LabellingEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
                                uint32_t aFlags) {
   if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  return mozilla::SchedulerGroup::DispatchWithDocGroup(
-      mozilla::TaskCategory::Other, std::move(aRunnable), mDocGroup);
+  return mozilla::SchedulerGroup::LabeledDispatch(
+      mozilla::TaskCategory::Other, std::move(aRunnable), mPerformanceCounter);
 }
 
 NS_IMETHODIMP
 LabellingEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
@@ -120,21 +116,41 @@ NS_IMETHODIMP LabellingEventTarget::Have
 
 NS_IMPL_ISUPPORTS(LabellingEventTarget, nsIEventTarget, nsISerialEventTarget,
                   nsIDirectTaskDispatcher)
 
 namespace mozilla::dom {
 
 AutoTArray<RefPtr<DocGroup>, 2>* DocGroup::sPendingDocGroups = nullptr;
 
+NS_IMPL_CYCLE_COLLECTION_CLASS(DocGroup)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DocGroup)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSignalSlotList)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContextGroup)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DocGroup)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSignalSlotList)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContextGroup)
+
+  // If we still have any documents in this array, they were just unlinked, so
+  // clear out our weak pointers to them.
+  tmp->mDocuments.Clear();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DocGroup, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DocGroup, Release)
+
 /* static */
 already_AddRefed<DocGroup> DocGroup::Create(
     BrowsingContextGroup* aBrowsingContextGroup, const nsACString& aKey) {
   RefPtr<DocGroup> docGroup = new DocGroup(aBrowsingContextGroup, aKey);
-  docGroup->mEventTarget = new LabellingEventTarget(docGroup);
+  docGroup->mEventTarget =
+      new LabellingEventTarget(docGroup->GetPerformanceCounter());
   return docGroup.forget();
 }
 
 /* static */
 nsresult DocGroup::GetKey(nsIPrincipal* aPrincipal, bool aCrossOriginIsolated,
                           nsACString& aKey) {
   // Use GetBaseDomain() to handle things like file URIs, IP address URIs,
   // etc. correctly.
@@ -170,18 +186,16 @@ void DocGroup::AddDocument(Document* aDo
 
 void DocGroup::RemoveDocument(Document* aDocument) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDocuments.Contains(aDocument));
   mDocuments.RemoveElement(aDocument);
 
   if (mDocuments.IsEmpty()) {
     mBrowsingContextGroup = nullptr;
-    // This clears the cycle DocGroup has with LabellingEventTarget.
-    mEventTarget = nullptr;
   }
 }
 
 DocGroup::DocGroup(BrowsingContextGroup* aBrowsingContextGroup,
                    const nsACString& aKey)
     : mKey(aKey),
       mBrowsingContextGroup(aBrowsingContextGroup),
       mAgentClusterId(nsContentUtils::GenerateUUID()) {
@@ -191,25 +205,18 @@ DocGroup::DocGroup(BrowsingContextGroup*
   if (StaticPrefs::dom_arena_allocator_enabled_AtStartup()) {
     mArena = new mozilla::dom::DOMArena();
   }
 
   mPerformanceCounter = new mozilla::PerformanceCounter("DocGroup:"_ns + aKey);
 }
 
 DocGroup::~DocGroup() {
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(mDocuments.IsEmpty());
-  MOZ_RELEASE_ASSERT(!mBrowsingContextGroup);
-
-  if (!NS_IsMainThread()) {
-    NS_ReleaseOnMainThread("DocGroup::mReactionsStack",
-                           mReactionsStack.forget());
-
-    NS_ReleaseOnMainThread("DocGroup::mArena", mArena.forget());
-  }
 
   if (mIframePostMessageQueue) {
     FlushIframePostMessageQueue();
   }
 }
 
 RefPtr<PerformanceInfoPromise> DocGroup::ReportPerformanceInfo() {
   AssertIsOnMainThread();
@@ -299,33 +306,33 @@ RefPtr<PerformanceInfoPromise> DocGroup:
           },
           [self](const nsresult rv) {
             return PerformanceInfoPromise::CreateAndReject(rv, __func__);
           });
 }
 
 nsresult DocGroup::Dispatch(TaskCategory aCategory,
                             already_AddRefed<nsIRunnable>&& aRunnable) {
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
   if (mPerformanceCounter) {
     mPerformanceCounter->IncrementDispatchCounter(DispatchCategory(aCategory));
   }
-  return SchedulerGroup::DispatchWithDocGroup(aCategory, std::move(aRunnable),
-                                              this);
+  return SchedulerGroup::LabeledDispatch(aCategory, std::move(aRunnable),
+                                         mPerformanceCounter);
 }
 
 nsISerialEventTarget* DocGroup::EventTargetFor(TaskCategory aCategory) const {
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mDocuments.IsEmpty());
+
   // Here we have the same event target for every TaskCategory. The
   // reason for that is that currently TaskCategory isn't used, and
   // it's unsure if it ever will be (See Bug 1624819).
-  if (mEventTarget) {
-    return mEventTarget;
-  }
-
-  return GetMainThreadSerialEventTarget();
+  return mEventTarget;
 }
 
 AbstractThread* DocGroup::AbstractMainThreadFor(TaskCategory aCategory) {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mDocuments.IsEmpty());
 
   // Here we have the same thread for every TaskCategory. The reason
   // for that is that currently TaskCategory isn't used, and it's
--- a/dom/base/DocGroup.h
+++ b/dom/base/DocGroup.h
@@ -36,17 +36,18 @@ class JSExecutionManager;
 // A BrowsingContextGroup is a set of browsing contexts which are all
 // "related".  Within a BrowsingContextGroup, browsing contexts are
 // broken into "similar-origin" DocGroups.  A DocGroup is a member
 // of exactly one BrowsingContextGroup.
 class DocGroup final {
  public:
   typedef nsTArray<Document*>::iterator Iterator;
 
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DocGroup)
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DocGroup)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(DocGroup)
 
   static already_AddRefed<DocGroup> Create(
       BrowsingContextGroup* aBrowsingContextGroup, const nsACString& aKey);
 
   // Returns NS_ERROR_FAILURE and sets |aString| to an empty string if the TLD
   // service isn't available. Returns NS_OK on success, but may still set
   // |aString| may still be set to an empty string.
   [[nodiscard]] static nsresult GetKey(nsIPrincipal* aPrincipal,
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -2393,16 +2393,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnchors);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnonymousContents)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCommandDispatcher)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuppressedEventListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrototypeDocument)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMidasCommandManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAll)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
 
   // Traverse all our nsCOMArrays.
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPreloadingImages)
 
   for (uint32_t i = 0; i < tmp->mFrameRequestCallbacks.Length(); ++i) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameRequestCallbacks[i]");
     cb.NoteXPCOMChild(tmp->mFrameRequestCallbacks[i].mCallback);
   }
@@ -2524,16 +2525,22 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Do
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuppressedEventListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrototypeDocument)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMidasCommandManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAll)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mReferrerInfo)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadReferrerInfo)
 
+  if (tmp->mDocGroup && tmp->mDocGroup->GetBrowsingContextGroup()) {
+    tmp->mDocGroup->GetBrowsingContextGroup()->RemoveDocument(
+        tmp->mDocGroup->GetKey(), tmp);
+  }
+  tmp->mDocGroup = nullptr;
+
   if (tmp->IsTopLevelContentDocument()) {
     RemoveToplevelLoadingDocument(tmp);
   }
 
   tmp->mParentDocument = nullptr;
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPreloadingImages)
 
--- a/dom/script/ScriptLoader.cpp
+++ b/dom/script/ScriptLoader.cpp
@@ -2119,44 +2119,46 @@ ReferrerPolicy ScriptLoader::GetReferrer
   return mDocument->GetReferrerPolicy();
 }
 
 namespace {
 
 class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable {
   RefPtr<ScriptLoadRequest> mRequest;
   RefPtr<ScriptLoader> mLoader;
-  RefPtr<DocGroup> mDocGroup;
+  nsCOMPtr<nsISerialEventTarget> mEventTarget;
   JS::OffThreadToken* mToken;
 
  public:
   ScriptLoadRequest* GetScriptLoadRequest() { return mRequest; }
 
   NotifyOffThreadScriptLoadCompletedRunnable(ScriptLoadRequest* aRequest,
                                              ScriptLoader* aLoader)
       : Runnable("dom::NotifyOffThreadScriptLoadCompletedRunnable"),
         mRequest(aRequest),
         mLoader(aLoader),
-        mDocGroup(aLoader->GetDocGroup()),
         mToken(nullptr) {
     MOZ_ASSERT(NS_IsMainThread());
+    if (DocGroup* docGroup = aLoader->GetDocGroup()) {
+      mEventTarget = docGroup->EventTargetFor(TaskCategory::Other);
+    }
   }
 
   virtual ~NotifyOffThreadScriptLoadCompletedRunnable();
 
   void SetToken(JS::OffThreadToken* aToken) {
     MOZ_ASSERT(aToken && !mToken);
     mToken = aToken;
   }
 
   static void Dispatch(
       already_AddRefed<NotifyOffThreadScriptLoadCompletedRunnable>&& aSelf) {
     RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> self = aSelf;
-    RefPtr<DocGroup> docGroup = self->mDocGroup;
-    docGroup->Dispatch(TaskCategory::Other, self.forget());
+    nsCOMPtr<nsISerialEventTarget> eventTarget = self->mEventTarget;
+    eventTarget->Dispatch(self.forget());
   }
 
   NS_DECL_NSIRUNNABLE
 };
 
 } /* anonymous namespace */
 
 void ScriptLoader::Shutdown() {
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -2176,27 +2176,29 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSheets);
   for (auto iter = tmp->mInlineSheets.Iter(); !iter.Done(); iter.Next()) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "Inline sheet cache in Loader");
     cb.NoteXPCOMChild(iter.UserData());
   }
   for (nsCOMPtr<nsICSSLoaderObserver>& obs : tmp->mObservers.ForwardRange()) {
     ImplCycleCollectionTraverse(cb, obs, "mozilla::css::Loader.mObservers");
   }
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocGroup)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Loader)
   if (tmp->mSheets) {
     if (tmp->mDocument) {
       tmp->DeregisterFromSheetCache();
     }
     tmp->mSheets = nullptr;
   }
   tmp->mInlineSheets.Clear();
   tmp->mObservers.Clear();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocGroup)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(Loader, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(Loader, Release)
 
 size_t Loader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
   size_t n = aMallocSizeOf(this);
 
--- a/parser/html/nsHtml5StreamParser.cpp
+++ b/parser/html/nsHtml5StreamParser.cpp
@@ -1251,24 +1251,25 @@ nsresult nsHtml5StreamParser::OnStartReq
 
   rv = NS_OK;
 
   // The line below means that the encoding can end up being wrong if
   // a view-source URL is loaded without having the encoding hint from a
   // previous normal load in the history.
   mReparseForbidden = !(mMode == NORMAL || mMode == PLAIN_TEXT);
 
-  mDocGroup = mExecutor->GetDocument()->GetDocGroup();
+  mNetworkEventTarget =
+      mExecutor->GetDocument()->EventTargetFor(TaskCategory::Network);
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mRequest, &rv));
   if (NS_SUCCEEDED(rv)) {
     // Non-HTTP channels are bogus enough that we let them work with unlabeled
     // runnables for now. Asserting for HTTP channels only.
-    MOZ_ASSERT(mDocGroup || mMode == LOAD_AS_DATA,
-               "How come the doc group is still null?");
+    MOZ_ASSERT(mNetworkEventTarget || mMode == LOAD_AS_DATA,
+               "How come the network event target is still null?");
 
     nsAutoCString method;
     Unused << httpChannel->GetRequestMethod(method);
     // XXX does Necko have a way to renavigate POST, etc. without hitting
     // the network?
     if (!method.EqualsLiteral("GET")) {
       // This is the old Gecko behavior but the HTML5 spec disagrees.
       // Don't reparse on POST.
@@ -2184,14 +2185,14 @@ void nsHtml5StreamParser::MarkAsBroken(n
   nsCOMPtr<nsIRunnable> runnable(mExecutorFlusher);
   if (NS_FAILED(DispatchToMain(runnable.forget()))) {
     NS_WARNING("failed to dispatch executor flush event");
   }
 }
 
 nsresult nsHtml5StreamParser::DispatchToMain(
     already_AddRefed<nsIRunnable>&& aRunnable) {
-  if (mDocGroup) {
-    return mDocGroup->Dispatch(TaskCategory::Network, std::move(aRunnable));
+  if (mNetworkEventTarget) {
+    return mNetworkEventTarget->Dispatch(std::move(aRunnable));
   }
   return SchedulerGroup::UnlabeledDispatch(TaskCategory::Network,
                                            std::move(aRunnable));
 }
--- a/parser/html/nsHtml5StreamParser.h
+++ b/parser/html/nsHtml5StreamParser.h
@@ -504,19 +504,19 @@ class nsHtml5StreamParser final : public
                     // NS_HTML5_STREAM_PARSER_READ_BUFFER_SIZE
 
   /**
    * The tree operation executor
    */
   nsHtml5TreeOpExecutor* mExecutor;
 
   /**
-   * The same as mExecutor->mDocument->mDocGroup.
+   * Network event target for mExecutor->mDocument
    */
-  RefPtr<mozilla::dom::DocGroup> mDocGroup;
+  nsCOMPtr<nsISerialEventTarget> mNetworkEventTarget;
 
   /**
    * The HTML5 tree builder
    */
   mozilla::UniquePtr<nsHtml5TreeBuilder> mTreeBuilder;
 
   /**
    * The HTML5 tokenizer
--- a/xpcom/tests/gtest/TestThreadMetrics.cpp
+++ b/xpcom/tests/gtest/TestThreadMetrics.cpp
@@ -87,17 +87,18 @@ class TimedRunnable final : public Runna
       thread->Dispatch(aRunnable, NS_DISPATCH_NORMAL);
     }
     (void)NS_ProcessNextEvent(thread, false);
   }
 
   static nsresult DispatchWithDocgroup(nsIRunnable* aRunnable,
                                        DocGroup* aDocGroup) {
     nsCOMPtr<nsIRunnable> runnable = aRunnable;
-    runnable = new SchedulerGroup::Runnable(runnable.forget(), aDocGroup);
+    runnable = new SchedulerGroup::Runnable(runnable.forget(),
+                                            aDocGroup->GetPerformanceCounter());
     return aDocGroup->Dispatch(TaskCategory::Other, runnable.forget());
   }
 
  private:
   uint32_t mExecutionTime1;
   uint32_t mExecutionTime2;
   // When we sleep, the actual time we sleep might not match how long
   // we asked to sleep for.  Record how much we actually slept.
--- a/xpcom/threads/SchedulerGroup.cpp
+++ b/xpcom/threads/SchedulerGroup.cpp
@@ -55,36 +55,29 @@ void SchedulerGroup::MarkVsyncReceived()
 }
 
 /* static */
 void SchedulerGroup::MarkVsyncRan() { gEarliestUnprocessedVsync = 0; }
 
 SchedulerGroup::SchedulerGroup() : mIsRunning(false) {}
 
 /* static */
-nsresult SchedulerGroup::DispatchWithDocGroup(
-    TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable,
-    dom::DocGroup* aDocGroup) {
-  return LabeledDispatch(aCategory, std::move(aRunnable), aDocGroup);
-}
-
-/* static */
 nsresult SchedulerGroup::Dispatch(TaskCategory aCategory,
                                   already_AddRefed<nsIRunnable>&& aRunnable) {
   return LabeledDispatch(aCategory, std::move(aRunnable), nullptr);
 }
 
 /* static */
 nsresult SchedulerGroup::LabeledDispatch(
     TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable,
-    dom::DocGroup* aDocGroup) {
+    mozilla::PerformanceCounter* aPerformanceCounter) {
   nsCOMPtr<nsIRunnable> runnable(aRunnable);
   if (XRE_IsContentProcess()) {
     RefPtr<Runnable> internalRunnable =
-        new Runnable(runnable.forget(), aDocGroup);
+        new Runnable(runnable.forget(), aPerformanceCounter);
     return InternalUnlabeledDispatch(aCategory, internalRunnable.forget());
   }
   return UnlabeledDispatch(aCategory, runnable.forget());
 }
 
 /*static*/
 nsresult SchedulerGroup::InternalUnlabeledDispatch(
     TaskCategory aCategory, already_AddRefed<Runnable>&& aRunnable) {
@@ -108,23 +101,27 @@ nsresult SchedulerGroup::InternalUnlabel
     Unused << runnable->mRunnable.forget().take();
     nsrefcnt refcnt = runnable.get()->Release();
     MOZ_RELEASE_ASSERT(refcnt == 1, "still holding an unexpected reference!");
   }
 
   return rv;
 }
 
-SchedulerGroup::Runnable::Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
-                                   dom::DocGroup* aDocGroup)
+SchedulerGroup::Runnable::Runnable(
+    already_AddRefed<nsIRunnable>&& aRunnable,
+    mozilla::PerformanceCounter* aPerformanceCounter)
     : mozilla::Runnable("SchedulerGroup::Runnable"),
       mRunnable(std::move(aRunnable)),
-      mDocGroup(aDocGroup) {}
+      mPerformanceCounter(aPerformanceCounter) {}
 
-dom::DocGroup* SchedulerGroup::Runnable::DocGroup() const { return mDocGroup; }
+mozilla::PerformanceCounter* SchedulerGroup::Runnable::GetPerformanceCounter()
+    const {
+  return mPerformanceCounter;
+}
 
 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
 NS_IMETHODIMP
 SchedulerGroup::Runnable::GetName(nsACString& aName) {
   // Try to get a name from the underlying runnable.
   nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable);
   if (named) {
     named->GetName(aName);
--- a/xpcom/threads/SchedulerGroup.h
+++ b/xpcom/threads/SchedulerGroup.h
@@ -4,16 +4,17 @@
  * 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_SchedulerGroup_h
 #define mozilla_SchedulerGroup_h
 
 #include "mozilla/RefPtr.h"
 #include "mozilla/TaskCategory.h"
+#include "mozilla/PerformanceCounter.h"
 #include "nsCOMPtr.h"
 #include "nsID.h"
 #include "nsIRunnable.h"
 #include "nsISupports.h"
 #include "nsStringFwd.h"
 #include "nsThreadUtils.h"
 #include "nscore.h"
 
@@ -29,38 +30,28 @@ class DocGroup;
 
 #define NS_SCHEDULERGROUPRUNNABLE_IID                \
   {                                                  \
     0xd31b7420, 0x872b, 0x4cfb, {                    \
       0xa9, 0xc6, 0xae, 0x4c, 0x0f, 0x06, 0x36, 0x74 \
     }                                                \
   }
 
-// The "main thread" in Gecko will soon be a set of cooperatively scheduled
-// "fibers". Global state in Gecko will be partitioned into a series of "groups"
-// (with roughly one group per tab). Runnables will be annotated with the set of
-// groups that they touch. Two runnables may run concurrently on different
-// fibers as long as they touch different groups.
-//
-// A SchedulerGroup is an abstract class to represent a "group". Essentially the
-// only functionality offered by a SchedulerGroup is the ability to dispatch
-// runnables to the group. DocGroup, and SystemGroup are the concrete
-// implementations of SchedulerGroup.
 class SchedulerGroup {
  public:
   SchedulerGroup();
 
   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 
   class Runnable final : public mozilla::Runnable, public nsIRunnablePriority {
    public:
     Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
-             dom::DocGroup* aDocGroup);
+             mozilla::PerformanceCounter* aPerformanceCounter);
 
-    dom::DocGroup* DocGroup() const;
+    mozilla::PerformanceCounter* GetPerformanceCounter() const;
 
 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
     NS_IMETHOD GetName(nsACString& aName) override;
 #endif
 
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIRUNNABLE
     NS_DECL_NSIRUNNABLEPRIORITY
@@ -68,42 +59,38 @@ class SchedulerGroup {
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_SCHEDULERGROUPRUNNABLE_IID);
 
    private:
     friend class SchedulerGroup;
 
     ~Runnable() = default;
 
     nsCOMPtr<nsIRunnable> mRunnable;
-    RefPtr<dom::DocGroup> mDocGroup;
+    RefPtr<mozilla::PerformanceCounter> mPerformanceCounter;
   };
   friend class Runnable;
 
   static nsresult Dispatch(TaskCategory aCategory,
                            already_AddRefed<nsIRunnable>&& aRunnable);
 
   static nsresult UnlabeledDispatch(TaskCategory aCategory,
                                     already_AddRefed<nsIRunnable>&& aRunnable);
 
   static void MarkVsyncReceived();
 
   static void MarkVsyncRan();
 
-  static nsresult DispatchWithDocGroup(
+  static nsresult LabeledDispatch(
       TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable,
-      dom::DocGroup* aDocGroup);
+      mozilla::PerformanceCounter* aPerformanceCounter);
 
  protected:
   static nsresult InternalUnlabeledDispatch(
       TaskCategory aCategory, already_AddRefed<Runnable>&& aRunnable);
 
-  static nsresult LabeledDispatch(TaskCategory aCategory,
-                                  already_AddRefed<nsIRunnable>&& aRunnable,
-                                  dom::DocGroup* aDocGroup);
-
   // Shuts down this dispatcher. If aXPCOMShutdown is true, invalidates this
   // dispatcher.
   void Shutdown(bool aXPCOMShutdown);
 
   bool mIsRunning;
 
   // Number of events that are currently enqueued for this SchedulerGroup
   // (across all queues).
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -969,20 +969,17 @@ mozilla::PerformanceCounter* nsThread::G
   return GetPerformanceCounterBase(aEvent);
 }
 
 // static
 mozilla::PerformanceCounter* nsThread::GetPerformanceCounterBase(
     nsIRunnable* aEvent) {
   RefPtr<SchedulerGroup::Runnable> docRunnable = do_QueryObject(aEvent);
   if (docRunnable) {
-    mozilla::dom::DocGroup* docGroup = docRunnable->DocGroup();
-    if (docGroup) {
-      return docGroup->GetPerformanceCounter();
-    }
+    return docRunnable->GetPerformanceCounter();
   }
   return nullptr;
 }
 
 size_t nsThread::ShallowSizeOfIncludingThis(
     mozilla::MallocSizeOf aMallocSizeOf) const {
   size_t n = 0;
   if (mShutdownContext) {