Bug 1533302 part 2 - Tie XPCWrappedNativeScope lifetime to CompartmentPrivate. r=mccr8
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 08 Mar 2019 13:28:47 +0000
changeset 521032 1b1bcfe92e21
parent 521031 e1ba6af4a982
child 521033 e6970424c0ef
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1533302, 1032928
milestone67.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 1533302 part 2 - Tie XPCWrappedNativeScope lifetime to CompartmentPrivate. r=mccr8 XPCWrappedNativeScope is now allocated and destroyed with the CompartmentPrivate that owns it. In follow-up bugs we could merge the two classes (see bug 1032928). This also removes the dying-scopes list. XPCJSRuntime now stores the list of all scopes as mozilla::LinkedList. Differential Revision: https://phabricator.services.mozilla.com/D22492
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/XPCWrappedNativeScope.cpp
js/xpconnect/src/nsXPConnect.cpp
js/xpconnect/src/xpcprivate.h
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -201,16 +201,17 @@ CompartmentPrivate::CompartmentPrivate(J
       wasShutdown(false),
       mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)) {
   MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
 }
 
 CompartmentPrivate::~CompartmentPrivate() {
   MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
   delete mWrappedJSMap;
+  delete scope;
 }
 
 void CompartmentPrivate::SystemIsBeingShutDown() {
   // We may call this multiple times when the compartment contains more than one
   // realm.
   if (!wasShutdown) {
     mWrappedJSMap->ShutdownMarker();
     wasShutdown = true;
@@ -738,17 +739,17 @@ void XPCJSRuntime::TraceNativeBlackRoots
       roots->TraceJSAll(trc);
     }
   }
 
   dom::TraceBlackJS(trc, nsIXPConnect::XPConnect()->GetIsShuttingDown());
 }
 
 void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer* trc) {
-  XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(trc);
+  XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(this, trc);
 
   for (XPCRootSetElem* e = mVariantRoots; e; e = e->GetNextRoot()) {
     static_cast<XPCTraceableVariant*>(e)->TraceJS(trc);
   }
 
   for (XPCRootSetElem* e = mWrappedJSRoots; e; e = e->GetNextRoot()) {
     static_cast<nsXPCWrappedJS*>(e)->TraceJS(trc);
   }
@@ -880,19 +881,16 @@ void XPCJSRuntime::FinalizeCallback(JSFr
       MOZ_ASSERT(self->mDoingFinalization, "bad state");
 
       MOZ_ASSERT(self->mGCIsRunning, "bad state");
       self->mGCIsRunning = false;
 
       break;
     }
     case JSFINALIZE_GROUP_END: {
-      // Sweep scopes needing cleanup
-      XPCWrappedNativeScope::KillDyingScopes();
-
       MOZ_ASSERT(self->mDoingFinalization, "bad state");
       self->mDoingFinalization = false;
 
       break;
     }
     case JSFINALIZE_COLLECTION_END: {
       MOZ_ASSERT(!self->mGCIsRunning, "bad state");
       self->mGCIsRunning = true;
@@ -966,18 +964,16 @@ void XPCJSRuntime::FinalizeCallback(JSFr
 void XPCJSRuntime::WeakPointerZonesCallback(JSContext* cx, void* data) {
   // Called before each sweeping slice -- after processing any final marking
   // triggered by barriers -- to clear out any references to things that are
   // about to be finalized and update any pointers to moved GC things.
   XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
 
   self->mWrappedJSMap->UpdateWeakPointersAfterGC();
   self->mUAWidgetScopeMap.sweep();
-
-  XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC();
 }
 
 /* static */
 void XPCJSRuntime::WeakPointerCompartmentCallback(JSContext* cx,
                                                   JS::Compartment* comp,
                                                   void* data) {
   // Called immediately after the ZoneGroup weak pointer callback, but only
   // once for each compartment that is being swept.
@@ -985,16 +981,17 @@ void XPCJSRuntime::WeakPointerCompartmen
   if (xpcComp) {
     xpcComp->UpdateWeakPointersAfterGC();
   }
 }
 
 void CompartmentPrivate::UpdateWeakPointersAfterGC() {
   mRemoteProxies.sweep();
   mWrappedJSMap->UpdateWeakPointersAfterGC();
+  scope->UpdateWeakPointersAfterGC();
 }
 
 void XPCJSRuntime::CustomOutOfMemoryCallback() {
   if (!Preferences::GetBool("memory.dump_reports_on_oom")) {
     return;
   }
 
   nsCOMPtr<nsIMemoryInfoDumper> dumper =
@@ -1161,16 +1158,19 @@ void XPCJSRuntime::Shutdown(JSContext* c
   mClassInfo2NativeSetMap = nullptr;
 
   delete mNativeSetMap;
   mNativeSetMap = nullptr;
 
   delete mDyingWrappedNativeProtoMap;
   mDyingWrappedNativeProtoMap = nullptr;
 
+  // Prevent ~LinkedList assertion failures if we leaked things.
+  mWrappedNativeScopes.clear();
+
   CycleCollectedJSRuntime::Shutdown(cx);
 }
 
 XPCJSRuntime::~XPCJSRuntime() {
   MOZ_COUNT_DTOR_INHERITED(XPCJSRuntime, CycleCollectedJSRuntime);
 }
 
 // If |*anonymizeID| is non-zero and this is a user realm, the name will
@@ -2937,16 +2937,17 @@ XPCJSRuntime::XPCJSRuntime(JSContext* aC
       mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)),
       mWrappedJSClassMap(
           IID2WrappedJSClassMap::newMap(XPC_JS_CLASS_MAP_LENGTH)),
       mIID2NativeInterfaceMap(
           IID2NativeInterfaceMap::newMap(XPC_NATIVE_INTERFACE_MAP_LENGTH)),
       mClassInfo2NativeSetMap(
           ClassInfo2NativeSetMap::newMap(XPC_NATIVE_SET_MAP_LENGTH)),
       mNativeSetMap(NativeSetMap::newMap(XPC_NATIVE_SET_MAP_LENGTH)),
+      mWrappedNativeScopes(),
       mDyingWrappedNativeProtoMap(
           XPCWrappedNativeProtoMap::newMap(XPC_DYING_NATIVE_PROTO_MAP_LENGTH)),
       mGCIsRunning(false),
       mNativesToReleaseArray(),
       mDoingFinalization(false),
       mVariantRoots(nullptr),
       mWrappedJSRoots(nullptr),
       mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()) {
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -9,29 +9,31 @@
 #include "xpcprivate.h"
 #include "XPCWrapper.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "ExpandedPrincipal.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
 #include "nsIXULRuntime.h"
 #include "mozJSComponentLoader.h"
 
 #include "mozilla/dom/BindingUtils.h"
 
 using namespace mozilla;
 using namespace xpc;
 using namespace JS;
 
 /***************************************************************************/
 
-XPCWrappedNativeScope* XPCWrappedNativeScope::gScopes = nullptr;
-XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nullptr;
+static XPCWrappedNativeScopeList& AllScopes() {
+  return XPCJSRuntime::Get()->GetWrappedNativeScopes();
+}
 
 static bool RemoteXULForbidsXBLScopeForPrincipal(nsIPrincipal* aPrincipal) {
   // AllowXULXBLForPrincipal will return true for system principal, but we
   // don't want that here.
   MOZ_ASSERT(nsContentUtils::IsInitialized());
   if (aPrincipal->IsSystemPrincipal()) {
     return false;
   }
@@ -61,27 +63,24 @@ static bool RemoteXULForbidsXBLScope(Han
 }
 
 XPCWrappedNativeScope::XPCWrappedNativeScope(JS::Compartment* aCompartment,
                                              JS::HandleObject aFirstGlobal)
     : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)),
       mWrappedNativeProtoMap(
           ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)),
       mComponents(nullptr),
-      mNext(nullptr),
       mCompartment(aCompartment) {
 #ifdef DEBUG
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     MOZ_ASSERT(aCompartment != cur->Compartment(), "dup object");
   }
 #endif
 
-  // add ourselves to the scopes list
-  mNext = gScopes;
-  gScopes = this;
+  AllScopes().insertBack(this);
 
   MOZ_COUNT_CTOR(XPCWrappedNativeScope);
 
   // Determine whether we would allow an XBL scope in this situation.
   // In addition to being pref-controlled, we also disable XBL scopes for
   // remote XUL domains, _except_ if we have an additional pref override set.
   //
   // Note that we can't quite remove this yet, even though we never actually
@@ -248,17 +247,18 @@ JSObject* GetUAWidgetScope(JSContext* cx
   scope = js::UncheckedUnwrap(scope);
   JS::ExposeObjectToActiveJS(scope);
   return scope;
 }
 
 bool AllowContentXBLScope(JS::Realm* realm) {
   JS::Compartment* comp = GetCompartmentForRealm(realm);
   XPCWrappedNativeScope* scope = CompartmentPrivate::Get(comp)->scope;
-  return scope && scope->AllowContentXBLScope(realm);
+  MOZ_ASSERT(scope);
+  return scope->AllowContentXBLScope(realm);
 }
 
 } /* namespace xpc */
 
 XPCWrappedNativeScope::~XPCWrappedNativeScope() {
   MOZ_COUNT_DTOR(XPCWrappedNativeScope);
 
   // We can do additional cleanup assertions here...
@@ -274,91 +274,75 @@ XPCWrappedNativeScope::~XPCWrappedNative
   if (mComponents) {
     mComponents->mScope = nullptr;
   }
 
   // XXX we should assert that we are dead or that xpconnect has shutdown
   // XXX might not want to do this at xpconnect shutdown time???
   mComponents = nullptr;
 
-  if (mXrayExpandos.initialized()) {
-    mXrayExpandos.destroy();
-  }
+  MOZ_RELEASE_ASSERT(!mXrayExpandos.initialized());
 
-  JSContext* cx = dom::danger::GetJSContext();
-  mIDProto.finalize(cx);
-  mIIDProto.finalize(cx);
-  mCIDProto.finalize(cx);
   mCompartment = nullptr;
 }
 
 // static
-void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(JSTracer* trc) {
+void XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt,
+                                                           JSTracer* trc) {
   // Do JS::TraceEdge for all wrapped natives with external references, as
   // well as any DOM expando objects.
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  //
+  // Note: the GC can call this from a JS helper thread. We don't use
+  // AllScopes() because that asserts we're on the main thread.
+
+  for (XPCWrappedNativeScope* cur : xpcrt->GetWrappedNativeScopes()) {
     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
       auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
       XPCWrappedNative* wrapper = entry->value;
       if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired()) {
         wrapper->TraceSelf(trc);
       }
     }
   }
 }
 
 // static
 void XPCWrappedNativeScope::SuspectAllWrappers(
     nsCycleCollectionNoteRootCallback& cb) {
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
       static_cast<Native2WrappedNativeMap::Entry*>(i.Get())->value->Suspect(cb);
     }
   }
 }
 
-// static
-void XPCWrappedNativeScope::UpdateWeakPointersInAllScopesAfterGC() {
-  // If this is called from the finalization callback in JSGC_MARK_END then
-  // JSGC_FINALIZE_END must always follow it calling
-  // FinishedFinalizationPhaseOfGC and clearing gDyingScopes in
-  // KillDyingScopes.
-  MOZ_ASSERT(!gDyingScopes, "JSGC_MARK_END without JSGC_FINALIZE_END");
-
-  XPCWrappedNativeScope** scopep = &gScopes;
-  while (*scopep) {
-    XPCWrappedNativeScope* cur = *scopep;
-    cur->UpdateWeakPointersAfterGC();
-    if (cur->Compartment()) {
-      scopep = &cur->mNext;
-    } else {
-      // The scope's global is dead so move it to the dying scopes list.
-      *scopep = cur->mNext;
-      cur->mNext = gDyingScopes;
-      gDyingScopes = cur;
-    }
-  }
-}
-
 void XPCWrappedNativeScope::UpdateWeakPointersAfterGC() {
   // Sweep waivers.
   if (mWaiverWrapperMap) {
     mWaiverWrapperMap->Sweep();
   }
 
   if (!js::IsCompartmentZoneSweepingOrCompacting(mCompartment)) {
     return;
   }
 
-  // Update our pointer to the compartment in case we finalized all globals.
   if (!js::CompartmentHasLiveGlobal(mCompartment)) {
-    CompartmentPrivate::Get(mCompartment)->scope = nullptr;
-    mCompartment = nullptr;
     GetWrappedNativeMap()->Clear();
     mWrappedNativeProtoMap->Clear();
+
+    // The fields below are traced only if there's a live global in the
+    // compartment, see TraceXPCGlobal. The compartment has no live globals so
+    // clear these pointers here.
+    if (mXrayExpandos.initialized()) {
+      mXrayExpandos.destroy();
+    }
+    JSContext* cx = dom::danger::GetJSContext();
+    mIDProto.finalize(cx);
+    mIIDProto.finalize(cx);
+    mCIDProto.finalize(cx);
     return;
   }
 
   // Sweep mWrappedNativeMap for dying flat JS objects. Moving has already
   // been handled by XPCWrappedNative::FlatJSObjectMoved.
   for (auto iter = GetWrappedNativeMap()->Iter(); !iter.Done(); iter.Next()) {
     auto entry = static_cast<Native2WrappedNativeMap::Entry*>(iter.Get());
     XPCWrappedNative* wrapper = entry->value;
@@ -382,66 +366,48 @@ void XPCWrappedNativeScope::UpdateWeakPo
     if (!obj) {
       i.Remove();
     }
   }
 }
 
 // static
 void XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs() {
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) {
       auto entry = static_cast<Native2WrappedNativeMap::Entry*>(i.Get());
       entry->value->SweepTearOffs();
     }
   }
 }
 
 // static
-void XPCWrappedNativeScope::KillDyingScopes() {
-  XPCWrappedNativeScope* cur = gDyingScopes;
-  while (cur) {
-    XPCWrappedNativeScope* next = cur->mNext;
-    if (cur->Compartment()) {
-      CompartmentPrivate::Get(cur->Compartment())->scope = nullptr;
-    }
-    delete cur;
-    cur = next;
-  }
-  gDyingScopes = nullptr;
-}
-
-// static
 void XPCWrappedNativeScope::SystemIsBeingShutDown() {
-  int liveScopeCount = 0;
-
-  XPCWrappedNativeScope* cur;
-
-  // First move all the scopes to the dying list.
-
-  cur = gScopes;
-  while (cur) {
-    XPCWrappedNativeScope* next = cur->mNext;
-    cur->mNext = gDyingScopes;
-    gDyingScopes = cur;
-    cur = next;
-    liveScopeCount++;
-  }
-  gScopes = nullptr;
-
   // We're forcibly killing scopes, rather than allowing them to go away
   // when they're ready. As such, we need to do some cleanup before they
   // can safely be destroyed.
 
-  for (cur = gDyingScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     // Give the Components object a chance to try to clean up.
     if (cur->mComponents) {
       cur->mComponents->SystemIsBeingShutDown();
     }
 
+    // Null out these pointers to prevent ~ObjectPtr assertion failures if we
+    // leaked things at shutdown.
+    JSContext* cx = dom::danger::GetJSContext();
+    cur->mIDProto.finalize(cx);
+    cur->mIIDProto.finalize(cx);
+    cur->mCIDProto.finalize(cx);
+
+    // Similarly, destroy mXrayExpandos to prevent assertion failures.
+    if (cur->mXrayExpandos.initialized()) {
+      cur->mXrayExpandos.destroy();
+    }
+
     // Walk the protos first. Wrapper shutdown can leave dangling
     // proto pointers in the proto map.
     for (auto i = cur->mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) {
       auto entry =
           static_cast<ClassInfo2WrappedNativeProtoMap::Entry*>(i.Get());
       entry->value->SystemIsBeingShutDown();
       i.Remove();
     }
@@ -452,19 +418,16 @@ void XPCWrappedNativeScope::SystemIsBein
         wrapper->SystemIsBeingShutDown();
       }
       i.Remove();
     }
 
     CompartmentPrivate* priv = CompartmentPrivate::Get(cur->Compartment());
     priv->SystemIsBeingShutDown();
   }
-
-  // Now it is safe to kill all the scopes.
-  KillDyingScopes();
 }
 
 /***************************************************************************/
 
 JSObject* XPCWrappedNativeScope::GetExpandoChain(HandleObject target) {
   MOZ_ASSERT(ObjectScope(target) == this);
   if (!mXrayExpandos.initialized()) {
     return nullptr;
@@ -495,39 +458,38 @@ bool XPCWrappedNativeScope::SetExpandoCh
 
 // static
 void XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth) {
 #ifdef DEBUG
   depth--;
 
   // get scope count.
   int count = 0;
-  XPCWrappedNativeScope* cur;
-  for (cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
+    mozilla::Unused << cur;
     count++;
   }
 
   XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count));
   XPC_LOG_INDENT();
-  XPC_LOG_ALWAYS(("gDyingScopes @ %p", gDyingScopes));
   if (depth) {
-    for (cur = gScopes; cur; cur = cur->mNext) {
+    for (XPCWrappedNativeScope* cur : AllScopes()) {
       cur->DebugDump(depth);
     }
   }
   XPC_LOG_OUTDENT();
 #endif
 }
 
 void XPCWrappedNativeScope::DebugDump(int16_t depth) {
 #ifdef DEBUG
   depth--;
   XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %p", this));
   XPC_LOG_INDENT();
-  XPC_LOG_ALWAYS(("mNext @ %p", mNext));
+  XPC_LOG_ALWAYS(("next @ %p", getNext()));
   XPC_LOG_ALWAYS(("mComponents @ %p", mComponents.get()));
   XPC_LOG_ALWAYS(("mCompartment @ %p", mCompartment));
 
   XPC_LOG_ALWAYS(("mWrappedNativeMap @ %p with %d wrappers(s)",
                   mWrappedNativeMap, mWrappedNativeMap->Count()));
   // iterate contexts...
   if (depth && mWrappedNativeMap->Count()) {
     XPC_LOG_INDENT();
@@ -551,17 +513,17 @@ void XPCWrappedNativeScope::DebugDump(in
     XPC_LOG_OUTDENT();
   }
   XPC_LOG_OUTDENT();
 #endif
 }
 
 void XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(
     JSContext* cx, ScopeSizeInfo* scopeSizeInfo) {
-  for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) {
+  for (XPCWrappedNativeScope* cur : AllScopes()) {
     cur->AddSizeOfIncludingThis(cx, scopeSizeInfo);
   }
 }
 
 void XPCWrappedNativeScope::AddSizeOfIncludingThis(
     JSContext* cx, ScopeSizeInfo* scopeSizeInfo) {
   scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this);
   scopeSizeInfo->mScopeAndMapSize +=
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -412,21 +412,20 @@ static inline T UnexpectedFailure(T rv) 
 }
 
 void xpc::TraceXPCGlobal(JSTracer* trc, JSObject* obj) {
   if (js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL) {
     mozilla::dom::TraceProtoAndIfaceCache(trc, obj);
   }
 
   // We might be called from a GC during the creation of a global, before we've
-  // been able to set up the compartment private or the XPCWrappedNativeScope,
-  // so we need to null-check those.
-  xpc::CompartmentPrivate* compPrivate = xpc::CompartmentPrivate::Get(obj);
-  if (compPrivate && compPrivate->scope) {
-    compPrivate->scope->TraceInside(trc);
+  // been able to set up the compartment private.
+  if (xpc::CompartmentPrivate* priv = xpc::CompartmentPrivate::Get(obj)) {
+    MOZ_ASSERT(priv->scope);
+    priv->scope->TraceInside(trc);
   }
 }
 
 namespace xpc {
 
 JSObject* CreateGlobalObject(JSContext* cx, const JSClass* clasp,
                              nsIPrincipal* principal,
                              JS::RealmOptions& aOptions) {
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -73,16 +73,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/CycleCollectedJSContext.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/DefineEnum.h"
 #include "mozilla/GuardObjects.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 
 #include "mozilla/dom/ScriptSettings.h"
 
@@ -300,16 +301,19 @@ MOZ_DEFINE_ENUM(WatchdogTimestampCategor
     TimestampWatchdogWakeup,
     TimestampWatchdogHibernateStart,
     TimestampWatchdogHibernateStop,
     TimestampContextStateChange
 ));
 // clang-format on
 
 class AsyncFreeSnowWhite;
+class XPCWrappedNativeScope;
+
+using XPCWrappedNativeScopeList = mozilla::LinkedList<XPCWrappedNativeScope>;
 
 class XPCJSContext final : public mozilla::CycleCollectedJSContext,
                            public mozilla::LinkedListElement<XPCJSContext> {
  public:
   static void InitTLS();
   static XPCJSContext* NewXPCJSContext(XPCJSContext* aPrimaryContext);
   static XPCJSContext* Get();
 
@@ -497,16 +501,20 @@ class XPCJSRuntime final : public mozill
   }
 
   NativeSetMap* GetNativeSetMap() const { return mNativeSetMap; }
 
   XPCWrappedNativeProtoMap* GetDyingWrappedNativeProtoMap() const {
     return mDyingWrappedNativeProtoMap;
   }
 
+  XPCWrappedNativeScopeList& GetWrappedNativeScopes() {
+    return mWrappedNativeScopes;
+  }
+
   bool InitializeStrings(JSContext* cx);
 
   virtual bool DescribeCustomObjects(JSObject* aObject, const js::Class* aClasp,
                                      char (&aName)[72]) const override;
   virtual bool NoteCustomGCThingXPCOMChildren(
       const js::Class* aClasp, JSObject* aObj,
       nsCycleCollectionTraversalCallback& aCb) const override;
 
@@ -617,16 +625,17 @@ class XPCJSRuntime final : public mozill
       Principal2JSObjectMap;
 
   JSObject2WrappedJSMap* mWrappedJSMap;
   IID2WrappedJSClassMap* mWrappedJSClassMap;
   IID2NativeInterfaceMap* mIID2NativeInterfaceMap;
   ClassInfo2NativeSetMap* mClassInfo2NativeSetMap;
   NativeSetMap* mNativeSetMap;
   Principal2JSObjectMap mUAWidgetScopeMap;
+  XPCWrappedNativeScopeList mWrappedNativeScopes;
   XPCWrappedNativeProtoMap* mDyingWrappedNativeProtoMap;
   bool mGCIsRunning;
   nsTArray<nsISupports*> mNativesToReleaseArray;
   bool mDoingFinalization;
   XPCRootSetElem* mVariantRoots;
   XPCRootSetElem* mWrappedJSRoots;
   nsTArray<xpcGCCallback> extraGCCallbacks;
   JS::GCSliceCallback mPrevGCSliceCallback;
@@ -802,17 +811,18 @@ extern const js::Class XPC_WN_NoHelper_P
 extern bool XPC_WN_CallMethod(JSContext* cx, unsigned argc, JS::Value* vp);
 
 extern bool XPC_WN_GetterSetter(JSContext* cx, unsigned argc, JS::Value* vp);
 
 /***************************************************************************/
 // XPCWrappedNativeScope is one-to-one with a JS compartment.
 
 class nsXPCComponentsBase;
-class XPCWrappedNativeScope final {
+class XPCWrappedNativeScope final
+    : public mozilla::LinkedListElement<XPCWrappedNativeScope> {
  public:
   XPCJSRuntime* GetRuntime() const { return XPCJSRuntime::Get(); }
 
   Native2WrappedNativeMap* GetWrappedNativeMap() const {
     return mWrappedNativeMap;
   }
 
   ClassInfo2WrappedNativeProtoMap* GetWrappedNativeProtoMap() const {
@@ -834,17 +844,18 @@ class XPCWrappedNativeScope final {
 
   JSObject* DetachExpandoChain(JS::HandleObject target);
 
   bool SetExpandoChain(JSContext* cx, JS::HandleObject target,
                        JS::HandleObject chain);
 
   static void SystemIsBeingShutDown();
 
-  static void TraceWrappedNativesInAllScopes(JSTracer* trc);
+  static void TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt,
+                                             JSTracer* trc);
 
   void TraceInside(JSTracer* trc) {
     if (mXrayExpandos.initialized()) {
       mXrayExpandos.trace(trc);
     }
     if (mIDProto) {
       mIDProto.trace(trc, "XPCWrappedNativeScope::mIDProto");
     }
@@ -855,22 +866,18 @@ class XPCWrappedNativeScope final {
       mCIDProto.trace(trc, "XPCWrappedNativeScope::mCIDProto");
     }
   }
 
   static void SuspectAllWrappers(nsCycleCollectionNoteRootCallback& cb);
 
   static void SweepAllWrappedNativeTearOffs();
 
-  static void UpdateWeakPointersInAllScopesAfterGC();
-
   void UpdateWeakPointersAfterGC();
 
-  static void KillDyingScopes();
-
   static void DebugDumpAllScopes(int16_t depth);
 
   void DebugDump(int16_t depth);
 
   struct ScopeSizeInfo {
     explicit ScopeSizeInfo(mozilla::MallocSizeOf mallocSizeOf)
         : mMallocSizeOf(mallocSizeOf),
           mScopeAndMapSize(0),
@@ -894,16 +901,17 @@ class XPCWrappedNativeScope final {
 
   // Check whether our mAllowContentXBLScope state matches the given
   // principal.  This is used to avoid sharing compartments on
   // mismatch.
   bool XBLScopeStateMatches(nsIPrincipal* aPrincipal);
 
   XPCWrappedNativeScope(JS::Compartment* aCompartment,
                         JS::HandleObject aFirstGlobal);
+  virtual ~XPCWrappedNativeScope();
 
   nsAutoPtr<JSObject2JSObjectMap> mWaiverWrapperMap;
 
   JS::Compartment* Compartment() const { return mCompartment; }
 
   // Returns the global to use for new WrappedNative objects allocated in this
   // compartment. This is better than using arbitrary globals we happen to be in
   // because it prevents leaks (objects keep their globals alive).
@@ -917,28 +925,22 @@ class XPCWrappedNativeScope final {
   bool AllowContentXBLScope(JS::Realm* aRealm);
 
   // ID Object prototype caches.
   JS::ObjectPtr mIDProto;
   JS::ObjectPtr mIIDProto;
   JS::ObjectPtr mCIDProto;
 
  protected:
-  virtual ~XPCWrappedNativeScope();
-
   XPCWrappedNativeScope() = delete;
 
  private:
-  static XPCWrappedNativeScope* gScopes;
-  static XPCWrappedNativeScope* gDyingScopes;
-
   Native2WrappedNativeMap* mWrappedNativeMap;
   ClassInfo2WrappedNativeProtoMap* mWrappedNativeProtoMap;
   RefPtr<nsXPCComponentsBase> mComponents;
-  XPCWrappedNativeScope* mNext;
   JS::Compartment* mCompartment;
 
   JS::WeakMapPtr<JSObject*, JSObject*> mXrayExpandos;
 
   // For remote XUL domains, we run all XBL in the content scope for compat
   // reasons (though we sometimes pref this off for automation). We
   // track the result of this decision (mAllowContentXBLScope) for now.
   bool mAllowContentXBLScope;