summaryrefslogtreecommitdiffstats
path: root/dom/bindings/CallbackObject.h
diff options
context:
space:
mode:
Diffstat (limited to 'dom/bindings/CallbackObject.h')
-rw-r--r--dom/bindings/CallbackObject.h607
1 files changed, 607 insertions, 0 deletions
diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h
new file mode 100644
index 000000000..8a3d45dfc
--- /dev/null
+++ b/dom/bindings/CallbackObject.h
@@ -0,0 +1,607 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "jswrapper.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsWrapperCache.h"
+#include "nsJSEnvironment.h"
+#include "xpcpublic.h"
+#include "jsapi.h"
+#include "js/TracingAPI.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)
+
+ // 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.
+ explicit CallbackObject(JSContext* aCx, JS::Handle<JSObject*> aCallback,
+ nsIGlobalObject* aIncumbentGlobal)
+ {
+ if (aCx && JS::ContextOptionsRef(aCx).asyncStack()) {
+ JS::RootedObject stack(aCx);
+ if (!JS::CaptureCurrentStack(aCx, &stack)) {
+ JS_ClearPendingException(aCx);
+ }
+ Init(aCallback, stack, aIncumbentGlobal);
+ } else {
+ Init(aCallback, nullptr, aIncumbentGlobal);
+ }
+ }
+
+ // Instead of capturing the current stack to use as an async parent when the
+ // callback is invoked, the caller can use this overload to pass in a stack
+ // for that purpose.
+ explicit CallbackObject(JS::Handle<JSObject*> aCallback,
+ JS::Handle<JSObject*> aAsyncStack,
+ nsIGlobalObject* aIncumbentGlobal)
+ {
+ Init(aCallback, aAsyncStack, aIncumbentGlobal);
+ }
+
+ JS::Handle<JSObject*> Callback() const
+ {
+ mCallback.exposeToActiveJS();
+ return CallbackPreserveColor();
+ }
+
+ JSObject* GetCreationStack() const
+ {
+ return mCreationStack;
+ }
+
+ void MarkForCC()
+ {
+ mCallback.exposeToActiveJS();
+ mCreationStack.exposeToActiveJS();
+ }
+
+ /*
+ * 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).
+ */
+ JS::Handle<JSObject*> CallbackPreserveColor() const
+ {
+ // Calling fromMarkedLocation() is safe because we trace our mCallback, and
+ // because the value of mCallback cannot change after if has been set.
+ return JS::Handle<JSObject*>::fromMarkedLocation(mCallback.address());
+ }
+
+ /*
+ * If the callback is known to be non-gray, then this method can be
+ * used instead of Callback() to avoid the overhead of
+ * ExposeObjectToActiveJS().
+ */
+ JS::Handle<JSObject*> CallbackKnownNotGray() const
+ {
+ MOZ_ASSERT(!JS::ObjectIsMarkedGray(mCallback));
+ return CallbackPreserveColor();
+ }
+
+ nsIGlobalObject* IncumbentGlobalOrNull() const
+ {
+ return mIncumbentGlobal;
+ }
+
+ enum ExceptionHandling {
+ // Report any exception and don't throw it to the caller code.
+ eReportExceptions,
+ // Throw an exception to the caller code if the thrown exception is a
+ // binding object for a DOMError or DOMException from the caller's scope,
+ // otherwise report it.
+ eRethrowContentExceptions,
+ // Throw exceptions to the caller code, unless the caller compartment is
+ // provided, the exception is not a DOMError or DOMException from the
+ // caller compartment, and the caller compartment does not subsume our
+ // unwrapped callback.
+ eRethrowExceptions
+ };
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+ {
+ return aMallocSizeOf(this);
+ }
+
+protected:
+ virtual ~CallbackObject()
+ {
+ DropJSObjects();
+ }
+
+ explicit CallbackObject(CallbackObject* aCallbackObject)
+ {
+ Init(aCallbackObject->mCallback, aCallbackObject->mCreationStack,
+ aCallbackObject->mIncumbentGlobal);
+ }
+
+ bool operator==(const CallbackObject& aOther) const
+ {
+ JSObject* thisObj =
+ js::UncheckedUnwrap(CallbackPreserveColor());
+ JSObject* otherObj =
+ js::UncheckedUnwrap(aOther.CallbackPreserveColor());
+ return thisObj == otherObj;
+ }
+
+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;
+ mCreationStack = aCreationStack;
+ if (aIncumbentGlobal) {
+ mIncumbentGlobal = aIncumbentGlobal;
+ mIncumbentJSGlobal = aIncumbentGlobal->GetGlobalJSObject();
+ }
+ }
+
+ inline void Init(JSObject* aCallback, JSObject* aCreationStack,
+ nsIGlobalObject* aIncumbentGlobal)
+ {
+ InitNoHold(aCallback, aCreationStack, aIncumbentGlobal);
+ mozilla::HoldJSObjects(this);
+ }
+
+ inline void ClearJSReferences()
+ {
+ mCallback = nullptr;
+ mCreationStack = nullptr;
+ mIncumbentJSGlobal = nullptr;
+ }
+
+ CallbackObject(const CallbackObject&) = delete;
+ CallbackObject& operator =(const CallbackObject&) = delete;
+
+protected:
+ void DropJSObjects()
+ {
+ MOZ_ASSERT_IF(mIncumbentJSGlobal, mCallback);
+ if (mCallback) {
+ ClearJSReferences();
+ mozilla::DropJSObjects(this);
+ }
+ }
+
+ // For use from subclasses that want to be usable with Rooted.
+ void Trace(JSTracer* aTracer);
+
+ // For use from subclasses that want to be traced for a bit then possibly
+ // switch to HoldJSObjects. If we have more than one owner, this will
+ // HoldJSObjects; otherwise it will just forget all our JS references.
+ void HoldJSObjectsIfMoreThanOneOwner();
+
+ // Struct used as a way to force a CallbackObject constructor to not call
+ // HoldJSObjects. We're putting it here so that CallbackObject subclasses will
+ // have access to it, but outside code will not.
+ //
+ // Places that use this need to ensure that the callback is traced (e.g. via a
+ // Rooted) until the HoldJSObjects call happens.
+ struct FastCallbackConstructor {
+ };
+
+ // Just like the public version without the FastCallbackConstructor argument,
+ // except for not calling HoldJSObjects. If you use this, you MUST ensure
+ // that the object is traced until the HoldJSObjects happens!
+ CallbackObject(JSContext* aCx, JS::Handle<JSObject*> aCallback,
+ nsIGlobalObject* aIncumbentGlobal,
+ const FastCallbackConstructor&)
+ {
+ if (aCx && JS::ContextOptionsRef(aCx).asyncStack()) {
+ JS::RootedObject stack(aCx);
+ if (!JS::CaptureCurrentStack(aCx, &stack)) {
+ JS_ClearPendingException(aCx);
+ }
+ InitNoHold(aCallback, stack, aIncumbentGlobal);
+ } else {
+ InitNoHold(aCallback, nullptr, aIncumbentGlobal);
+ }
+ }
+
+ // mCallback is not unwrapped, so it can be a cross-compartment-wrapper.
+ // This is done to ensure that, if JS code can't call a callback f(), or get
+ // its members, directly itself, this code won't call f(), or get its members,
+ // on the code's behalf.
+ JS::Heap<JSObject*> mCallback;
+ JS::Heap<JSObject*> mCreationStack;
+ // Ideally, we'd just hold a reference to the nsIGlobalObject, since that's
+ // what we need to pass to AutoIncumbentScript. Unfortunately, that doesn't
+ // hold the actual JS global alive. So we maintain an additional pointer to
+ // the JS global itself so that we can trace it.
+ //
+ // At some point we should consider trying to make native globals hold their
+ // scripted global alive, at which point we can get rid of the duplication
+ // here.
+ nsCOMPtr<nsIGlobalObject> mIncumbentGlobal;
+ JS::TenuredHeap<JSObject*> mIncumbentJSGlobal;
+
+ 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:
+ // If aExceptionHandling == eRethrowContentExceptions then aCompartment
+ // needs to be set to the compartment in which exceptions will be rethrown.
+ //
+ // If aExceptionHandling == eRethrowExceptions then aCompartment may be set
+ // to the compartment in which exceptions will be rethrown. In that case
+ // they will only be rethrown if that compartment's principal subsumes the
+ // principal of our (unwrapped) callback.
+ CallSetup(CallbackObject* aCallback, ErrorResult& aRv,
+ const char* aExecutionReason,
+ ExceptionHandling aExceptionHandling,
+ JSCompartment* aCompartment = nullptr,
+ bool aIsJSImplementedWebIDL = false);
+ ~CallSetup();
+
+ JSContext* GetContext() const
+ {
+ return mCx;
+ }
+
+ private:
+ // We better not get copy-constructed
+ CallSetup(const CallSetup&) = delete;
+
+ bool ShouldRethrowException(JS::Handle<JS::Value> aException);
+
+ // Members which can go away whenever
+ JSContext* mCx;
+
+ // Caller's compartment. This will only have a sensible value if
+ // mExceptionHandling == eRethrowContentExceptions or eRethrowExceptions.
+ JSCompartment* mCompartment;
+
+ // And now members whose construction/destruction order we need to control.
+ Maybe<AutoEntryScript> mAutoEntryScript;
+ Maybe<AutoIncumbentScript> mAutoIncumbentScript;
+
+ Maybe<JS::Rooted<JSObject*> > mRootedCallable;
+
+ // Members which are used to set the async stack.
+ Maybe<JS::Rooted<JSObject*>> mAsyncStack;
+ Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
+
+ // Can't construct a JSAutoCompartment without a JSContext either. Also,
+ // Put mAc after mAutoEntryScript so that we exit the compartment before
+ // we pop the JSContext. Though in practice we'll often manually order
+ // those two things.
+ Maybe<JSAutoCompartment> mAc;
+
+ // An ErrorResult to possibly re-throw exceptions on and whether
+ // we should re-throw them.
+ ErrorResult& mErrorResult;
+ const ExceptionHandling mExceptionHandling;
+ const bool mIsMainThread;
+ };
+};
+
+template<class WebIDLCallbackT, class XPCOMCallbackT>
+class CallbackObjectHolder;
+
+template<class T, class U>
+void ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField);
+
+class CallbackObjectHolderBase
+{
+protected:
+ // Returns null on all failures
+ already_AddRefed<nsISupports> ToXPCOMCallback(CallbackObject* aCallback,
+ const nsIID& aIID) const;
+};
+
+template<class WebIDLCallbackT, class XPCOMCallbackT>
+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<uintptr_t>(aCallback))
+ {
+ NS_IF_ADDREF(aCallback);
+ }
+
+ explicit CallbackObjectHolder(XPCOMCallbackT* aCallback)
+ : mPtrBits(reinterpret_cast<uintptr_t>(aCallback) | XPCOMCallbackFlag)
+ {
+ NS_IF_ADDREF(aCallback);
+ }
+
+ CallbackObjectHolder(CallbackObjectHolder&& aOther)
+ : mPtrBits(aOther.mPtrBits)
+ {
+ aOther.mPtrBits = 0;
+ static_assert(sizeof(CallbackObjectHolder) == sizeof(void*),
+ "This object is expected to be as small as a pointer, and it "
+ "is currently passed by value in various places. If it is "
+ "bloating, we may want to pass it by reference then.");
+ }
+
+ CallbackObjectHolder(const CallbackObjectHolder& aOther) = delete;
+
+ CallbackObjectHolder()
+ : mPtrBits(0)
+ {}
+
+ ~CallbackObjectHolder()
+ {
+ UnlinkSelf();
+ }
+
+ void operator=(WebIDLCallbackT* aCallback)
+ {
+ UnlinkSelf();
+ mPtrBits = reinterpret_cast<uintptr_t>(aCallback);
+ NS_IF_ADDREF(aCallback);
+ }
+
+ void operator=(XPCOMCallbackT* aCallback)
+ {
+ UnlinkSelf();
+ mPtrBits = reinterpret_cast<uintptr_t>(aCallback) | XPCOMCallbackFlag;
+ NS_IF_ADDREF(aCallback);
+ }
+
+ void operator=(CallbackObjectHolder&& aOther)
+ {
+ UnlinkSelf();
+ mPtrBits = aOther.mPtrBits;
+ aOther.mPtrBits = 0;
+ }
+
+ void operator=(const CallbackObjectHolder& aOther) = delete;
+
+ nsISupports* GetISupports() const
+ {
+ return reinterpret_cast<nsISupports*>(mPtrBits & ~XPCOMCallbackFlag);
+ }
+
+ // Boolean conversion operator so people can use this in boolean tests
+ explicit operator bool() const
+ {
+ return GetISupports();
+ }
+
+ CallbackObjectHolder Clone() const
+ {
+ CallbackObjectHolder result;
+ result.mPtrBits = mPtrBits;
+ NS_IF_ADDREF(GetISupports());
+ return result;
+ }
+
+ // 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<WebIDLCallbackT*>(mPtrBits);
+ }
+
+ XPCOMCallbackT* GetXPCOMCallback() const
+ {
+ MOZ_ASSERT(!HasWebIDLCallback());
+ return reinterpret_cast<XPCOMCallbackT*>(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;
+ }
+
+ return *GetWebIDLCallback() == *aOtherCallback;
+ }
+
+ 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<XPCOMCallbackT> ToXPCOMCallback() const
+ {
+ if (!HasWebIDLCallback()) {
+ RefPtr<XPCOMCallbackT> callback = GetXPCOMCallback();
+ return callback.forget();
+ }
+
+ nsCOMPtr<nsISupports> supp =
+ CallbackObjectHolderBase::ToXPCOMCallback(GetWebIDLCallback(),
+ NS_GET_TEMPLATE_IID(XPCOMCallbackT));
+ // ToXPCOMCallback already did the right QI for us.
+ return supp.forget().downcast<XPCOMCallbackT>();
+ }
+
+ // Try to return a WebIDLCallbackT version of this object.
+ already_AddRefed<WebIDLCallbackT> ToWebIDLCallback() const
+ {
+ if (HasWebIDLCallback()) {
+ RefPtr<WebIDLCallbackT> callback = GetWebIDLCallback();
+ return callback.forget();
+ }
+ return nullptr;
+ }
+
+private:
+ static const uintptr_t XPCOMCallbackFlag = 1u;
+
+ friend void
+ ImplCycleCollectionUnlink<WebIDLCallbackT,
+ XPCOMCallbackT>(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<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);
+}
+
+template<class T, class U>
+void
+ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField)
+{
+ aField.UnlinkSelf();
+}
+
+// T is expected to be a RefPtr or OwningNonNull around a CallbackObject
+// subclass. This class is used in bindings to safely handle Fast* callbacks;
+// it ensures that the callback is traced, and that if something is holding onto
+// the callback when we're done with it HoldJSObjects is called.
+template<typename T>
+class RootedCallback : public JS::Rooted<T>
+{
+public:
+ explicit RootedCallback(JSContext* cx)
+ : JS::Rooted<T>(cx)
+ {}
+
+ // We need a way to make assignment from pointers (how we're normally used)
+ // work.
+ template<typename S>
+ void operator=(S* arg)
+ {
+ this->get().operator=(arg);
+ }
+
+ // But nullptr can't use the above template, because it doesn't know which S
+ // to select. So we need a special overload for nullptr.
+ void operator=(decltype(nullptr) arg)
+ {
+ this->get().operator=(arg);
+ }
+
+ // Codegen relies on being able to do Callback() on us.
+ JS::Handle<JSObject*> Callback() const
+ {
+ return this->get()->Callback();
+ }
+
+ ~RootedCallback()
+ {
+ // Ensure that our callback starts holding on to its own JS objects as
+ // needed. Having to null-check here when T is OwningNonNull is a bit
+ // silly, but it's simpler than creating two separate RootedCallback
+ // instantiations for OwningNonNull and RefPtr.
+ if (IsInitialized(this->get())) {
+ this->get()->HoldJSObjectsIfMoreThanOneOwner();
+ }
+ }
+
+private:
+ template<typename U>
+ static bool IsInitialized(U& aArg); // Not implemented
+
+ template<typename U>
+ static bool IsInitialized(RefPtr<U>& aRefPtr)
+ {
+ return aRefPtr;
+ }
+
+ template<typename U>
+ static bool IsInitialized(OwningNonNull<U>& aOwningNonNull)
+ {
+ return aOwningNonNull.isInitialized();
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CallbackObject_h