michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * A common base class for representing WebIDL callback function and michael@0: * callback interface types in C++. michael@0: * michael@0: * This class implements common functionality like lifetime michael@0: * management, initialization with the JS object, and setup of the michael@0: * call environment. Subclasses are responsible for providing methods michael@0: * that do the call into JS as needed. michael@0: */ michael@0: michael@0: #ifndef mozilla_dom_CallbackObject_h michael@0: #define mozilla_dom_CallbackObject_h michael@0: michael@0: #include "nsISupports.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: #include "jswrapper.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/ErrorResult.h" michael@0: #include "mozilla/HoldDropJSObjects.h" michael@0: #include "mozilla/MemoryReporting.h" michael@0: #include "mozilla/dom/ScriptSettings.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsWrapperCache.h" michael@0: #include "nsJSEnvironment.h" michael@0: #include "xpcpublic.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: #define DOM_CALLBACKOBJECT_IID \ michael@0: { 0xbe74c190, 0x6d76, 0x4991, \ michael@0: { 0x84, 0xb9, 0x65, 0x06, 0x99, 0xe6, 0x93, 0x2b } } michael@0: michael@0: class CallbackObject : public nsISupports michael@0: { michael@0: public: michael@0: NS_DECLARE_STATIC_IID_ACCESSOR(DOM_CALLBACKOBJECT_IID) michael@0: michael@0: NS_DECL_CYCLE_COLLECTING_ISUPPORTS michael@0: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackObject) michael@0: michael@0: // The caller may pass a global object which will act as an override for the michael@0: // incumbent script settings object when the callback is invoked (overriding michael@0: // the entry point computed from aCallback). If no override is required, the michael@0: // caller should pass null. michael@0: explicit CallbackObject(JS::Handle aCallback, nsIGlobalObject *aIncumbentGlobal) michael@0: { michael@0: Init(aCallback, aIncumbentGlobal); michael@0: } michael@0: michael@0: virtual ~CallbackObject() michael@0: { michael@0: DropJSObjects(); michael@0: } michael@0: michael@0: JS::Handle Callback() const michael@0: { michael@0: JS::ExposeObjectToActiveJS(mCallback); michael@0: return CallbackPreserveColor(); michael@0: } michael@0: michael@0: /* michael@0: * This getter does not change the color of the JSObject meaning that the michael@0: * object returned is not guaranteed to be kept alive past the next CC. michael@0: * michael@0: * This should only be called if you are certain that the return value won't michael@0: * be passed into a JS API function and that it won't be stored without being michael@0: * rooted (or otherwise signaling the stored value to the CC). michael@0: */ michael@0: JS::Handle CallbackPreserveColor() const michael@0: { michael@0: // Calling fromMarkedLocation() is safe because we trace our mCallback, and michael@0: // because the value of mCallback cannot change after if has been set. michael@0: return JS::Handle::fromMarkedLocation(mCallback.address()); michael@0: } michael@0: michael@0: nsIGlobalObject* IncumbentGlobalOrNull() const michael@0: { michael@0: return mIncumbentGlobal; michael@0: } michael@0: michael@0: enum ExceptionHandling { michael@0: // Report any exception and don't throw it to the caller code. michael@0: eReportExceptions, michael@0: // Throw an exception to the caller code if the thrown exception is a michael@0: // binding object for a DOMError from the caller's scope, otherwise report michael@0: // it. michael@0: eRethrowContentExceptions, michael@0: // Throw any exception to the caller code. michael@0: eRethrowExceptions michael@0: }; michael@0: michael@0: size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this); michael@0: } michael@0: michael@0: protected: michael@0: explicit CallbackObject(CallbackObject* aCallbackObject) michael@0: { michael@0: Init(aCallbackObject->mCallback, aCallbackObject->mIncumbentGlobal); michael@0: } michael@0: michael@0: bool operator==(const CallbackObject& aOther) const michael@0: { michael@0: JSObject* thisObj = michael@0: js::UncheckedUnwrap(CallbackPreserveColor()); michael@0: JSObject* otherObj = michael@0: js::UncheckedUnwrap(aOther.CallbackPreserveColor()); michael@0: return thisObj == otherObj; michael@0: } michael@0: michael@0: private: michael@0: inline void Init(JSObject* aCallback, nsIGlobalObject* aIncumbentGlobal) michael@0: { michael@0: MOZ_ASSERT(aCallback && !mCallback); michael@0: // Set script objects before we hold, on the off chance that a GC could michael@0: // somehow happen in there... (which would be pretty odd, granted). michael@0: mCallback = aCallback; michael@0: if (aIncumbentGlobal) { michael@0: mIncumbentGlobal = aIncumbentGlobal; michael@0: mIncumbentJSGlobal = aIncumbentGlobal->GetGlobalJSObject(); michael@0: } michael@0: mozilla::HoldJSObjects(this); michael@0: } michael@0: michael@0: CallbackObject(const CallbackObject&) MOZ_DELETE; michael@0: CallbackObject& operator =(const CallbackObject&) MOZ_DELETE; michael@0: michael@0: protected: michael@0: void DropJSObjects() michael@0: { michael@0: MOZ_ASSERT_IF(mIncumbentJSGlobal, mCallback); michael@0: if (mCallback) { michael@0: mCallback = nullptr; michael@0: mIncumbentJSGlobal = nullptr; michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: } michael@0: michael@0: JS::Heap mCallback; michael@0: // Ideally, we'd just hold a reference to the nsIGlobalObject, since that's michael@0: // what we need to pass to AutoIncumbentScript. Unfortunately, that doesn't michael@0: // hold the actual JS global alive. So we maintain an additional pointer to michael@0: // the JS global itself so that we can trace it. michael@0: // michael@0: // At some point we should consider trying to make native globals hold their michael@0: // scripted global alive, at which point we can get rid of the duplication michael@0: // here. michael@0: nsCOMPtr mIncumbentGlobal; michael@0: JS::TenuredHeap mIncumbentJSGlobal; michael@0: michael@0: class MOZ_STACK_CLASS CallSetup michael@0: { michael@0: /** michael@0: * A class that performs whatever setup we need to safely make a michael@0: * call while this class is on the stack, After the constructor michael@0: * returns, the call is safe to make if GetContext() returns michael@0: * non-null. michael@0: */ michael@0: public: michael@0: // If aExceptionHandling == eRethrowContentExceptions then aCompartment michael@0: // needs to be set to the compartment in which exceptions will be rethrown. michael@0: CallSetup(CallbackObject* aCallback, ErrorResult& aRv, michael@0: ExceptionHandling aExceptionHandling, michael@0: JSCompartment* aCompartment = nullptr, michael@0: bool aIsJSImplementedWebIDL = false); michael@0: ~CallSetup(); michael@0: michael@0: JSContext* GetContext() const michael@0: { michael@0: return mCx; michael@0: } michael@0: michael@0: private: michael@0: // We better not get copy-constructed michael@0: CallSetup(const CallSetup&) MOZ_DELETE; michael@0: michael@0: bool ShouldRethrowException(JS::Handle aException); michael@0: michael@0: // Members which can go away whenever michael@0: JSContext* mCx; michael@0: michael@0: // Caller's compartment. This will only have a sensible value if michael@0: // mExceptionHandling == eRethrowContentExceptions. michael@0: JSCompartment* mCompartment; michael@0: michael@0: // And now members whose construction/destruction order we need to control. michael@0: Maybe mAutoEntryScript; michael@0: Maybe mAutoIncumbentScript; michael@0: michael@0: // Constructed the rooter within the scope of mCxPusher above, so that it's michael@0: // always within a request during its lifetime. michael@0: Maybe > mRootedCallable; michael@0: michael@0: // Can't construct a JSAutoCompartment without a JSContext either. Also, michael@0: // Put mAc after mAutoEntryScript so that we exit the compartment before michael@0: // we pop the JSContext. Though in practice we'll often manually order michael@0: // those two things. michael@0: Maybe mAc; michael@0: michael@0: // An ErrorResult to possibly re-throw exceptions on and whether michael@0: // we should re-throw them. michael@0: ErrorResult& mErrorResult; michael@0: const ExceptionHandling mExceptionHandling; michael@0: JS::ContextOptions mSavedJSContextOptions; michael@0: const bool mIsMainThread; michael@0: }; michael@0: }; michael@0: michael@0: template michael@0: class CallbackObjectHolder; michael@0: michael@0: template michael@0: void ImplCycleCollectionUnlink(CallbackObjectHolder& aField); michael@0: michael@0: class CallbackObjectHolderBase michael@0: { michael@0: protected: michael@0: // Returns null on all failures michael@0: already_AddRefed ToXPCOMCallback(CallbackObject* aCallback, michael@0: const nsIID& aIID) const; michael@0: }; michael@0: michael@0: template michael@0: class CallbackObjectHolder : CallbackObjectHolderBase michael@0: { michael@0: /** michael@0: * A class which stores either a WebIDLCallbackT* or an XPCOMCallbackT*. Both michael@0: * types must inherit from nsISupports. The pointer that's stored can be michael@0: * null. michael@0: * michael@0: * When storing a WebIDLCallbackT*, mPtrBits is set to the pointer value. michael@0: * When storing an XPCOMCallbackT*, mPtrBits is the pointer value with low bit michael@0: * set. michael@0: */ michael@0: public: michael@0: explicit CallbackObjectHolder(WebIDLCallbackT* aCallback) michael@0: : mPtrBits(reinterpret_cast(aCallback)) michael@0: { michael@0: NS_IF_ADDREF(aCallback); michael@0: } michael@0: michael@0: explicit CallbackObjectHolder(XPCOMCallbackT* aCallback) michael@0: : mPtrBits(reinterpret_cast(aCallback) | XPCOMCallbackFlag) michael@0: { michael@0: NS_IF_ADDREF(aCallback); michael@0: } michael@0: michael@0: explicit CallbackObjectHolder(const CallbackObjectHolder& aOther) michael@0: : mPtrBits(aOther.mPtrBits) michael@0: { michael@0: NS_IF_ADDREF(GetISupports()); michael@0: } michael@0: michael@0: CallbackObjectHolder() michael@0: : mPtrBits(0) michael@0: {} michael@0: michael@0: ~CallbackObjectHolder() michael@0: { michael@0: UnlinkSelf(); michael@0: } michael@0: michael@0: void operator=(WebIDLCallbackT* aCallback) michael@0: { michael@0: UnlinkSelf(); michael@0: mPtrBits = reinterpret_cast(aCallback); michael@0: NS_IF_ADDREF(aCallback); michael@0: } michael@0: michael@0: void operator=(XPCOMCallbackT* aCallback) michael@0: { michael@0: UnlinkSelf(); michael@0: mPtrBits = reinterpret_cast(aCallback) | XPCOMCallbackFlag; michael@0: NS_IF_ADDREF(aCallback); michael@0: } michael@0: michael@0: void operator=(const CallbackObjectHolder& aOther) michael@0: { michael@0: UnlinkSelf(); michael@0: mPtrBits = aOther.mPtrBits; michael@0: NS_IF_ADDREF(GetISupports()); michael@0: } michael@0: michael@0: nsISupports* GetISupports() const michael@0: { michael@0: return reinterpret_cast(mPtrBits & ~XPCOMCallbackFlag); michael@0: } michael@0: michael@0: // Boolean conversion operator so people can use this in boolean tests michael@0: operator bool() const michael@0: { michael@0: return GetISupports(); michael@0: } michael@0: michael@0: // Even if HasWebIDLCallback returns true, GetWebIDLCallback() might still michael@0: // return null. michael@0: bool HasWebIDLCallback() const michael@0: { michael@0: return !(mPtrBits & XPCOMCallbackFlag); michael@0: } michael@0: michael@0: WebIDLCallbackT* GetWebIDLCallback() const michael@0: { michael@0: MOZ_ASSERT(HasWebIDLCallback()); michael@0: return reinterpret_cast(mPtrBits); michael@0: } michael@0: michael@0: XPCOMCallbackT* GetXPCOMCallback() const michael@0: { michael@0: MOZ_ASSERT(!HasWebIDLCallback()); michael@0: return reinterpret_cast(mPtrBits & ~XPCOMCallbackFlag); michael@0: } michael@0: michael@0: bool operator==(WebIDLCallbackT* aOtherCallback) const michael@0: { michael@0: if (!aOtherCallback) { michael@0: // If other is null, then we must be null to be equal. michael@0: return !GetISupports(); michael@0: } michael@0: michael@0: if (!HasWebIDLCallback() || !GetWebIDLCallback()) { michael@0: // If other is non-null, then we can't be equal if we have a michael@0: // non-WebIDL callback or a null callback. michael@0: return false; michael@0: } michael@0: michael@0: return *GetWebIDLCallback() == *aOtherCallback; michael@0: } michael@0: michael@0: bool operator==(XPCOMCallbackT* aOtherCallback) const michael@0: { michael@0: return (!aOtherCallback && !GetISupports()) || michael@0: (!HasWebIDLCallback() && GetXPCOMCallback() == aOtherCallback); michael@0: } michael@0: michael@0: bool operator==(const CallbackObjectHolder& aOtherCallback) const michael@0: { michael@0: if (aOtherCallback.HasWebIDLCallback()) { michael@0: return *this == aOtherCallback.GetWebIDLCallback(); michael@0: } michael@0: michael@0: return *this == aOtherCallback.GetXPCOMCallback(); michael@0: } michael@0: michael@0: // Try to return an XPCOMCallbackT version of this object. michael@0: already_AddRefed ToXPCOMCallback() const michael@0: { michael@0: if (!HasWebIDLCallback()) { michael@0: nsRefPtr callback = GetXPCOMCallback(); michael@0: return callback.forget(); michael@0: } michael@0: michael@0: nsCOMPtr supp = michael@0: CallbackObjectHolderBase::ToXPCOMCallback(GetWebIDLCallback(), michael@0: NS_GET_TEMPLATE_IID(XPCOMCallbackT)); michael@0: // ToXPCOMCallback already did the right QI for us. michael@0: return supp.forget().downcast(); michael@0: } michael@0: michael@0: // Try to return a WebIDLCallbackT version of this object. michael@0: already_AddRefed ToWebIDLCallback() const michael@0: { michael@0: if (HasWebIDLCallback()) { michael@0: nsRefPtr callback = GetWebIDLCallback(); michael@0: return callback.forget(); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: private: michael@0: static const uintptr_t XPCOMCallbackFlag = 1u; michael@0: michael@0: friend void michael@0: ImplCycleCollectionUnlink(CallbackObjectHolder& aField); michael@0: michael@0: void UnlinkSelf() michael@0: { michael@0: // NS_IF_RELEASE because we might have been unlinked before michael@0: nsISupports* ptr = GetISupports(); michael@0: NS_IF_RELEASE(ptr); michael@0: mPtrBits = 0; michael@0: } michael@0: michael@0: uintptr_t mPtrBits; michael@0: }; michael@0: michael@0: NS_DEFINE_STATIC_IID_ACCESSOR(CallbackObject, DOM_CALLBACKOBJECT_IID) michael@0: michael@0: template michael@0: inline void michael@0: ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, michael@0: CallbackObjectHolder& aField, michael@0: const char* aName, michael@0: uint32_t aFlags = 0) michael@0: { michael@0: CycleCollectionNoteChild(aCallback, aField.GetISupports(), aName, aFlags); michael@0: } michael@0: michael@0: template michael@0: void michael@0: ImplCycleCollectionUnlink(CallbackObjectHolder& aField) michael@0: { michael@0: aField.UnlinkSelf(); michael@0: } michael@0: michael@0: } // namespace dom michael@0: } // namespace mozilla michael@0: michael@0: #endif // mozilla_dom_CallbackObject_h