Bug 1254092 - TraceIncomingCCWs should work at the JSCompartment level of granularity. r=jimb
authorNick Fitzgerald <fitzgen@gmail.com>
Mon, 14 Mar 2016 16:11:00 +0100
changeset 288746 c275dd17ab5b2f58a365770fd05177eea3ef6d23
parent 288745 75422250c55f8eca6977cb69e4069081ccbba40b
child 288747 043d7553482fc3027092be751498ef0e9a2b463b
push id30089
push userkwierso@gmail.com
push dateWed, 16 Mar 2016 00:26:08 +0000
treeherdermozilla-central@7773387a9a2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjimb
bugs1254092
milestone48.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 1254092 - TraceIncomingCCWs should work at the JSCompartment level of granularity. r=jimb There can be multiple compartments within the same zone, only one of which is a debuggee. In this scenario, CCWs from other compartments into the debuggee compartment should be traced and treated as roots. Therefore, dealing with CCWs at the JS::Zone level is incorrect, and this patch changes the granularity level to JSCompartments. If you look at the callers and uses of the function, it makes much more sense now. Additionally, it renames `JS_TraceIncomingCCWs` to `JS::TraceIncomingCCWs`.
devtools/shared/heapsnapshot/HeapSnapshot.cpp
devtools/shared/heapsnapshot/HeapSnapshot.h
devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
devtools/shared/heapsnapshot/tests/gtest/DoesCrossZoneBoundaries.cpp
devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
devtools/shared/heapsnapshot/tests/gtest/DoesntCrossZoneBoundaries.cpp
devtools/shared/heapsnapshot/tests/gtest/moz.build
js/public/TracingAPI.h
js/public/UbiNode.h
js/src/gc/Tracer.cpp
js/src/jsapi-tests/testGCMarking.cpp
js/src/vm/UbiNode.cpp
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -26,16 +26,17 @@
 #include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
 #include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/HeapSnapshotBinding.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/Telemetry.h"
 
 #include "jsapi.h"
+#include "jsfriendapi.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCRTGlue.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsIOutputStream.h"
 #include "nsISupportsImpl.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
@@ -717,27 +718,27 @@ HeapSnapshot::ComputeShortestPaths(JSCon
   }
 
   results.set(resultsMap);
 }
 
 /*** Saving Heap Snapshots ************************************************************************/
 
 // If we are only taking a snapshot of the heap affected by the given set of
-// globals, find the set of zones the globals are allocated within. Returns
-// false on OOM failure.
+// globals, find the set of compartments the globals are allocated
+// within. Returns false on OOM failure.
 static bool
-PopulateZonesWithGlobals(ZoneSet& zones, AutoObjectVector& globals)
+PopulateCompartmentsWithGlobals(CompartmentSet& compartments, AutoObjectVector& globals)
 {
-  if (!zones.init())
+  if (!compartments.init())
     return false;
 
   unsigned length = globals.length();
   for (unsigned i = 0; i < length; i++) {
-    if (!zones.put(GetObjectZone(globals[i])))
+    if (!compartments.put(GetObjectCompartment(globals[i])))
       return false;
   }
 
   return true;
 }
 
 // Add the given set of globals as explicit roots in the given roots
 // list. Returns false on OOM failure.
@@ -752,32 +753,33 @@ AddGlobalsAsRoots(AutoObjectVector& glob
       return false;
     }
   }
   return true;
 }
 
 // Choose roots and limits for a traversal, given `boundaries`. Set `roots` to
 // the set of nodes within the boundaries that are referred to by nodes
-// outside. If `boundaries` does not include all JS zones, initialize `zones` to
-// the set of included zones; otherwise, leave `zones` uninitialized. (You can
-// use zones.initialized() to check.)
+// outside. If `boundaries` does not include all JS compartments, initialize
+// `compartments` to the set of included compartments; otherwise, leave
+// `compartments` uninitialized. (You can use compartments.initialized() to
+// check.)
 //
 // If `boundaries` is incoherent, or we encounter an error while trying to
 // handle it, or we run out of memory, set `rv` appropriately and return
 // `false`.
 static bool
 EstablishBoundaries(JSContext* cx,
                     ErrorResult& rv,
                     const HeapSnapshotBoundaries& boundaries,
                     ubi::RootList& roots,
-                    ZoneSet& zones)
+                    CompartmentSet& compartments)
 {
   MOZ_ASSERT(!roots.initialized());
-  MOZ_ASSERT(!zones.initialized());
+  MOZ_ASSERT(!compartments.initialized());
 
   bool foundBoundaryProperty = false;
 
   if (boundaries.mRuntime.WasPassed()) {
     foundBoundaryProperty = true;
 
     if (!boundaries.mRuntime.Value()) {
       rv.Throw(NS_ERROR_INVALID_ARG);
@@ -800,18 +802,18 @@ EstablishBoundaries(JSContext* cx,
     JSObject* dbgObj = boundaries.mDebugger.Value();
     if (!dbgObj || !dbg::IsDebugger(*dbgObj)) {
       rv.Throw(NS_ERROR_INVALID_ARG);
       return false;
     }
 
     AutoObjectVector globals(cx);
     if (!dbg::GetDebuggeeGlobals(cx, *dbgObj, globals) ||
-        !PopulateZonesWithGlobals(zones, globals) ||
-        !roots.init(zones) ||
+        !PopulateCompartmentsWithGlobals(compartments, globals) ||
+        !roots.init(compartments) ||
         !AddGlobalsAsRoots(globals, roots))
     {
       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return false;
     }
   }
 
   if (boundaries.mGlobals.WasPassed()) {
@@ -835,33 +837,33 @@ EstablishBoundaries(JSContext* cx,
         return false;
       }
       if (!globals.append(global)) {
         rv.Throw(NS_ERROR_OUT_OF_MEMORY);
         return false;
       }
     }
 
-    if (!PopulateZonesWithGlobals(zones, globals) ||
-        !roots.init(zones) ||
+    if (!PopulateCompartmentsWithGlobals(compartments, globals) ||
+        !roots.init(compartments) ||
         !AddGlobalsAsRoots(globals, roots))
     {
       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return false;
     }
   }
 
   if (!foundBoundaryProperty) {
     rv.Throw(NS_ERROR_INVALID_ARG);
     return false;
   }
 
   MOZ_ASSERT(roots.initialized());
-  MOZ_ASSERT_IF(boundaries.mDebugger.WasPassed(), zones.initialized());
-  MOZ_ASSERT_IF(boundaries.mGlobals.WasPassed(), zones.initialized());
+  MOZ_ASSERT_IF(boundaries.mDebugger.WasPassed(), compartments.initialized());
+  MOZ_ASSERT_IF(boundaries.mGlobals.WasPassed(), compartments.initialized());
   return true;
 }
 
 
 // A variant covering all the various two-byte strings that we can get from the
 // ubi::Node API.
 class TwoByteString : public Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName>
 {
@@ -1316,28 +1318,28 @@ public:
     return writeMessage(protobufNode);
   }
 };
 
 // A JS::ubi::BreadthFirst handler that serializes a snapshot of the heap into a
 // core dump.
 class MOZ_STACK_CLASS HeapSnapshotHandler
 {
-  CoreDumpWriter& writer;
-  JS::ZoneSet*    zones;
+  CoreDumpWriter&     writer;
+  JS::CompartmentSet* compartments;
 
 public:
   // For telemetry.
   uint32_t nodeCount;
   uint32_t edgeCount;
 
   HeapSnapshotHandler(CoreDumpWriter& writer,
-                      JS::ZoneSet* zones)
+                      JS::CompartmentSet* compartments)
     : writer(writer),
-      zones(zones)
+      compartments(compartments)
   { }
 
   // JS::ubi::BreadthFirst handler interface.
 
   class NodeData { };
   typedef JS::ubi::BreadthFirst<HeapSnapshotHandler> Traversal;
   bool operator() (Traversal& traversal,
                    JS::ubi::Node origin,
@@ -1355,59 +1357,59 @@ public:
     // visited and serialized the origin node and its edges.
     if (!first)
       return true;
 
     nodeCount++;
 
     const JS::ubi::Node& referent = edge.referent;
 
-    if (!zones)
-      // We aren't targeting a particular set of zones, so serialize all the
+    if (!compartments)
+      // We aren't targeting a particular set of compartments, so serialize all the
       // things!
       return writer.writeNode(referent, CoreDumpWriter::INCLUDE_EDGES);
 
-    // We are targeting a particular set of zones. If this node is in our target
+    // We are targeting a particular set of compartments. If this node is in our target
     // set, serialize it and all of its edges. If this node is _not_ in our
     // target set, we also serialize under the assumption that it is a shared
-    // resource being used by something in our target zones since we reached it
+    // resource being used by something in our target compartments since we reached it
     // by traversing the heap graph. However, we do not serialize its outgoing
     // edges and we abandon further traversal from this node.
 
-    JS::Zone* zone = referent.zone();
+    JSCompartment* compartment = referent.compartment();
 
-    if (zones->has(zone))
+    if (compartments->has(compartment))
       return writer.writeNode(referent, CoreDumpWriter::INCLUDE_EDGES);
 
     traversal.abandonReferent();
     return writer.writeNode(referent, CoreDumpWriter::EXCLUDE_EDGES);
   }
 };
 
 
 bool
 WriteHeapGraph(JSContext* cx,
                const JS::ubi::Node& node,
                CoreDumpWriter& writer,
                bool wantNames,
-               JS::ZoneSet* zones,
+               JS::CompartmentSet* compartments,
                JS::AutoCheckCannotGC& noGC,
                uint32_t& outNodeCount,
                uint32_t& outEdgeCount)
 {
   // Serialize the starting node to the core dump.
 
   if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) {
     return false;
   }
 
   // Walk the heap graph starting from the given node and serialize it into the
   // core dump.
 
-  HeapSnapshotHandler handler(writer, zones);
+  HeapSnapshotHandler handler(writer, compartments);
   HeapSnapshotHandler::Traversal traversal(JS_GetRuntime(cx), handler, noGC);
   if (!traversal.init())
     return false;
   traversal.wantNames = wantNames;
 
   bool ok = traversal.addStartVisited(node) &&
             traversal.traverse();
 
@@ -1543,17 +1545,17 @@ using namespace devtools;
 ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global,
                                         const HeapSnapshotBoundaries& boundaries,
                                         nsAString& outFilePath,
                                         ErrorResult& rv)
 {
   auto start = TimeStamp::Now();
 
   bool wantNames = true;
-  ZoneSet zones;
+  CompartmentSet compartments;
   uint32_t nodeCount = 0;
   uint32_t edgeCount = 0;
 
   nsCOMPtr<nsIOutputStream> outputStream = getCoreDumpOutputStream(rv, start, outFilePath);
   if (NS_WARN_IF(rv.Failed()))
     return;
 
   ZeroCopyNSIOutputStream zeroCopyStream(outputStream);
@@ -1564,31 +1566,31 @@ ThreadSafeChromeUtils::SaveHeapSnapshot(
   if (NS_WARN_IF(!writer.init())) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   {
     Maybe<AutoCheckCannotGC> maybeNoGC;
     ubi::RootList rootList(JS_GetRuntime(cx), maybeNoGC, wantNames);
-    if (!EstablishBoundaries(cx, rv, boundaries, rootList, zones))
+    if (!EstablishBoundaries(cx, rv, boundaries, rootList, compartments))
       return;
 
     MOZ_ASSERT(maybeNoGC.isSome());
     ubi::Node roots(&rootList);
 
     // Serialize the initial heap snapshot metadata to the core dump.
     if (!writer.writeMetadata(PR_Now()) ||
         // Serialize the heap graph to the core dump, starting from our list of
         // roots.
         !WriteHeapGraph(cx,
                         roots,
                         writer,
                         wantNames,
-                        zones.initialized() ? &zones : nullptr,
+                        compartments.initialized() ? &compartments : nullptr,
                         maybeNoGC.ref(),
                         nodeCount,
                         edgeCount))
     {
       rv.Throw(zeroCopyStream.failed()
                ? zeroCopyStream.result()
                : NS_ERROR_UNEXPECTED);
       return;
--- a/devtools/shared/heapsnapshot/HeapSnapshot.h
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.h
@@ -207,31 +207,31 @@ public:
 // If `wantNames` is true, capture edge names. If `zones` is non-null, only
 // capture the sub-graph within the zone set, otherwise capture the whole heap
 // graph. Returns false on failure.
 bool
 WriteHeapGraph(JSContext* cx,
                const JS::ubi::Node& node,
                CoreDumpWriter& writer,
                bool wantNames,
-               JS::ZoneSet* zones,
+               JS::CompartmentSet* compartments,
                JS::AutoCheckCannotGC& noGC,
                uint32_t& outNodeCount,
                uint32_t& outEdgeCount);
 inline bool
 WriteHeapGraph(JSContext* cx,
                const JS::ubi::Node& node,
                CoreDumpWriter& writer,
                bool wantNames,
-               JS::ZoneSet* zones,
+               JS::CompartmentSet* compartments,
                JS::AutoCheckCannotGC& noGC)
 {
   uint32_t ignoreNodeCount;
   uint32_t ignoreEdgeCount;
-  return WriteHeapGraph(cx, node, writer, wantNames, zones, noGC,
+  return WriteHeapGraph(cx, node, writer, wantNames, compartments, noGC,
                         ignoreNodeCount, ignoreEdgeCount);
 }
 
 // Get the mozilla::MallocSizeOf for the current thread's JSRuntime.
 MallocSizeOf GetCurrentThreadDebuggerMallocSizeOf();
 
 } // namespace devtools
 } // namespace mozilla
rename from devtools/shared/heapsnapshot/tests/gtest/DoesCrossZoneBoundaries.cpp
rename to devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesCrossZoneBoundaries.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
@@ -1,72 +1,72 @@
 /* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-// Test that heap snapshots cross zone boundaries when expected.
+// Test that heap snapshots cross compartment boundaries when expected.
 
 #include "DevTools.h"
 
-DEF_TEST(DoesCrossZoneBoundaries, {
-    // Create a new global to get a new zone.
+DEF_TEST(DoesCrossCompartmentBoundaries, {
+    // Create a new global to get a new compartment.
     JS::CompartmentOptions options;
     JS::RootedObject newGlobal(cx, JS_NewGlobalObject(cx,
                                                       getGlobalClass(),
                                                       nullptr,
                                                       JS::FireOnNewGlobalHook,
                                                       options));
     ASSERT_TRUE(newGlobal);
-    JS::Zone* newZone = nullptr;
+    JSCompartment* newCompartment = nullptr;
     {
       JSAutoCompartment ac(cx, newGlobal);
       ASSERT_TRUE(JS_InitStandardClasses(cx, newGlobal));
-      newZone = js::GetContextZone(cx);
+      newCompartment = js::GetContextCompartment(cx);
     }
-    ASSERT_TRUE(newZone);
-    ASSERT_NE(newZone, zone);
+    ASSERT_TRUE(newCompartment);
+    ASSERT_NE(newCompartment, compartment);
 
-    // Our set of target zones is both the old and new zones.
-    JS::ZoneSet targetZones;
-    ASSERT_TRUE(targetZones.init());
-    ASSERT_TRUE(targetZones.put(zone));
-    ASSERT_TRUE(targetZones.put(newZone));
+    // Our set of target compartments is both the old and new compartments.
+    JS::CompartmentSet targetCompartments;
+    ASSERT_TRUE(targetCompartments.init());
+    ASSERT_TRUE(targetCompartments.put(compartment));
+    ASSERT_TRUE(targetCompartments.put(newCompartment));
 
     FakeNode nodeA;
     FakeNode nodeB;
     FakeNode nodeC;
     FakeNode nodeD;
 
-    nodeA.zone = zone;
-    nodeB.zone = nullptr;
-    nodeC.zone = newZone;
-    nodeD.zone = nullptr;
+    nodeA.compartment = compartment;
+    nodeB.compartment = nullptr;
+    nodeC.compartment = newCompartment;
+    nodeD.compartment = nullptr;
 
     AddEdge(nodeA, nodeB);
     AddEdge(nodeA, nodeC);
     AddEdge(nodeB, nodeD);
 
     ::testing::NiceMock<MockWriter> writer;
 
-    // Should serialize nodeA, because it is in one of our target zones.
+    // Should serialize nodeA, because it is in one of our target compartments.
     ExpectWriteNode(writer, nodeA);
 
-    // Should serialize nodeB, because it doesn't belong to a zone and is
+    // Should serialize nodeB, because it doesn't belong to a compartment and is
     // therefore assumed to be shared.
     ExpectWriteNode(writer, nodeB);
 
-    // Should also serialize nodeC, which is in our target zones, but a
-    // different zone than A.
+    // Should also serialize nodeC, which is in our target compartments, but a
+    // different compartment than A.
     ExpectWriteNode(writer, nodeC);
 
     // However, should not serialize nodeD because nodeB doesn't belong to one
-    // of our target zones and so its edges are excluded from serialization.
+    // of our target compartments and so its edges are excluded from serialization.
 
     JS::AutoCheckCannotGC noGC(rt);
 
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&nodeA),
                                writer,
                                /* wantNames = */ false,
-                               &targetZones,
+                               &targetCompartments,
                                noGC));
   });
rename from devtools/shared/heapsnapshot/tests/gtest/DoesntCrossZoneBoundaries.cpp
rename to devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossZoneBoundaries.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
@@ -1,64 +1,64 @@
 /* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-// Test that heap snapshots walk the zone boundaries correctly.
+// Test that heap snapshots walk the compartment boundaries correctly.
 
 #include "DevTools.h"
 
-DEF_TEST(DoesntCrossZoneBoundaries, {
-    // Create a new global to get a new zone.
+DEF_TEST(DoesntCrossCompartmentBoundaries, {
+    // Create a new global to get a new compartment.
     JS::CompartmentOptions options;
     JS::RootedObject newGlobal(cx, JS_NewGlobalObject(cx,
                                                       getGlobalClass(),
                                                       nullptr,
                                                       JS::FireOnNewGlobalHook,
                                                       options));
     ASSERT_TRUE(newGlobal);
-    JS::Zone* newZone = nullptr;
+    JSCompartment* newCompartment = nullptr;
     {
       JSAutoCompartment ac(cx, newGlobal);
       ASSERT_TRUE(JS_InitStandardClasses(cx, newGlobal));
-      newZone = js::GetContextZone(cx);
+      newCompartment = js::GetContextCompartment(cx);
     }
-    ASSERT_TRUE(newZone);
-    ASSERT_NE(newZone, zone);
+    ASSERT_TRUE(newCompartment);
+    ASSERT_NE(newCompartment, compartment);
 
-    // Our set of target zones is only the pre-existing zone and does not
-    // include the new zone.
-    JS::ZoneSet targetZones;
-    ASSERT_TRUE(targetZones.init());
-    ASSERT_TRUE(targetZones.put(zone));
+    // Our set of target compartments is only the pre-existing compartment and
+    // does not include the new compartment.
+    JS::CompartmentSet targetCompartments;
+    ASSERT_TRUE(targetCompartments.init());
+    ASSERT_TRUE(targetCompartments.put(compartment));
 
     FakeNode nodeA;
     FakeNode nodeB;
     FakeNode nodeC;
 
-    nodeA.zone = zone;
-    nodeB.zone = nullptr;
-    nodeC.zone = newZone;
+    nodeA.compartment = compartment;
+    nodeB.compartment = nullptr;
+    nodeC.compartment = newCompartment;
 
     AddEdge(nodeA, nodeB);
     AddEdge(nodeB, nodeC);
 
     ::testing::NiceMock<MockWriter> writer;
 
-    // Should serialize nodeA, because it is in our target zones.
+    // Should serialize nodeA, because it is in our target compartments.
     ExpectWriteNode(writer, nodeA);
 
-    // Should serialize nodeB, because it doesn't belong to a zone and is
+    // Should serialize nodeB, because it doesn't belong to a compartment and is
     // therefore assumed to be shared.
     ExpectWriteNode(writer, nodeB);
 
     // But we shouldn't ever serialize nodeC.
 
     JS::AutoCheckCannotGC noGC(rt);
 
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&nodeA),
                                writer,
                                /* wantNames = */ false,
-                               &targetZones,
+                               &targetCompartments,
                                noGC));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/moz.build
+++ b/devtools/shared/heapsnapshot/tests/gtest/moz.build
@@ -8,18 +8,18 @@ Library('devtoolstests')
 
 LOCAL_INCLUDES += [
     '../..',
 ]
 
 UNIFIED_SOURCES = [
     'DeserializedNodeUbiNodes.cpp',
     'DeserializedStackFrameUbiStackFrames.cpp',
-    'DoesCrossZoneBoundaries.cpp',
-    'DoesntCrossZoneBoundaries.cpp',
+    'DoesCrossCompartmentBoundaries.cpp',
+    'DoesntCrossCompartmentBoundaries.cpp',
     'SerializesEdgeNames.cpp',
     'SerializesEverythingInHeapGraphOnce.cpp',
     'SerializesTypeNames.cpp',
 ]
 
 # THE MOCK_METHOD2 macro from gtest triggers this clang warning and it's hard
 # to work around, so we just ignore it.
 if CONFIG['CLANG_CXX']:
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -312,25 +312,29 @@ TraceEdge(JSTracer* trc, JS::TenuredHeap
 // nullptr.
 template <typename T>
 extern JS_PUBLIC_API(void)
 UnsafeTraceRoot(JSTracer* trc, T* edgep, const char* name);
 
 extern JS_PUBLIC_API(void)
 TraceChildren(JSTracer* trc, GCCellPtr thing);
 
-typedef js::HashSet<Zone*, js::DefaultHasher<Zone*>, js::SystemAllocPolicy> ZoneSet;
-} // namespace JS
+using ZoneSet = js::HashSet<Zone*, js::DefaultHasher<Zone*>, js::SystemAllocPolicy>;
+using CompartmentSet = js::HashSet<JSCompartment*, js::DefaultHasher<JSCompartment*>,
+                                   js::SystemAllocPolicy>;
 
 /**
- * Trace every value within |zones| that is wrapped by a cross-compartment
- * wrapper from a zone that is not an element of |zones|.
+ * Trace every value within |compartments| that is wrapped by a
+ * cross-compartment wrapper from a compartment that is not an element of
+ * |compartments|.
  */
 extern JS_PUBLIC_API(void)
-JS_TraceIncomingCCWs(JSTracer* trc, const JS::ZoneSet& zones);
+TraceIncomingCCWs(JSTracer* trc, const JS::CompartmentSet& compartments);
+
+} // namespace JS
 
 extern JS_PUBLIC_API(void)
 JS_GetTraceThingInfo(char* buf, size_t bufsize, JSTracer* trc,
                      void* thing, JS::TraceKind kind, bool includeDetails);
 
 namespace js {
 
 // Trace an edge that is not a GC root and is not wrapped in a barriered
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -961,19 +961,20 @@ class MOZ_STACK_CLASS RootList {
     JSRuntime* rt;
     EdgeVector edges;
     bool       wantNames;
 
     RootList(JSRuntime* rt, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false);
 
     // Find all GC roots.
     bool init();
-    // Find only GC roots in the provided set of |Zone|s.
-    bool init(ZoneSet& debuggees);
-    // Find only GC roots in the given Debugger object's set of debuggee zones.
+    // Find only GC roots in the provided set of |JSCompartment|s.
+    bool init(CompartmentSet& debuggees);
+    // Find only GC roots in the given Debugger object's set of debuggee
+    // compartments.
     bool init(HandleObject debuggees);
 
     // Returns true if the RootList has been initialized successfully, false
     // otherwise.
     bool initialized() { return noGC.isSome(); }
 
     // Explicitly add the given Node as a root in this RootList. If wantNames is
     // true, you must pass an edgeName. The RootList does not take ownership of
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -122,64 +122,58 @@ void
 js::TraceChildren(JSTracer* trc, void* thing, JS::TraceKind kind)
 {
     MOZ_ASSERT(thing);
     TraceChildrenFunctor f;
     DispatchTraceKindTyped(f, kind, trc, thing);
 }
 
 JS_PUBLIC_API(void)
-JS_TraceIncomingCCWs(JSTracer* trc, const JS::ZoneSet& zones)
+JS::TraceIncomingCCWs(JSTracer* trc, const JS::CompartmentSet& compartments)
 {
-    for (js::ZonesIter z(trc->runtime(), SkipAtoms); !z.done(); z.next()) {
-        Zone* zone = z.get();
-        if (!zone || zones.has(zone))
+    for (js::CompartmentsIter comp(trc->runtime(), SkipAtoms); !comp.done(); comp.next()) {
+        if (compartments.has(comp))
             continue;
 
-        for (js::CompartmentsInZoneIter c(zone); !c.done(); c.next()) {
-            JSCompartment* comp = c.get();
-            if (!comp)
+        for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) {
+            const CrossCompartmentKey& key = e.front().key();
+            JSObject* obj;
+            JSScript* script;
+
+            switch (key.kind) {
+              case CrossCompartmentKey::StringWrapper:
+                // StringWrappers are just used to avoid copying strings
+                // across zones multiple times, and don't hold a strong
+                // reference.
                 continue;
 
-            for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) {
-                const CrossCompartmentKey& key = e.front().key();
-                JSObject* obj;
-                JSScript* script;
-
-                switch (key.kind) {
-                  case CrossCompartmentKey::StringWrapper:
-                    // StringWrappers are just used to avoid copying strings
-                    // across zones multiple times, and don't hold a strong
-                    // reference.
+              case CrossCompartmentKey::ObjectWrapper:
+              case CrossCompartmentKey::DebuggerObject:
+              case CrossCompartmentKey::DebuggerSource:
+              case CrossCompartmentKey::DebuggerEnvironment:
+                obj = static_cast<JSObject*>(key.wrapped);
+                // Ignore CCWs whose wrapped value doesn't live in our given
+                // set of zones.
+                if (!compartments.has(obj->compartment()))
                     continue;
 
-                  case CrossCompartmentKey::ObjectWrapper:
-                  case CrossCompartmentKey::DebuggerObject:
-                  case CrossCompartmentKey::DebuggerSource:
-                  case CrossCompartmentKey::DebuggerEnvironment:
-                    obj = static_cast<JSObject*>(key.wrapped);
-                    // Ignore CCWs whose wrapped value doesn't live in our given
-                    // set of zones.
-                    if (!zones.has(obj->zone()))
-                        continue;
+                TraceManuallyBarrieredEdge(trc, &obj, "cross-compartment wrapper");
+                MOZ_ASSERT(obj == key.wrapped);
+                break;
 
-                    TraceManuallyBarrieredEdge(trc, &obj, "cross-compartment wrapper");
-                    MOZ_ASSERT(obj == key.wrapped);
-                    break;
+              case CrossCompartmentKey::DebuggerScript:
+                script = static_cast<JSScript*>(key.wrapped);
+                // Ignore CCWs whose wrapped value doesn't live in our given
+                // set of compartments.
+                if (!compartments.has(script->compartment()))
+                    continue;
 
-                  case CrossCompartmentKey::DebuggerScript:
-                    script = static_cast<JSScript*>(key.wrapped);
-                    // Ignore CCWs whose wrapped value doesn't live in our given
-                    // set of zones.
-                    if (!zones.has(script->zone()))
-                        continue;
-                    TraceManuallyBarrieredEdge(trc, &script, "cross-compartment wrapper");
-                    MOZ_ASSERT(script == key.wrapped);
-                    break;
-                }
+                TraceManuallyBarrieredEdge(trc, &script, "cross-compartment wrapper");
+                MOZ_ASSERT(script == key.wrapped);
+                break;
             }
         }
     }
 }
 
 
 /*** Cycle Collector Helpers **********************************************************************/
 
--- a/js/src/jsapi-tests/testGCMarking.cpp
+++ b/js/src/jsapi-tests/testGCMarking.cpp
@@ -45,38 +45,39 @@ BEGIN_TEST(testTracingIncomingCCWs)
     // Get two globals, in two different zones.
 
     JS::RootedObject global1(cx, JS::CurrentGlobalOrNull(cx));
     CHECK(global1);
     JS::CompartmentOptions options;
     JS::RootedObject global2(cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
                                                     JS::FireOnNewGlobalHook, options));
     CHECK(global2);
-    CHECK(global1->zone() != global2->zone());
+    CHECK(global1->compartment() != global2->compartment());
 
-    // Define an object in one zone, that is wrapped by a CCW in another zone.
+    // Define an object in one compartment, that is wrapped by a CCW in another
+    // compartment.
 
     JS::RootedObject obj(cx, JS_NewPlainObject(cx));
-    CHECK(obj->zone() == global1->zone());
+    CHECK(obj->compartment() == global1->compartment());
 
     JSAutoCompartment ac(cx, global2);
     JS::RootedObject wrapper(cx, obj);
     CHECK(JS_WrapObject(cx, &wrapper));
     JS::RootedValue v(cx, JS::ObjectValue(*wrapper));
     CHECK(JS_SetProperty(cx, global2, "ccw", v));
 
-    // Ensure that |JS_TraceIncomingCCWs| finds the object wrapped by the CCW.
+    // Ensure that |TraceIncomingCCWs| finds the object wrapped by the CCW.
 
-    JS::ZoneSet zones;
-    CHECK(zones.init());
-    CHECK(zones.put(global1->zone()));
+    JS::CompartmentSet compartments;
+    CHECK(compartments.init());
+    CHECK(compartments.put(global1->compartment()));
 
     void* thing = obj.get();
     CCWTestTracer trc(cx, &thing, JS::TraceKind::Object);
-    JS_TraceIncomingCCWs(&trc, zones);
+    JS::TraceIncomingCCWs(&trc, compartments);
     CHECK(trc.numberOfThingsTraced == 1);
     CHECK(trc.okay);
 
     return true;
 }
 END_TEST(testTracingIncomingCCWs)
 
 BEGIN_TEST(testIncrementalRoots)
--- a/js/src/vm/UbiNode.cpp
+++ b/js/src/vm/UbiNode.cpp
@@ -421,57 +421,71 @@ RootList::init()
     js::TraceRuntime(&tracer);
     if (!tracer.okay)
         return false;
     noGC.emplace(rt);
     return true;
 }
 
 bool
-RootList::init(ZoneSet& debuggees)
+RootList::init(CompartmentSet& debuggees)
 {
     EdgeVector allRootEdges;
     EdgeVectorTracer tracer(rt, &allRootEdges, wantNames);
 
+    ZoneSet debuggeeZones;
+    if (!debuggeeZones.init())
+        return false;
+    for (auto range = debuggees.all(); !range.empty(); range.popFront()) {
+        if (!debuggeeZones.put(range.front()->zone()))
+            return false;
+    }
+
     js::TraceRuntime(&tracer);
     if (!tracer.okay)
         return false;
-    JS_TraceIncomingCCWs(&tracer, debuggees);
+    TraceIncomingCCWs(&tracer, debuggees);
     if (!tracer.okay)
         return false;
 
     for (EdgeVector::Range r = allRootEdges.all(); !r.empty(); r.popFront()) {
         Edge& edge = r.front();
+
+        JSCompartment* compartment = edge.referent.compartment();
+        if (compartment && !debuggees.has(compartment))
+            continue;
+
         Zone* zone = edge.referent.zone();
-        if (zone && !debuggees.has(zone))
+        if (zone && !debuggeeZones.has(zone))
             continue;
+
         if (!edges.append(mozilla::Move(edge)))
             return false;
     }
 
     noGC.emplace(rt);
     return true;
 }
 
 bool
 RootList::init(HandleObject debuggees)
 {
     MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(*debuggees));
     js::Debugger* dbg = js::Debugger::fromJSObject(debuggees.get());
 
-    ZoneSet debuggeeZones;
-    if (!debuggeeZones.init())
+    CompartmentSet debuggeeCompartments;
+    if (!debuggeeCompartments.init())
         return false;
 
     for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
-        if (!debuggeeZones.put(r.front()->zone()))
+        if (!debuggeeCompartments.put(r.front()->compartment()))
             return false;
     }
 
-    if (!init(debuggeeZones))
+    if (!init(debuggeeCompartments))
         return false;
 
     // Ensure that each of our debuggee globals are in the root list.
     for (js::WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) {
         if (!addRoot(JS::ubi::Node(static_cast<JSObject*>(r.front())),
                      MOZ_UTF16("debuggee global")))
         {
             return false;