Bug 1214961 - Sweep XPConnect incrementally; r=mccr8, r=jonco
authorTerrence Cole <terrence@mozilla.com>
Thu, 15 Oct 2015 13:43:28 -0700
changeset 270508 7ecec6573ae9e323ea1f4ca401d160ad776405a6
parent 270507 16ab2ae184c0c4e46d174326cecb5191633e6a09
child 270509 662df9054efa217a23c7174742e8ed38338f3f48
push id29616
push userphilringnalda@gmail.com
push dateSun, 01 Nov 2015 00:00:59 +0000
treeherdermozilla-central@e958f522a72c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8, jonco
bugs1214961
milestone45.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 1214961 - Sweep XPConnect incrementally; r=mccr8, r=jonco This patch splits up the "wrapped-js" table by compartment in order to allow us to visit the miminal number of wrapped-js in each GC sweeping slice, instead of visiting the entire table repeatedly. This dramatically reduces our sweeping overhead, reducing the number of GC slices, and making them more likely to fall within budget.
js/ipc/JavaScriptChild.cpp
js/src/gc/GCRuntime.h
js/src/gc/Statistics.cpp
js/src/gc/Statistics.h
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsgc.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/XPCMaps.cpp
js/xpconnect/src/XPCMaps.h
js/xpconnect/src/XPCWrappedJS.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/src/xpcpublic.h
--- a/js/ipc/JavaScriptChild.cpp
+++ b/js/ipc/JavaScriptChild.cpp
@@ -16,41 +16,41 @@
 
 using namespace JS;
 using namespace mozilla;
 using namespace mozilla::jsipc;
 
 using mozilla::AutoSafeJSContext;
 
 static void
-UpdateChildWeakPointersAfterGC(JSRuntime* rt, void* data)
+UpdateChildWeakPointersBeforeSweepingZoneGroup(JSRuntime* rt, void* data)
 {
     static_cast<JavaScriptChild*>(data)->updateWeakPointers();
 }
 
 JavaScriptChild::JavaScriptChild(JSRuntime* rt)
   : JavaScriptShared(rt),
     JavaScriptBase<PJavaScriptChild>(rt)
 {
 }
 
 JavaScriptChild::~JavaScriptChild()
 {
-    JS_RemoveWeakPointerCallback(rt_, UpdateChildWeakPointersAfterGC);
+    JS_RemoveWeakPointerZoneGroupCallback(rt_, UpdateChildWeakPointersBeforeSweepingZoneGroup);
 }
 
 bool
 JavaScriptChild::init()
 {
     if (!WrapperOwner::init())
         return false;
     if (!WrapperAnswer::init())
         return false;
 
-    JS_AddWeakPointerCallback(rt_, UpdateChildWeakPointersAfterGC, this);
+    JS_AddWeakPointerZoneGroupCallback(rt_, UpdateChildWeakPointersBeforeSweepingZoneGroup, this);
     return true;
 }
 
 void
 JavaScriptChild::updateWeakPointers()
 {
     objects_.sweep();
     unwaivedObjectIds_.sweep();
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -752,18 +752,20 @@ class GCRuntime
     void resetMallocBytes();
     bool isTooMuchMalloc() const { return mallocBytesUntilGC <= 0; }
     void updateMallocCounter(JS::Zone* zone, size_t nbytes);
     void onTooMuchMalloc();
 
     void setGCCallback(JSGCCallback callback, void* data);
     bool addFinalizeCallback(JSFinalizeCallback callback, void* data);
     void removeFinalizeCallback(JSFinalizeCallback func);
-    bool addWeakPointerCallback(JSWeakPointerCallback callback, void* data);
-    void removeWeakPointerCallback(JSWeakPointerCallback func);
+    bool addWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback, void* data);
+    void removeWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback);
+    bool addWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback, void* data);
+    void removeWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback);
     JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback);
 
     void setValidate(bool enable);
     void setFullCompartmentChecks(bool enable);
 
     bool isManipulatingDeadZones() { return manipulatingDeadZones; }
     void setManipulatingDeadZones(bool value) { manipulatingDeadZones = value; }
     unsigned objectsMarkedInDeadZonesCount() { return objectsMarkedInDeadZones; }
@@ -964,17 +966,18 @@ class GCRuntime
     void validateIncrementalMarking();
     void finishMarkingValidation();
 
 #ifdef DEBUG
     void checkForCompartmentMismatches();
 #endif
 
     void callFinalizeCallbacks(FreeOp* fop, JSFinalizeStatus status) const;
-    void callWeakPointerCallbacks() const;
+    void callWeakPointerZoneGroupCallbacks() const;
+    void callWeakPointerCompartmentCallbacks(JSCompartment* comp) const;
 
   public:
     JSRuntime* rt;
 
     /* Embedders can use this zone however they wish. */
     JS::Zone* systemZone;
 
     /* List of compartments and zones (protected by the GC lock). */
@@ -1264,17 +1267,18 @@ class GCRuntime
     js::Vector<JSObject*, 0, js::SystemAllocPolicy> selectedForMarking;
 #endif
 
     bool validate;
     bool fullCompartmentChecks;
 
     Callback<JSGCCallback> gcCallback;
     CallbackVector<JSFinalizeCallback> finalizeCallbacks;
-    CallbackVector<JSWeakPointerCallback> updateWeakPointerCallbacks;
+    CallbackVector<JSWeakPointerZoneGroupCallback> updateWeakPointerZoneGroupCallbacks;
+    CallbackVector<JSWeakPointerCompartmentCallback> updateWeakPointerCompartmentCallbacks;
 
     /*
      * Malloc counter to measure memory pressure for GC scheduling. It runs
      * from maxMallocBytes down to zero.
      */
     mozilla::Atomic<ptrdiff_t, mozilla::ReleaseAcquire> mallocBytesUntilGC;
 
     /*
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -133,17 +133,19 @@ static const PhaseInfo phases[] = {
     { PHASE_SWEEP, "Sweep", PHASE_NO_PARENT, 9 },
         { PHASE_SWEEP_MARK, "Mark During Sweeping", PHASE_SWEEP, 10 },
             { PHASE_SWEEP_MARK_TYPES, "Mark Types During Sweeping", PHASE_SWEEP_MARK, 11 },
             { PHASE_SWEEP_MARK_INCOMING_BLACK, "Mark Incoming Black Pointers", PHASE_SWEEP_MARK, 12 },
             { PHASE_SWEEP_MARK_WEAK, "Mark Weak", PHASE_SWEEP_MARK, 13 },
             { PHASE_SWEEP_MARK_INCOMING_GRAY, "Mark Incoming Gray Pointers", PHASE_SWEEP_MARK, 14 },
             { PHASE_SWEEP_MARK_GRAY, "Mark Gray", PHASE_SWEEP_MARK, 15 },
             { PHASE_SWEEP_MARK_GRAY_WEAK, "Mark Gray and Weak", PHASE_SWEEP_MARK, 16 },
-        { PHASE_FINALIZE_START, "Finalize Start Callback", PHASE_SWEEP, 17 },
+        { PHASE_FINALIZE_START, "Finalize Start Callbacks", PHASE_SWEEP, 17 },
+            { PHASE_WEAK_ZONEGROUP_CALLBACK, "Per-Slice Weak Callback", PHASE_FINALIZE_START, 56 },
+            { PHASE_WEAK_COMPARTMENT_CALLBACK, "Per-Compartment Weak Callback", PHASE_FINALIZE_START, 57 },
         { PHASE_SWEEP_ATOMS, "Sweep Atoms", PHASE_SWEEP, 18 },
         { PHASE_SWEEP_SYMBOL_REGISTRY, "Sweep Symbol Registry", PHASE_SWEEP, 19 },
         { PHASE_SWEEP_COMPARTMENTS, "Sweep Compartments", PHASE_SWEEP, 20 },
             { PHASE_SWEEP_DISCARD_CODE, "Sweep Discard Code", PHASE_SWEEP_COMPARTMENTS, 21 },
             { PHASE_SWEEP_INNER_VIEWS, "Sweep Inner Views", PHASE_SWEEP_COMPARTMENTS, 22 },
             { PHASE_SWEEP_CC_WRAPPER, "Sweep Cross Compartment Wrappers", PHASE_SWEEP_COMPARTMENTS, 23 },
             { PHASE_SWEEP_BASE_SHAPE, "Sweep Base Shapes", PHASE_SWEEP_COMPARTMENTS, 24 },
             { PHASE_SWEEP_INITIAL_SHAPE, "Sweep Initial Shapes", PHASE_SWEEP_COMPARTMENTS, 25 },
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -39,16 +39,18 @@ enum Phase : uint8_t {
     PHASE_SWEEP_MARK,
     PHASE_SWEEP_MARK_TYPES,
     PHASE_SWEEP_MARK_INCOMING_BLACK,
     PHASE_SWEEP_MARK_WEAK,
     PHASE_SWEEP_MARK_INCOMING_GRAY,
     PHASE_SWEEP_MARK_GRAY,
     PHASE_SWEEP_MARK_GRAY_WEAK,
     PHASE_FINALIZE_START,
+    PHASE_WEAK_ZONEGROUP_CALLBACK,
+    PHASE_WEAK_COMPARTMENT_CALLBACK,
     PHASE_SWEEP_ATOMS,
     PHASE_SWEEP_SYMBOL_REGISTRY,
     PHASE_SWEEP_COMPARTMENTS,
     PHASE_SWEEP_DISCARD_CODE,
     PHASE_SWEEP_INNER_VIEWS,
     PHASE_SWEEP_CC_WRAPPER,
     PHASE_SWEEP_BASE_SHAPE,
     PHASE_SWEEP_INITIAL_SHAPE,
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1534,27 +1534,42 @@ JS_AddFinalizeCallback(JSRuntime* rt, JS
 
 JS_PUBLIC_API(void)
 JS_RemoveFinalizeCallback(JSRuntime* rt, JSFinalizeCallback cb)
 {
     rt->gc.removeFinalizeCallback(cb);
 }
 
 JS_PUBLIC_API(bool)
-JS_AddWeakPointerCallback(JSRuntime* rt, JSWeakPointerCallback cb, void* data)
+JS_AddWeakPointerZoneGroupCallback(JSRuntime* rt, JSWeakPointerZoneGroupCallback cb, void* data)
 {
     AssertHeapIsIdle(rt);
-    return rt->gc.addWeakPointerCallback(cb, data);
+    return rt->gc.addWeakPointerZoneGroupCallback(cb, data);
 }
 
 JS_PUBLIC_API(void)
-JS_RemoveWeakPointerCallback(JSRuntime* rt, JSWeakPointerCallback cb)
-{
-    rt->gc.removeWeakPointerCallback(cb);
-}
+JS_RemoveWeakPointerZoneGroupCallback(JSRuntime* rt, JSWeakPointerZoneGroupCallback cb)
+{
+    rt->gc.removeWeakPointerZoneGroupCallback(cb);
+}
+
+JS_PUBLIC_API(bool)
+JS_AddWeakPointerCompartmentCallback(JSRuntime* rt, JSWeakPointerCompartmentCallback cb,
+                                     void* data)
+{
+    AssertHeapIsIdle(rt);
+    return rt->gc.addWeakPointerCompartmentCallback(cb, data);
+}
+
+JS_PUBLIC_API(void)
+JS_RemoveWeakPointerCompartmentCallback(JSRuntime* rt, JSWeakPointerCompartmentCallback cb)
+{
+    rt->gc.removeWeakPointerCompartmentCallback(cb);
+}
+
 
 JS_PUBLIC_API(void)
 JS_UpdateWeakPointerAfterGC(JS::Heap<JSObject*>* objp)
 {
     JS_UpdateWeakPointerAfterGCUnbarriered(objp->unsafeGet());
 }
 
 JS_PUBLIC_API(void)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -596,17 +596,20 @@ typedef enum JSFinalizeStatus {
      */
     JSFINALIZE_COLLECTION_END
 } JSFinalizeStatus;
 
 typedef void
 (* JSFinalizeCallback)(JSFreeOp* fop, JSFinalizeStatus status, bool isCompartment, void* data);
 
 typedef void
-(* JSWeakPointerCallback)(JSRuntime* rt, void* data);
+(* JSWeakPointerZoneGroupCallback)(JSRuntime* rt, void* data);
+
+typedef void
+(* JSWeakPointerCompartmentCallback)(JSRuntime* rt, JSCompartment* comp, void* data);
 
 typedef bool
 (* JSInterruptCallback)(JSContext* cx);
 
 typedef void
 (* JSErrorReporter)(JSContext* cx, const char* message, JSErrorReport* report);
 
 /**
@@ -1716,35 +1719,51 @@ JS_RemoveFinalizeCallback(JSRuntime* rt,
  * Weak pointers are by their nature not marked as part of garbage collection,
  * but they may need to be updated in two cases after a GC:
  *
  *  1) Their referent was found not to be live and is about to be finalized
  *  2) Their referent has been moved by a compacting GC
  *
  * To handle this, any part of the system that maintain weak pointers to
  * JavaScript GC things must register a callback with
- * JS_(Add,Remove)WeakPointerCallback().  This callback must then call
- * JS_UpdateWeakPointerAfterGC() on all weak pointers it knows about.
+ * JS_(Add,Remove)WeakPointer{ZoneGroup,Compartment}Callback(). This callback
+ * must then call JS_UpdateWeakPointerAfterGC() on all weak pointers it knows
+ * about.
  *
- * The argument to JS_UpdateWeakPointerAfterGC() is an in-out param.  If the
- * referent is about to be finalized the pointer will be set to null.  If the
+ * Since sweeping is incremental, we have several callbacks to avoid repeatedly
+ * having to visit all embedder structures. The WeakPointerZoneGroupCallback is
+ * called once for each strongly connected group of zones, whereas the
+ * WeakPointerCompartmentCallback is called once for each compartment that is
+ * visited while sweeping. Structures that cannot contain references in more
+ * than one compartment should sweep the relevant per-compartment structures
+ * using the latter callback to minimizer per-slice overhead.
+ *
+ * The argument to JS_UpdateWeakPointerAfterGC() is an in-out param. If the
+ * referent is about to be finalized the pointer will be set to null. If the
  * referent has been moved then the pointer will be updated to point to the new
  * location.
  *
  * Callers of this method are responsible for updating any state that is
  * dependent on the object's address. For example, if the object's address is
  * used as a key in a hashtable, then the object must be removed and
  * re-inserted with the correct hash.
  */
 
 extern JS_PUBLIC_API(bool)
-JS_AddWeakPointerCallback(JSRuntime* rt, JSWeakPointerCallback cb, void* data);
+JS_AddWeakPointerZoneGroupCallback(JSRuntime* rt, JSWeakPointerZoneGroupCallback cb, void* data);
 
 extern JS_PUBLIC_API(void)
-JS_RemoveWeakPointerCallback(JSRuntime* rt, JSWeakPointerCallback cb);
+JS_RemoveWeakPointerZoneGroupCallback(JSRuntime* rt, JSWeakPointerZoneGroupCallback cb);
+
+extern JS_PUBLIC_API(bool)
+JS_AddWeakPointerCompartmentCallback(JSRuntime* rt, JSWeakPointerCompartmentCallback cb,
+                                     void* data);
+
+extern JS_PUBLIC_API(void)
+JS_RemoveWeakPointerCompartmentCallback(JSRuntime* rt, JSWeakPointerCompartmentCallback cb);
 
 extern JS_PUBLIC_API(void)
 JS_UpdateWeakPointerAfterGC(JS::Heap<JSObject*>* objp);
 
 extern JS_PUBLIC_API(void)
 JS_UpdateWeakPointerAfterGCUnbarriered(JSObject** objp);
 
 typedef enum JSGCParamKey {
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1614,41 +1614,64 @@ GCRuntime::callFinalizeCallbacks(FreeOp*
     for (const Callback<JSFinalizeCallback>* p = finalizeCallbacks.begin();
          p < finalizeCallbacks.end(); p++)
     {
         p->op(fop, status, !isFull, p->data);
     }
 }
 
 bool
-GCRuntime::addWeakPointerCallback(JSWeakPointerCallback callback, void* data)
-{
-    return updateWeakPointerCallbacks.append(Callback<JSWeakPointerCallback>(callback, data));
-}
-
-void
-GCRuntime::removeWeakPointerCallback(JSWeakPointerCallback callback)
-{
-    for (Callback<JSWeakPointerCallback>* p = updateWeakPointerCallbacks.begin();
-         p < updateWeakPointerCallbacks.end(); p++)
-    {
-        if (p->op == callback) {
-            updateWeakPointerCallbacks.erase(p);
+GCRuntime::addWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback, void* data)
+{
+    return updateWeakPointerZoneGroupCallbacks.append(
+            Callback<JSWeakPointerZoneGroupCallback>(callback, data));
+}
+
+void
+GCRuntime::removeWeakPointerZoneGroupCallback(JSWeakPointerZoneGroupCallback callback)
+{
+    for (auto& p : updateWeakPointerZoneGroupCallbacks) {
+        if (p.op == callback) {
+            updateWeakPointerZoneGroupCallbacks.erase(&p);
             break;
         }
     }
 }
 
 void
-GCRuntime::callWeakPointerCallbacks() const
-{
-    for (const Callback<JSWeakPointerCallback>* p = updateWeakPointerCallbacks.begin();
-         p < updateWeakPointerCallbacks.end(); p++)
-    {
-        p->op(rt, p->data);
+GCRuntime::callWeakPointerZoneGroupCallbacks() const
+{
+    for (auto const& p : updateWeakPointerZoneGroupCallbacks) {
+        p.op(rt, p.data);
+    }
+}
+
+bool
+GCRuntime::addWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback, void* data)
+{
+    return updateWeakPointerCompartmentCallbacks.append(
+            Callback<JSWeakPointerCompartmentCallback>(callback, data));
+}
+
+void
+GCRuntime::removeWeakPointerCompartmentCallback(JSWeakPointerCompartmentCallback callback)
+{
+    for (auto& p : updateWeakPointerCompartmentCallbacks) {
+        if (p.op == callback) {
+            updateWeakPointerCompartmentCallbacks.erase(&p);
+            break;
+        }
+    }
+}
+
+void
+GCRuntime::callWeakPointerCompartmentCallbacks(JSCompartment* comp) const
+{
+    for (auto const& p : updateWeakPointerCompartmentCallbacks) {
+        p.op(rt, comp, p.data);
     }
 }
 
 JS::GCSliceCallback
 GCRuntime::setSliceCallback(JS::GCSliceCallback callback) {
     return stats.setSliceCallback(callback);
 }
 
@@ -2660,17 +2683,19 @@ GCRuntime::updatePointersToRelocatedCell
     freeLifoAlloc.freeAll();
 
     // Clear runtime caches that can contain cell pointers.
     // TODO: Should possibly just call purgeRuntime() here.
     rt->newObjectCache.purge();
     rt->nativeIterCache.purge();
 
     // Call callbacks to get the rest of the system to fixup other untraced pointers.
-    callWeakPointerCallbacks();
+    callWeakPointerZoneGroupCallbacks();
+    for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
+        callWeakPointerCompartmentCallbacks(comp);
 
     // Finally, iterate through all cells that can contain JSObject pointers to
     // update them. Since updating each cell is independent we try to
     // parallelize this as much as possible.
     if (CanUseExtraThreads())
         updateAllCellPointersParallel(&trc, zone);
     else
         updateAllCellPointersSerial(&trc, zone);
@@ -5015,17 +5040,27 @@ GCRuntime::beginSweepingZoneGroup()
     SweepInitialShapesTask sweepInitialShapesTask(rt);
     SweepObjectGroupsTask sweepObjectGroupsTask(rt);
     SweepRegExpsTask sweepRegExpsTask(rt);
     SweepMiscTask sweepMiscTask(rt);
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_FINALIZE_START);
         callFinalizeCallbacks(&fop, JSFINALIZE_GROUP_START);
-        callWeakPointerCallbacks();
+        {
+            gcstats::AutoPhase ap2(stats, gcstats::PHASE_WEAK_ZONEGROUP_CALLBACK);
+            callWeakPointerZoneGroupCallbacks();
+        }
+        {
+            gcstats::AutoPhase ap2(stats, gcstats::PHASE_WEAK_COMPARTMENT_CALLBACK);
+            for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
+                for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
+                    callWeakPointerCompartmentCallbacks(comp);
+            }
+        }
     }
 
     if (sweepingAtoms) {
         AutoLockHelperThreadState helperLock;
         startTask(sweepAtomsTask, gcstats::PHASE_SWEEP_ATOMS);
     }
 
     {
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -183,19 +183,37 @@ public:
 public:
   bool mContinuation;
   bool mActive;
   bool mPurge;
 };
 
 namespace xpc {
 
+CompartmentPrivate::CompartmentPrivate(JSCompartment* c)
+    : wantXrays(false)
+    , allowWaivers(true)
+    , writeToGlobalPrototype(false)
+    , skipWriteToGlobalPrototype(false)
+    , isWebExtensionContentScript(false)
+    , universalXPConnectEnabled(false)
+    , forcePermissiveCOWs(false)
+    , scriptability(c)
+    , scope(nullptr)
+    , mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH))
+{
+    MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
+    mozilla::PodArrayZero(wrapperDenialWarnings);
+}
+
 CompartmentPrivate::~CompartmentPrivate()
 {
     MOZ_COUNT_DTOR(xpc::CompartmentPrivate);
+    mWrappedJSMap->ShutdownMarker();
+    delete mWrappedJSMap;
 }
 
 static bool
 TryParseLocationURICandidate(const nsACString& uristr,
                              CompartmentPrivate::LocationHint aLocationHint,
                              nsIURI** aURI)
 {
     static NS_NAMED_LITERAL_CSTRING(kGRE, "resource://gre/");
@@ -914,28 +932,46 @@ XPCJSRuntime::FinalizeCallback(JSFreeOp*
             self->mGCIsRunning = false;
 
             break;
         }
     }
 }
 
 /* static */ void
-XPCJSRuntime::WeakPointerCallback(JSRuntime* rt, void* data)
+XPCJSRuntime::WeakPointerZoneGroupCallback(JSRuntime* rt, void* data)
 {
-    // Called to remove any weak pointers to GC things that are about to be
-    // finalized and fixup any pointers that may have been moved.
-
+    // 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);
 
+    MOZ_ASSERT(self->WrappedJSToReleaseArray().IsEmpty());
     self->mWrappedJSMap->UpdateWeakPointersAfterGC(self);
 
     XPCWrappedNativeScope::UpdateWeakPointersAfterGC(self);
 }
 
+/* static */ void
+XPCJSRuntime::WeakPointerCompartmentCallback(JSRuntime* rt, JSCompartment* comp, void* data)
+{
+    // Called immediately after the ZoneGroup weak pointer callback, but only
+    // once for each compartment that is being swept.
+    XPCJSRuntime* self = static_cast<XPCJSRuntime*>(data);
+    CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp);
+    if (xpcComp)
+        xpcComp->UpdateWeakPointersAfterGC(self);
+}
+
+void
+CompartmentPrivate::UpdateWeakPointersAfterGC(XPCJSRuntime* runtime)
+{
+    mWrappedJSMap->UpdateWeakPointersAfterGC(runtime);
+}
+
 static void WatchdogMain(void* arg);
 class Watchdog;
 class WatchdogManager;
 class AutoLockWatchdog {
     Watchdog* const mWatchdog;
   public:
     explicit AutoLockWatchdog(Watchdog* aWatchdog);
     ~AutoLockWatchdog();
@@ -1474,16 +1510,22 @@ XPCJSRuntime::SizeOfIncludingThis(Malloc
 
     // There are other XPCJSRuntime members that could be measured; the above
     // ones have been seen by DMD to be worth measuring.  More stuff may be
     // added later.
 
     return n;
 }
 
+size_t
+CompartmentPrivate::SizeOfIncludingThis(MallocSizeOf mallocSizeOf)
+{
+    return mallocSizeOf(this) + mWrappedJSMap->SizeOfWrappedJS(mallocSizeOf);
+}
+
 /***************************************************************************/
 
 void XPCJSRuntime::DestroyJSContextStack()
 {
     delete mJSContextStack;
     mJSContextStack = nullptr;
 }
 
@@ -1555,17 +1597,18 @@ ReloadPrefsCallback(const char* pref, vo
 XPCJSRuntime::~XPCJSRuntime()
 {
     // This destructor runs before ~CycleCollectedJSRuntime, which does the
     // actual JS_DestroyRuntime() call. But destroying the runtime triggers
     // one final GC, which can call back into the runtime with various
     // callback if we aren't careful. Null out the relevant callbacks.
     js::SetActivityCallback(Runtime(), nullptr, nullptr);
     JS_RemoveFinalizeCallback(Runtime(), FinalizeCallback);
-    JS_RemoveWeakPointerCallback(Runtime(), WeakPointerCallback);
+    JS_RemoveWeakPointerZoneGroupCallback(Runtime(), WeakPointerZoneGroupCallback);
+    JS_RemoveWeakPointerCompartmentCallback(Runtime(), WeakPointerCompartmentCallback);
 
     // Clear any pending exception.  It might be an XPCWrappedJS, and if we try
     // to destroy it later we will crash.
     SetPendingException(nullptr);
 
     JS::SetGCSliceCallback(Runtime(), mPrevGCSliceCallback);
 
     xpc_DelocalizeRuntime(Runtime());
@@ -2247,16 +2290,21 @@ ReportCompartmentStats(const JS::Compart
     // Note that we use cDOMPathPrefix here.  This is because we measure orphan
     // DOM nodes in the JS reporter, but we want to report them in a "dom"
     // sub-tree rather than a "js" sub-tree.
     ZCREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("orphan-nodes"),
         cStats.objectsPrivate,
         "Orphan DOM nodes, i.e. those that are only reachable from JavaScript "
         "objects.");
 
+    ZCREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("private-data"),
+        extras.sizeOfXPCPrivate,
+        "Extra data attached to the compartment by XPConnect, including "\
+        "wrapped-js that is local to a single compartment.");
+
     ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/gc-heap"),
         cStats.scriptsGCHeap,
         "JSScript instances. There is one per user-defined function in a "
         "script, and one for the top-level code in a script.");
 
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/malloc-heap/data"),
         cStats.scriptsMallocHeapData,
         "Various variable-length tables in JSScripts.");
@@ -2753,24 +2801,26 @@ class XPCJSRuntimeStats : public JS::Run
     }
 
     virtual void initExtraCompartmentStats(JSCompartment* c,
                                            JS::CompartmentStats* cstats) override
     {
         xpc::CompartmentStatsExtras* extras = new xpc::CompartmentStatsExtras;
         nsCString cName;
         GetCompartmentName(c, cName, &mAnonymizeID, /* replaceSlashes = */ true);
-        if (mGetLocations) {
-            CompartmentPrivate* cp = CompartmentPrivate::Get(c);
-            if (cp)
-              cp->GetLocationURI(CompartmentPrivate::LocationHintAddon,
-                                 getter_AddRefs(extras->location));
+        CompartmentPrivate* cp = CompartmentPrivate::Get(c);
+        if (cp) {
+            if (mGetLocations) {
+                cp->GetLocationURI(CompartmentPrivate::LocationHintAddon,
+                                   getter_AddRefs(extras->location));
+            }
             // Note: cannot use amIAddonManager implementation at this point,
             // as it is a JS service and the JS heap is currently not idle.
             // Otherwise, we could have computed the add-on id at this point.
+            extras->sizeOfXPCPrivate = cp->SizeOfIncludingThis(mallocSizeOf_);
         }
 
         // Get the compartment's global.
         nsXPConnect* xpc = nsXPConnect::XPConnect();
         AutoSafeJSContext cx;
         bool needZone = true;
         RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, c));
         if (global) {
@@ -2851,17 +2901,17 @@ JSReporter::CollectReports(WindowPaths* 
     if (!JS::CollectRuntimeStats(xpcrt->Runtime(), &rtStats, &orphanReporter,
                                  anonymize))
     {
         return NS_ERROR_FAILURE;
     }
 
     size_t xpcJSRuntimeSize = xpcrt->SizeOfIncludingThis(JSMallocSizeOf);
 
-    size_t wrappedJSSize = xpcrt->GetWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf);
+    size_t wrappedJSSize = xpcrt->GetMultiCompartmentWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf);
 
     XPCWrappedNativeScope::ScopeSizeInfo sizeInfo(JSMallocSizeOf);
     XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(&sizeInfo);
 
     mozJSComponentLoader* loader = mozJSComponentLoader::Get();
     size_t jsComponentLoaderSize = loader ? loader->SizeOfIncludingThis(JSMallocSizeOf) : 0;
 
     // This is the second step (see above).  First we report stuff in the
@@ -3393,17 +3443,18 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
                            kStackQuota - kSystemCodeBuffer,
                            kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer);
 
     JS_SetErrorReporter(runtime, xpc::SystemErrorReporter);
     JS_SetDestroyCompartmentCallback(runtime, CompartmentDestroyedCallback);
     JS_SetCompartmentNameCallback(runtime, CompartmentNameCallback);
     mPrevGCSliceCallback = JS::SetGCSliceCallback(runtime, GCSliceCallback);
     JS_AddFinalizeCallback(runtime, FinalizeCallback, nullptr);
-    JS_AddWeakPointerCallback(runtime, WeakPointerCallback, this);
+    JS_AddWeakPointerZoneGroupCallback(runtime, WeakPointerZoneGroupCallback, this);
+    JS_AddWeakPointerCompartmentCallback(runtime, WeakPointerCompartmentCallback, this);
     JS_SetWrapObjectCallbacks(runtime, &WrapObjectCallbacks);
     js::SetPreserveWrapperCallback(runtime, PreserveWrapper);
 #ifdef MOZ_ENABLE_PROFILER_SPS
     if (PseudoStack* stack = mozilla_get_pseudo_stack())
         stack->sampleRuntime(runtime);
 #endif
     JS_SetAccumulateTelemetryCallback(runtime, AccumulateTelemetryCallback);
     js::SetScriptEnvironmentPreparer(runtime, &mEnvironmentPreparer);
@@ -3454,17 +3505,17 @@ XPCJSRuntime*
 XPCJSRuntime::newXPCJSRuntime(nsXPConnect* aXPConnect)
 {
     NS_PRECONDITION(aXPConnect,"bad param");
 
     XPCJSRuntime* self = new XPCJSRuntime(aXPConnect);
 
     if (self                                    &&
         self->Runtime()                         &&
-        self->GetWrappedJSMap()                 &&
+        self->GetMultiCompartmentWrappedJSMap() &&
         self->GetWrappedJSClassMap()            &&
         self->GetIID2NativeInterfaceMap()       &&
         self->GetClassInfo2NativeSetMap()       &&
         self->GetNativeSetMap()                 &&
         self->GetThisTranslatorMap()            &&
         self->GetNativeScriptableSharedMap()    &&
         self->GetDyingWrappedNativeProtoMap()   &&
         self->mWatchdogManager) {
@@ -3646,19 +3697,20 @@ XPCJSRuntime::DebugDump(int16_t depth)
         if (depth && mWrappedJSClassMap->Count()) {
             XPC_LOG_INDENT();
             for (auto i = mWrappedJSClassMap->Iter(); !i.Done(); i.Next()) {
                 auto entry = static_cast<IID2WrappedJSClassMap::Entry*>(i.Get());
                 entry->value->DebugDump(depth);
             }
             XPC_LOG_OUTDENT();
         }
+
+        // iterate wrappers...
         XPC_LOG_ALWAYS(("mWrappedJSMap @ %x with %d wrappers(s)",
                         mWrappedJSMap, mWrappedJSMap->Count()));
-        // iterate wrappers...
         if (depth && mWrappedJSMap->Count()) {
             XPC_LOG_INDENT();
             mWrappedJSMap->Dump(depth);
             XPC_LOG_OUTDENT();
         }
 
         XPC_LOG_ALWAYS(("mIID2NativeInterfaceMap @ %x with %d interface(s)",
                         mIID2NativeInterfaceMap,
--- a/js/xpconnect/src/XPCMaps.cpp
+++ b/js/xpconnect/src/XPCMaps.cpp
@@ -90,17 +90,16 @@ JSObject2WrappedJSMap::UpdateWeakPointer
     // Check all wrappers and update their JSObject pointer if it has been
     // moved, or if it is about to be finalized queue the wrapper for
     // destruction by adding it to an array held by the runtime.
     // Note that we do not want to be changing the refcount of these wrappers.
     // We add them to the array now and Release the array members later to avoid
     // the posibility of doing any JS GCThing allocations during the gc cycle.
 
     nsTArray<nsXPCWrappedJS*>& dying = runtime->WrappedJSToReleaseArray();
-    MOZ_ASSERT(dying.IsEmpty());
 
     for (Map::Enum e(mTable); !e.empty(); e.popFront()) {
         nsXPCWrappedJS* wrapper = e.front().value();
         MOZ_ASSERT(wrapper, "found a null JS wrapper!");
 
         // Walk the wrapper chain and update all JSObjects.
         while (wrapper) {
 #ifdef DEBUG
--- a/js/xpconnect/src/XPCMaps.h
+++ b/js/xpconnect/src/XPCMaps.h
@@ -42,16 +42,26 @@ public:
     }
 
     inline nsXPCWrappedJS* Find(JSObject* Obj) {
         NS_PRECONDITION(Obj,"bad param");
         Map::Ptr p = mTable.lookup(Obj);
         return p ? p->value() : nullptr;
     }
 
+#ifdef DEBUG
+    inline bool HasWrapper(nsXPCWrappedJS* wrapper) {
+        for (auto r = mTable.all(); !r.empty(); r.popFront()) {
+            if (r.front().value() == wrapper)
+                return true;
+        }
+        return false;
+    }
+#endif
+
     inline nsXPCWrappedJS* Add(JSContext* cx, nsXPCWrappedJS* wrapper) {
         NS_PRECONDITION(wrapper,"bad param");
         JSObject* obj = wrapper->GetJSObjectPreserveColor();
         Map::AddPtr p = mTable.lookupForAdd(obj);
         if (p)
             return p->value();
         if (!mTable.add(p, obj, wrapper))
             return nullptr;
--- a/js/xpconnect/src/XPCWrappedJS.cpp
+++ b/js/xpconnect/src/XPCWrappedJS.cpp
@@ -318,47 +318,53 @@ nsXPCWrappedJS::GetNewOrUsed(JS::HandleO
                              REFNSIID aIID,
                              nsXPCWrappedJS** wrapperResult)
 {
     // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread.
     if (!MOZ_LIKELY(NS_IsMainThread()))
         MOZ_CRASH();
 
     AutoJSContext cx;
-    XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
-    JSObject2WrappedJSMap* map = rt->GetWrappedJSMap();
-    if (!map) {
-        MOZ_ASSERT(map,"bad map");
-        return NS_ERROR_FAILURE;
-    }
 
     bool allowNonScriptable = mozilla::jsipc::IsWrappedCPOW(jsObj);
     RefPtr<nsXPCWrappedJSClass> clasp = nsXPCWrappedJSClass::GetNewOrUsed(cx, aIID,
-                                                                            allowNonScriptable);
+                                                                          allowNonScriptable);
     if (!clasp)
         return NS_ERROR_FAILURE;
 
     JS::RootedObject rootJSObj(cx, clasp->GetRootJSObject(cx, jsObj));
     if (!rootJSObj)
         return NS_ERROR_FAILURE;
 
+    xpc::CompartmentPrivate* rootComp = xpc::CompartmentPrivate::Get(rootJSObj);
+    MOZ_ASSERT(rootComp);
+
+    // Find any existing wrapper.
+    RefPtr<nsXPCWrappedJS> root = rootComp->GetWrappedJSMap()->Find(rootJSObj);
+    MOZ_ASSERT_IF(root, !nsXPConnect::GetRuntimeInstance()->GetMultiCompartmentWrappedJSMap()->
+                        Find(rootJSObj));
+    if (!root) {
+        root = nsXPConnect::GetRuntimeInstance()->GetMultiCompartmentWrappedJSMap()->
+            Find(rootJSObj);
+    }
+
     nsresult rv = NS_ERROR_FAILURE;
-    RefPtr<nsXPCWrappedJS> root = map->Find(rootJSObj);
     if (root) {
         RefPtr<nsXPCWrappedJS> wrapper = root->FindOrFindInherited(aIID);
         if (wrapper) {
             wrapper.forget(wrapperResult);
             return NS_OK;
         }
     } else if (rootJSObj != jsObj) {
 
         // Make a new root wrapper, because there is no existing
         // root wrapper, and the wrapper we are trying to make isn't
         // a root.
-        RefPtr<nsXPCWrappedJSClass> rootClasp = nsXPCWrappedJSClass::GetNewOrUsed(cx, NS_GET_IID(nsISupports));
+        RefPtr<nsXPCWrappedJSClass> rootClasp =
+            nsXPCWrappedJSClass::GetNewOrUsed(cx, NS_GET_IID(nsISupports));
         if (!rootClasp)
             return NS_ERROR_FAILURE;
 
         root = new nsXPCWrappedJS(cx, rootJSObj, rootClasp, nullptr, &rv);
         if (NS_FAILED(rv)) {
             return rv;
         }
     }
@@ -386,54 +392,104 @@ nsXPCWrappedJS::nsXPCWrappedJS(JSContext
     // behavior works correctly.
 
     // There is an extra AddRef to support weak references to wrappers
     // that are subject to finalization. See the top of the file for more
     // details.
     NS_ADDREF_THIS();
 
     if (IsRootWrapper()) {
-        nsXPConnect::GetRuntimeInstance()->GetWrappedJSMap()->Add(cx, this);
+        MOZ_ASSERT(!IsMultiCompartment(), "mNext is always nullptr here");
+        xpc::CompartmentPrivate::Get(mJSObj)->GetWrappedJSMap()->Add(cx, this);
     } else {
         NS_ADDREF(mRoot);
         mNext = mRoot->mNext;
         mRoot->mNext = this;
+
+        // We always start wrappers in the per-compartment table. If adding
+        // this wrapper to the chain causes it to cross compartments, we need
+        // to migrate the chain to the global table on the XPCJSRuntime.
+        if (mRoot->IsMultiCompartment()) {
+            xpc::CompartmentPrivate::Get(mRoot->mJSObj)->GetWrappedJSMap()->Remove(mRoot);
+            MOZ_RELEASE_ASSERT(nsXPConnect::GetRuntimeInstance()->
+                    GetMultiCompartmentWrappedJSMap()->Add(cx, mRoot));
+        }
     }
 }
 
 nsXPCWrappedJS::~nsXPCWrappedJS()
 {
     Destroy();
 }
 
 void
+XPCJSRuntime::RemoveWrappedJS(nsXPCWrappedJS* wrapper)
+{
+    AssertInvalidWrappedJSNotInTable(wrapper);
+    if (!wrapper->IsValid())
+        return;
+
+    // It is possible for the same JS XPCOM implementation object to be wrapped
+    // with a different interface in multiple JSCompartments. In this case, the
+    // wrapper chain will contain references to multiple compartments. While we
+    // always store single-compartment chains in the per-compartment wrapped-js
+    // table, chains in the multi-compartment wrapped-js table may contain
+    // single-compartment chains, if they have ever contained a wrapper in a
+    // different compartment. Since removal requires a lookup anyway, we just do
+    // the remove on both tables unconditionally.
+    MOZ_ASSERT_IF(wrapper->IsMultiCompartment(),
+                  !xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor())->
+                      GetWrappedJSMap()->HasWrapper(wrapper));
+    GetMultiCompartmentWrappedJSMap()->Remove(wrapper);
+    xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor())->GetWrappedJSMap()->
+        Remove(wrapper);
+}
+
+#ifdef DEBUG
+static void
+NotHasWrapperAssertionCallback(JSRuntime* rt, void* data, JSCompartment* comp)
+{
+    auto wrapper = static_cast<nsXPCWrappedJS*>(data);
+    auto xpcComp = xpc::CompartmentPrivate::Get(comp);
+    MOZ_ASSERT_IF(xpcComp, !xpcComp->GetWrappedJSMap()->HasWrapper(wrapper));
+}
+#endif
+
+void
+XPCJSRuntime::AssertInvalidWrappedJSNotInTable(nsXPCWrappedJS* wrapper) const
+{
+#ifdef DEBUG
+    if (!wrapper->IsValid()) {
+        MOZ_ASSERT(!GetMultiCompartmentWrappedJSMap()->HasWrapper(wrapper));
+        if (!mGCIsRunning)
+            JS_IterateCompartments(Runtime(), wrapper, NotHasWrapperAssertionCallback);
+    }
+#endif
+}
+
+void
 nsXPCWrappedJS::Destroy()
 {
     MOZ_ASSERT(1 == int32_t(mRefCnt), "should be stabilized for deletion");
 
-    if (IsRootWrapper()) {
-        XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
-        JSObject2WrappedJSMap* map = rt->GetWrappedJSMap();
-        if (map)
-            map->Remove(this);
-    }
+    if (IsRootWrapper())
+        nsXPConnect::GetRuntimeInstance()->RemoveWrappedJS(this);
     Unlink();
 }
 
 void
 nsXPCWrappedJS::Unlink()
 {
+    nsXPConnect::GetRuntimeInstance()->AssertInvalidWrappedJSNotInTable(this);
+
     if (IsValid()) {
         XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
         if (rt) {
-            if (IsRootWrapper()) {
-                JSObject2WrappedJSMap* map = rt->GetWrappedJSMap();
-                if (map)
-                    map->Remove(this);
-            }
+            if (IsRootWrapper())
+                rt->RemoveWrappedJS(this);
 
             if (mRefCnt > 1)
                 RemoveFromRootSet();
         }
 
         mJSObj = nullptr;
     }
 
@@ -445,31 +501,52 @@ nsXPCWrappedJS::Unlink()
         while (1) {
             if (cur->mNext == this) {
                 cur->mNext = mNext;
                 break;
             }
             cur = cur->mNext;
             MOZ_ASSERT(cur, "failed to find wrapper in its own chain");
         }
+
+        // Note: unlinking this wrapper may have changed us from a multi-
+        // compartment wrapper chain to a single-compartment wrapper chain. We
+        // leave the wrapper in the multi-compartment table as it is likely to
+        // need to be multi-compartment again in the future and, moreover, we
+        // cannot get a JSContext here.
+
         // let the root go
         NS_RELEASE(mRoot);
     }
 
     mClass = nullptr;
     if (mOuter) {
         XPCJSRuntime* rt = nsXPConnect::GetRuntimeInstance();
         if (rt->GCIsRunning()) {
             DeferredFinalize(mOuter.forget().take());
         } else {
             mOuter = nullptr;
         }
     }
 }
 
+bool
+nsXPCWrappedJS::IsMultiCompartment() const
+{
+    MOZ_ASSERT(IsRootWrapper());
+    JSCompartment* compartment = js::GetObjectCompartment(mJSObj);
+    nsXPCWrappedJS* next = mNext;
+    while (next) {
+        if (js::GetObjectCompartment(next->mJSObj) != compartment)
+            return true;
+        next = next->mNext;
+    }
+    return false;
+}
+
 nsXPCWrappedJS*
 nsXPCWrappedJS::Find(REFNSIID aIID)
 {
     if (aIID.Equals(NS_GET_IID(nsISupports)))
         return mRoot;
 
     for (nsXPCWrappedJS* cur = mRoot; cur; cur = cur->mNext) {
         if (aIID.Equals(cur->GetIID()))
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -425,33 +425,36 @@ class XPCJSRuntime : public mozilla::Cyc
 {
 public:
     static XPCJSRuntime* newXPCJSRuntime(nsXPConnect* aXPConnect);
     static XPCJSRuntime* Get() { return nsXPConnect::XPConnect()->GetRuntime(); }
 
     XPCJSContextStack* GetJSContextStack() {return mJSContextStack;}
     void DestroyJSContextStack();
 
+    void RemoveWrappedJS(nsXPCWrappedJS* wrapper);
+    void AssertInvalidWrappedJSNotInTable(nsXPCWrappedJS* wrapper) const;
+
     XPCCallContext*  GetCallContext() const {return mCallContext;}
     XPCCallContext*  SetCallContext(XPCCallContext* ccx)
         {XPCCallContext* old = mCallContext; mCallContext = ccx; return old;}
 
     jsid GetResolveName() const {return mResolveName;}
     jsid SetResolveName(jsid name)
         {jsid old = mResolveName; mResolveName = name; return old;}
 
     XPCWrappedNative* GetResolvingWrapper() const {return mResolvingWrapper;}
     XPCWrappedNative* SetResolvingWrapper(XPCWrappedNative* w)
         {XPCWrappedNative* old = mResolvingWrapper;
          mResolvingWrapper = w; return old;}
 
-    JSObject2WrappedJSMap*     GetWrappedJSMap()        const
+    JSObject2WrappedJSMap* GetMultiCompartmentWrappedJSMap() const
         {return mWrappedJSMap;}
 
-    IID2WrappedJSClassMap*     GetWrappedJSClassMap()   const
+    IID2WrappedJSClassMap* GetWrappedJSClassMap() const
         {return mWrappedJSClassMap;}
 
     IID2NativeInterfaceMap* GetIID2NativeInterfaceMap() const
         {return mIID2NativeInterfaceMap;}
 
     ClassInfo2NativeSetMap* GetClassInfo2NativeSetMap() const
         {return mClassInfo2NativeSetMap;}
 
@@ -562,17 +565,18 @@ public:
     bool CustomContextCallback(JSContext* cx, unsigned operation) override;
     static void GCSliceCallback(JSRuntime* rt,
                                 JS::GCProgress progress,
                                 const JS::GCDescription& desc);
     static void FinalizeCallback(JSFreeOp* fop,
                                  JSFinalizeStatus status,
                                  bool isCompartmentGC,
                                  void* data);
-    static void WeakPointerCallback(JSRuntime* rt, void* data);
+    static void WeakPointerZoneGroupCallback(JSRuntime* rt, void* data);
+    static void WeakPointerCompartmentCallback(JSRuntime* rt, JSCompartment* comp, void* data);
 
     inline void AddVariantRoot(XPCTraceableVariant* variant);
     inline void AddWrappedJSRoot(nsXPCWrappedJS* wrappedJS);
     inline void AddObjectHolderRoot(XPCJSObjectHolder* holder);
 
     void DebugDump(int16_t depth);
 
     void SystemIsBeingShutDown();
@@ -606,17 +610,17 @@ public:
     void InitSingletonScopes();
     void DeleteSingletonScopes();
 
     PRTime GetWatchdogTimestamp(WatchdogTimestampCategory aCategory);
 
     nsTArray<nsXPCWrappedJS*>& WrappedJSToReleaseArray() { return mWrappedJSToReleaseArray; }
 
 private:
-    XPCJSRuntime(); // no implementation
+    XPCJSRuntime() = delete;
     explicit XPCJSRuntime(nsXPConnect* aXPConnect);
 
     void ReleaseIncrementally(nsTArray<nsISupports*>& array);
 
     static const char* const mStrings[IDX_TOTAL_COUNT];
     jsid mStrIDs[IDX_TOTAL_COUNT];
     JS::Value mStrJSVals[IDX_TOTAL_COUNT];
 
@@ -2444,16 +2448,24 @@ public:
      * object returned is not guaranteed to be kept alive past the next CC.
      *
      * This should only be called if you are certain that the return value won't
      * be passed into a JS API function and that it won't be stored without
      * being rooted (or otherwise signaling the stored value to the CC).
      */
     JSObject* GetJSObjectPreserveColor() const {return mJSObj;}
 
+    // Returns true if the wrapper chain contains references to multiple
+    // compartments. If the wrapper chain contains references to multiple
+    // compartments, then it must be registered on the XPCJSRuntime. Otherwise,
+    // it should be registered in the CompartmentPrivate for the compartment of
+    // the root's JS object. This will only return correct results when called
+    // on the root wrapper and will assert if not called on a root wrapper.
+    bool IsMultiCompartment() const;
+
     nsXPCWrappedJSClass*  GetClass() const {return mClass;}
     REFNSIID GetIID() const {return GetClass()->GetIID();}
     nsXPCWrappedJS* GetRootWrapper() const {return mRoot;}
     nsXPCWrappedJS* GetNextWrapper() const {return mNext;}
 
     nsXPCWrappedJS* Find(REFNSIID aIID);
     nsXPCWrappedJS* FindInherited(REFNSIID aIID);
     nsXPCWrappedJS* FindOrFindInherited(REFNSIID aIID) {
@@ -2486,17 +2498,17 @@ public:
     }
 
     void TraceJS(JSTracer* trc);
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
     virtual ~nsXPCWrappedJS();
 protected:
-    nsXPCWrappedJS();   // not implemented
+    nsXPCWrappedJS() = delete;
     nsXPCWrappedJS(JSContext* cx,
                    JSObject* aJSObj,
                    nsXPCWrappedJSClass* aClass,
                    nsXPCWrappedJS* root,
                    nsresult* rv);
 
     bool CanSkip();
     void Destroy();
@@ -3672,30 +3684,17 @@ bool ReportWrapperDenial(JSContext* cx, 
 class CompartmentPrivate
 {
 public:
     enum LocationHint {
         LocationHintRegular,
         LocationHintAddon
     };
 
-    explicit CompartmentPrivate(JSCompartment* c)
-        : wantXrays(false)
-        , allowWaivers(true)
-        , writeToGlobalPrototype(false)
-        , skipWriteToGlobalPrototype(false)
-        , isWebExtensionContentScript(false)
-        , universalXPConnectEnabled(false)
-        , forcePermissiveCOWs(false)
-        , scriptability(c)
-        , scope(nullptr)
-    {
-        MOZ_COUNT_CTOR(xpc::CompartmentPrivate);
-        mozilla::PodArrayZero(wrapperDenialWarnings);
-    }
+    explicit CompartmentPrivate(JSCompartment* c);
 
     ~CompartmentPrivate();
 
     static CompartmentPrivate* Get(JSCompartment* compartment)
     {
         MOZ_ASSERT(compartment);
         void* priv = JS_GetCompartmentPrivate(compartment);
         return static_cast<CompartmentPrivate*>(priv);
@@ -3797,19 +3796,25 @@ public:
     void SetLocationURI(nsIURI* aLocationURI) {
         if (!aLocationURI)
             return;
         if (locationURI)
             return;
         locationURI = aLocationURI;
     }
 
+    JSObject2WrappedJSMap* GetWrappedJSMap() const { return mWrappedJSMap; }
+    void UpdateWeakPointersAfterGC(XPCJSRuntime* runtime);
+
+    size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
 private:
     nsCString location;
     nsCOMPtr<nsIURI> locationURI;
+    JSObject2WrappedJSMap* mWrappedJSMap;
 
     bool TryParseLocationURI(LocationHint aType, nsIURI** aURI);
 };
 
 bool IsUniversalXPConnectEnabled(JSCompartment* compartment);
 bool IsUniversalXPConnectEnabled(JSContext* cx);
 bool EnableUniversalXPConnect(JSContext* cx);
 
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -378,21 +378,23 @@ private:
     ZoneStatsExtras& operator=(const ZoneStatsExtras& other) = delete;
 };
 
 // ReportJSRuntimeExplicitTreeStats will expect this in the |extra| member
 // of JS::CompartmentStats.
 class CompartmentStatsExtras {
 public:
     CompartmentStatsExtras()
+      : sizeOfXPCPrivate(0)
     {}
 
     nsAutoCString jsPathPrefix;
     nsAutoCString domPathPrefix;
     nsCOMPtr<nsIURI> location;
+    size_t sizeOfXPCPrivate;
 
 private:
     CompartmentStatsExtras(const CompartmentStatsExtras& other) = delete;
     CompartmentStatsExtras& operator=(const CompartmentStatsExtras& other) = delete;
 };
 
 // This reports all the stats in |rtStats| that belong in the "explicit" tree,
 // (which isn't all of them).