Bug 1273251: Part 4 - Drop CallbackObject's JS objects for nuked compartments during CC. r?peterv,mccr8
MozReview-Commit-ID: 6lPdmUtKREt
--- 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();
}