Fix for bug 791347 (Support non-nsISupports refcounted natives and non-refcounted natives in new DOM bindings). r=bz/smaug.
authorPeter Van der Beken <peterv@propagandism.org>
Wed, 19 Sep 2012 15:02:37 +0200
changeset 108848 6362581f442e31019d0c147edb958a9fbe47d298
parent 108847 1caa3c482541911876778e6f1c26e95975a5419c
child 108849 f4c544d409bdcee68326c74775313ce72c573858
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersbz, smaug
bugs791347
milestone18.0a1
Fix for bug 791347 (Support non-nsISupports refcounted natives and non-refcounted natives in new DOM bindings). r=bz/smaug.
dom/bindings/BindingUtils.h
dom/bindings/Bindings.conf
dom/bindings/Codegen.py
dom/bindings/Configuration.py
dom/bindings/Makefile.in
dom/bindings/NonRefcountedDOMObject.h
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -4,16 +4,17 @@
  * 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/. */
 
 #ifndef mozilla_dom_BindingUtils_h__
 #define mozilla_dom_BindingUtils_h__
 
 #include "mozilla/dom/DOMJSClass.h"
 #include "mozilla/dom/DOMJSProxyHandler.h"
+#include "mozilla/dom/NonRefcountedDOMObject.h"
 #include "mozilla/dom/workers/Workers.h"
 #include "mozilla/ErrorResult.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "jswrapper.h"
 
 #include "nsIXPConnect.h"
@@ -1140,12 +1141,33 @@ XrayEnumerateProperties(JS::AutoIdVector
                         jsid* attributeIds,
                         JSPropertySpec* attributeSpecs,
                         size_t attributeCount,
                         Prefable<ConstantSpec>* constants,
                         jsid* constantIds,
                         ConstantSpec* constantSpecs,
                         size_t constantCount);
 
+// Transfer reference in ptr to smartPtr.
+template<class T>
+inline void
+Take(nsRefPtr<T>& smartPtr, T* ptr)
+{
+  smartPtr = dont_AddRef(ptr);
+}
+
+// Transfer ownership of ptr to smartPtr.
+template<class T>
+inline void
+Take(nsAutoPtr<T>& smartPtr, T* ptr)
+{
+  smartPtr = ptr;
+}
+
+inline void
+MustInheritFromNonRefcountedDOMObject(NonRefcountedDOMObject*)
+{
+}
+
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_BindingUtils_h__ */
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -49,16 +49,33 @@
 #                   dict). The keys are the property names as they appear in the
 #                   .webidl file and the values are the names as they should be
 #                   in the WebIDL.
 #   * wrapperCache: True if this object is a wrapper cache.  Objects that are
 #                   not can only be returned from a limited set of methods,
 #                   cannot be prefable, and must ensure that they disallow
 #                   XPConnect wrapping.  Always true for worker descriptors.
 #                   Defaults to true.
+#   * nativeOwnership: Describes how the native object is held. 4 possible
+#                      types: worker object ("worker"), non-refcounted object
+#                      ("owned"), refcounted non-nsISupports object
+#                      ("refcounted") or nsISupports ("nsisupports").
+#                      Non-refcounted objects need to inherit from
+#                      mozilla::dom::NonRefcountedDOMObject and preferably use
+#                      MOZ_COUNT_CTOR/MOZ_COUNT_DTOR in their
+#                      constructor/destructor so they participate in leak
+#                      logging.
+#                      This mostly determines how the finalizer releases the
+#                      binding's hold on the native object. For a worker object
+#                      it'll call Release, for a non-refcounted object it'll
+#                      call delete through XPConnect's deferred finalization
+#                      mechanism, for a refcounted object it'll call Release
+#                      through XPConnect's deferred finalization mechanism.
+#                      Always "worker" for worker descriptors. Defaults to
+#                      "nsisupports".
 #
 #   The following fields are either a string, an array (defaults to an empty
 #   array) or a dictionary with three possible keys (all, getterOnly and
 #   setterOnly) each having such an array as the value
 #
 #   * implicitJSContext - attributes and methods specified in the .webidl file
 #                         that require a JSContext as the first argument
 #   * resultNotAddRefed - attributes and methods specified in the .webidl file
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -91,17 +91,17 @@ def DOMClass(descriptor):
         # is never the ID of any prototype, so it's safe to use as
         # padding.
         protoList.extend(['prototypes::id::_ID_Count'] * (descriptor.config.maxProtoChainLength - len(protoList)))
         prototypeChainString = ', '.join(protoList)
         nativeHooks = "NULL" if descriptor.workers else "&NativeHooks"
         return """{
   { %s },
   %s, %s
-}""" % (prototypeChainString, toStringBool(descriptor.nativeIsISupports),
+}""" % (prototypeChainString, toStringBool(descriptor.nativeOwnership == 'nsisupports'),
           nativeHooks)
 
 class CGDOMJSClass(CGThing):
     """
     Generate a DOMJSClass for a given descriptor
     """
     def __init__(self, descriptor):
         CGThing.__init__(self)
@@ -629,36 +629,106 @@ class CGAddPropertyHook(CGAbstractClassH
                 Argument('JSHandleId', 'id'), Argument('JSMutableHandleValue', 'vp')]
         CGAbstractClassHook.__init__(self, descriptor, ADDPROPERTY_HOOK_NAME,
                                      'JSBool', args)
 
     def generate_code(self):
         # FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=774279
         # Using a real trace hook might enable us to deal with non-nsISupports
         # wrappercached things here.
-        assert self.descriptor.nativeIsISupports
+        assert self.descriptor.nativeOwnership == 'nsisupports'
         return """  nsContentUtils::PreserveWrapper(reinterpret_cast<nsISupports*>(self), self);
   return true;"""
 
+def DeferredFinalizeSmartPtr(descriptor):
+    if descriptor.nativeOwnership == 'owned':
+        smartPtr = 'nsAutoPtr<%s>'
+    else:
+        assert descriptor.nativeOwnership == 'refcounted'
+        smartPtr = 'nsRefPtr<%s>'
+    return smartPtr % descriptor.nativeType
+
+class CGDeferredFinalizePointers(CGThing):
+    def __init__(self, descriptor):
+        CGThing.__init__(self)
+        self.descriptor = descriptor
+
+    def declare(self):
+        return ""
+
+    def define(self):
+        return """nsTArray<%s >* sDeferredFinalizePointers;
+""" % DeferredFinalizeSmartPtr(self.descriptor)
+
+class CGGetDeferredFinalizePointers(CGAbstractStaticMethod):
+    def __init__(self, descriptor):
+        CGAbstractStaticMethod.__init__(self, descriptor, "GetDeferredFinalizePointers", "void*", [])
+
+    def definition_body(self):
+        return """  nsTArray<%s >* pointers = sDeferredFinalizePointers;
+  sDeferredFinalizePointers = nullptr;
+  return pointers;""" % DeferredFinalizeSmartPtr(self.descriptor)
+
+class CGDeferredFinalize(CGAbstractStaticMethod):
+    def __init__(self, descriptor):
+        CGAbstractStaticMethod.__init__(self, descriptor, "DeferredFinalize", "bool", [Argument('int32_t', 'slice'), Argument('void*', 'data')])
+
+    def definition_body(self):
+        smartPtr = DeferredFinalizeSmartPtr(self.descriptor)
+        return """  nsTArray<%(smartPtr)s >* pointers = static_cast<nsTArray<%(smartPtr)s >*>(data);
+  uint32_t oldLen = pointers->Length();
+  if (slice == -1 || slice > oldLen) {
+    slice = oldLen;
+  }
+  uint32_t newLen = oldLen - slice;
+  pointers->RemoveElementsAt(newLen, slice);
+  if (newLen == 0) {
+    delete pointers;
+    return true;
+  }
+  return false;""" % { 'smartPtr': smartPtr }
+
 def finalizeHook(descriptor, hookName, context):
     if descriptor.customFinalize:
         return """if (self) {
   self->%s(%s);
 }""" % (hookName, context)
     clearWrapper = "ClearWrapper(self, self);\n" if descriptor.wrapperCache else ""
     if descriptor.workers:
         release = "self->Release();"
-    else:
-        assert descriptor.nativeIsISupports
+    elif descriptor.nativeOwnership == 'nsisupports':
         release = """XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance();
 if (rt) {
   rt->DeferredRelease(reinterpret_cast<nsISupports*>(self));
 } else {
   NS_RELEASE(self);
 }"""
+    else:
+        smartPtr = DeferredFinalizeSmartPtr(descriptor)
+        release = """static bool registered = false;
+if (!registered) {
+  XPCJSRuntime *rt = nsXPConnect::GetRuntimeInstance();
+  if (!rt) {
+    %(smartPtr)s dying;
+    Take(dying, self);
+    return;
+  }
+  rt->RegisterDeferredFinalize(GetDeferredFinalizePointers, DeferredFinalize);
+  registered = true;
+}
+if (!sDeferredFinalizePointers) {
+  sDeferredFinalizePointers = new nsAutoTArray<%(smartPtr)s, 16>();
+}
+%(smartPtr)s* defer = sDeferredFinalizePointers->AppendElement();
+if (!defer) {
+  %(smartPtr)s dying;
+  Take(dying, self);
+  return;
+}
+Take(*defer, self);""" % { 'smartPtr': smartPtr }
     return clearWrapper + release
 
 class CGClassFinalizeHook(CGAbstractClassHook):
     """
     A hook for finalize, used to release our native object.
     """
     def __init__(self, descriptor):
         args = [Argument('JSFreeOp*', 'fop'), Argument('JSObject*', 'obj')]
@@ -738,16 +808,17 @@ class CGClassHasInstanceHook(CGAbstractS
         if not self.descriptor.hasInstanceInterface:
             return ""
         return CGAbstractStaticMethod.define(self)
 
     def definition_body(self):
         return self.generate_code()
 
     def generate_code(self):
+        assert self.descriptor.nativeOwnership == 'nsisupports'
         return """  if (!vp.isObject()) {
     *bp = false;
     return true;
   }
 
   jsval protov;
   if (!JS_GetProperty(cx, obj, "prototype", &protov))
     return false;
@@ -956,17 +1027,17 @@ class MethodDefiner(PropertyDefiner):
                                 "pref": None })
             self.regular.append({"name": 'iterator',
                                  "methodInfo": False,
                                  "nativeName": "JS_ArrayIterator",
                                  "length": 0,
                                  "flags": "JSPROP_ENUMERATE",
                                  "pref": None })
 
-        if not descriptor.interface.parent and not static and not descriptor.workers:
+        if not descriptor.interface.parent and not static and descriptor.nativeOwnership == 'nsisupports':
             self.chrome.append({"name": 'QueryInterface',
                                 "methodInfo": False,
                                 "length": 1,
                                 "flags": "0",
                                 "pref": None })
 
         if static:
             if not descriptor.interface.hasInterfaceObject():
@@ -1381,16 +1452,25 @@ def CreateBindingJSObject(descriptor, pa
     else:
         create = """  JSObject* obj = JS_NewObject(aCx, &Class.mBase, proto, %s);
   if (!obj) {
     return NULL;
   }
 
   js::SetReservedSlot(obj, DOM_OBJECT_SLOT, PRIVATE_TO_JSVAL(aObject));
 """
+    if descriptor.nativeOwnership in ['refcounted', 'nsisupports']:
+        create += """  NS_ADDREF(aObject);
+"""
+    else:
+        assert descriptor.nativeOwnership == 'owned'
+        create += """  // Make sure the native objects inherit from NonRefcountedDOMObject so that we
+  // log their ctor and dtor.
+  MustInheritFromNonRefcountedDOMObject(aObject);
+"""
     return create % parent
 
 class CGWrapWithCacheMethod(CGAbstractMethod):
     def __init__(self, descriptor):
         assert descriptor.interface.hasInterfacePrototypeObject()
         args = [Argument('JSContext*', 'aCx'), Argument('JSObject*', 'aScope'),
                 Argument(descriptor.nativeType + '*', 'aObject'),
                 Argument('nsWrapperCache*', 'aCache'),
@@ -1413,17 +1493,16 @@ class CGWrapWithCacheMethod(CGAbstractMe
   JSObject* global = JS_GetGlobalForObject(aCx, parent);
 %s
   JSObject* proto = GetProtoObject(aCx, global, global);
   if (!proto) {
     return NULL;
   }
 
 %s
-  NS_ADDREF(aObject);
 
   aCache->SetWrapper(obj);
 
   return obj;""" % (CheckPref(self.descriptor, "global", "*aTriedToWrap", "NULL", "aCache"),
                     CreateBindingJSObject(self.descriptor, "parent"))
 
 class CGWrapMethod(CGAbstractMethod):
     def __init__(self, descriptor):
@@ -1448,17 +1527,16 @@ class CGWrapNonWrapperCacheMethod(CGAbst
         return """
   JSObject* global = JS_GetGlobalForObject(aCx, aScope);
   JSObject* proto = GetProtoObject(aCx, global, global);
   if (!proto) {
     return NULL;
   }
 
 %s
-  NS_ADDREF(aObject);
 
   return obj;""" % CreateBindingJSObject(self.descriptor, "global")
 
 builtinNames = {
     IDLType.Tags.bool: 'bool',
     IDLType.Tags.int8: 'int8_t',
     IDLType.Tags.int16: 'int16_t',
     IDLType.Tags.int32: 'int32_t',
@@ -5100,27 +5178,33 @@ class CGDescriptor(CGThing):
             if hasMethod: cgThings.append(CGGenericMethod(descriptor))
             if hasGetter: cgThings.append(CGGenericGetter(descriptor))
             if hasLenientGetter: cgThings.append(CGGenericGetter(descriptor,
                                                                  lenientThis=True))
             if hasSetter: cgThings.append(CGGenericSetter(descriptor))
             if hasLenientSetter: cgThings.append(CGGenericSetter(descriptor,
                                                                  lenientThis=True))
 
-        if descriptor.concrete and not descriptor.proxy:
-            if not descriptor.workers and descriptor.wrapperCache:
-                cgThings.append(CGAddPropertyHook(descriptor))
-
-            # Always have a finalize hook, regardless of whether the class wants a
-            # custom hook.
-            cgThings.append(CGClassFinalizeHook(descriptor))
-
-            # Only generate a trace hook if the class wants a custom hook.
-            if (descriptor.customTrace):
-                cgThings.append(CGClassTraceHook(descriptor))
+        if descriptor.concrete:
+            if descriptor.nativeOwnership == 'owned' or descriptor.nativeOwnership == 'refcounted':
+                cgThings.append(CGDeferredFinalizePointers(descriptor))
+                cgThings.append(CGGetDeferredFinalizePointers(descriptor))
+                cgThings.append(CGDeferredFinalize(descriptor))
+
+            if not descriptor.proxy:
+                if not descriptor.workers and descriptor.wrapperCache:
+                    cgThings.append(CGAddPropertyHook(descriptor))
+
+                # Always have a finalize hook, regardless of whether the class
+                # wants a custom hook.
+                cgThings.append(CGClassFinalizeHook(descriptor))
+
+                # Only generate a trace hook if the class wants a custom hook.
+                if (descriptor.customTrace):
+                    cgThings.append(CGClassTraceHook(descriptor))
 
         if descriptor.interface.hasInterfaceObject():
             cgThings.append(CGClassConstructHook(descriptor))
             cgThings.append(CGClassHasInstanceHook(descriptor))
             cgThings.append(CGInterfaceObjectJSClass(descriptor))
 
         if descriptor.interface.hasInterfacePrototypeObject():
             cgThings.append(CGPrototypeJSClass(descriptor))
@@ -5603,17 +5687,18 @@ class CGBindingRoot(CGThing):
                       "\n")
 
         # Add header includes.
         curr = CGHeaders(descriptors,
                          dictionaries,
                          ['mozilla/dom/BindingUtils.h',
                           'mozilla/dom/DOMJSClass.h',
                           'mozilla/dom/DOMJSProxyHandler.h'],
-                         ['mozilla/dom/Nullable.h',
+                         ['mozilla/dom/NonRefcountedDOMObject.h',
+                          'mozilla/dom/Nullable.h',
                           'PrimitiveConversions.h',
                           'XPCQuickStubs.h',
                           'nsDOMQS.h',
                           'AccessCheck.h',
                           'WorkerPrivate.h',
                           'nsContentUtils.h',
                           'mozilla/Preferences.h',
                           # Have to include nsDOMQS.h to get fast arg unwrapping
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -222,17 +222,28 @@ class Descriptor(DescriptorProvider):
                     iface.setUserData('hasProxyDescendant', True)
                     iface = iface.parent
 
         if self.interface.isExternal() and 'prefable' in desc:
             raise TypeError("%s is external but has a prefable setting" %
                             self.interface.identifier.name)
         self.prefable = desc.get('prefable', False)
 
-        self.nativeIsISupports = not self.workers
+        if self.workers:
+            if desc.get('nativeOwnership', 'worker') != 'worker':
+                raise TypeError("Worker descriptor for %s should have 'worker' "
+                                "as value for nativeOwnership" %
+                                self.interface.identifier.name)
+            self.nativeOwnership = "worker"
+        else:
+            self.nativeOwnership = desc.get('nativeOwnership', 'nsisupports')
+            if not self.nativeOwnership in ['owned', 'refcounted', 'nsisupports']:
+                raise TypeError("Descriptor for %s has unrecognized value (%s) "
+                                "for nativeOwnership" %
+                                (self.interface.identifier.name, self.nativeOwnership))
         self.customTrace = desc.get('customTrace', self.workers)
         self.customFinalize = desc.get('customFinalize', self.workers)
         self.wrapperCache = self.workers or desc.get('wrapperCache', True)
 
         if not self.wrapperCache and self.prefable:
             raise TypeError("Descriptor for %s is prefable but not wrappercached" %
                             self.interface.identifier.name)
 
--- a/dom/bindings/Makefile.in
+++ b/dom/bindings/Makefile.in
@@ -53,16 +53,17 @@ EXPORTS_mozilla = \
   ErrorResult.h \
   $(NULL)
 
 EXPORTS_$(binding_include_path) = \
   BindingUtils.h \
   DOMJSClass.h \
   DOMJSProxyHandler.h \
   Errors.msg \
+  NonRefcountedDOMObject.h \
   Nullable.h \
   PrimitiveConversions.h \
   PrototypeList.h \
   RegisterBindings.h \
   TypedArray.h \
   UnionConversions.h \
   UnionTypes.h \
   $(exported_binding_headers) \
new file mode 100644
--- /dev/null
+++ b/dom/bindings/NonRefcountedDOMObject.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* vim: set ts=2 sw=2 et tw=79: */
+/* 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/. */
+
+#ifndef mozilla_dom_NonRefcountedDOMObject_h__
+#define mozilla_dom_NonRefcountedDOMObject_h__
+
+#include "nsTraceRefcnt.h"
+
+namespace mozilla {
+namespace dom {
+
+// Natives for DOM classes with 'owned' as the value for nativeOwnership in
+// Bindings.conf need to inherit from this class.
+// If you're seeing objects of this class leak then natives for one of the DOM
+// classes with 'owned' as the value for nativeOwnership in Bindings.conf is
+// leaking. If the native for that class has MOZ_COUNT_CTOR/DTOR in its
+// constructor/destructor then it should show up in the leak log too.
+class NonRefcountedDOMObject
+{
+protected:
+  NonRefcountedDOMObject()
+  {
+    MOZ_COUNT_CTOR(NonRefcountedDOMObject);
+  }
+  ~NonRefcountedDOMObject()
+  {
+    MOZ_COUNT_DTOR(NonRefcountedDOMObject);
+  }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_NonRefcountedDOMObject_h__ */
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -523,77 +523,129 @@ DoDeferredRelease(nsTArray<T> &array)
             break;
         }
         T wrapper = array[count-1];
         array.RemoveElementAt(count-1);
         NS_RELEASE(wrapper);
     }
 }
 
+struct DeferredFinalizeFunction
+{
+  XPCJSRuntime::DeferredFinalizeFunction run;
+  void* data;
+};
+
 class XPCIncrementalReleaseRunnable : public nsRunnable
 {
     XPCJSRuntime *runtime;
     nsTArray<nsISupports *> items;
+    nsAutoTArray<DeferredFinalizeFunction, 16> deferredFinalizeFunctions;
+    uint32_t finalizeFunctionToRun;
 
     static const PRTime SliceMillis = 10; /* ms */
 
   public:
     XPCIncrementalReleaseRunnable(XPCJSRuntime *rt, nsTArray<nsISupports *> &items);
     virtual ~XPCIncrementalReleaseRunnable();
 
     void ReleaseNow(bool limited);
 
     NS_DECL_NSIRUNNABLE
 };
 
+bool
+ReleaseSliceNow(int32_t slice, void* data)
+{
+    nsTArray<nsISupports *>* items =
+        static_cast<nsTArray<nsISupports *>*>(data);
+    uint32_t counter = 0;
+    while (1) {
+        uint32_t count = items->Length();
+        if (!count) {
+            break;
+        }
+
+        nsISupports *wrapper = items->ElementAt(count - 1);
+        items->RemoveElementAt(count - 1);
+        NS_RELEASE(wrapper);
+
+        if (slice > 0 && ++counter == slice) {
+            return items->IsEmpty();
+        }
+    }
+
+    return true;
+}
+
+
 XPCIncrementalReleaseRunnable::XPCIncrementalReleaseRunnable(XPCJSRuntime *rt,
                                                              nsTArray<nsISupports *> &items)
-  : runtime(rt)
+  : runtime(rt),
+    finalizeFunctionToRun(0)
 {
     nsLayoutStatics::AddRef();
     this->items.SwapElements(items);
+    DeferredFinalizeFunction* function =
+        deferredFinalizeFunctions.AppendElement();
+    function->run = ReleaseSliceNow;
+    function->data = &this->items;
+    for (uint32_t i = 0; i < rt->mDeferredFinalizeFunctions.Length(); ++i) {
+        void* data = (rt->mDeferredFinalizeFunctions[i].start)();
+        if (data) {
+            function = deferredFinalizeFunctions.AppendElement();
+            function->run = rt->mDeferredFinalizeFunctions[i].run;
+            function->data = data;
+        }
+    }
 }
 
 XPCIncrementalReleaseRunnable::~XPCIncrementalReleaseRunnable()
 {
     MOZ_ASSERT(this != runtime->mReleaseRunnable);
     nsLayoutStatics::Release();
 }
 
 void
 XPCIncrementalReleaseRunnable::ReleaseNow(bool limited)
 {
     MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(deferredFinalizeFunctions.Length() != 0,
+               "We should have at least ReleaseSliceNow to run");
+    MOZ_ASSERT(finalizeFunctionToRun < deferredFinalizeFunctions.Length(),
+               "No more finalizers to run?");
 
     TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis);
     TimeStamp started = TimeStamp::Now();
-    uint32_t counter = 0;
-    while (1) {
-        uint32_t count = items.Length();
-        if (!count)
-            break;
-
-        nsISupports *wrapper = items[count - 1];
-        items.RemoveElementAt(count - 1);
-        NS_RELEASE(wrapper);
-
+    bool timeout = false;
+    do {
+        const DeferredFinalizeFunction& function =
+            deferredFinalizeFunctions[finalizeFunctionToRun];
         if (limited) {
-            /* We don't want to read the clock too often. */
-            counter++;
-            if (counter == 100) {
-                counter = 0;
-                if (TimeStamp::Now() - started >= sliceTime)
-                    break;
+            bool done = false;
+            while (!timeout && !done) {
+                /* We don't want to read the clock too often, so we try to
+                   release slices of 100 items. */
+                done = function.run(100, function.data);
+                timeout = TimeStamp::Now() - started >= sliceTime;
+            }
+            if (done) {
+                ++finalizeFunctionToRun;
             }
+            if (timeout) {
+                break;
+            }
+        } else {
+            function.run(-1, function.data);
+            MOZ_ASSERT(!items.Length());
+            ++finalizeFunctionToRun;
         }
-    }
+    } while (finalizeFunctionToRun < deferredFinalizeFunctions.Length());
 
-    MOZ_ASSERT_IF(items.Length(), limited);
-
-    if (!items.Length()) {
+    if (finalizeFunctionToRun == deferredFinalizeFunctions.Length()) {
         MOZ_ASSERT(runtime->mReleaseRunnable == this);
         runtime->mReleaseRunnable = nullptr;
     }
 }
 
 NS_IMETHODIMP
 XPCIncrementalReleaseRunnable::Run()
 {
@@ -670,21 +722,27 @@ XPCJSRuntime::GCCallback(JSRuntime *rt, 
              * don't want these to build up. We also don't want to allow any
              * existing incremental release runnables to run after a
              * non-incremental GC, since they are often used to detect leaks.
              */
             if (self->mReleaseRunnable)
                 self->mReleaseRunnable->ReleaseNow(false);
 
             // Do any deferred releases of native objects.
-            if (js::WasIncrementalGC(rt))
+            if (js::WasIncrementalGC(rt)) {
                 self->ReleaseIncrementally(self->mNativesToReleaseArray);
-            else
+            } else {
                 DoDeferredRelease(self->mNativesToReleaseArray);
-
+                for (uint32_t i = 0; i < self->mDeferredFinalizeFunctions.Length(); ++i) {
+                    void* data = self->mDeferredFinalizeFunctions[i].start();
+                    if (data) {
+                        self->mDeferredFinalizeFunctions[i].run(-1, data);
+                    }
+                }
+            }
             self->GetXPConnect()->ClearGCBeforeCC();
             break;
         }
     }
 
     nsTArray<JSGCCallback> callbacks(self->extraGCCallbacks);
     for (uint32_t i = 0; i < callbacks.Length(); ++i)
         callbacks[i](rt, status);
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -722,16 +722,55 @@ public:
         {return mCompartmentSet;}
 
     XPCLock* GetMapLock() const {return mMapLock;}
 
     JSBool OnJSContextNew(JSContext* cx);
 
     bool DeferredRelease(nsISupports* obj);
 
+
+    /**
+     * Infrastructure for classes that need to defer part of the finalization
+     * until after the GC has run, for example for objects that we don't want to
+     * destroy during the GC.
+     */
+
+    // Called once before the deferred finalization starts. Should hand off the
+    // buffer with things to finalize in the return value.
+    typedef void* (*DeferredFinalizeStartFunction)();
+
+    // Called to finalize a number of objects. Slice is the number of objects to
+    // finalize, if it's -1 all objects should be finalized. data is the pointer
+    // returned by DeferredFinalizeStartFunction. Should return if it finalized
+    // all objects remaining in the buffer.
+    typedef bool (*DeferredFinalizeFunction)(int32_t slice, void* data);
+
+private:
+    struct DeferredFinalizeFunctions
+    {
+        DeferredFinalizeStartFunction start;
+        DeferredFinalizeFunction run;
+    };
+    nsAutoTArray<DeferredFinalizeFunctions, 16> mDeferredFinalizeFunctions;
+
+public:
+    // Register deferred finalization functions. Should only be called once per
+    // pair of start and run.
+    bool RegisterDeferredFinalize(DeferredFinalizeStartFunction start,
+                                  DeferredFinalizeFunction run)
+    {
+        DeferredFinalizeFunctions* item =
+            mDeferredFinalizeFunctions.AppendElement();
+        item->start = start;
+        item->run = run;
+        return true;
+    }
+
+
     JSBool GetDoingFinalization() const {return mDoingFinalization;}
 
     // Mapping of often used strings to jsid atoms that live 'forever'.
     //
     // To add a new string: add to this list and to XPCJSRuntime::mStrings
     // at the top of xpcjsruntime.cpp
     enum {
         IDX_CONSTRUCTOR             = 0 ,