Bug 1273251: Part 4 - Drop CallbackObject's JS objects for nuked compartments during CC. r?peterv,mccr8 draft
authorKris Maglione <maglione.k@gmail.com>
Mon, 14 Nov 2016 19:49:28 -0800
changeset 440650 a4208140ef07713fe6259b4d3e62a43ad0e39d41
parent 440649 30b422627c215456cd5cbdc01d6be2edae05b183
child 440651 fc8916a9983f427cfc45e39b4d54c34349846d4e
push id36285
push usermaglione.k@gmail.com
push dateThu, 17 Nov 2016 23:17:43 +0000
reviewerspeterv, mccr8
bugs1273251
milestone52.0a1
Bug 1273251: Part 4 - Drop CallbackObject's JS objects for nuked compartments during CC. r?peterv,mccr8 MozReview-Commit-ID: 6lPdmUtKREt
dom/bindings/CallbackObject.cpp
dom/bindings/CallbackObject.h
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -31,26 +31,62 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(Callback
 NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject)
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject)
   tmp->DropJSObjects();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(CallbackObject)
+  // If our callback has been cleared, we can't be part of a garbage cycle.
+  return !tmp->mCallback;
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(CallbackObject)
+  // mCallback is always wrapped for the CallbackObject's incumbent global. In
+  // the case where the real callback is in a different compartment, we have a
+  // cross-compartment wrapper, and it will automatically be cut when its
+  // compartment is nuked. In the case where it is in the same compartment, we
+  // have a reference to the real function. Since that means there are no
+  // wrappers to cut, we need to check whether the compartment is still alive,
+  // and drop the references if it is not.
+
+  JSObject* callback = tmp->CallbackPreserveColor();
+  if (MOZ_UNLIKELY(!callback)) {
+    return true;
+  }
+  auto pvt = xpc::CompartmentPrivate::Get(callback);
+  if (tmp->mIncumbentGlobal && MOZ_UNLIKELY(pvt && pvt->wasNuked)) {
+    // It's not safe to release our global reference or drop our JS objects at
+    // this point, so defer their finalization until GC is finished.
+    nsCOMPtr<nsISupports> finalizer = new JSObjectsDropper(tmp);
+    DeferredFinalize(finalizer.forget().take());
+    DeferredFinalize(tmp->mIncumbentGlobal.forget().take());
+    return true;
+  }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(CallbackObject)
+  return !tmp->mCallback;
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCreationStack)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
+NS_IMPL_ISUPPORTS0(CallbackObject::JSObjectsDropper)
+
 void
 CallbackObject::Trace(JSTracer* aTracer)
 {
   JS::TraceEdge(aTracer, &mCallback, "CallbackObject.mCallback");
   JS::TraceEdge(aTracer, &mCreationStack, "CallbackObject.mCreationStack");
   JS::TraceEdge(aTracer, &mIncumbentJSGlobal,
                 "CallbackObject.mIncumbentJSGlobal");
 }
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -41,17 +41,17 @@ namespace dom {
  { 0x84, 0xb9, 0x65, 0x06, 0x99, 0xe6, 0x93, 0x2b } }
 
 class CallbackObject : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(DOM_CALLBACKOBJECT_IID)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackObject)
+  NS_DECL_CYCLE_COLLECTION_SKIPPABLE_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.
@@ -174,16 +174,35 @@ protected:
       return this == &aOther;
     }
 
     JSObject* thisObj = js::UncheckedUnwrap(wrappedThis);
     JSObject* otherObj = js::UncheckedUnwrap(wrappedOther);
     return thisObj == otherObj;
   }
 
+  class JSObjectsDropper final : public nsISupports
+  {
+  public:
+    explicit JSObjectsDropper(CallbackObject *aHolder)
+      : mHolder(aHolder)
+    {}
+
+    NS_DECL_ISUPPORTS
+
+  protected:
+    virtual ~JSObjectsDropper()
+    {
+      mHolder->DropJSObjects();
+    }
+
+  private:
+    RefPtr<CallbackObject> mHolder;
+  };
+
 private:
   inline void InitNoHold(JSObject* aCallback, JSObject* aCreationStack,
                          nsIGlobalObject* aIncumbentGlobal)
   {
     MOZ_ASSERT(aCallback && !mCallback);
     // Set script objects before we hold, on the off chance that a GC could
     // somehow happen in there... (which would be pretty odd, granted).
     mCallback = aCallback;
@@ -537,17 +556,19 @@ NS_DEFINE_STATIC_IID_ACCESSOR(CallbackOb
 
 template<class T, class U>
 inline void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
                             CallbackObjectHolder<T, U>& aField,
                             const char* aName,
                             uint32_t aFlags = 0)
 {
-  CycleCollectionNoteChild(aCallback, aField.GetISupports(), aName, aFlags);
+  if (aField) {
+    CycleCollectionNoteChild(aCallback, aField.GetISupports(), aName, aFlags);
+  }
 }
 
 template<class T, class U>
 void
 ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField)
 {
   aField.UnlinkSelf();
 }