summaryrefslogtreecommitdiffstats
path: root/dom/bindings/CallbackObject.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/bindings/CallbackObject.h
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/bindings/CallbackObject.h')
-rw-r--r--dom/bindings/CallbackObject.h664
1 files changed, 664 insertions, 0 deletions
diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h
new file mode 100644
index 0000000000..74d27886a9
--- /dev/null
+++ b/dom/bindings/CallbackObject.h
@@ -0,0 +1,664 @@
+/* -*- 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 <cstddef>
+#include <cstdint>
+#include <utility>
+#include "js/Exception.h"
+#include "js/RootingAPI.h"
+#include "js/Wrapper.h"
+#include "jsapi.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/BindingCallContext.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsID.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+#include "nsISupportsUtils.h"
+#include "nsStringFwd.h"
+
+class JSAutoRealm;
+class JSObject;
+class JSTracer;
+class nsCycleCollectionTraversalCallback;
+struct JSContext;
+
+namespace JS {
+class AutoSetAsyncStackForNewCalls;
+class Realm;
+class Value;
+} // namespace JS
+
+namespace mozilla {
+
+class ErrorResult;
+class PromiseJobRunnable;
+template <class T>
+class OwningNonNull;
+
+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_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.
+ explicit CallbackObject(JSContext* aCx, JS::Handle<JSObject*> aCallback,
+ JS::Handle<JSObject*> aCallbackGlobal,
+ nsIGlobalObject* aIncumbentGlobal) {
+ if (aCx && JS::IsAsyncStackCaptureEnabledForRealm(aCx)) {
+ JS::Rooted<JSObject*> stack(aCx);
+ if (!JS::CaptureCurrentStack(aCx, &stack)) {
+ JS_ClearPendingException(aCx);
+ }
+ Init(aCallback, aCallbackGlobal, stack, aIncumbentGlobal);
+ } else {
+ Init(aCallback, aCallbackGlobal, 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(JSObject* aCallback, JSObject* aCallbackGlobal,
+ JSObject* aAsyncStack,
+ nsIGlobalObject* aIncumbentGlobal) {
+ Init(aCallback, aCallbackGlobal, aAsyncStack, aIncumbentGlobal);
+ }
+
+ // This is guaranteed to be non-null from the time the CallbackObject is
+ // created until JavaScript has had a chance to run. It will only return null
+ // after a JavaScript caller has called nukeSandbox on a Sandbox object and
+ // the cycle collector has had a chance to run, unless Reset() is explicitly
+ // called (see below).
+ //
+ // This means that any native callee which receives a CallbackObject as an
+ // argument can safely rely on the callback being non-null so long as it
+ // doesn't trigger any scripts before it accesses it.
+ JSObject* CallbackOrNull() const {
+ mCallback.exposeToActiveJS();
+ return CallbackPreserveColor();
+ }
+
+ JSObject* CallbackGlobalOrNull() const {
+ mCallbackGlobal.exposeToActiveJS();
+ return mCallbackGlobal;
+ }
+
+ // Like CallbackOrNull(), but will return a new dead proxy object in the
+ // caller's realm if the callback is null.
+ JSObject* Callback(JSContext* aCx);
+
+ JSObject* GetCreationStack() const { return mCreationStack; }
+
+ void MarkForCC() {
+ mCallback.exposeToActiveJS();
+ mCallbackGlobal.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.
+ */
+ JSObject* CallbackPreserveColor() const { return mCallback.unbarrieredGet(); }
+ JSObject* CallbackGlobalPreserveColor() const {
+ return mCallbackGlobal.unbarrieredGet();
+ }
+
+ /*
+ * If the callback is known to be non-gray, then this method can be
+ * used instead of CallbackOrNull() to avoid the overhead of
+ * ExposeObjectToActiveJS().
+ */
+ JSObject* CallbackKnownNotGray() const {
+ JS::AssertObjectIsNotGray(mCallback);
+ return CallbackPreserveColor();
+ }
+
+ nsIGlobalObject* IncumbentGlobalOrNull() const { return mIncumbentGlobal; }
+
+ enum ExceptionHandling {
+ // Report any exception and don't throw it to the caller code.
+ eReportExceptions,
+ // Throw any exception to the caller code and don't report it.
+ eRethrowExceptions,
+ // Throw an exception to the caller code if the thrown exception is a
+ // binding object for a DOMException from the caller's scope, otherwise
+ // report it.
+ eRethrowContentExceptions
+ };
+
+ // Append a UTF-8 string to aOutString that describes the callback function,
+ // for use in logging or profiler markers.
+ // The string contains the function name and its source location, if
+ // available, in the following format:
+ // "<functionName> (<sourceURL>:<lineNumber>)"
+ void GetDescription(nsACString& aOutString);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+ }
+
+ // Used for cycle collection optimization. Should return true only if all our
+ // outgoing edges are to known-live objects. In that case, there's no point
+ // traversing our edges to them, because we know they can't be collected
+ // anyway.
+ bool IsBlackForCC() const {
+ // Play it safe in case this gets called after unlink.
+ return (!mCallback || !JS::ObjectIsMarkedGray(mCallback)) &&
+ (!mCallbackGlobal || !JS::ObjectIsMarkedGray(mCallbackGlobal)) &&
+ (!mCreationStack || !JS::ObjectIsMarkedGray(mCreationStack)) &&
+ (!mIncumbentJSGlobal ||
+ !JS::ObjectIsMarkedGray(mIncumbentJSGlobal)) &&
+ // mIncumbentGlobal is known-live if we have a known-live
+ // mIncumbentJSGlobal, since mIncumbentJSGlobal will keep a ref to
+ // it. At this point if mIncumbentJSGlobal is not null, it's
+ // known-live.
+ (!mIncumbentGlobal || mIncumbentJSGlobal);
+ }
+
+ protected:
+ virtual ~CallbackObject() { mozilla::DropJSObjects(this); }
+
+ explicit CallbackObject(CallbackObject* aCallbackObject) {
+ Init(aCallbackObject->mCallback, aCallbackObject->mCallbackGlobal,
+ aCallbackObject->mCreationStack, aCallbackObject->mIncumbentGlobal);
+ }
+
+ bool operator==(const CallbackObject& aOther) const {
+ JSObject* wrappedThis = CallbackPreserveColor();
+ JSObject* wrappedOther = aOther.CallbackPreserveColor();
+ if (!wrappedThis || !wrappedOther) {
+ return this == &aOther;
+ }
+
+ JSObject* thisObj = js::UncheckedUnwrap(wrappedThis);
+ JSObject* otherObj = js::UncheckedUnwrap(wrappedOther);
+ return thisObj == otherObj;
+ }
+
+ class JSObjectsDropper final {
+ public:
+ explicit JSObjectsDropper(CallbackObject* aHolder) : mHolder(aHolder) {}
+
+ ~JSObjectsDropper() { mHolder->ClearJSObjects(); }
+
+ private:
+ RefPtr<CallbackObject> mHolder;
+ };
+
+ private:
+ inline void InitNoHold(JSObject* aCallback, JSObject* aCallbackGlobal,
+ JSObject* aCreationStack,
+ nsIGlobalObject* aIncumbentGlobal) {
+ MOZ_ASSERT(aCallback && !mCallback);
+ MOZ_ASSERT(aCallbackGlobal);
+ MOZ_DIAGNOSTIC_ASSERT(JS::GetCompartment(aCallback) ==
+ JS::GetCompartment(aCallbackGlobal));
+ MOZ_ASSERT(JS_IsGlobalObject(aCallbackGlobal));
+ mCallback = aCallback;
+ mCallbackGlobal = aCallbackGlobal;
+ mCreationStack = aCreationStack;
+ if (aIncumbentGlobal) {
+ mIncumbentGlobal = aIncumbentGlobal;
+ // We don't want to expose to JS here (change the color). If someone ever
+ // reads mIncumbentJSGlobal, that will expose. If not, no need to expose
+ // here.
+ mIncumbentJSGlobal = aIncumbentGlobal->GetGlobalJSObjectPreserveColor();
+ }
+ }
+
+ inline void Init(JSObject* aCallback, JSObject* aCallbackGlobal,
+ JSObject* aCreationStack,
+ nsIGlobalObject* aIncumbentGlobal) {
+ // Set script objects before we hold, on the off chance that a GC could
+ // somehow happen in there... (which would be pretty odd, granted).
+ InitNoHold(aCallback, aCallbackGlobal, aCreationStack, aIncumbentGlobal);
+ mozilla::HoldJSObjects(this);
+ }
+
+ // Provide a way to clear this object's pointers to GC things after the
+ // callback has been run. Note that CallbackOrNull() will return null after
+ // this point. This should only be called if the object is known not to be
+ // used again, and no handles (e.g. those returned by CallbackPreserveColor)
+ // are in use.
+ void Reset() { ClearJSReferences(); }
+ friend class mozilla::PromiseJobRunnable;
+
+ inline void ClearJSReferences() {
+ mCallback = nullptr;
+ mCallbackGlobal = nullptr;
+ mCreationStack = nullptr;
+ mIncumbentJSGlobal = nullptr;
+ }
+
+ CallbackObject(const CallbackObject&) = delete;
+ CallbackObject& operator=(const CallbackObject&) = delete;
+
+ protected:
+ void ClearJSObjects() {
+ MOZ_ASSERT_IF(mIncumbentJSGlobal, mCallback);
+ if (mCallback) {
+ ClearJSReferences();
+ }
+ }
+
+ // 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 and do other slow JS-related init work we might do.
+ // If we have more than one owner, this will HoldJSObjects and do said slow
+ // init work; otherwise it will just forget all our JS references.
+ void FinishSlowJSInitIfMoreThanOneOwner(JSContext* aCx);
+
+ // 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 and not capturing async stacks (on the
+ // assumption that we will do that last whenever we decide to actually
+ // HoldJSObjects; see FinishSlowJSInitIfMoreThanOneOwner). If you use this,
+ // you MUST ensure that the object is traced until the HoldJSObjects happens!
+ CallbackObject(JSObject* aCallback, JSObject* aCallbackGlobal,
+ const FastCallbackConstructor&) {
+ InitNoHold(aCallback, aCallbackGlobal, nullptr, nullptr);
+ }
+
+ // 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;
+ // mCallbackGlobal is the global that we were in when we created the
+ // callback. In particular, it is guaranteed to be same-compartment with
+ // aCallback. We store it separately, because we have no way to recover the
+ // global if mCallback is a cross-compartment wrapper.
+ JS::Heap<JSObject*> mCallbackGlobal;
+ 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 aRealm
+ // needs to be set to the realm in which exceptions will be rethrown.
+ //
+ // If aExceptionHandling == eRethrowExceptions then aRealm may be set
+ // to the realm in which exceptions will be rethrown. In that case
+ // they will only be rethrown if that realm's principal subsumes the
+ // principal of our (unwrapped) callback.
+ CallSetup(CallbackObject* aCallback, ErrorResult& aRv,
+ const char* aExecutionReason,
+ ExceptionHandling aExceptionHandling, JS::Realm* aRealm = nullptr,
+ bool aIsJSImplementedWebIDL = false);
+ MOZ_CAN_RUN_SCRIPT ~CallSetup();
+
+ JSContext* GetContext() const { return mCx; }
+
+ // Safe to call this after the constructor has run without throwing on the
+ // ErrorResult it was handed.
+ BindingCallContext& GetCallContext() { return *mCallContext; }
+
+ 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 realm. This will only have a sensible value if
+ // mExceptionHandling == eRethrowContentExceptions.
+ JS::Realm* mRealm;
+
+ // And now members whose construction/destruction order we need to control.
+ Maybe<AutoEntryScript> mAutoEntryScript;
+ Maybe<AutoIncumbentScript> mAutoIncumbentScript;
+
+ Maybe<JS::Rooted<JSObject*>> mRootedCallable;
+ // The global of mRootedCallable.
+ Maybe<JS::Rooted<JSObject*>> mRootedCallableGlobal;
+
+ // Members which are used to set the async stack.
+ Maybe<JS::Rooted<JSObject*>> mAsyncStack;
+ Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
+
+ // Can't construct a JSAutoRealm without a JSContext either. Also,
+ // Put mAr after mAutoEntryScript so that we exit the realm before we
+ // pop the script settings stack. Though in practice we'll often manually
+ // order those two things.
+ Maybe<JSAutoRealm> mAr;
+
+ // Our BindingCallContext. This is a Maybe so we can avoid constructing it
+ // until after we have a JSContext to construct it with.
+ Maybe<BindingCallContext> mCallContext;
+
+ // 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;
+
+ void Reset() { UnlinkSelf(); }
+
+ nsISupports* GetISupports() const {
+ return reinterpret_cast<nsISupports*>(mPtrBits & ~XPCOMCallbackFlag);
+ }
+
+ already_AddRefed<nsISupports> Forget() {
+ // This can be called from random threads. Make sure to not refcount things
+ // in here!
+ nsISupports* supp = GetISupports();
+ mPtrBits = 0;
+ return dont_AddRef(supp);
+ }
+
+ // 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));
+ if (supp) {
+ // ToXPCOMCallback already did the right QI for us.
+ return supp.forget().downcast<XPCOMCallbackT>();
+ }
+ return nullptr;
+ }
+
+ // 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();
+ // Clear mPtrBits before the release to prevent reentrance.
+ mPtrBits = 0;
+ NS_IF_RELEASE(ptr);
+ }
+
+ 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) {
+ if (aField) {
+ 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.
+//
+// Since we effectively hold a ref to a refcounted thing (like RefPtr or
+// OwningNonNull), we are also MOZ_IS_SMARTPTR_TO_REFCOUNTED for static analysis
+// purposes.
+template <typename T>
+class MOZ_RAII MOZ_IS_SMARTPTR_TO_REFCOUNTED RootedCallback
+ : public JS::Rooted<T> {
+ public:
+ explicit RootedCallback(JSContext* cx) : JS::Rooted<T>(cx), mCx(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 CallbackOrNull() and Callback() on us.
+ JSObject* CallbackOrNull() const { return this->get()->CallbackOrNull(); }
+
+ JSObject* Callback(JSContext* aCx) const {
+ return this->get()->Callback(aCx);
+ }
+
+ ~RootedCallback() {
+ // Ensure that our callback starts holding on to its own JS objects as
+ // needed. We really do need to check that things are initialized even when
+ // T is OwningNonNull, because we might be running before the OwningNonNull
+ // ever got assigned to!
+ if (IsInitialized(this->get())) {
+ this->get()->FinishSlowJSInitIfMoreThanOneOwner(mCx);
+ }
+ }
+
+ 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();
+ }
+
+ JSContext* mCx;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_CallbackObject_h