[mq]: multi-zone-holders draft
authorJon Coppeard <jcoppeard@mozilla.com>
Fri, 16 Aug 2019 14:31:31 +0100
changeset 2220754 82d89fa41ed1d02b0468a0a7844b68ad6ea8d0c5
parent 2220753 f84fca9a987aeea005b6e719bdae2cc0eded0e20
child 2220755 5b7007ce4796e668266114c7f9ec00b5dcaa9ccf
push id407002
push userjcoppeard@mozilla.com
push dateFri, 16 Aug 2019 13:32:05 +0000
treeherdertry@5b7007ce4796 [default view] [failures only]
milestone70.0a1
[mq]: multi-zone-holders
dom/bindings/CallbackObject.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
xpcom/base/CycleCollectedJSRuntime.cpp
xpcom/base/CycleCollectedJSRuntime.h
xpcom/base/nsCycleCollectionParticipant.h
xpcom/base/nsCycleCollector.cpp
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -47,17 +47,17 @@ namespace dom {
     }                                                \
   }
 
 class CallbackObject : public nsISupports {
  public:
   NS_DECLARE_STATIC_IID_ACCESSOR(DOM_CALLBACKOBJECT_IID)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(CallbackObject)
+  NS_DECL_CYCLE_COLLECTION_MULTI_ZONE_SCRIPT_HOLDER_CLASS(CallbackObject)
 
   // The caller may pass a global object which will act as an override for the
   // incumbent script settings object when the callback is invoked (overriding
   // the entry point computed from aCallback). If no override is required, the
   // caller should pass null.  |aCx| is used to capture the current
   // stack, which is later used as an async parent when the callback
   // is invoked.  aCx can be nullptr, in which case no stack is
   // captured.
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -1400,14 +1400,20 @@ JS_FRIEND_API JS::Value js::MaybeGetScri
   return object->as<ScriptSourceObject>().canonicalPrivate();
 }
 
 JS_FRIEND_API uint64_t js::GetGCHeapUsageForObjectZone(JSObject* obj) {
   return obj->zone()->zoneSize.gcBytes();
 }
 
 #ifdef DEBUG
+
 JS_FRIEND_API bool js::RuntimeIsBeingDestroyed() {
   JSRuntime* runtime = TlsContext.get()->runtime();
   MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(runtime));
   return runtime->isBeingDestroyed();
 }
+
+JS_FRIEND_API JS::Zone* js::GetObjectZoneFromAnyThread(JSObject* obj) {
+  return obj->zoneFromAnyThread();
+}
+
 #endif
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2691,11 +2691,15 @@ class JS_FRIEND_API CompartmentTransplan
 // get swapped with newTarget, and the value of newTarget will be updated. If
 // the callback returns null for a compartment, no cross-compartment wrapper
 // will be created for that compartment. Any non-null values it returns must be
 // DOM remote proxies from the compartment that was passed in.
 extern JS_FRIEND_API void RemapRemoteWindowProxies(
     JSContext* cx, CompartmentTransplantCallback* callback,
     JS::MutableHandleObject newTarget);
 
+#ifdef DEBUG
+extern JS_FRIEND_API JS::Zone* GetObjectZoneFromAnyThread(JSObject* obj);
+#endif
+
 } /* namespace js */
 
 #endif /* jsfriendapi_h */
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -648,29 +648,46 @@ void CycleCollectedJSRuntime::AddContext
 }
 
 void CycleCollectedJSRuntime::RemoveContext(CycleCollectedJSContext* aContext) {
   aContext->removeFrom(mContexts);
 }
 
 size_t CycleCollectedJSRuntime::SizeOfExcludingThis(
     MallocSizeOf aMallocSizeOf) const {
-  return mJSHolders.SizeOfExcludingThis(aMallocSizeOf);
+  size_t size = 0;
+  size += mSingleZoneJSHolders.SizeOfExcludingThis(aMallocSizeOf);
+  size += mMultiZoneJSHolders.SizeOfExcludingThis(aMallocSizeOf);
+  return size;
+}
+
+inline JSHolderMap& CycleCollectedJSRuntime::GetJSHolderMap(bool aMultiZone) {
+  return aMultiZone ? mMultiZoneJSHolders : mSingleZoneJSHolders;
+}
+
+template <typename F>
+inline void CycleCollectedJSRuntime::IterateJSHolders(F&& f) {
+  for (auto iter = mSingleZoneJSHolders.Iter(); !iter.Done(); iter.Next()) {
+    f(iter.Get());
+  }
+  for (auto iter = mMultiZoneJSHolders.Iter(); !iter.Done(); iter.Next()) {
+    f(iter.Get());
+  }
 }
 
 void CycleCollectedJSRuntime::UnmarkSkippableJSHolders() {
   // Prevent nsWrapperCaches accessed under CanSkip from adding recorded events
   // which might not replay in the same order.
   recordreplay::AutoDisallowThreadEvents disallow;
 
-  for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) {
-    void* holder = iter.Get().mHolder;
-    nsScriptObjectTracer* tracer = iter.Get().mTracer;
+  IterateJSHolders([](const JSHolderMap::Entry& aEntry) {
+    void* holder = aEntry.mHolder;
+    nsScriptObjectTracer* tracer = aEntry.mTracer;
     tracer->CanSkip(holder, true);
-  }
+  });
 }
 
 void CycleCollectedJSRuntime::DescribeGCThing(
     bool aIsMarked, JS::GCCellPtr aThing,
     nsCycleCollectionTraversalCallback& aCb) const {
   if (!aCb.WantDebugInfo()) {
     aCb.DescribeGCedNode(aIsMarked, "JS Object");
     return;
@@ -842,33 +859,33 @@ void CycleCollectedJSRuntime::TraverseOb
 }
 
 void CycleCollectedJSRuntime::TraverseNativeRoots(
     nsCycleCollectionNoteRootCallback& aCb) {
   // NB: This is here just to preserve the existing XPConnect order. I doubt it
   // would hurt to do this after the JS holders.
   TraverseAdditionalNativeRoots(aCb);
 
-  for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) {
-    void* holder = iter.Get().mHolder;
-    nsScriptObjectTracer* tracer = iter.Get().mTracer;
+  IterateJSHolders([&aCb](const JSHolderMap::Entry& aEntry) {
+    void* holder = aEntry.mHolder;
+    nsScriptObjectTracer* tracer = aEntry.mTracer;
 
     bool noteRoot = false;
     if (MOZ_UNLIKELY(aCb.WantAllTraces())) {
       noteRoot = true;
     } else {
       tracer->Trace(holder,
                     TraceCallbackFunc(CheckParticipatesInCycleCollection),
                     &noteRoot);
     }
 
     if (noteRoot) {
       aCb.NoteNativeRoot(holder, tracer);
     }
-  }
+  });
 }
 
 /* static */
 void CycleCollectedJSRuntime::TraceBlackJS(JSTracer* aTracer, void* aData) {
   CycleCollectedJSRuntime* self = static_cast<CycleCollectedJSRuntime*>(aData);
 
   self->TraceNativeBlackRoots(aTracer);
 }
@@ -1100,17 +1117,17 @@ struct CheckZoneTracer : public TraceCal
         "found in %s",
         mClassName, aName);
   }
 
   virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
                      void* aClosure) const override {
     JS::Value value = aPtr->unbarrieredGet();
     if (value.isObject()) {
-      checkZone(JS::GetObjectZone(&value.toObject()), aName);
+      checkZone(js::GetObjectZoneFromAnyThread(&value.toObject()), aName);
     } else if (value.isString()) {
       checkZone(JS::GetStringZone(value.toString()), aName);
     } else if (value.isGCThing()) {
       checkZone(JS::GetTenuredGCThingZone(value.toGCCellPtr()), aName);
     }
   }
   virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
                      void* aClosure) const override {
@@ -1118,31 +1135,31 @@ struct CheckZoneTracer : public TraceCal
     if (JSID_IS_GCTHING(id)) {
       checkZone(JS::GetTenuredGCThingZone(JSID_TO_GCTHING(id)), aName);
     }
   }
   virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
                      void* aClosure) const override {
     JSObject* obj = aPtr->unbarrieredGet();
     if (obj) {
-      checkZone(JS::GetObjectZone(obj), aName);
+      checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
     }
   }
   virtual void Trace(JSObject** aPtr, const char* aName,
                      void* aClosure) const override {
     JSObject* obj = *aPtr;
     if (obj) {
-      checkZone(JS::GetObjectZone(obj), aName);
+      checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
     }
   }
   virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
                      void* aClosure) const override {
     JSObject* obj = aPtr->unbarrieredGetPtr();
     if (obj) {
-      checkZone(JS::GetObjectZone(obj), aName);
+      checkZone(js::GetObjectZoneFromAnyThread(obj), aName);
     }
   }
   virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
                      void* aClosure) const override {
     JSString* str = aPtr->unbarrieredGet();
     if (str) {
       checkZone(JS::GetStringZone(str), aName);
     }
@@ -1153,17 +1170,18 @@ struct CheckZoneTracer : public TraceCal
     if (script) {
       checkZone(JS::GetTenuredGCThingZone(JS::GCCellPtr(script)), aName);
     }
   }
   virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
                      void* aClosure) const override {
     JSFunction* fun = aPtr->unbarrieredGet();
     if (fun) {
-      checkZone(JS::GetObjectZone(JS_GetFunctionObject(fun)), aName);
+      checkZone(js::GetObjectZoneFromAnyThread(JS_GetFunctionObject(fun)),
+                aName);
     }
   }
 };
 
 #endif
 
 static inline void CheckHolderIsSingleZone(void* aHolder,
                                            nsCycleCollectionParticipant* aParticipant) {
@@ -1173,28 +1191,40 @@ static inline void CheckHolderIsSingleZo
 #endif
 }
 
 void CycleCollectedJSRuntime::TraceNativeGrayRoots(JSTracer* aTracer) {
   // NB: This is here just to preserve the existing XPConnect order. I doubt it
   // would hurt to do this after the JS holders.
   TraceAdditionalNativeGrayRoots(aTracer);
 
-  for (auto iter = mJSHolders.Iter(); !iter.Done(); iter.Next()) {
+  IterateJSHolders([aTracer](const JSHolderMap::Entry& aEntry) {
+    void* holder = aEntry.mHolder;
+    nsScriptObjectTracer* tracer = aEntry.mTracer;
+    tracer->Trace(holder, JsGcTracer(), aTracer);
+  });
+
+#ifdef DEBUG
+  // Check this after tracing in case we update moved pointers.
+  for (auto iter = mSingleZoneJSHolders.Iter(); !iter.Done(); iter.Next()) {
     void* holder = iter.Get().mHolder;
     nsScriptObjectTracer* tracer = iter.Get().mTracer;
     CheckHolderIsSingleZone(holder, tracer);
-    tracer->Trace(holder, JsGcTracer(), aTracer);
   }
+#endif
 }
 
 void CycleCollectedJSRuntime::AddJSHolder(void* aHolder,
                                           nsScriptObjectTracer* aTracer) {
-  CheckHolderIsSingleZone(aHolder, aTracer);
-  mJSHolders.Put(aHolder, aTracer);
+  bool multiZone = aTracer->HasMultiZones();
+  MOZ_ASSERT(!GetJSHolderMap(!multiZone).Has(aHolder));
+  if (!multiZone) {
+    CheckHolderIsSingleZone(aHolder, aTracer);
+  }
+  GetJSHolderMap(multiZone).Put(aHolder, aTracer);
 }
 
 struct ClearJSHolder : public TraceCallbacks {
   virtual void Trace(JS::Heap<JS::Value>* aPtr, const char*,
                      void*) const override {
     aPtr->setUndefined();
   }
 
@@ -1229,34 +1259,45 @@ struct ClearJSHolder : public TraceCallb
 
   virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char*,
                      void*) const override {
     *aPtr = nullptr;
   }
 };
 
 void CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder) {
-  nsScriptObjectTracer* tracer = mJSHolders.GetAndRemove(aHolder);
+  nsScriptObjectTracer* tracer = mSingleZoneJSHolders.GetAndRemove(aHolder);
+  if (tracer) {
+    MOZ_ASSERT(!tracer->HasMultiZones());
+    MOZ_ASSERT(!mMultiZoneJSHolders.Has(aHolder));
+  } else {
+    tracer = mMultiZoneJSHolders.GetAndRemove(aHolder);
+    MOZ_ASSERT_IF(tracer, tracer->HasMultiZones());
+  }
+
   if (tracer) {
     tracer->Trace(aHolder, ClearJSHolder(), nullptr);
   }
 }
 
 #ifdef DEBUG
 bool CycleCollectedJSRuntime::IsJSHolder(void* aHolder) {
-  return mJSHolders.Has(aHolder);
+  return mSingleZoneJSHolders.Has(aHolder) || mMultiZoneJSHolders.Has(aHolder);
 }
 
 static void AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName,
                             void* aClosure) {
   MOZ_ASSERT(!aGCThing);
 }
 
 void CycleCollectedJSRuntime::AssertNoObjectsToTrace(void* aPossibleJSHolder) {
-  nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder);
+  nsScriptObjectTracer* tracer = mSingleZoneJSHolders.Get(aPossibleJSHolder);
+  if (!tracer) {
+    tracer = mMultiZoneJSHolders.Get(aPossibleJSHolder);
+  }
   if (tracer) {
     tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing),
                   nullptr);
   }
 }
 #endif
 
 nsCycleCollectionParticipant* CycleCollectedJSRuntime::GCThingParticipant() {
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -261,16 +261,20 @@ class CycleCollectedJSRuntime {
     }
   }
 
   bool IsIdleGCTaskNeeded() {
     return !HasPendingIdleGCTask() && Runtime() &&
            JS::IsIdleGCTaskNeeded(Runtime());
   }
 
+  JSHolderMap& GetJSHolderMap(bool aMultiZone);
+  template <typename F>
+  void IterateJSHolders(F&& f);
+
  public:
   void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer);
   void RemoveJSHolder(void* aHolder);
 #ifdef DEBUG
   bool IsJSHolder(void* aHolder);
   void AssertNoObjectsToTrace(void* aPossibleJSHolder);
 #endif
 
@@ -332,17 +336,18 @@ class CycleCollectedJSRuntime {
   JSRuntime* mJSRuntime;
   bool mHasPendingIdleGCTask;
 
   JS::GCSliceCallback mPrevGCSliceCallback;
   JS::GCNurseryCollectionCallback mPrevGCNurseryCollectionCallback;
 
   mozilla::TimeStamp mLatestNurseryCollectionStart;
 
-  JSHolderMap mJSHolders;
+  JSHolderMap mSingleZoneJSHolders;
+  JSHolderMap mMultiZoneJSHolders;
 
   typedef nsDataHashtable<nsFuncPtrHashKey<DeferredFinalizeFunction>, void*>
       DeferredFinalizerTable;
   DeferredFinalizerTable mDeferredFinalizerTable;
 
   RefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable;
 
   OOMState mOutOfMemoryState;
--- a/xpcom/base/nsCycleCollectionParticipant.h
+++ b/xpcom/base/nsCycleCollectionParticipant.h
@@ -228,28 +228,37 @@ class NS_NO_VTABLE nsCycleCollectionPart
 
  private:
   const bool mMightSkip;
   const bool mTraverseShouldTrace;
 };
 
 class NS_NO_VTABLE nsScriptObjectTracer : public nsCycleCollectionParticipant {
  public:
-  constexpr explicit nsScriptObjectTracer(bool aMightSkip)
-      : nsCycleCollectionParticipant(aMightSkip, true) {}
+  constexpr explicit nsScriptObjectTracer(bool aMightSkip,
+                                          bool aMultiZone = false)
+      : nsCycleCollectionParticipant(aMightSkip, true),
+        mMultiZone(aMultiZone) {}
 
   NS_IMETHOD_(void)
   Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override = 0;
+
+  // Whether referenced JS GC things can be in different zones.
+  bool HasMultiZones() const { return mMultiZone; }
+
+ private:
+  const bool mMultiZone;
 };
 
 class NS_NO_VTABLE nsXPCOMCycleCollectionParticipant
     : public nsScriptObjectTracer {
  public:
-  constexpr explicit nsXPCOMCycleCollectionParticipant(bool aMightSkip)
-      : nsScriptObjectTracer(aMightSkip) {}
+  constexpr explicit nsXPCOMCycleCollectionParticipant(bool aMightSkip,
+                                                       bool aMultiZone = false)
+      : nsScriptObjectTracer(aMightSkip, aMultiZone) {}
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID)
 
   NS_IMETHOD_(void) Root(void* aPtr) override;
   NS_IMETHOD_(void) Unroot(void* aPtr) override;
 
   NS_IMETHOD_(void)
   Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override;
@@ -678,16 +687,43 @@ T* DowncastCCParticipant(void* aPtr) {
   NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class)                               \
   static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;      \
   NOT_INHERITED_CANT_OVERRIDE
 
 #define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(_class)     \
   NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, \
                                                                    _class)
 
+#define NS_DECL_CYCLE_COLLECTION_MULTI_ZONE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(  \
+    _class, _base)                                                          \
+  class NS_CYCLE_COLLECTION_INNERCLASS                                      \
+      : public nsXPCOMCycleCollectionParticipant {                          \
+   public:                                                                  \
+    constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(                      \
+        bool aMightSkip =                                                   \
+            true) /* Ignore aMightSkip: we always want skippability. */     \
+        : nsXPCOMCycleCollectionParticipant(true, true) {}                  \
+                                                                            \
+   private:                                                                 \
+    NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base)                      \
+    NS_IMETHOD_(void)                                                       \
+    Trace(void* p, const TraceCallbacks& cb, void* closure) override;       \
+    NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \
+    NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override;                    \
+    NS_IMETHOD_(bool) CanSkipThisReal(void* p) override;                    \
+    NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class)                  \
+  };                                                                        \
+  NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class)                               \
+  static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;      \
+  NOT_INHERITED_CANT_OVERRIDE
+
+#define NS_DECL_CYCLE_COLLECTION_MULTI_ZONE_SCRIPT_HOLDER_CLASS(_class)     \
+  NS_DECL_CYCLE_COLLECTION_MULTI_ZONE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, \
+                                                                    _class)
+
 #define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED(   \
     _class, _base_class)                                                    \
   class NS_CYCLE_COLLECTION_INNERCLASS                                      \
       : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) {                 \
    public:                                                                  \
     constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(                      \
         bool aMightSkip =                                                   \
             true) /* Ignore aMightSkip: we always want skippability. */     \
@@ -853,16 +889,33 @@ T* DowncastCCParticipant(void* aPtr) {
     NS_IMETHOD_(void)                                                          \
     Trace(void* p, const TraceCallbacks& cb, void* closure) override;          \
     static constexpr nsScriptObjectTracer* GetParticipant() {                  \
       return &_class::NS_CYCLE_COLLECTION_INNERNAME;                           \
     }                                                                          \
   };                                                                           \
   static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
 
+#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_MULTI_ZONE_NATIVE_CLASS(_class) \
+  void DeleteCycleCollectable(void) { delete this; }                           \
+  class NS_CYCLE_COLLECTION_INNERCLASS : public nsScriptObjectTracer {         \
+   public:                                                                     \
+    constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(bool aMightSkip = false) \
+        : nsScriptObjectTracer(aMightSkip, true) {}                            \
+                                                                               \
+   private:                                                                    \
+    NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class)                         \
+    NS_IMETHOD_(void)                                                          \
+    Trace(void* p, const TraceCallbacks& cb, void* closure) override;          \
+    static constexpr nsScriptObjectTracer* GetParticipant() {                  \
+      return &_class::NS_CYCLE_COLLECTION_INNERNAME;                           \
+    }                                                                          \
+  };                                                                           \
+  static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME;
+
 #define NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(_class, _root_function) \
   NS_IMETHODIMP_(void)                                               \
   NS_CYCLE_COLLECTION_CLASSNAME(_class)::Root(void* p) {             \
     _class* tmp = static_cast<_class*>(p);                           \
     tmp->_root_function();                                           \
   }
 
 #define NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(_class, _unroot_function) \
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -2342,17 +2342,17 @@ class JSPurpleBuffer {
     RefPtr<JSPurpleBuffer> referenceToThis;
     mReferenceToThis.swap(referenceToThis);
     mValues.Clear();
     mObjects.Clear();
     mozilla::DropJSObjects(this);
   }
 
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(JSPurpleBuffer)
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(JSPurpleBuffer)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_MULTI_ZONE_NATIVE_CLASS(JSPurpleBuffer)
 
   RefPtr<JSPurpleBuffer>& mReferenceToThis;
 
   // These are raw pointers instead of Heap<T> because we only need Heap<T> for
   // pointers which may point into the nursery. The purple buffer never contains
   // pointers to the nursery because nursery gcthings can never be gray and only
   // gray things can be inserted into the purple buffer.
   static const size_t kSegmentSize = 512;