/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* 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/. */ /** * A common base class for representing WebIDL callback function and * callback interface types in C++. * * This class implements common functionality like lifetime * management, initialization with the JS object, and setup of the * call environment. Subclasses are responsible for providing methods * that do the call into JS as needed. */ #ifndef mozilla_dom_CallbackObject_h #define mozilla_dom_CallbackObject_h #include "nsISupports.h" #include "nsISupportsImpl.h" #include "nsCycleCollectionParticipant.h" #include "jsapi.h" #include "jswrapper.h" #include "mozilla/Assertions.h" #include "mozilla/ErrorResult.h" #include "mozilla/Util.h" #include "nsContentUtils.h" // nsCxPusher #include "nsWrapperCache.h" #include "nsJSEnvironment.h" #include "xpcpublic.h" #include "nsLayoutStatics.h" namespace mozilla { namespace dom { #define DOM_CALLBACKOBJECT_IID \ { 0xbe74c190, 0x6d76, 0x4991, \ { 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) /** * Create a CallbackObject. aCallback is the callback object we're wrapping. * aOwner is the object that will be receiving this CallbackObject as a method * argument, if any. We need this so we can store our callback object in the * same compartment as our owner. If *aInited is set to false, an exception * has been thrown. */ CallbackObject(JSContext* cx, JSObject* aOwner, JSObject* aCallback, bool* aInited) : mCallback(nullptr) { // If aOwner is not null, enter the compartment of aOwner's // underlying object. if (aOwner) { aOwner = js::UncheckedUnwrap(aOwner); JSAutoCompartment ac(cx, aOwner); if (!JS_WrapObject(cx, &aCallback)) { *aInited = false; return; } } Init(aCallback); *aInited = true; } /* * Create a CallbackObject without any sort of interesting games with * compartments, for cases when you want to just use the existing object * as-is. This constructor can never fail. */ explicit CallbackObject(JSObject* aCallback) { Init(aCallback); } virtual ~CallbackObject() { DropCallback(); } JSObject* Callback() const { xpc_UnmarkGrayObject(mCallback); 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 }; protected: explicit CallbackObject(CallbackObject* aCallbackObject) { Init(aCallbackObject->mCallback); } private: inline void Init(JSObject* aCallback) { // Set mCallback before we hold, on the off chance that a GC could somehow // happen in there... (which would be pretty odd, granted). mCallback = aCallback; // Make sure we'll be able to drop as needed nsLayoutStatics::AddRef(); NS_HOLD_JS_OBJECTS(this, CallbackObject); } protected: void DropCallback() { if (mCallback) { mCallback = nullptr; NS_DROP_JS_OBJECTS(this, CallbackObject); nsLayoutStatics::Release(); } } JSObject* mCallback; class MOZ_STACK_CLASS CallSetup { /** * A class that performs whatever setup we need to safely make a * call while this class is on the stack, After the constructor * returns, the call is safe to make if GetContext() returns * non-null. */ public: CallSetup(JSObject* const aCallable, ErrorResult& aRv, ExceptionHandling aExceptionHandling); ~CallSetup(); JSContext* GetContext() const { return mCx; } private: // We better not get copy-constructed CallSetup(const CallSetup&) MOZ_DELETE; // Members which can go away whenever JSContext* mCx; nsCOMPtr mCtx; // And now members whose construction/destruction order we need to control. // Put our nsAutoMicrotask first, so it gets destroyed after everything else // is gone nsAutoMicroTask mMt; // Can't construct an XPCAutoRequest until we have a JSContext, so // this needs to be a Maybe. Maybe mAr; // Can't construct a TerminationFuncHolder without an nsJSContext. But we // generally want its destructor to come after the destructor of mCxPusher. Maybe mTerminationFuncHolder; nsCxPusher mCxPusher; // Can't construct a JSAutoCompartment without a JSContext either. Also, // Put mAc after mCxPusher so that we exit the compartment before we pop the // JSContext. Though in practice we'll often manually order those two // things. Maybe mAc; // An ErrorResult to possibly re-throw exceptions on and whether // we should re-throw them. ErrorResult& mErrorResult; const ExceptionHandling mExceptionHandling; uint32_t mSavedJSContextOptions; }; }; template class CallbackObjectHolder; template 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::UncheckedUnwrap(GetWebIDLCallback()->CallbackPreserveColor()); JSObject* otherObj = js::UncheckedUnwrap(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 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; }; NS_DEFINE_STATIC_IID_ACCESSOR(CallbackObject, DOM_CALLBACKOBJECT_IID) template inline void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, CallbackObjectHolder& aField, const char* aName, uint32_t aFlags = 0) { CycleCollectionNoteChild(aCallback, aField.GetISupports(), aName, aFlags); } template void ImplCycleCollectionUnlink(CallbackObjectHolder& aField) { aField.UnlinkSelf(); } } // namespace dom } // namespace mozilla #endif // mozilla_dom_CallbackObject_h