Bug 1120016 - Allocate short lived JS wrappers in the Nursery, r=mccr8,terrence
☠☠ backed out by 22f51211915b ☠ ☠
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Wed, 30 Dec 2015 19:16:13 +0200
changeset 318116 0d55a6e4e98e6e420ca9810688f9921434a94eef
parent 318115 8130a1608ca05e55785987273750cd162f48a40c
child 318117 eb4dc9b5a928f3391492a40076b3fcd23559cc41
push id8842
push usergijskruitbosch@gmail.com
push dateThu, 31 Dec 2015 13:04:55 +0000
reviewersmccr8, terrence
bugs1120016
milestone46.0a1
Bug 1120016 - Allocate short lived JS wrappers in the Nursery, r=mccr8,terrence
dom/base/nsWrapperCache.cpp
dom/base/nsWrapperCache.h
dom/base/nsWrapperCacheInlines.h
dom/bindings/Codegen.py
dom/bindings/parser/WebIDL.py
dom/events/Event.cpp
dom/webidl/Event.webidl
dom/webidl/MutationObserver.webidl
js/src/devtools/rootAnalysis/annotations.js
js/src/gc/GCRuntime.h
js/src/gc/Marking.cpp
js/src/gc/Nursery.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsgc.cpp
xpcom/base/CycleCollectedJSRuntime.cpp
xpcom/base/CycleCollectedJSRuntime.h
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsCycleCollectorTraceJSHelpers.cpp
xpcom/glue/nsCycleCollectionParticipant.h
--- a/dom/base/nsWrapperCache.cpp
+++ b/dom/base/nsWrapperCache.cpp
@@ -4,36 +4,51 @@
  * 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/. */
 
 #include "nsWrapperCacheInlines.h"
 
 #include "js/Class.h"
 #include "js/Proxy.h"
 #include "mozilla/dom/DOMJSProxyHandler.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/HoldDropJSObjects.h"
 #include "nsCycleCollectionTraversalCallback.h"
 #include "nsCycleCollector.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #ifdef DEBUG
 /* static */ bool
 nsWrapperCache::HasJSObjectMovedOp(JSObject* aWrapper)
 {
     return js::HasObjectMovedOp(aWrapper);
 }
 #endif
 
-/* static */ void
+void
 nsWrapperCache::HoldJSObjects(void* aScriptObjectHolder,
                               nsScriptObjectTracer* aTracer)
 {
   cyclecollector::HoldJSObjectsImpl(aScriptObjectHolder, aTracer);
+  if (mWrapper && !JS::ObjectIsTenured(mWrapper)) {
+    CycleCollectedJSRuntime::Get()->NurseryWrapperPreserved(mWrapper);
+  }
+}
+
+void
+nsWrapperCache::SetWrapperJSObject(JSObject* aWrapper)
+{
+  mWrapper = aWrapper;
+  UnsetWrapperFlags(kWrapperFlagsMask & ~WRAPPER_IS_NOT_DOM_BINDING);
+
+  if (aWrapper && !JS::ObjectIsTenured(aWrapper)) {
+    CycleCollectedJSRuntime::Get()->NurseryWrapperAdded(this);
+  }
 }
 
 void
 nsWrapperCache::ReleaseWrapper(void* aScriptObjectHolder)
 {
   if (PreservingWrapper()) {
     // PreserveWrapper puts new DOM bindings in the JS holders hash, but they
     // can also be in the DOM expando hash, so we need to try to remove them
--- a/dom/base/nsWrapperCache.h
+++ b/dom/base/nsWrapperCache.h
@@ -253,24 +253,25 @@ public:
   }
 
   void ReleaseWrapper(void* aScriptObjectHolder);
 
 protected:
   void TraceWrapper(JSTracer* aTrc, const char* name)
   {
     if (mWrapper) {
-      JS_CallObjectTracer(aTrc, &mWrapper, name);
+      JS_CallUnbarrieredObjectTracer(aTrc, &mWrapper, name);
     }
   }
 
   void PoisonWrapper()
   {
     if (mWrapper) {
-      mWrapper.setToCrashOnTouch();
+      // See setToCrashOnTouch() in RootingAPI.h
+      mWrapper = reinterpret_cast<JSObject*>(1);
     }
   }
 
 private:
   friend class mozilla::dom::TabChildGlobal;
   friend class mozilla::dom::ProcessGlobal;
   friend class SandboxPrivate;
   friend class nsInProcessTabChildGlobal;
@@ -282,23 +283,17 @@ private:
     SetWrapperFlags(WRAPPER_IS_NOT_DOM_BINDING);
   }
 
   JSObject *GetWrapperJSObject() const
   {
     return mWrapper;
   }
 
-  void SetWrapperJSObject(JSObject* aWrapper)
-  {
-    mWrapper = aWrapper;
-    UnsetWrapperFlags(kWrapperFlagsMask & ~WRAPPER_IS_NOT_DOM_BINDING);
-  }
-
-  void TraceWrapperJSObject(JSTracer* aTrc, const char* aName);
+  void SetWrapperJSObject(JSObject* aWrapper);
 
   FlagsType GetWrapperFlags() const
   {
     return mFlags & kWrapperFlagsMask;
   }
 
   bool HasWrapperFlag(FlagsType aFlag) const
   {
@@ -313,18 +308,18 @@ private:
   }
 
   void UnsetWrapperFlags(FlagsType aFlagsToUnset)
   {
     MOZ_ASSERT((aFlagsToUnset & ~kWrapperFlagsMask) == 0, "Bad wrapper flag bits");
     mFlags &= ~aFlagsToUnset;
   }
 
-  static void HoldJSObjects(void* aScriptObjectHolder,
-                            nsScriptObjectTracer* aTracer);
+  void HoldJSObjects(void* aScriptObjectHolder,
+                     nsScriptObjectTracer* aTracer);
 
 #ifdef DEBUG
 public:
   void CheckCCWrapperTraversal(void* aScriptObjectHolder,
                                nsScriptObjectTracer* aTracer);
 private:
 #endif // DEBUG
 
@@ -344,18 +339,18 @@ private:
   /**
    * If this bit is set then the wrapper for the native object is not a DOM
    * binding.
    */
   enum { WRAPPER_IS_NOT_DOM_BINDING = 1 << 1 };
 
   enum { kWrapperFlagsMask = (WRAPPER_BIT_PRESERVED | WRAPPER_IS_NOT_DOM_BINDING) };
 
-  JS::Heap<JSObject*> mWrapper;
-  FlagsType           mFlags;
+  JSObject* mWrapper;
+  FlagsType mFlags;
 };
 
 enum { WRAPPER_CACHE_FLAGS_BITS_USED = 2 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsWrapperCache, NS_WRAPPERCACHE_IID)
 
 #define NS_WRAPPERCACHE_INTERFACE_TABLE_ENTRY                                 \
   if ( aIID.Equals(NS_GET_IID(nsWrapperCache)) ) {                            \
--- a/dom/base/nsWrapperCacheInlines.h
+++ b/dom/base/nsWrapperCacheInlines.h
@@ -48,15 +48,9 @@ nsWrapperCache::HasNothingToTrace(nsISup
 }
 
 inline bool
 nsWrapperCache::IsBlackAndDoesNotNeedTracing(nsISupports* aThis)
 {
   return IsBlack() && HasNothingToTrace(aThis);
 }
 
-inline void
-nsWrapperCache::TraceWrapperJSObject(JSTracer* aTrc, const char* aName)
-{
-  JS_CallObjectTracer(aTrc, &mWrapper, aName);
-}
-
 #endif /* nsWrapperCache_h___ */
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -417,16 +417,18 @@ class CGDOMJSClass(CGThing):
         if self.descriptor.isGlobal():
             classFlags += "JSCLASS_DOM_GLOBAL | JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS)"
             traceHook = "JS_GlobalObjectTraceHook"
             reservedSlots = "JSCLASS_GLOBAL_APPLICATION_SLOTS"
         else:
             classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount
             traceHook = 'nullptr'
             reservedSlots = slotCount
+        if self.descriptor.interface.isProbablyShortLivingObject():
+            classFlags += " | JSCLASS_SKIP_NURSERY_FINALIZE"
         if self.descriptor.interface.getExtendedAttribute("NeedResolve"):
             resolveHook = RESOLVE_HOOK_NAME
             mayResolveHook = MAY_RESOLVE_HOOK_NAME
             enumerateHook = ENUMERATE_HOOK_NAME
         elif self.descriptor.isGlobal():
             resolveHook = "mozilla::dom::ResolveGlobal"
             mayResolveHook = "mozilla::dom::MayResolveGlobal"
             enumerateHook = "mozilla::dom::EnumerateGlobal"
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -559,16 +559,19 @@ class IDLExternalInterface(IDLObjectWith
         pass
 
     def getJSImplementation(self):
         return None
 
     def isJSImplemented(self):
         return False
 
+    def isProbablyShortLivingObject(self):
+        return False
+
     def getNavigatorProperty(self):
         return None
 
     def _getDependentObjects(self):
         return set()
 
 
 class IDLPartialInterface(IDLObject):
@@ -1403,17 +1406,18 @@ class IDLInterface(IDLObjectWithScope, I
                 self.parentScope.globalNames.add(self.identifier.name)
                 self.parentScope.globalNameMapping[self.identifier.name].add(self.identifier.name)
                 self._isOnGlobalProtoChain = True
             elif (identifier == "NeedResolve" or
                   identifier == "OverrideBuiltins" or
                   identifier == "ChromeOnly" or
                   identifier == "Unforgeable" or
                   identifier == "UnsafeInPrerendering" or
-                  identifier == "LegacyEventInit"):
+                  identifier == "LegacyEventInit" or
+                  identifier == "ProbablyShortLivingObject"):
                 # Known extended attributes that do not take values
                 if not attr.noArguments():
                     raise WebIDLError("[%s] must take no arguments" % identifier,
                                       [attr.location])
             elif identifier == "Exposed":
                 convertExposedAttrToGlobalNameSet(attr,
                                                   self._exposureGlobalNames)
             elif (identifier == "Pref" or
@@ -1517,16 +1521,24 @@ class IDLInterface(IDLObjectWithScope, I
             return classId
         assert isinstance(classId, list)
         assert len(classId) == 1
         return classId[0]
 
     def isJSImplemented(self):
         return bool(self.getJSImplementation())
 
+    def isProbablyShortLivingObject(self):
+        current = self
+        while current:
+            if current.getExtendedAttribute("ProbablyShortLivingObject"):
+                return True
+            current = current.parent
+        return False
+
     def getNavigatorProperty(self):
         naviProp = self.getExtendedAttribute("NavigatorProperty")
         if not naviProp:
             return None
         assert len(naviProp) == 1
         assert isinstance(naviProp, list)
         assert len(naviProp[0]) != 0
         return naviProp[0]
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -232,19 +232,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 
 JSObject*
 Event::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
-  if (mIsMainThreadEvent && !GetWrapperPreserveColor()) {
-    nsJSContext::LikelyShortLivingObjectCreated();
-  }
   return WrapObjectInternal(aCx, aGivenProto);
 }
 
 JSObject*
 Event::WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return EventBinding::Wrap(aCx, this, aGivenProto);
 }
--- a/dom/webidl/Event.webidl
+++ b/dom/webidl/Event.webidl
@@ -6,17 +6,17 @@
  * The origin of this IDL file is
  * http://www.w3.org/TR/2012/WD-dom-20120105/
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 [Constructor(DOMString type, optional EventInit eventInitDict),
- Exposed=(Window,Worker,System)]
+ Exposed=(Window,Worker,System), ProbablyShortLivingObject]
 interface Event {
   [Pure]
   readonly attribute DOMString type;
   [Pure]
   readonly attribute EventTarget? target;
   [Pure]
   readonly attribute EventTarget? currentTarget;
 
--- a/dom/webidl/MutationObserver.webidl
+++ b/dom/webidl/MutationObserver.webidl
@@ -2,16 +2,17 @@
 /* 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/.
  *
  * The origin of this IDL file is
  * http://dom.spec.whatwg.org
  */
 
+[ProbablyShortLivingObject]
 interface MutationRecord {
   [Constant]
   readonly attribute DOMString type;
   // .target is not nullable per the spec, but in order to prevent crashes,
   // if there are GC/CC bugs in Gecko, we let the property to be null.
   [Constant]
   readonly attribute Node? target;
   [Constant]
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -73,17 +73,18 @@ var ignoreCallees = {
     "JSRuntime.destroyPrincipals" : true,
     "icu_50::UObject.__deleting_dtor" : true, // destructors in ICU code can't cause GC
     "mozilla::CycleCollectedJSRuntime.DescribeCustomObjects" : true, // During tracing, cannot GC.
     "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
     "PLDHashTableOps.hashKey" : true,
     "z_stream_s.zfree" : true,
     "GrGLInterface.fCallback" : true,
     "std::strstreambuf._M_alloc_fun" : true,
-    "std::strstreambuf._M_free_fun" : true
+    "std::strstreambuf._M_free_fun" : true,
+    "struct js::gc::Callback<void (*)(JSRuntime*, void*)>.op" : true,
 };
 
 function fieldCallCannotGC(csu, fullfield)
 {
     if (csu in ignoreClasses)
         return true;
     if (fullfield in ignoreCallees)
         return true;
@@ -182,16 +183,18 @@ var ignoreFunctions = {
     // particular runnable it posts can't even GC, but the analysis isn't
     // currently smart enough to determine that. In either case, this is (a)
     // only in GTests, and (b) only when the Gtest has already failed. We have
     // static and dynamic checks for no GC in the non-test code, and in the test
     // code we fall back to only the dynamic checks.
     "void test::RingbufferDumper::OnTestPartResult(testing::TestPartResult*)" : true,
 
     "float64 JS_GetCurrentEmbedderTime()" : true,
+
+    "uint64 js::TenuringTracer::moveObjectToTenured(JSObject*, JSObject*, int32)" : true,
 };
 
 function isProtobuf(name)
 {
     return name.match(/\bgoogle::protobuf\b/) ||
            name.match(/\bmozilla::devtools::protobuf\b/);
 }
 
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -751,16 +751,19 @@ class GCRuntime
     int32_t getMallocBytes() const { return mallocBytesUntilGC; }
     void resetMallocBytes();
     bool isTooMuchMalloc() const { return mallocBytesUntilGC <= 0; }
     void updateMallocCounter(JS::Zone* zone, size_t nbytes);
     void onTooMuchMalloc();
 
     void setGCCallback(JSGCCallback callback, void* data);
     void callGCCallback(JSGCStatus status) const;
+    void setObjectsTenuredCallback(JSObjectsTenuredCallback callback,
+                                   void* data);
+    void callObjectsTenuredCallback();
     bool addFinalizeCallback(JSFinalizeCallback callback, void* data);
     void removeFinalizeCallback(JSFinalizeCallback 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);
 
@@ -1268,16 +1271,17 @@ class GCRuntime
 
     js::Vector<JSObject*, 0, js::SystemAllocPolicy> selectedForMarking;
 #endif
 
     bool validate;
     bool fullCompartmentChecks;
 
     Callback<JSGCCallback> gcCallback;
+    Callback<JSObjectsTenuredCallback> tenuredCallback;
     CallbackVector<JSFinalizeCallback> finalizeCallbacks;
     CallbackVector<JSWeakPointerZoneGroupCallback> updateWeakPointerZoneGroupCallbacks;
     CallbackVector<JSWeakPointerCompartmentCallback> updateWeakPointerCompartmentCallbacks;
 
     /*
      * Malloc counter to measure memory pressure for GC scheduling. It runs
      * from maxMallocBytes down to zero.
      */
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2210,16 +2210,18 @@ js::TenuringTracer::moveObjectToTenured(
 
     if (src->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE) {
         if (src->is<InlineTypedObject>()) {
             InlineTypedObject::objectMovedDuringMinorGC(this, dst, src);
         } else if (src->is<UnboxedArrayObject>()) {
             tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(this, dst, src, dstKind);
         } else if (src->is<ArgumentsObject>()) {
             tenuredSize += ArgumentsObject::objectMovedDuringMinorGC(this, dst, src);
+        } else if (JSObjectMovedOp op = dst->getClass()->ext.objectMovedOp) {
+            op(dst, src);
         } else {
             // Objects with JSCLASS_SKIP_NURSERY_FINALIZE need to be handled above
             // to ensure any additional nursery buffers they hold are moved.
             MOZ_CRASH("Unhandled JSCLASS_SKIP_NURSERY_FINALIZE Class");
         }
     }
 
     return tenuredSize;
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -491,16 +491,20 @@ js::Nursery::collect(JSRuntime* rt, JS::
     TIME_END(sweepArrayBufferViewList);
 
     // Update any slot or element pointers whose destination has been tenured.
     TIME_START(updateJitActivations);
     js::jit::UpdateJitActivationsForMinorGC(rt, &mover);
     forwardedBuffers.finish();
     TIME_END(updateJitActivations);
 
+    TIME_START(objectsTenuredCallback);
+    rt->gc.callObjectsTenuredCallback();
+    TIME_END(objectsTenuredCallback);
+
     // Sweep.
     TIME_START(freeMallocedBuffers);
     freeMallocedBuffers();
     TIME_END(freeMallocedBuffers);
 
     TIME_START(sweep);
     sweep();
     TIME_END(sweep);
@@ -571,16 +575,17 @@ js::Nursery::collect(JSRuntime* rt, JS::
             {"mkSlts", TIME_TOTAL(traceSlots)},
             {"mcWCll", TIME_TOTAL(traceWholeCells)},
             {"mkGnrc", TIME_TOTAL(traceGenericEntries)},
             {"ckTbls", TIME_TOTAL(checkHashTables)},
             {"mkRntm", TIME_TOTAL(markRuntime)},
             {"mkDbgr", TIME_TOTAL(markDebugger)},
             {"clrNOC", TIME_TOTAL(clearNewObjectCache)},
             {"collct", TIME_TOTAL(collectToFP)},
+            {" tenCB", TIME_TOTAL(objectsTenuredCallback)},
             {"swpABO", TIME_TOTAL(sweepArrayBufferViewList)},
             {"updtIn", TIME_TOTAL(updateJitActivations)},
             {"frSlts", TIME_TOTAL(freeMallocedBuffers)},
             {" clrSB", TIME_TOTAL(clearStoreBuffer)},
             {" sweep", TIME_TOTAL(sweep)},
             {"resize", TIME_TOTAL(resize)},
             {"pretnr", TIME_TOTAL(pretenure)},
             {"logPtT", TIME_TOTAL(logPromotionsToTenured)}
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1390,16 +1390,24 @@ JS_MaybeGC(JSContext* cx)
 
 JS_PUBLIC_API(void)
 JS_SetGCCallback(JSRuntime* rt, JSGCCallback cb, void* data)
 {
     AssertHeapIsIdle(rt);
     rt->gc.setGCCallback(cb, data);
 }
 
+JS_PUBLIC_API(void)
+JS_SetObjectsTenuredCallback(JSRuntime* rt, JSObjectsTenuredCallback cb,
+                             void* data)
+{
+    AssertHeapIsIdle(rt);
+    rt->gc.setObjectsTenuredCallback(cb, data);
+}
+
 JS_PUBLIC_API(bool)
 JS_AddFinalizeCallback(JSRuntime* rt, JSFinalizeCallback cb, void* data)
 {
     AssertHeapIsIdle(rt);
     return rt->gc.addFinalizeCallback(cb, data);
 }
 
 JS_PUBLIC_API(void)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -567,16 +567,19 @@ typedef bool
 typedef enum JSGCStatus {
     JSGC_BEGIN,
     JSGC_END
 } JSGCStatus;
 
 typedef void
 (* JSGCCallback)(JSRuntime* rt, JSGCStatus status, void* data);
 
+typedef void
+(* JSObjectsTenuredCallback)(JSRuntime* rt, void* data);
+
 typedef enum JSFinalizeStatus {
     /**
      * Called when preparing to sweep a group of compartments, before anything
      * has been swept.  The collector will not yield to the mutator before
      * calling the callback with JSFINALIZE_GROUP_END status.
      */
     JSFINALIZE_GROUP_START,
 
@@ -1649,16 +1652,20 @@ extern JS_PUBLIC_API(void)
 JS_GC(JSRuntime* rt);
 
 extern JS_PUBLIC_API(void)
 JS_MaybeGC(JSContext* cx);
 
 extern JS_PUBLIC_API(void)
 JS_SetGCCallback(JSRuntime* rt, JSGCCallback cb, void* data);
 
+extern JS_PUBLIC_API(void)
+JS_SetObjectsTenuredCallback(JSRuntime* rt, JSObjectsTenuredCallback cb,
+                             void* data);
+
 extern JS_PUBLIC_API(bool)
 JS_AddFinalizeCallback(JSRuntime* rt, JSFinalizeCallback cb, void* data);
 
 extern JS_PUBLIC_API(void)
 JS_RemoveFinalizeCallback(JSRuntime* rt, JSFinalizeCallback cb);
 
 /*
  * Weak pointers and garbage collection
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1601,16 +1601,31 @@ GCRuntime::setGCCallback(JSGCCallback ca
 
 void
 GCRuntime::callGCCallback(JSGCStatus status) const
 {
     if (gcCallback.op)
         gcCallback.op(rt, status, gcCallback.data);
 }
 
+void
+GCRuntime::setObjectsTenuredCallback(JSObjectsTenuredCallback callback,
+                                     void* data)
+{
+    tenuredCallback.op = callback;
+    tenuredCallback.data = data;
+}
+
+void
+GCRuntime::callObjectsTenuredCallback()
+{
+    if (tenuredCallback.op)
+        tenuredCallback.op(rt, tenuredCallback.data);
+}
+
 namespace {
 
 class AutoNotifyGCActivity {
   public:
     explicit AutoNotifyGCActivity(GCRuntime& gc) : gc_(gc) {
         if (!gc_.isIncrementalGCInProgress()) {
             gcstats::AutoPhase ap(gc_.stats, gcstats::PHASE_GC_BEGIN);
             gc_.callGCCallback(JSGC_BEGIN);
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -68,16 +68,17 @@
 #include "jsprf.h"
 #include "js/Debug.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsCycleCollector.h"
 #include "nsDOMJSUtils.h"
 #include "nsJSUtils.h"
+#include "nsWrapperCache.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #endif
 
 #include "nsIException.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
@@ -392,16 +393,22 @@ NoteJSChildGrayWrapperShim(void* aData, 
  * purple buffer during every CC, which may contain the last reference to a garbage
  * cycle.)
  */
 
 // NB: This is only used to initialize the participant in
 // CycleCollectedJSRuntime. It should never be used directly.
 static const JSZoneParticipant sJSZoneCycleCollectorGlobal;
 
+static
+void JSObjectsTenuredCb(JSRuntime* aRuntime, void* aData)
+{
+  static_cast<CycleCollectedJSRuntime*>(aData)->JSObjectsTenured(aRuntime);
+}
+
 CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSRuntime* aParentRuntime,
                                                  uint32_t aMaxBytes,
                                                  uint32_t aMaxNurseryBytes)
   : mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal)
   , mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal)
   , mJSRuntime(nullptr)
   , mPrevGCSliceCallback(nullptr)
   , mJSHolders(256)
@@ -425,16 +432,17 @@ CycleCollectedJSRuntime::CycleCollectedJ
   }
 
   if (!JS_AddExtraGCRootsTracer(mJSRuntime, TraceBlackJS, this)) {
     MOZ_CRASH();
   }
   JS_SetGrayGCRootsTracer(mJSRuntime, TraceGrayJS, this);
   JS_SetGCCallback(mJSRuntime, GCCallback, this);
   mPrevGCSliceCallback = JS::SetGCSliceCallback(mJSRuntime, GCSliceCallback);
+  JS_SetObjectsTenuredCallback(mJSRuntime, JSObjectsTenuredCb, this);
   JS::SetOutOfMemoryCallback(mJSRuntime, OutOfMemoryCallback, this);
   JS::SetLargeAllocationFailureCallback(mJSRuntime,
                                         LargeAllocationFailureCallback, this);
   JS_SetContextCallback(mJSRuntime, ContextCallback, this);
   JS_SetDestroyZoneCallback(mJSRuntime, XPCStringConvert::FreeZoneCache);
   JS_SetSweepZoneCallback(mJSRuntime, XPCStringConvert::ClearZoneCache);
 
   static js::DOMCallbacks DOMcallbacks = {
@@ -780,16 +788,21 @@ struct JsGcTracer : public TraceCallback
   {
     JS_CallIdTracer(static_cast<JSTracer*>(aClosure), aPtr, aName);
   }
   virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
                      void* aClosure) const override
   {
     JS_CallObjectTracer(static_cast<JSTracer*>(aClosure), aPtr, aName);
   }
+  virtual void Trace(JSObject** aPtr, const char* aName,
+                     void* aClosure) const override
+  {
+    JS_CallUnbarrieredObjectTracer(static_cast<JSTracer*>(aClosure), aPtr, aName);
+  }
   virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
                      void* aClosure) const override
   {
     JS_CallTenuredObjectTracer(static_cast<JSTracer*>(aClosure), aPtr, aName);
   }
   virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
                      void* aClosure) const override
   {
@@ -847,16 +860,22 @@ struct ClearJSHolder : TraceCallbacks
     *aPtr = JSID_VOID;
   }
 
   virtual void Trace(JS::Heap<JSObject*>* aPtr, const char*, void*) const override
   {
     *aPtr = nullptr;
   }
 
+  virtual void Trace(JSObject** aPtr, const char* aName,
+                     void* aClosure) const override
+  {
+    *aPtr = nullptr;
+  }
+
   virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char*, void*) const override
   {
     *aPtr = nullptr;
   }
 
   virtual void Trace(JS::Heap<JSString*>* aPtr, const char*, void*) const override
   {
     *aPtr = nullptr;
@@ -1004,16 +1023,56 @@ CycleCollectedJSRuntime::GarbageCollect(
   MOZ_ASSERT(aReason < JS::gcreason::NUM_REASONS);
   JS::gcreason::Reason gcreason = static_cast<JS::gcreason::Reason>(aReason);
 
   JS::PrepareForFullGC(mJSRuntime);
   JS::GCForReason(mJSRuntime, GC_NORMAL, gcreason);
 }
 
 void
+CycleCollectedJSRuntime::JSObjectsTenured(JSRuntime* aRuntime)
+{
+  for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
+    nsWrapperCache* cache = iter.Get();
+    JSObject* wrapper = cache->GetWrapperPreserveColor();
+    MOZ_ASSERT(wrapper);
+    if (!JS::ObjectIsTenured(wrapper)) {
+      MOZ_ASSERT(!cache->PreservingWrapper());
+      const JSClass* jsClass = js::GetObjectJSClass(wrapper);
+      jsClass->finalize(nullptr, wrapper);
+    }
+  }
+
+#ifdef DEBUG
+for (auto iter = mPreservedNurseryObjects.Iter(); !iter.Done(); iter.Next()) {
+  MOZ_ASSERT(JS::ObjectIsTenured(iter.Get().get()));
+}
+#endif
+
+  mNurseryObjects.Clear();
+  mPreservedNurseryObjects.Clear();
+}
+
+void
+CycleCollectedJSRuntime::NurseryWrapperAdded(nsWrapperCache* aCache)
+{
+  MOZ_ASSERT(aCache);
+  MOZ_ASSERT(aCache->GetWrapperPreserveColor());
+  MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperPreserveColor()));
+  mNurseryObjects.InfallibleAppend(aCache);
+}
+
+void
+CycleCollectedJSRuntime::NurseryWrapperPreserved(JSObject* aWrapper)
+{
+  mPreservedNurseryObjects.InfallibleAppend(
+    JS::PersistentRooted<JSObject*>(mJSRuntime, aWrapper));
+}
+
+void
 CycleCollectedJSRuntime::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
                                           DeferredFinalizeFunction aFunc,
                                           void* aThing)
 {
   void* thingArray = nullptr;
   bool hadThingArray = mDeferredFinalizerTable.Get(aFunc, &thingArray);
 
   thingArray = aAppendFunc(thingArray, aThing);
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -5,28 +5,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_CycleCollectedJSRuntime_h__
 #define mozilla_CycleCollectedJSRuntime_h__
 
 #include <queue>
 
 #include "mozilla/DeferredFinalize.h"
+#include "mozilla/mozalloc.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/SegmentedVector.h"
 #include "jsapi.h"
 
 #include "nsCycleCollectionParticipant.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 
 class nsCycleCollectionNoteRootCallback;
 class nsIException;
 class nsIRunnable;
 class nsThread;
+class nsWrapperCache;
 
 namespace js {
 struct Class;
 } // namespace js
 
 namespace mozilla {
 
 class JSGCThingParticipant: public nsCycleCollectionParticipant
@@ -275,16 +278,20 @@ public:
   nsCycleCollectionParticipant* ZoneParticipant();
 
   nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb);
   bool UsefulToMergeZones() const;
   void FixWeakMappingGrayBits() const;
   bool AreGCGrayBitsValid() const;
   void GarbageCollect(uint32_t aReason) const;
 
+  void NurseryWrapperAdded(nsWrapperCache* aCache);
+  void NurseryWrapperPreserved(JSObject* aWrapper);
+  void JSObjectsTenured(JSRuntime* aRuntime);
+
   void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
                         DeferredFinalizeFunction aFunc,
                         void* aThing);
   void DeferredFinalize(nsISupports* aSupports);
 
   void DumpJSHeap(FILE* aFile);
 
   virtual void PrepareForForgetSkippable() = 0;
@@ -356,16 +363,23 @@ private:
 
   nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
   nsTArray<RunInMetastableStateData> mMetastableStateEvents;
   uint32_t mBaseRecursionDepth;
   bool mDoingStableStates;
 
   OOMState mOutOfMemoryState;
   OOMState mLargeAllocationFailureState;
+
+  static const size_t kSegmentSize = 512;
+  SegmentedVector<nsWrapperCache*, kSegmentSize, InfallibleAllocPolicy>
+    mNurseryObjects;
+  SegmentedVector<JS::PersistentRooted<JSObject*>, kSegmentSize,
+                  InfallibleAllocPolicy>
+    mPreservedNurseryObjects;
 };
 
 void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer);
 
 // Returns true if the JS::TraceKind is one the cycle collector cares about.
 inline bool AddToCCKind(JS::TraceKind aKind)
 {
   return aKind == JS::TraceKind::Object || aKind == JS::TraceKind::Script;
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -2672,61 +2672,67 @@ public:
   }
 
   bool HasSnowWhiteObjects() const
   {
     return !mObjects.IsEmpty();
   }
 
   virtual void Trace(JS::Heap<JS::Value>* aValue, const char* aName,
-                     void* aClosure) const
+                     void* aClosure) const override
   {
     if (aValue->isMarkable() && ValueIsGrayCCThing(*aValue)) {
       MOZ_ASSERT(!js::gc::IsInsideNursery(aValue->toGCThing()));
       mCollector->GetJSPurpleBuffer()->mValues.InfallibleAppend(*aValue);
     }
   }
 
   virtual void Trace(JS::Heap<jsid>* aId, const char* aName,
-                     void* aClosure) const
+                     void* aClosure) const override
   {
   }
 
   void AppendJSObjectToPurpleBuffer(JSObject* obj) const
   {
     if (obj && JS::ObjectIsMarkedGray(obj)) {
       MOZ_ASSERT(JS::ObjectIsTenured(obj));
       mCollector->GetJSPurpleBuffer()->mObjects.InfallibleAppend(obj);
     }
   }
 
   virtual void Trace(JS::Heap<JSObject*>* aObject, const char* aName,
-                     void* aClosure) const
+                     void* aClosure) const override
+  {
+    AppendJSObjectToPurpleBuffer(*aObject);
+  }
+
+  virtual void Trace(JSObject** aObject, const char* aName,
+                     void* aClosure) const override
   {
     AppendJSObjectToPurpleBuffer(*aObject);
   }
 
   virtual void Trace(JS::TenuredHeap<JSObject*>* aObject, const char* aName,
-                     void* aClosure) const
+                     void* aClosure) const override
   {
     AppendJSObjectToPurpleBuffer(*aObject);
   }
 
   virtual void Trace(JS::Heap<JSString*>* aString, const char* aName,
-                     void* aClosure) const
+                     void* aClosure) const override
   {
   }
 
   virtual void Trace(JS::Heap<JSScript*>* aScript, const char* aName,
-                     void* aClosure) const
+                     void* aClosure) const override
   {
   }
 
   virtual void Trace(JS::Heap<JSFunction*>* aFunction, const char* aName,
-                     void* aClosure) const
+                     void* aClosure) const override
   {
   }
 
 private:
   RefPtr<nsCycleCollector> mCollector;
   ObjectsVector mObjects;
 };
 
--- a/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp
+++ b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp
@@ -57,16 +57,23 @@ TraceCallbackFunc::Trace(JS::Heap<jsid>*
 void
 TraceCallbackFunc::Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
                          void* aClosure) const
 {
   mCallback(JS::GCCellPtr(aPtr->get()), aName, aClosure);
 }
 
 void
+TraceCallbackFunc::Trace(JSObject** aPtr, const char* aName,
+                         void* aClosure) const
+{
+  mCallback(JS::GCCellPtr(*aPtr), aName, aClosure);
+}
+
+void
 TraceCallbackFunc::Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
                          void* aClosure) const
 {
   mCallback(JS::GCCellPtr(aPtr->getPtr()), aName, aClosure);
 }
 
 void
 TraceCallbackFunc::Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
--- a/xpcom/glue/nsCycleCollectionParticipant.h
+++ b/xpcom/glue/nsCycleCollectionParticipant.h
@@ -59,16 +59,18 @@ template<class T> class Heap;
 struct TraceCallbacks
 {
   virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
                      void* aClosure) const = 0;
   virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
                      void* aClosure) const = 0;
   virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
                      void* aClosure) const = 0;
+  virtual void Trace(JSObject** aPtr, const char* aName,
+                     void* aClosure) const = 0;
   virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
                      void* aClosure) const = 0;
   virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
                      void* aClosure) const = 0;
   virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
                      void* aClosure) const = 0;
   virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
                      void* aClosure) const = 0;
@@ -85,16 +87,18 @@ struct TraceCallbackFunc : public TraceC
   explicit TraceCallbackFunc(Func aCb) : mCallback(aCb) {}
 
   virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
                      void* aClosure) const override;
   virtual void Trace(JS::Heap<jsid>* aPtr, const char* aName,
                      void* aClosure) const override;
   virtual void Trace(JS::Heap<JSObject*>* aPtr, const char* aName,
                      void* aClosure) const override;
+  virtual void Trace(JSObject** aPtr, const char* aName,
+                     void* aClosure) const override;
   virtual void Trace(JS::TenuredHeap<JSObject*>* aPtr, const char* aName,
                      void* aClosure) const override;
   virtual void Trace(JS::Heap<JSString*>* aPtr, const char* aName,
                      void* aClosure) const override;
   virtual void Trace(JS::Heap<JSScript*>* aPtr, const char* aName,
                      void* aClosure) const override;
   virtual void Trace(JS::Heap<JSFunction*>* aPtr, const char* aName,
                      void* aClosure) const override;