From af3b8795b8f0d25ee476c2e4b891a0a0ef55d0ed Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 26 Feb 2013 15:10:15 -0500 Subject: [PATCH] Bug 838686 part 1. Add a helper class that can store a WebIDL callback or an XPCOM interface. r=peterv --- dom/bindings/CallbackObject.cpp | 35 ++++++ dom/bindings/CallbackObject.h | 211 ++++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp index 14fdf237950e..d3badc9e5e68 100644 --- a/dom/bindings/CallbackObject.cpp +++ b/dom/bindings/CallbackObject.cpp @@ -12,6 +12,7 @@ #include "nsPIDOMWindow.h" #include "nsJSUtils.h" #include "nsIScriptSecurityManager.h" +#include "xpcprivate.h" namespace mozilla { namespace dom { @@ -181,5 +182,39 @@ CallbackObject::CallSetup::~CallSetup() } } +already_AddRefed +CallbackObjectHolderBase::ToXPCOMCallback(CallbackObject* aCallback, + const nsIID& aIID) +{ + if (!aCallback) { + return nullptr; + } + + JSObject* callback = aCallback->Callback(); + + SafeAutoJSContext cx; + JSAutoCompartment ac(cx, callback); + XPCCallContext ccx(NATIVE_CALLER, cx); + if (!ccx.IsValid()) { + return nullptr; + } + + nsRefPtr wrappedJS; + nsresult rv = + nsXPCWrappedJS::GetNewOrUsed(ccx, callback, aIID, + nullptr, getter_AddRefs(wrappedJS)); + if (NS_FAILED(rv) || !wrappedJS) { + return nullptr; + } + + nsCOMPtr retval; + rv = wrappedJS->QueryInterface(aIID, getter_AddRefs(retval)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return retval.forget(); +} + } // namespace dom } // namespace mozilla diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h index 76d95db88b7e..3cebb588f40c 100644 --- a/dom/bindings/CallbackObject.h +++ b/dom/bindings/CallbackObject.h @@ -81,6 +81,19 @@ public: return mCallback; } + /* + * This getter does not change the color of the JSObject meaning that the + * object returned is not guaranteed to be kept alive past the next CC. + * + * This should only be called if you are certain that the return value won't + * be passed into a JS API function and that it won't be stored without being + * rooted (or otherwise signaling the stored value to the CC). + */ + JSObject* CallbackPreserveColor() const + { + return mCallback; + } + enum ExceptionHandling { eReportExceptions, eRethrowExceptions @@ -164,6 +177,204 @@ protected: }; }; +template +class CallbackObjectHolder; + +template +inline void ImplCycleCollectionUnlink(CallbackObjectHolder& aField); + +class CallbackObjectHolderBase +{ +protected: + // Returns null on all failures + already_AddRefed ToXPCOMCallback(CallbackObject* aCallback, + const nsIID& aIID); +}; + +template +class CallbackObjectHolder : CallbackObjectHolderBase +{ + /** + * A class which stores either a WebIDLCallbackT* or an XPCOMCallbackT*. Both + * types must inherit from nsISupports. The pointer that's stored can be + * null. + * + * When storing a WebIDLCallbackT*, mPtrBits is set to the pointer value. + * When storing an XPCOMCallbackT*, mPtrBits is the pointer value with low bit + * set. + */ +public: + explicit CallbackObjectHolder(WebIDLCallbackT* aCallback) + : mPtrBits(reinterpret_cast(aCallback)) + { + NS_IF_ADDREF(aCallback); + } + + explicit CallbackObjectHolder(XPCOMCallbackT* aCallback) + : mPtrBits(reinterpret_cast(aCallback) | XPCOMCallbackFlag) + { + NS_IF_ADDREF(aCallback); + } + + explicit CallbackObjectHolder(const CallbackObjectHolder& aOther) + : mPtrBits(aOther.mPtrBits) + { + NS_IF_ADDREF(GetISupports()); + } + + CallbackObjectHolder() + : mPtrBits(0) + {} + + ~CallbackObjectHolder() + { + UnlinkSelf(); + } + + nsISupports* GetISupports() const + { + return reinterpret_cast(mPtrBits & ~XPCOMCallbackFlag); + } + + // Even if HasWebIDLCallback returns true, GetWebIDLCallback() might still + // return null. + bool HasWebIDLCallback() const + { + return !(mPtrBits & XPCOMCallbackFlag); + } + + WebIDLCallbackT* GetWebIDLCallback() const + { + MOZ_ASSERT(HasWebIDLCallback()); + return reinterpret_cast(mPtrBits); + } + + XPCOMCallbackT* GetXPCOMCallback() const + { + MOZ_ASSERT(!HasWebIDLCallback()); + return reinterpret_cast(mPtrBits & ~XPCOMCallbackFlag); + } + + bool operator==(WebIDLCallbackT* aOtherCallback) const + { + if (!aOtherCallback) { + // If other is null, then we must be null to be equal. + return !GetISupports(); + } + + if (!HasWebIDLCallback() || !GetWebIDLCallback()) { + // If other is non-null, then we can't be equal if we have a + // non-WebIDL callback or a null callback. + return false; + } + + JSObject* thisObj = + js::UnwrapObject(GetWebIDLCallback()->CallbackPreserveColor()); + JSObject* otherObj = + js::UnwrapObject(aOtherCallback->CallbackPreserveColor()); + return thisObj == otherObj; + } + + bool operator==(XPCOMCallbackT* aOtherCallback) const + { + return (!aOtherCallback && !GetISupports()) || + (!HasWebIDLCallback() && GetXPCOMCallback() == aOtherCallback); + } + + bool operator==(const CallbackObjectHolder& aOtherCallback) const + { + if (aOtherCallback.HasWebIDLCallback()) { + return *this == aOtherCallback.GetWebIDLCallback(); + } + + return *this == aOtherCallback.GetXPCOMCallback(); + } + + // Try to return an XPCOMCallbackT version of this object. + already_AddRefed ToXPCOMCallback() + { + if (!HasWebIDLCallback()) { + nsRefPtr callback = GetXPCOMCallback(); + return callback.forget(); + } + + nsCOMPtr supp = + CallbackObjectHolderBase::ToXPCOMCallback(GetWebIDLCallback(), + NS_GET_TEMPLATE_IID(XPCOMCallbackT)); + // ToXPCOMCallback already did the right QI for us. + return static_cast(supp.forget().get()); + } + + // Try to return a WebIDLCallbackT version of this object. + already_AddRefed ToWebIDLCallback() + { + if (HasWebIDLCallback()) { + nsRefPtr callback = GetWebIDLCallback(); + return callback.forget(); + } + + XPCOMCallbackT* callback = GetXPCOMCallback(); + if (!callback) { + return nullptr; + } + + nsCOMPtr wrappedJS = do_QueryInterface(callback); + if (!wrappedJS) { + return nullptr; + } + + JSObject* obj; + if (NS_FAILED(wrappedJS->GetJSObject(&obj)) || !obj) { + return nullptr; + } + + SafeAutoJSContext cx; + JSAutoCompartment ac(cx, obj); + + bool inited; + nsRefPtr newCallback = + new WebIDLCallbackT(cx, nullptr, obj, &inited); + if (!inited) { + return nullptr; + } + return newCallback.forget(); + } + +private: + static const uintptr_t XPCOMCallbackFlag = 1u; + + friend inline void + ImplCycleCollectionUnlink(CallbackObjectHolder& aField); + + void UnlinkSelf() + { + // NS_IF_RELEASE because we might have been unlinked before + nsISupports* ptr = GetISupports(); + NS_IF_RELEASE(ptr); + mPtrBits = 0; + } + + uintptr_t mPtrBits; +}; + +template +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + CallbackObjectHolder& aField, + const char* aName, + uint32_t aFlags = 0) +{ + CycleCollectionNoteChild(aCallback, aField.GetISupports(), aName, aFlags); +} + +template +inline void +ImplCycleCollectionUnlink(CallbackObjectHolder& aField) +{ + aField.UnlinkSelf(); +} + } // namespace dom } // namespace mozilla