/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #if !defined(MediaPromise_h_) #define MediaPromise_h_ #include "prlog.h" #include "mozilla/DebugOnly.h" #include "mozilla/Maybe.h" #include "mozilla/Mutex.h" #include "mozilla/Monitor.h" #include "MediaTaskQueue.h" #include "nsIEventTarget.h" /* Polyfill __func__ on MSVC for consumers to pass to the MediaPromise API. */ #ifdef _MSC_VER #define __func__ __FUNCTION__ #endif namespace mozilla { extern PRLogModuleInfo* gMediaPromiseLog; void EnsureMediaPromiseLog(); #define PROMISE_LOG(x, ...) \ MOZ_ASSERT(gMediaPromiseLog); \ PR_LOG(gMediaPromiseLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__)) /* * 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). * * By default, resolve and reject callbacks are always invoked on the same thread * where Then() was invoked. */ template class MediaPromiseHolder; template class MediaPromise { public: typedef ResolveValueT ResolveValueType; typedef RejectValueT RejectValueType; NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaPromise) MediaPromise(const char* aCreationSite) : mCreationSite(aCreationSite) , mMutex("MediaPromise Mutex") { MOZ_COUNT_CTOR(MediaPromise); PROMISE_LOG("%s creating MediaPromise (%p)", mCreationSite, this); } 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: class ResolveRunnable : public nsRunnable { public: ResolveRunnable(ThenValueBase* aThenValue, ResolveValueType aResolveValue) : mThenValue(aThenValue) , mResolveValue(aResolveValue) { MOZ_COUNT_CTOR(ResolveRunnable); } ~ResolveRunnable() { MOZ_COUNT_DTOR(ResolveRunnable); MOZ_ASSERT(!mThenValue); } NS_IMETHODIMP Run() { PROMISE_LOG("ResolveRunnable::Run() [this=%p]", this); mThenValue->DoResolve(mResolveValue); delete mThenValue; mThenValue = nullptr; return NS_OK; } private: ThenValueBase* mThenValue; ResolveValueType mResolveValue; }; class RejectRunnable : public nsRunnable { public: RejectRunnable(ThenValueBase* aThenValue, RejectValueType aRejectValue) : mThenValue(aThenValue) , mRejectValue(aRejectValue) { MOZ_COUNT_CTOR(RejectRunnable); } ~RejectRunnable() { MOZ_COUNT_DTOR(RejectRunnable); MOZ_ASSERT(!mThenValue); } NS_IMETHODIMP Run() { PROMISE_LOG("RejectRunnable::Run() [this=%p]", this); mThenValue->DoReject(mRejectValue); delete mThenValue; mThenValue = nullptr; return NS_OK; } private: ThenValueBase* mThenValue; RejectValueType mRejectValue; }; ThenValueBase(const char* aCallSite) : mCallSite(aCallSite) { MOZ_COUNT_CTOR(ThenValueBase); } virtual void Dispatch(MediaPromise *aPromise) = 0; protected: // This may only be deleted by {Resolve,Reject}Runnable::Run. virtual ~ThenValueBase() { MOZ_COUNT_DTOR(ThenValueBase); } virtual void DoResolve(ResolveValueType aResolveValue) = 0; virtual void DoReject(RejectValueType aRejectValue) = 0; const char* mCallSite; }; template class ThenValue : public ThenValueBase { public: ThenValue(TargetType* aResponseTarget, ThisType* aThisVal, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, const char* aCallSite) : ThenValueBase(aCallSite) , mResponseTarget(aResponseTarget) , mThisVal(aThisVal) , mResolveMethod(aResolveMethod) , mRejectMethod(aRejectMethod) {} void Dispatch(MediaPromise *aPromise) MOZ_OVERRIDE { aPromise->mMutex.AssertCurrentThreadOwns(); MOZ_ASSERT(!aPromise->IsPending()); bool resolved = aPromise->mResolveValue.isSome(); nsRefPtr runnable = resolved ? static_cast(new (typename ThenValueBase::ResolveRunnable)(this, aPromise->mResolveValue.ref())) : static_cast(new (typename ThenValueBase::RejectRunnable)(this, aPromise->mRejectValue.ref())); PROMISE_LOG("%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p]", resolved ? "Resolving" : "Rejecting", ThenValueBase::mCallSite, runnable.get(), aPromise, this); DebugOnly rv = DoDispatch(mResponseTarget, runnable); MOZ_ASSERT(NS_SUCCEEDED(rv)); } protected: virtual void DoResolve(ResolveValueType aResolveValue) { ((*mThisVal).*mResolveMethod)(aResolveValue); } virtual void DoReject(RejectValueType aRejectValue) { ((*mThisVal).*mRejectMethod)(aRejectValue); } static nsresult DoDispatch(MediaTaskQueue* aTaskQueue, nsIRunnable* aRunnable) { return aTaskQueue->ForceDispatch(aRunnable); } static nsresult DoDispatch(nsIEventTarget* aEventTarget, nsIRunnable* aRunnable) { return aEventTarget->Dispatch(aRunnable, NS_DISPATCH_NORMAL); } virtual ~ThenValue() {} private: nsRefPtr mResponseTarget; nsRefPtr mThisVal; ResolveMethodType mResolveMethod; RejectMethodType mRejectMethod; }; public: template void Then(TargetType* aResponseTarget, const char* aCallSite, ThisType* aThisVal, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod) { MutexAutoLock lock(mMutex); ThenValueBase* thenValue = new ThenValue(aResponseTarget, aThisVal, aResolveMethod, aRejectMethod, aCallSite); PROMISE_LOG("%s invoking Then() [this=%p, thenValue=%p, aThisVal=%p, isPending=%d]", aCallSite, this, thenValue, aThisVal, (int) IsPending()); if (!IsPending()) { thenValue->Dispatch(this); } else { mThenValues.AppendElement(thenValue); } } private: // Resolve and Reject may only be invoked via MediaPromiseHolder. friend class MediaPromiseHolder>; void Resolve(ResolveValueType aResolveValue, const char* aResolveSite) { MutexAutoLock lock(mMutex); MOZ_ASSERT(IsPending()); PROMISE_LOG("%s resolving MediaPromise (%p created at %s)", aResolveSite, this, mCreationSite); mResolveValue.emplace(aResolveValue); DispatchAll(); } void Reject(RejectValueType aRejectValue, const char* aRejectSite) { MutexAutoLock lock(mMutex); MOZ_ASSERT(IsPending()); PROMISE_LOG("%s rejecting MediaPromise (%p created at %s)", aRejectSite, this, mCreationSite); mRejectValue.emplace(aRejectValue); DispatchAll(); } protected: bool IsPending() { return mResolveValue.isNothing() && mRejectValue.isNothing(); } void DispatchAll() { mMutex.AssertCurrentThreadOwns(); for (size_t i = 0; i < mThenValues.Length(); ++i) mThenValues[i]->Dispatch(this); mThenValues.Clear(); } ~MediaPromise() { MOZ_COUNT_DTOR(MediaPromise); PROMISE_LOG("MediaPromise::~MediaPromise [this=%p]", this); MOZ_ASSERT(!IsPending()); }; const char* mCreationSite; // For logging Mutex mMutex; Maybe mResolveValue; Maybe mRejectValue; nsTArray mThenValues; }; /* * 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 MediaPromiseHolder { public: MediaPromiseHolder() : mMonitor(nullptr) {} ~MediaPromiseHolder() { MOZ_ASSERT(!mPromise); } already_AddRefed Ensure(const char* aMethodName) { if (mMonitor) { mMonitor->AssertCurrentThreadOwns(); } if (!mPromise) { mPromise = new PromiseType(aMethodName); } nsRefPtr p = mPromise; return p.forget(); } // Provide a Monitor that should always be held when accessing this instance. void SetMonitor(Monitor* aMonitor) { mMonitor = aMonitor; } bool IsEmpty() { if (mMonitor) { mMonitor->AssertCurrentThreadOwns(); } return !mPromise; } void Resolve(typename PromiseType::ResolveValueType aResolveValue, const char* aMethodName) { if (mMonitor) { mMonitor->AssertCurrentThreadOwns(); } MOZ_ASSERT(mPromise); mPromise->Resolve(aResolveValue, aMethodName); mPromise = nullptr; } void Reject(typename PromiseType::RejectValueType aRejectValue, const char* aMethodName) { if (mMonitor) { mMonitor->AssertCurrentThreadOwns(); } MOZ_ASSERT(mPromise); mPromise->Reject(aRejectValue, aMethodName); mPromise = nullptr; } private: Monitor* mMonitor; nsRefPtr mPromise; }; #undef PROMISE_LOG } // namespace mozilla #endif