/* -*- 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/. */ #ifndef XPCOM_THREADS_MOZPROMISE_H_ #define XPCOM_THREADS_MOZPROMISE_H_ #include #include #include "mozilla/Attributes.h" #include "mozilla/ErrorNames.h" #include "mozilla/Logging.h" #include "mozilla/Maybe.h" #include "mozilla/Monitor.h" #include "mozilla/Mutex.h" #include "mozilla/RefPtr.h" #include "mozilla/UniquePtr.h" #include "mozilla/Variant.h" #include "nsIDirectTaskDispatcher.h" #include "nsISerialEventTarget.h" #include "nsTArray.h" #include "nsThreadUtils.h" #ifdef MOZ_WIDGET_ANDROID # include "mozilla/jni/GeckoResultUtils.h" #endif #if MOZ_DIAGNOSTIC_ASSERT_ENABLED # define PROMISE_DEBUG #endif #ifdef PROMISE_DEBUG # define PROMISE_ASSERT MOZ_RELEASE_ASSERT #else # define PROMISE_ASSERT(...) \ do { \ } while (0) #endif #if DEBUG # include "nsPrintfCString.h" #endif namespace mozilla { namespace dom { class Promise; } extern LazyLogModule gMozPromiseLog; #define PROMISE_LOG(x, ...) \ MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__)) namespace detail { template struct MethodTraitsHelper : MethodTraitsHelper {}; template struct MethodTraitsHelper { using ReturnType = Ret; static const size_t ArgSize = sizeof...(ArgTypes); }; template struct MethodTraitsHelper { using ReturnType = Ret; static const size_t ArgSize = sizeof...(ArgTypes); }; template struct MethodTraitsHelper { using ReturnType = Ret; static const size_t ArgSize = sizeof...(ArgTypes); }; template struct MethodTraitsHelper { using ReturnType = Ret; static const size_t ArgSize = sizeof...(ArgTypes); }; template struct MethodTrait : MethodTraitsHelper> {}; } // namespace detail template using MethodReturnType = typename detail::MethodTrait::ReturnType; template constexpr bool TakesAnyArguments = detail::MethodTrait::ArgSize != 0; template class MozPromise; template constexpr bool IsMozPromise = false; template constexpr bool IsMozPromise> = true; /* * A promise manages an asynchronous request that may or may not be able to be * fulfilled immediately. When an API returns a promise, the consumer may attach * callbacks to be invoked (asynchronously, on a specified thread) when the * request is either completed (resolved) or cannot be completed (rejected). * Whereas JS promise callbacks are dispatched from Microtask checkpoints, * MozPromises resolution/rejection make a normal round-trip through the event * loop, which simplifies their ordering semantics relative to other native * code. * * MozPromises attempt to mirror the spirit of JS Promises to the extent that * is possible (and desirable) in C++. While the intent is that MozPromises * feel familiar to programmers who are accustomed to their JS-implemented * cousin, we don't shy away from imposing restrictions and adding features that * make sense for the use cases we encounter. * * A MozPromise is ThreadSafe, and may be ->Then()ed on any thread. The Then() * call accepts resolve and reject callbacks, and returns a magic object which * will be implicitly converted to a MozPromise::Request or a MozPromise object * depending on how the return value is used. The magic object serves several * purposes for the consumer. * * (1) When converting to a MozPromise::Request, it allows the caller to * cancel the delivery of the resolve/reject value if it has not already * occurred, via Disconnect() (this must be done on the target thread to * avoid racing). * * (2) When converting to a MozPromise (which is called a completion promise), * it allows promise chaining so ->Then() can be called again to attach * more resolve and reject callbacks. If the resolve/reject callback * returns a new MozPromise, that promise is chained to the completion * promise, such that its resolve/reject value will be forwarded along * when it arrives. If the resolve/reject callback returns void, the * completion promise is resolved/rejected with the same value that was * passed to the callback. * * The MozPromise APIs skirt traditional XPCOM convention by returning nsRefPtrs * (rather than already_AddRefed) from various methods. This is done to allow * elegant chaining of calls without cluttering up the code with intermediate * variables, and without introducing separate API variants for callers that * want a return value (from, say, ->Then()) from those that don't. * * When IsExclusive is true, the MozPromise does a release-mode assertion that * there is at most one call to either Then(...) or ChainTo(...). */ class MozPromiseRefcountable { public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable) protected: virtual ~MozPromiseRefcountable() = default; }; class MozPromiseBase : public MozPromiseRefcountable { public: virtual void AssertIsDead() = 0; }; template class MozPromiseHolder; template class MozPromiseRequestHolder; template class MozPromise : public MozPromiseBase { static const uint32_t sMagic = 0xcecace11; // Return a |T&&| to enable move when IsExclusive is true or // a |const T&| to enforce copy otherwise. template > static R MaybeMove(T& aX) { return static_cast(aX); } public: using ResolveValueType = ResolveValueT; using RejectValueType = RejectValueT; class ResolveOrRejectValue { public: template void SetResolve(ResolveValueType_&& aResolveValue) { MOZ_ASSERT(IsNothing()); mValue = Storage(VariantIndex{}, std::forward(aResolveValue)); } template void SetReject(RejectValueType_&& aRejectValue) { MOZ_ASSERT(IsNothing()); mValue = Storage(VariantIndex{}, std::forward(aRejectValue)); } template static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue) { ResolveOrRejectValue val; val.SetResolve(std::forward(aResolveValue)); return val; } template static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue) { ResolveOrRejectValue val; val.SetReject(std::forward(aRejectValue)); return val; } bool IsResolve() const { return mValue.template is(); } bool IsReject() const { return mValue.template is(); } bool IsNothing() const { return mValue.template is(); } const ResolveValueType& ResolveValue() const { return mValue.template as(); } ResolveValueType& ResolveValue() { return mValue.template as(); } const RejectValueType& RejectValue() const { return mValue.template as(); } RejectValueType& RejectValue() { return mValue.template as(); } private: enum { NothingIndex, ResolveIndex, RejectIndex }; using Storage = Variant; Storage mValue = Storage(VariantIndex{}); }; protected: // MozPromise is the public type, and never constructed directly. Construct // a MozPromise::Private, defined below. MozPromise(const char* aCreationSite, bool aIsCompletionPromise) : mCreationSite(aCreationSite), mMutex("MozPromise Mutex"), mHaveRequest(false), mIsCompletionPromise(aIsCompletionPromise) #ifdef PROMISE_DEBUG , mMagic4(&mMutex) #endif { PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite, this); } public: // MozPromise::Private allows us to separate the public interface (upon which // consumers of the promise may invoke methods like Then()) from the private // interface (upon which the creator of the promise may invoke Resolve() or // Reject()). APIs should create and store a MozPromise::Private (usually // via a MozPromiseHolder), and return a MozPromise to consumers. // // NB: We can include the definition of this class inline once B2G ICS is // gone. class Private; template [[nodiscard]] static RefPtr CreateAndResolve( ResolveValueType_&& aResolveValue, const char* aResolveSite) { static_assert(std::is_convertible_v, "Resolve() argument must be implicitly convertible to " "MozPromise's ResolveValueT"); RefPtr p = new MozPromise::Private(aResolveSite); p->Resolve(std::forward(aResolveValue), aResolveSite); return p; } template [[nodiscard]] static RefPtr CreateAndReject( RejectValueType_&& aRejectValue, const char* aRejectSite) { static_assert(std::is_convertible_v, "Reject() argument must be implicitly convertible to " "MozPromise's RejectValueT"); RefPtr p = new MozPromise::Private(aRejectSite); p->Reject(std::forward(aRejectValue), aRejectSite); return p; } template [[nodiscard]] static RefPtr CreateAndResolveOrReject( ResolveOrRejectValueType_&& aValue, const char* aSite) { RefPtr p = new MozPromise::Private(aSite); p->ResolveOrReject(std::forward(aValue), aSite); return p; } using AllPromiseType = MozPromise, RejectValueType, IsExclusive>; using AllSettledPromiseType = MozPromise, bool, IsExclusive>; private: class AllPromiseHolder : public MozPromiseRefcountable { public: explicit AllPromiseHolder(size_t aDependentPromises) : mPromise(new typename AllPromiseType::Private(__func__)), mOutstandingPromises(aDependentPromises) { MOZ_ASSERT(aDependentPromises > 0); mResolveValues.SetLength(aDependentPromises); } template void Resolve(size_t aIndex, ResolveValueType_&& aResolveValue) { if (!mPromise) { // Already rejected. return; } mResolveValues[aIndex].emplace( std::forward(aResolveValue)); if (--mOutstandingPromises == 0) { nsTArray resolveValues; resolveValues.SetCapacity(mResolveValues.Length()); for (auto&& resolveValue : mResolveValues) { resolveValues.AppendElement(std::move(resolveValue.ref())); } mPromise->Resolve(std::move(resolveValues), __func__); mPromise = nullptr; mResolveValues.Clear(); } } template void Reject(RejectValueType_&& aRejectValue) { if (!mPromise) { // Already rejected. return; } mPromise->Reject(std::forward(aRejectValue), __func__); mPromise = nullptr; mResolveValues.Clear(); } AllPromiseType* Promise() { return mPromise; } private: nsTArray> mResolveValues; RefPtr mPromise; size_t mOutstandingPromises; }; // Trying to pass ResolveOrRejectValue by value fails static analysis checks, // so we need to use either a const& or an rvalue reference, depending on // whether IsExclusive is true or not. using ResolveOrRejectValueParam = std::conditional_t; using ResolveValueTypeParam = std::conditional_t; using RejectValueTypeParam = std::conditional_t; class AllSettledPromiseHolder : public MozPromiseRefcountable { public: explicit AllSettledPromiseHolder(size_t aDependentPromises) : mPromise(new typename AllSettledPromiseType::Private(__func__)), mOutstandingPromises(aDependentPromises) { MOZ_ASSERT(aDependentPromises > 0); mValues.SetLength(aDependentPromises); } void Settle(size_t aIndex, ResolveOrRejectValueParam aValue) { if (!mPromise) { // Already rejected. return; } mValues[aIndex].emplace(MaybeMove(aValue)); if (--mOutstandingPromises == 0) { nsTArray values; values.SetCapacity(mValues.Length()); for (auto&& value : mValues) { values.AppendElement(std::move(value.ref())); } mPromise->Resolve(std::move(values), __func__); mPromise = nullptr; mValues.Clear(); } } AllSettledPromiseType* Promise() { return mPromise; } private: nsTArray> mValues; RefPtr mPromise; size_t mOutstandingPromises; }; public: [[nodiscard]] static RefPtr All( nsISerialEventTarget* aProcessingTarget, nsTArray>& aPromises) { if (aPromises.Length() == 0) { return AllPromiseType::CreateAndResolve( CopyableTArray(), __func__); } RefPtr holder = new AllPromiseHolder(aPromises.Length()); RefPtr promise = holder->Promise(); for (size_t i = 0; i < aPromises.Length(); ++i) { aPromises[i]->Then( aProcessingTarget, __func__, [holder, i](ResolveValueTypeParam aResolveValue) -> void { holder->Resolve(i, MaybeMove(aResolveValue)); }, [holder](RejectValueTypeParam aRejectValue) -> void { holder->Reject(MaybeMove(aRejectValue)); }); } return promise; } [[nodiscard]] static RefPtr AllSettled( nsISerialEventTarget* aProcessingTarget, nsTArray>& aPromises) { if (aPromises.Length() == 0) { return AllSettledPromiseType::CreateAndResolve( CopyableTArray(), __func__); } RefPtr holder = new AllSettledPromiseHolder(aPromises.Length()); RefPtr promise = holder->Promise(); for (size_t i = 0; i < aPromises.Length(); ++i) { aPromises[i]->Then(aProcessingTarget, __func__, [holder, i](ResolveOrRejectValueParam aValue) -> void { holder->Settle(i, MaybeMove(aValue)); }); } return promise; } class Request : public MozPromiseRefcountable { public: virtual void Disconnect() = 0; protected: Request() : mComplete(false), mDisconnected(false) {} virtual ~Request() = default; bool mComplete; bool mDisconnected; }; protected: /* * A ThenValue tracks a single consumer waiting on the promise. When a * consumer invokes promise->Then(...), a ThenValue is created. Once the * Promise is resolved or rejected, a {Resolve,Reject}Runnable is dispatched, * which invokes the resolve/reject method and then deletes the ThenValue. */ class ThenValueBase : public Request { friend class MozPromise; static const uint32_t sMagic = 0xfadece11; public: class ResolveOrRejectRunnable final : public PrioritizableCancelableRunnable { public: ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise) : PrioritizableCancelableRunnable( aPromise->mPriority, "MozPromise::ThenValueBase::ResolveOrRejectRunnable"), mThenValue(aThenValue), mPromise(aPromise) { MOZ_DIAGNOSTIC_ASSERT(!mPromise->IsPending()); } ~ResolveOrRejectRunnable() { if (mThenValue) { mThenValue->AssertIsDead(); } } NS_IMETHOD Run() override { PROMISE_LOG("ResolveOrRejectRunnable::Run() [this=%p]", this); mThenValue->DoResolveOrReject(mPromise->Value()); mThenValue = nullptr; mPromise = nullptr; return NS_OK; } nsresult Cancel() override { return Run(); } private: RefPtr mThenValue; RefPtr mPromise; }; ThenValueBase(nsISerialEventTarget* aResponseTarget, const char* aCallSite) : mResponseTarget(aResponseTarget), mCallSite(aCallSite) { MOZ_ASSERT(aResponseTarget); } #ifdef PROMISE_DEBUG ~ThenValueBase() { mMagic1 = 0; mMagic2 = 0; } #endif void AssertIsDead() { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); // We want to assert that this ThenValues is dead - that is to say, that // there are no consumers waiting for the result. In the case of a normal // ThenValue, we check that it has been disconnected, which is the way // that the consumer signals that it no longer wishes to hear about the // result. If this ThenValue has a completion promise (which is mutually // exclusive with being disconnectable), we recursively assert that every // ThenValue associated with the completion promise is dead. if (MozPromiseBase* p = CompletionPromise()) { p->AssertIsDead(); } else { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED if (MOZ_UNLIKELY(!Request::mDisconnected)) { MOZ_CRASH_UNSAFE_PRINTF( "MozPromise::ThenValue created from '%s' destroyed without being " "either disconnected, resolved, or rejected (dispatchRv: %s)", mCallSite, mDispatchRv ? GetStaticErrorName(*mDispatchRv) : "not dispatched"); } #endif } } void Dispatch(MozPromise* aPromise) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); aPromise->mMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(!aPromise->IsPending()); nsCOMPtr r = new ResolveOrRejectRunnable(this, aPromise); PROMISE_LOG( "%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p] " "%s dispatch", aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", mCallSite, r.get(), aPromise, this, aPromise->mUseSynchronousTaskDispatch ? "synchronous" : aPromise->mUseDirectTaskDispatch ? "directtask" : "normal"); if (aPromise->mUseSynchronousTaskDispatch && mResponseTarget->IsOnCurrentThread()) { PROMISE_LOG("ThenValue::Dispatch running task synchronously [this=%p]", this); r->Run(); return; } if (aPromise->mUseDirectTaskDispatch && mResponseTarget->IsOnCurrentThread()) { PROMISE_LOG( "ThenValue::Dispatch dispatch task via direct task queue [this=%p]", this); nsCOMPtr dispatcher = do_QueryInterface(mResponseTarget); if (dispatcher) { SetDispatchRv(dispatcher->DispatchDirectTask(r.forget())); return; } NS_WARNING( nsPrintfCString( "Direct Task dispatching not available for thread \"%s\"", PR_GetThreadName(PR_GetCurrentThread())) .get()); MOZ_DIAGNOSTIC_ASSERT( false, "mResponseTarget must implement nsIDirectTaskDispatcher for direct " "task dispatching"); } // Promise consumers are allowed to disconnect the Request object and // then shut down the thread or task queue that the promise result would // be dispatched on. So we unfortunately can't assert that promise // dispatch succeeds. :-( // We do record whether or not it succeeds so that if the ThenValueBase is // then destroyed and it was not disconnected, we can include that // information in the assertion message. SetDispatchRv(mResponseTarget->Dispatch(r.forget())); } void Disconnect() override { MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete); Request::mDisconnected = true; // We could support rejecting the completion promise on disconnection, but // then we'd need to have some sort of default reject value. The use cases // of disconnection and completion promise chaining seem pretty // orthogonal, so let's use assert against it. MOZ_DIAGNOSTIC_ASSERT(!CompletionPromise()); } protected: virtual MozPromiseBase* CompletionPromise() const = 0; virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0; void DoResolveOrReject(ResolveOrRejectValue& aValue) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); Request::mComplete = true; if (Request::mDisconnected) { PROMISE_LOG( "ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", this); return; } // Invoke the resolve or reject method. DoResolveOrRejectInternal(aValue); } void SetDispatchRv(nsresult aRv) { #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED mDispatchRv = Some(aRv); #endif } nsCOMPtr mResponseTarget; // May be released on any thread. #ifdef PROMISE_DEBUG uint32_t mMagic1 = sMagic; #endif const char* mCallSite; #ifdef PROMISE_DEBUG uint32_t mMagic2 = sMagic; #endif #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED Maybe mDispatchRv; #endif }; /* * We create two overloads for invoking Resolve/Reject Methods so as to * make the resolve/reject value argument "optional". */ template static std::enable_if_t, MethodReturnType> InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { return (aThisVal->*aMethod)(std::forward(aValue)); } template static std::enable_if_t, MethodReturnType> InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { return (aThisVal->*aMethod)(); } // Called when promise chaining is supported. template static std::enable_if_t InvokeCallbackMethod( ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, CompletionPromiseType&& aCompletionPromise) { auto p = InvokeMethod(aThisVal, aMethod, std::forward(aValue)); if (aCompletionPromise) { p->ChainTo(aCompletionPromise.forget(), ""); } } // Called when promise chaining is not supported. template static std::enable_if_t InvokeCallbackMethod( ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, CompletionPromiseType&& aCompletionPromise) { MOZ_DIAGNOSTIC_ASSERT( !aCompletionPromise, "Can't do promise chaining for a non-promise-returning method."); InvokeMethod(aThisVal, aMethod, std::forward(aValue)); } template class ThenCommand; template class ThenValue; template class ThenValue : public ThenValueBase { friend class ThenCommand; using R1 = RemoveSmartPointer>; using R2 = RemoveSmartPointer>; constexpr static bool SupportChaining = IsMozPromise && std::is_same_v; // Fall back to MozPromise when promise chaining is not supported to make // code compile. using PromiseType = std::conditional_t; public: ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mThisVal(aThisVal), mResolveMethod(aResolveMethod), mRejectMethod(aRejectMethod) {} void Disconnect() override { ThenValueBase::Disconnect(); // If a Request has been disconnected, we don't guarantee that the // resolve/reject runnable will be dispatched. Null out our refcounted // this-value now so that it's released predictably on the dispatch // thread. mThisVal = nullptr; } protected: MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { if (aValue.IsResolve()) { InvokeCallbackMethod(mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise)); } else { InvokeCallbackMethod(mThisVal.get(), mRejectMethod, MaybeMove(aValue.RejectValue()), std::move(mCompletionPromise)); } // Null out mThisVal after invoking the callback so that any references // are released predictably on the dispatch thread. Otherwise, it would be // released on whatever thread last drops its reference to the ThenValue, // which may or may not be ok. mThisVal = nullptr; } private: RefPtr mThisVal; // Only accessed and refcounted on dispatch thread. ResolveMethodType mResolveMethod; RejectMethodType mRejectMethod; RefPtr mCompletionPromise; }; template class ThenValue : public ThenValueBase { friend class ThenCommand; using R1 = RemoveSmartPointer>; constexpr static bool SupportChaining = IsMozPromise; // Fall back to MozPromise when promise chaining is not supported to make // code compile. using PromiseType = std::conditional_t; public: ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, ResolveRejectMethodType aResolveRejectMethod, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mThisVal(aThisVal), mResolveRejectMethod(aResolveRejectMethod) {} void Disconnect() override { ThenValueBase::Disconnect(); // If a Request has been disconnected, we don't guarantee that the // resolve/reject runnable will be dispatched. Null out our refcounted // this-value now so that it's released predictably on the dispatch // thread. mThisVal = nullptr; } protected: MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { InvokeCallbackMethod( mThisVal.get(), mResolveRejectMethod, MaybeMove(aValue), std::move(mCompletionPromise)); // Null out mThisVal after invoking the callback so that any references // are released predictably on the dispatch thread. Otherwise, it would be // released on whatever thread last drops its reference to the ThenValue, // which may or may not be ok. mThisVal = nullptr; } private: RefPtr mThisVal; // Only accessed and refcounted on dispatch thread. ResolveRejectMethodType mResolveRejectMethod; RefPtr mCompletionPromise; }; // NB: We could use std::function here instead of a template if it were // supported. :-( template class ThenValue : public ThenValueBase { friend class ThenCommand; using R1 = RemoveSmartPointer>; using R2 = RemoveSmartPointer>; constexpr static bool SupportChaining = IsMozPromise && std::is_same_v; // Fall back to MozPromise when promise chaining is not supported to make // code compile. using PromiseType = std::conditional_t; public: ThenValue(nsISerialEventTarget* aResponseTarget, ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite) { mResolveFunction.emplace(std::move(aResolveFunction)); mRejectFunction.emplace(std::move(aRejectFunction)); } void Disconnect() override { ThenValueBase::Disconnect(); // If a Request has been disconnected, we don't guarantee that the // resolve/reject runnable will be dispatched. Destroy our callbacks // now so that any references in closures are released predictable on // the dispatch thread. mResolveFunction.reset(); mRejectFunction.reset(); } protected: MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { // Note: The usage of InvokeCallbackMethod here requires that // ResolveFunction/RejectFunction are capture-lambdas (i.e. anonymous // classes with ::operator()), since it allows us to share code more // easily. We could fix this if need be, though it's quite easy to work // around by just capturing something. if (aValue.IsResolve()) { InvokeCallbackMethod( mResolveFunction.ptr(), &ResolveFunction::operator(), MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise)); } else { InvokeCallbackMethod( mRejectFunction.ptr(), &RejectFunction::operator(), MaybeMove(aValue.RejectValue()), std::move(mCompletionPromise)); } // Destroy callbacks after invocation so that any references in closures // are released predictably on the dispatch thread. Otherwise, they would // be released on whatever thread last drops its reference to the // ThenValue, which may or may not be ok. mResolveFunction.reset(); mRejectFunction.reset(); } private: Maybe mResolveFunction; // Only accessed and deleted on dispatch thread. Maybe mRejectFunction; // Only accessed and deleted on dispatch thread. RefPtr mCompletionPromise; }; template class ThenValue : public ThenValueBase { friend class ThenCommand; using R1 = RemoveSmartPointer>; constexpr static bool SupportChaining = IsMozPromise; // Fall back to MozPromise when promise chaining is not supported to make // code compile. using PromiseType = std::conditional_t; public: ThenValue(nsISerialEventTarget* aResponseTarget, ResolveRejectFunction&& aResolveRejectFunction, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite) { mResolveRejectFunction.emplace(std::move(aResolveRejectFunction)); } void Disconnect() override { ThenValueBase::Disconnect(); // If a Request has been disconnected, we don't guarantee that the // resolve/reject runnable will be dispatched. Destroy our callbacks // now so that any references in closures are released predictable on // the dispatch thread. mResolveRejectFunction.reset(); } protected: MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { // Note: The usage of InvokeCallbackMethod here requires that // ResolveRejectFunction is capture-lambdas (i.e. anonymous // classes with ::operator()), since it allows us to share code more // easily. We could fix this if need be, though it's quite easy to work // around by just capturing something. InvokeCallbackMethod( mResolveRejectFunction.ptr(), &ResolveRejectFunction::operator(), MaybeMove(aValue), std::move(mCompletionPromise)); // Destroy callbacks after invocation so that any references in closures // are released predictably on the dispatch thread. Otherwise, they would // be released on whatever thread last drops its reference to the // ThenValue, which may or may not be ok. mResolveRejectFunction.reset(); } private: Maybe mResolveRejectFunction; // Only accessed and deleted on dispatch // thread. RefPtr mCompletionPromise; }; template class MapValue final : public ThenValueBase { friend class ThenCommand; constexpr static const bool SupportChaining = true; using ResolveValueT_ = std::invoke_result_t; using PromiseType = MozPromise; public: explicit MapValue(nsISerialEventTarget* aResponseTarget, ResolveFunction&& f, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mResolveFunction(Some(std::forward(f))) {} protected: void Disconnect() override { ThenValueBase::Disconnect(); mResolveFunction.reset(); } MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { // Note that promise-chaining is always supported here; this function can // only transform from MozPromise to MozPromise. auto value = MaybeMove(aValue); typename PromiseType::ResolveOrRejectValue output; if (value.IsResolve()) { output.SetResolve((*mResolveFunction)(std::move(value.ResolveValue()))); } else { output.SetReject(std::move(value.RejectValue())); } if (mCompletionPromise) { mCompletionPromise->ResolveOrReject(std::move(output), ThenValueBase::mCallSite); } } private: Maybe mResolveFunction; RefPtr mCompletionPromise; }; template class MapErrValue final : public ThenValueBase { friend class ThenCommand; constexpr static const bool SupportChaining = true; using RejectValueT_ = std::invoke_result_t; using PromiseType = MozPromise; public: explicit MapErrValue(nsISerialEventTarget* aResponseTarget, RejectFunction&& f, const char* aCallSite) : ThenValueBase(aResponseTarget, aCallSite), mRejectFunction(Some(std::forward(f))) {} protected: void Disconnect() override { ThenValueBase::Disconnect(); mRejectFunction.reset(); } MozPromiseBase* CompletionPromise() const override { return mCompletionPromise; } void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { // Note that promise-chaining is always supported here; this function can // only transform from MozPromise to MozPromise. auto value = MaybeMove(aValue); typename PromiseType::ResolveOrRejectValue output; if (value.IsResolve()) { output.SetResolve(std::move(value.ResolveValue())); } else { output.SetReject((*mRejectFunction)(std::move(value.RejectValue()))); } if (mCompletionPromise) { mCompletionPromise->ResolveOrReject(std::move(output), ThenValueBase::mCallSite); } } private: Maybe mRejectFunction; RefPtr mCompletionPromise; }; public: void ThenInternal(already_AddRefed aThenValue, const char* aCallSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); RefPtr thenValue = aThenValue; MutexAutoLock lock(mMutex); MOZ_DIAGNOSTIC_ASSERT( !IsExclusive || !mHaveRequest, "Using an exclusive promise in a non-exclusive fashion"); mHaveRequest = true; PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]", aCallSite, this, thenValue.get(), (int)IsPending()); if (!IsPending()) { thenValue->Dispatch(this); } else { mThenValues.AppendElement(thenValue.forget()); } } protected: /* * A command object to store all information needed to make a request to the * promise. This allows us to delay the request until further use is known * (whether it is ->Then() again for more promise chaining or ->Track() to * terminate chaining and issue the request). * * This allows a unified syntax for promise chaining and disconnection, and * feels more like its JS counterpart. * * Note that a ThenCommand is always exclusive, even if its source or result * promises are not. To attach multiple continuations, explicitly convert it * to a promise first. */ template class MOZ_TEMPORARY_CLASS ThenCommand { // Allow Promise1::ThenCommand to access the private constructor // Promise2::ThenCommand(ThenCommand&&). template friend class MozPromise; using PromiseType = typename ThenValueType::PromiseType; using Private = typename PromiseType::Private; ThenCommand(const char* aCallSite, already_AddRefed aThenValue, MozPromise* aReceiver) : mCallSite(aCallSite), mThenValue(aThenValue), mReceiver(aReceiver) {} ThenCommand(ThenCommand&& aOther) noexcept = default; public: ~ThenCommand() { // Issue the request now if the return value of Then() is not used. if (mThenValue) { mReceiver->ThenInternal(mThenValue.forget(), mCallSite); } } // Allow RefPtr p = somePromise->Then(); // p->Then(thread1, ...); // p->Then(thread2, ...); operator RefPtr() { static_assert( ThenValueType::SupportChaining, "The resolve/reject callback needs to return a RefPtr " "in order to do promise chaining."); // mCompletionPromise must be created before ThenInternal() to avoid race. RefPtr p = new Private("", true /* aIsCompletionPromise */); mThenValue->mCompletionPromise = p; // Note ThenInternal() might nullify mCompletionPromise before return. // So we need to return p instead of mCompletionPromise. mReceiver->ThenInternal(mThenValue.forget(), mCallSite); return p; } template auto Then(Ts&&... aArgs) -> decltype(std::declval().Then( std::forward(aArgs)...)) { return static_cast>(*this)->Then( std::forward(aArgs)...); } template auto Map(Ts&&... aArgs) -> decltype(std::declval().Map( std::forward(aArgs)...)) { return static_cast>(*this)->Map( std::forward(aArgs)...); } template auto MapErr(Ts&&... aArgs) -> decltype(std::declval().MapErr( std::forward(aArgs)...)) { return static_cast>(*this)->MapErr( std::forward(aArgs)...); } void Track(MozPromiseRequestHolder& aRequestHolder) { aRequestHolder.Track(do_AddRef(mThenValue)); mReceiver->ThenInternal(mThenValue.forget(), mCallSite); } // Allow calling ->Then() again for more promise chaining or ->Track() to // end chaining and track the request for future disconnection. ThenCommand* operator->() { return this; } private: const char* mCallSite; RefPtr mThenValue; RefPtr mReceiver; }; public: template , typename ReturnType = ThenCommand> ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, ThisType* aThisVal, Methods... aMethods) { RefPtr thenValue = new ThenValueType(aResponseTarget, aThisVal, aMethods..., aCallSite); return ReturnType(aCallSite, thenValue.forget(), this); } template , typename ReturnType = ThenCommand> ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, Functions&&... aFunctions) { RefPtr thenValue = new ThenValueType(aResponseTarget, std::move(aFunctions)..., aCallSite); return ReturnType(aCallSite, thenValue.forget(), this); } // Shorthand for a `Then` which simply forwards the reject-value, but performs // some additional work with the resolve-value. template auto Map(nsISerialEventTarget* aResponseTarget, const char* aCallSite, Function&& function) { RefPtr> thenValue = new MapValue( aResponseTarget, std::forward(function), aCallSite); return ThenCommand>(aCallSite, thenValue.forget(), this); } // Shorthand for a `Then` which simply forwards the resolve-value, but // performs some additional work with the reject-value. template auto MapErr(nsISerialEventTarget* aResponseTarget, const char* aCallSite, Function&& function) { RefPtr> thenValue = new MapErrValue( aResponseTarget, std::forward(function), aCallSite); return ThenCommand>(aCallSite, thenValue.forget(), this); } void ChainTo(already_AddRefed aChainedPromise, const char* aCallSite) { MutexAutoLock lock(mMutex); MOZ_DIAGNOSTIC_ASSERT( !IsExclusive || !mHaveRequest, "Using an exclusive promise in a non-exclusive fashion"); mHaveRequest = true; RefPtr chainedPromise = aChainedPromise; PROMISE_LOG( "%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]", aCallSite, this, chainedPromise.get(), (int)IsPending()); // We want to use the same type of dispatching method with the chained // promises. // We need to ensure that the UseSynchronousTaskDispatch branch isn't taken // at compilation time to ensure we're not triggering the static_assert in // UseSynchronousTaskDispatch method. if constexpr (IsExclusive) ensures // that. if (mUseDirectTaskDispatch) { chainedPromise->UseDirectTaskDispatch(aCallSite); } else if constexpr (IsExclusive) { if (mUseSynchronousTaskDispatch) { chainedPromise->UseSynchronousTaskDispatch(aCallSite); } } else { chainedPromise->SetTaskPriority(mPriority, aCallSite); } if (!IsPending()) { ForwardTo(chainedPromise); } else { mChainedPromises.AppendElement(chainedPromise); } } #ifdef MOZ_WIDGET_ANDROID // Creates a C++ MozPromise from its Java counterpart, GeckoResult. [[nodiscard]] static RefPtr FromGeckoResult( java::GeckoResult::Param aGeckoResult) { using jni::GeckoResultCallback; RefPtr p = new Private("GeckoResult Glue", false); auto resolve = GeckoResultCallback::CreateAndAttach( [p](ResolveValueType&& aArg) { p->Resolve(MaybeMove(aArg), __func__); }); auto reject = GeckoResultCallback::CreateAndAttach( [p](RejectValueType&& aArg) { p->Reject(MaybeMove(aArg), __func__); }); aGeckoResult->NativeThen(resolve, reject); return p; } #endif // Note we expose the function AssertIsDead() instead of IsDead() since // checking IsDead() is a data race in the situation where the request is not // dead. Therefore we enforce the form |Assert(IsDead())| by exposing // AssertIsDead() only. void AssertIsDead() override { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); for (auto&& then : mThenValues) { then->AssertIsDead(); } for (auto&& chained : mChainedPromises) { chained->AssertIsDead(); } } bool IsResolved() const { return mValue.IsResolve(); } protected: bool IsPending() const { return mValue.IsNothing(); } ResolveOrRejectValue& Value() { // This method should only be called once the value has stabilized. As // such, we don't need to acquire the lock here. MOZ_DIAGNOSTIC_ASSERT(!IsPending()); return mValue; } void DispatchAll() { mMutex.AssertCurrentThreadOwns(); for (auto&& thenValue : mThenValues) { thenValue->Dispatch(this); } mThenValues.Clear(); for (auto&& chainedPromise : mChainedPromises) { ForwardTo(chainedPromise); } mChainedPromises.Clear(); } void ForwardTo(Private* aOther) { MOZ_ASSERT(!IsPending()); if (mValue.IsResolve()) { aOther->Resolve(MaybeMove(mValue.ResolveValue()), ""); } else { aOther->Reject(MaybeMove(mValue.RejectValue()), ""); } } virtual ~MozPromise() { PROMISE_LOG("MozPromise::~MozPromise [this=%p]", this); AssertIsDead(); // We can't guarantee a completion promise will always be revolved or // rejected since ResolveOrRejectRunnable might not run when dispatch fails. if (!mIsCompletionPromise) { MOZ_ASSERT(!IsPending()); MOZ_ASSERT(mThenValues.IsEmpty()); MOZ_ASSERT(mChainedPromises.IsEmpty()); } #ifdef PROMISE_DEBUG mMagic1 = 0; mMagic2 = 0; mMagic3 = 0; mMagic4 = nullptr; #endif }; const char* mCreationSite; // For logging Mutex mMutex MOZ_UNANNOTATED; ResolveOrRejectValue mValue; bool mUseSynchronousTaskDispatch = false; bool mUseDirectTaskDispatch = false; uint32_t mPriority = nsIRunnablePriority::PRIORITY_NORMAL; #ifdef PROMISE_DEBUG uint32_t mMagic1 = sMagic; #endif // Try shows we never have more than 3 elements when IsExclusive is false. // So '3' is a good value to avoid heap allocation in most cases. AutoTArray, IsExclusive ? 1 : 3> mThenValues; #ifdef PROMISE_DEBUG uint32_t mMagic2 = sMagic; #endif nsTArray> mChainedPromises; #ifdef PROMISE_DEBUG uint32_t mMagic3 = sMagic; #endif bool mHaveRequest; const bool mIsCompletionPromise; #ifdef PROMISE_DEBUG void* mMagic4; #endif }; template class MozPromise::Private : public MozPromise { public: explicit Private(const char* aCreationSite, bool aIsCompletionPromise = false) : MozPromise(aCreationSite, aIsCompletionPromise) {} template void Resolve(ResolveValueT_&& aResolveValue, const char* aResolveSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite, this, mCreationSite); if (!IsPending()) { PROMISE_LOG( "%s ignored already resolved or rejected MozPromise (%p created at " "%s)", aResolveSite, this, mCreationSite); return; } mValue.SetResolve(std::forward(aResolveValue)); DispatchAll(); } template void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this, mCreationSite); if (!IsPending()) { PROMISE_LOG( "%s ignored already resolved or rejected MozPromise (%p created at " "%s)", aRejectSite, this, mCreationSite); return; } mValue.SetReject(std::forward(aRejectValue)); DispatchAll(); } template void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite, this, mCreationSite); if (!IsPending()) { PROMISE_LOG( "%s ignored already resolved or rejected MozPromise (%p created at " "%s)", aSite, this, mCreationSite); return; } mValue = std::forward(aValue); DispatchAll(); } // If the caller and target are both on the same thread, run the the resolve // or reject callback synchronously. Otherwise, the task will be dispatched // via the target Dispatch method. void UseSynchronousTaskDispatch(const char* aSite) { static_assert( IsExclusive, "Synchronous dispatch can only be used with exclusive promises"); PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s UseSynchronousTaskDispatch MozPromise (%p created at %s)", aSite, this, mCreationSite); MOZ_ASSERT(IsPending(), "A Promise must not have been already resolved or rejected to " "set dispatch state"); mUseSynchronousTaskDispatch = true; } // If the caller and target are both on the same thread, run the // resolve/reject callback off the direct task queue instead. This avoids a // full trip to the back of the event queue for each additional asynchronous // step when using MozPromise, and is similar (but not identical to) the // microtask semantics of JS promises. void UseDirectTaskDispatch(const char* aSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s UseDirectTaskDispatch MozPromise (%p created at %s)", aSite, this, mCreationSite); MOZ_ASSERT(IsPending(), "A Promise must not have been already resolved or rejected to " "set dispatch state"); MOZ_ASSERT(!mUseSynchronousTaskDispatch, "Promise already set for synchronous dispatch"); mUseDirectTaskDispatch = true; } // If the resolve/reject will be handled on a thread supporting priorities, // one may want to tweak the priority of the task by passing a // nsIRunnablePriority::PRIORITY_* to SetTaskPriority. void SetTaskPriority(uint32_t aPriority, const char* aSite) { PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && mMagic3 == sMagic && mMagic4 == &mMutex); MutexAutoLock lock(mMutex); PROMISE_LOG("%s TaskPriority MozPromise (%p created at %s)", aSite, this, mCreationSite); MOZ_ASSERT(IsPending(), "A Promise must not have been already resolved or rejected to " "set dispatch state"); MOZ_ASSERT(!mUseSynchronousTaskDispatch, "Promise already set for synchronous dispatch"); MOZ_ASSERT(!mUseDirectTaskDispatch, "Promise already set for direct dispatch"); mPriority = aPriority; } }; // A generic promise type that does the trick for simple use cases. // // Vaguely deprecated: prefer explicitly naming the resolve- and reject-type. // Additionally, prefer `mozilla::Ok` as the resolve-type if the boolean's value // is irrelevant. using GenericPromise = MozPromise; // A generic, non-exclusive promise type that does the trick for simple use // cases. // // Vaguely deprecated, as above. using GenericNonExclusivePromise = MozPromise; /* * Class to encapsulate a promise for a particular role. Use this as the member * variable for a class whose method returns a promise. */ template class MozPromiseHolderBase { public: MozPromiseHolderBase() = default; MozPromiseHolderBase(MozPromiseHolderBase&& aOther) noexcept = default; MozPromiseHolderBase& operator=(MozPromiseHolderBase&& aOther) noexcept = default; ~MozPromiseHolderBase() { MOZ_ASSERT(!mPromise); } already_AddRefed Ensure(const char* aMethodName) { static_cast(this)->Check(); if (!mPromise) { mPromise = new (typename PromiseType::Private)(aMethodName); } RefPtr p = mPromise.get(); return p.forget(); } bool IsEmpty() const { static_cast(this)->Check(); return !mPromise; } already_AddRefed Steal() { static_cast(this)->Check(); return mPromise.forget(); } template void Resolve(ResolveValueType_&& aResolveValue, const char* aMethodName) { static_assert(std::is_convertible_v, "Resolve() argument must be implicitly convertible to " "MozPromise's ResolveValueT"); static_cast(this)->Check(); MOZ_ASSERT(mPromise); mPromise->Resolve(std::forward(aResolveValue), aMethodName); mPromise = nullptr; } template void ResolveIfExists(ResolveValueType_&& aResolveValue, const char* aMethodName) { if (!IsEmpty()) { Resolve(std::forward(aResolveValue), aMethodName); } } template void Reject(RejectValueType_&& aRejectValue, const char* aMethodName) { static_assert(std::is_convertible_v, "Reject() argument must be implicitly convertible to " "MozPromise's RejectValueT"); static_cast(this)->Check(); MOZ_ASSERT(mPromise); mPromise->Reject(std::forward(aRejectValue), aMethodName); mPromise = nullptr; } template void RejectIfExists(RejectValueType_&& aRejectValue, const char* aMethodName) { if (!IsEmpty()) { Reject(std::forward(aRejectValue), aMethodName); } } template void ResolveOrReject(ResolveOrRejectValueType_&& aValue, const char* aMethodName) { static_cast(this)->Check(); MOZ_ASSERT(mPromise); mPromise->ResolveOrReject(std::forward(aValue), aMethodName); mPromise = nullptr; } template void ResolveOrRejectIfExists(ResolveOrRejectValueType_&& aValue, const char* aMethodName) { if (!IsEmpty()) { ResolveOrReject(std::forward(aValue), aMethodName); } } void UseSynchronousTaskDispatch(const char* aSite) { MOZ_ASSERT(mPromise); mPromise->UseSynchronousTaskDispatch(aSite); } void UseDirectTaskDispatch(const char* aSite) { MOZ_ASSERT(mPromise); mPromise->UseDirectTaskDispatch(aSite); } void SetTaskPriority(uint32_t aPriority, const char* aSite) { MOZ_ASSERT(mPromise); mPromise->SetTaskPriority(aPriority, aSite); } private: RefPtr mPromise; }; template class MozPromiseHolder : public MozPromiseHolderBase> { public: using MozPromiseHolderBase< PromiseType, MozPromiseHolder>::MozPromiseHolderBase; static constexpr void Check(){}; }; template class MozMonitoredPromiseHolder : public MozPromiseHolderBase> { public: // Provide a Monitor that should always be held when accessing this instance. explicit MozMonitoredPromiseHolder(Monitor* const aMonitor) : mMonitor(aMonitor) { MOZ_ASSERT(aMonitor); } MozMonitoredPromiseHolder(MozMonitoredPromiseHolder&& aOther) = delete; MozMonitoredPromiseHolder& operator=(MozMonitoredPromiseHolder&& aOther) = delete; void Check() const { mMonitor->AssertCurrentThreadOwns(); } private: Monitor* const mMonitor; }; /* * Class to encapsulate a MozPromise::Request reference. Use this as the member * variable for a class waiting on a MozPromise. */ template class MozPromiseRequestHolder { public: MozPromiseRequestHolder() = default; ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); } void Track(already_AddRefed aRequest) { MOZ_DIAGNOSTIC_ASSERT(!Exists()); mRequest = aRequest; } void Complete() { MOZ_DIAGNOSTIC_ASSERT(Exists()); mRequest = nullptr; } // Disconnects and forgets an outstanding promise. The resolve/reject methods // will never be called. void Disconnect() { MOZ_ASSERT(Exists()); RefPtr request = std::move(mRequest); request->Disconnect(); } void DisconnectIfExists() { if (Exists()) { Disconnect(); } } bool Exists() const { return !!mRequest; } private: RefPtr mRequest; }; // Asynchronous Potentially-Cross-Thread Method Calls. // // This machinery allows callers to schedule a promise-returning function // (a method and object, or a function object like a lambda) to be invoked // asynchronously on a given thread, while at the same time receiving a // promise upon which to invoke Then() immediately. InvokeAsync dispatches a // task to invoke the function on the proper thread and also chain the // resulting promise to the one that the caller received, so that resolve/ // reject values are forwarded through. namespace detail { // Non-templated base class to allow us to use MOZ_COUNT_{C,D}TOR, which cause // assertions when used on templated types. class MethodCallBase { public: MOZ_COUNTED_DEFAULT_CTOR(MethodCallBase) MOZ_COUNTED_DTOR_VIRTUAL(MethodCallBase) }; template class MethodCall : public MethodCallBase { public: template MethodCall(MethodType aMethod, ThisType* aThisVal, Args&&... aArgs) : mMethod(aMethod), mThisVal(aThisVal), mArgs(std::forward(aArgs)...) { static_assert(sizeof...(Storages) == sizeof...(Args), "Storages and Args should have equal sizes"); } RefPtr Invoke() { return mArgs.apply(mThisVal.get(), mMethod); } private: MethodType mMethod; RefPtr mThisVal; RunnableMethodArguments mArgs; }; template class ProxyRunnable : public CancelableRunnable { public: ProxyRunnable( typename PromiseType::Private* aProxyPromise, MethodCall* aMethodCall) : CancelableRunnable("detail::ProxyRunnable"), mProxyPromise(aProxyPromise), mMethodCall(aMethodCall) {} NS_IMETHOD Run() override { RefPtr p = mMethodCall->Invoke(); mMethodCall = nullptr; p->ChainTo(mProxyPromise.forget(), ""); return NS_OK; } nsresult Cancel() override { return Run(); } private: RefPtr mProxyPromise; UniquePtr> mMethodCall; }; template static RefPtr InvokeAsyncImpl( nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, RefPtr (ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { MOZ_ASSERT(aTarget); typedef RefPtr (ThisType::*MethodType)(ArgTypes...); typedef detail::MethodCall MethodCallType; typedef detail::ProxyRunnable ProxyRunnableType; MethodCallType* methodCall = new MethodCallType( aMethod, aThisVal, std::forward(aArgs)...); RefPtr p = new (typename PromiseType::Private)(aCallerName); RefPtr r = new ProxyRunnableType(p, methodCall); aTarget->Dispatch(r.forget()); return p; } constexpr bool Any() { return false; } template constexpr bool Any(T1 a) { return static_cast(a); } template constexpr bool Any(T1 a, Ts... aOthers) { return a || Any(aOthers...); } } // namespace detail // InvokeAsync with explicitly-specified storages. // See ParameterStorage in nsThreadUtils.h for help. template = 0> static RefPtr InvokeAsync( nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, RefPtr (ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { static_assert( sizeof...(Storages) == sizeof...(ArgTypes), "Provided Storages and method's ArgTypes should have equal sizes"); static_assert(sizeof...(Storages) == sizeof...(ActualArgTypes), "Provided Storages and ActualArgTypes should have equal sizes"); return detail::InvokeAsyncImpl( aTarget, aThisVal, aCallerName, aMethod, std::forward(aArgs)...); } // InvokeAsync with no explicitly-specified storages, will copy arguments and // then move them out of the runnable into the target method parameters. template = 0> static RefPtr InvokeAsync( nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, RefPtr (ThisType::*aMethod)(ArgTypes...), ActualArgTypes&&... aArgs) { static_assert( !detail::Any( std::is_pointer_v>...), "Cannot pass pointer types through InvokeAsync, Storages must be " "provided"); static_assert(sizeof...(ArgTypes) == sizeof...(ActualArgTypes), "Method's ArgTypes and ActualArgTypes should have equal sizes"); return detail::InvokeAsyncImpl< StoreCopyPassByRRef>...>( aTarget, aThisVal, aCallerName, aMethod, std::forward(aArgs)...); } namespace detail { template class ProxyFunctionRunnable : public CancelableRunnable { using FunctionStorage = std::decay_t; public: template ProxyFunctionRunnable(typename PromiseType::Private* aProxyPromise, F&& aFunction) : CancelableRunnable("detail::ProxyFunctionRunnable"), mProxyPromise(aProxyPromise), mFunction(new FunctionStorage(std::forward(aFunction))) {} NS_IMETHOD Run() override { RefPtr p = (*mFunction)(); mFunction = nullptr; p->ChainTo(mProxyPromise.forget(), ""); return NS_OK; } nsresult Cancel() override { return Run(); } private: RefPtr mProxyPromise; UniquePtr mFunction; }; template constexpr static bool IsRefPtrMozPromise = false; template constexpr static bool IsRefPtrMozPromise>> = true; } // namespace detail // Invoke a function object (e.g., lambda) asynchronously. // Return a promise that the function should eventually resolve or reject. template static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, Function&& aFunction) -> decltype(aFunction()) { static_assert(!std::is_lvalue_reference_v, "Function object must not be passed by lvalue-ref (to avoid " "unplanned copies); Consider move()ing the object."); static_assert(detail::IsRefPtrMozPromise, "Function object must return RefPtr"); MOZ_ASSERT(aTarget); typedef RemoveSmartPointer PromiseType; typedef detail::ProxyFunctionRunnable ProxyRunnableType; auto p = MakeRefPtr(aCallerName); auto r = MakeRefPtr(p, std::forward(aFunction)); aTarget->Dispatch(r.forget()); return p; } #undef PROMISE_LOG #undef PROMISE_ASSERT #undef PROMISE_DEBUG } // namespace mozilla #endif