diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/media/MediaEventSource.h | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/media/MediaEventSource.h')
-rw-r--r-- | dom/media/MediaEventSource.h | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/dom/media/MediaEventSource.h b/dom/media/MediaEventSource.h new file mode 100644 index 0000000000..eb0451e493 --- /dev/null +++ b/dom/media/MediaEventSource.h @@ -0,0 +1,618 @@ +/* -*- 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/. */ + +#ifndef MediaEventSource_h_ +#define MediaEventSource_h_ + +#include "mozilla/AbstractThread.h" +#include "mozilla/Atomics.h" +#include "mozilla/IndexSequence.h" +#include "mozilla/Mutex.h" +#include "mozilla/Tuple.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/UniquePtr.h" + +#include "nsISupportsImpl.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +/** + * A thread-safe tool to communicate "revocation" across threads. It is used to + * disconnect a listener from the event source to prevent future notifications + * from coming. Revoke() can be called on any thread. However, it is recommended + * to be called on the target thread to avoid race condition. + * + * RevocableToken is not exposed to the client code directly. + * Use MediaEventListener below to do the job. + */ +class RevocableToken { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RevocableToken); + +public: + RevocableToken() : mRevoked(false) {} + + void Revoke() { + mRevoked = true; + } + + bool IsRevoked() const { + return mRevoked; + } + +private: + ~RevocableToken() {} + Atomic<bool> mRevoked; +}; + +enum class ListenerPolicy : int8_t { + // Allow at most one listener. Move will be used when possible + // to pass the event data to save copy. + Exclusive, + // Allow multiple listeners. Event data will always be copied when passed + // to the listeners. + NonExclusive +}; + +enum class DispatchPolicy : int8_t { + Sync, // Events are passed synchronously to the listeners. + Async // Events are passed asynchronously to the listeners. +}; + +namespace detail { + +/** + * Define how an event type is passed internally in MediaEventSource and to the + * listeners. Specialized for the void type to pass a dummy bool instead. + */ +template <typename T> +struct EventTypeTraits { + typedef T ArgType; +}; + +template <> +struct EventTypeTraits<void> { + typedef bool ArgType; +}; + +/** + * Test if a method function or lambda accepts one or more arguments. + */ +template <typename T> +class TakeArgsHelper { + template <typename C> static FalseType test(void(C::*)(), int); + template <typename C> static FalseType test(void(C::*)() const, int); + template <typename C> static FalseType test(void(C::*)() volatile, int); + template <typename C> static FalseType test(void(C::*)() const volatile, int); + template <typename F> static FalseType test(F&&, decltype(DeclVal<F>()(), 0)); + static TrueType test(...); +public: + typedef decltype(test(DeclVal<T>(), 0)) Type; +}; + +template <typename T> +struct TakeArgs : public TakeArgsHelper<T>::Type {}; + +template <DispatchPolicy Dp, typename T> struct EventTarget; + +template <> +struct EventTarget<DispatchPolicy::Async, nsIEventTarget> { + static void + Dispatch(nsIEventTarget* aTarget, already_AddRefed<nsIRunnable> aTask) { + aTarget->Dispatch(Move(aTask), NS_DISPATCH_NORMAL); + } +}; + +template <> +struct EventTarget<DispatchPolicy::Async, AbstractThread> { + static void + Dispatch(AbstractThread* aTarget, already_AddRefed<nsIRunnable> aTask) { + aTarget->Dispatch(Move(aTask), AbstractThread::DontAssertDispatchSuccess); + } +}; + +template <> +struct EventTarget<DispatchPolicy::Sync, nsIEventTarget> { + static bool IsOnCurrentThread(nsIEventTarget* aTarget) { + bool current = false; + auto rv = aTarget->IsOnCurrentThread(¤t); + return NS_SUCCEEDED(rv) && current; + } + static void + Dispatch(nsIEventTarget* aTarget, already_AddRefed<nsIRunnable> aTask) { + MOZ_ASSERT(IsOnCurrentThread(aTarget)); + nsCOMPtr<nsIRunnable> r = aTask; + r->Run(); + } +}; + +template <> +struct EventTarget<DispatchPolicy::Sync, AbstractThread> { + static void + Dispatch(AbstractThread* aTarget, already_AddRefed<nsIRunnable> aTask) { + MOZ_ASSERT(aTarget->IsCurrentThreadIn()); + nsCOMPtr<nsIRunnable> r = aTask; + r->Run(); + } +}; + +/** + * Encapsulate a raw pointer to be captured by a lambda without causing + * static-analysis errors. + */ +template <typename T> +class RawPtr { +public: + explicit RawPtr(T* aPtr) : mPtr(aPtr) {} + T* get() const { return mPtr; } +private: + T* const mPtr; +}; + +/** + * A helper class to pass event data to the listeners. Optimized to save + * copy when Move is possible or |Function| takes no arguments. + */ +template<DispatchPolicy Dp, typename Target, typename Function> +class ListenerHelper { + // Define our custom runnable to minimize copy of the event data. + // NS_NewRunnableFunction will result in 2 copies of the event data. + // One is captured by the lambda and the other is the copy of the lambda. + template <typename... Ts> + class R : public Runnable { + public: + template <typename... Us> + R(RevocableToken* aToken, const Function& aFunction, Us&&... aEvents) + : mToken(aToken) + , mFunction(aFunction) + , mEvents(Forward<Us>(aEvents)...) {} + + template <typename... Vs, size_t... Is> + void Invoke(Tuple<Vs...>& aEvents, IndexSequence<Is...>) { + // Enable move whenever possible since mEvent won't be used anymore. + mFunction(Move(Get<Is>(aEvents))...); + } + + NS_IMETHOD Run() override { + // Don't call the listener if it is disconnected. + if (!mToken->IsRevoked()) { + Invoke(mEvents, typename IndexSequenceFor<Ts...>::Type()); + } + return NS_OK; + } + + private: + RefPtr<RevocableToken> mToken; + Function mFunction; + + template <typename T> + using ArgType = typename RemoveCV<typename RemoveReference<T>::Type>::Type; + Tuple<ArgType<Ts>...> mEvents; + }; + +public: + ListenerHelper(RevocableToken* aToken, Target* aTarget, const Function& aFunc) + : mToken(aToken), mTarget(aTarget), mFunction(aFunc) {} + + // |F| takes one or more arguments. + template <typename F, typename... Ts> + typename EnableIf<TakeArgs<F>::value, void>::Type + DispatchHelper(const F& aFunc, Ts&&... aEvents) { + nsCOMPtr<nsIRunnable> r = + new R<Ts...>(mToken, aFunc, Forward<Ts>(aEvents)...); + EventTarget<Dp, Target>::Dispatch(mTarget.get(), r.forget()); + } + + // |F| takes no arguments. Don't bother passing aEvent. + template <typename F, typename... Ts> + typename EnableIf<!TakeArgs<F>::value, void>::Type + DispatchHelper(const F& aFunc, Ts&&...) { + const RefPtr<RevocableToken>& token = mToken; + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () { + // Don't call the listener if it is disconnected. + if (!token->IsRevoked()) { + aFunc(); + } + }); + EventTarget<Dp, Target>::Dispatch(mTarget.get(), r.forget()); + } + + template <typename... Ts> + void Dispatch(Ts&&... aEvents) { + DispatchHelper(mFunction, Forward<Ts>(aEvents)...); + } + +private: + RefPtr<RevocableToken> mToken; + const RefPtr<Target> mTarget; + Function mFunction; +}; + +/** + * Define whether an event data should be copied or moved to the listeners. + * + * @Copy Data will always be copied. Each listener gets a copy. + * @Move Data will always be moved. + */ +enum class EventPassMode : int8_t { + Copy, + Move +}; + +class ListenerBase { +public: + ListenerBase() : mToken(new RevocableToken()) {} + ~ListenerBase() { + MOZ_ASSERT(Token()->IsRevoked(), "Must disconnect the listener."); + } + RevocableToken* Token() const { + return mToken; + } +private: + const RefPtr<RevocableToken> mToken; +}; + +/** + * Stored by MediaEventSource to send notifications to the listener. + * Since virtual methods can not be templated, this class is specialized + * to provide different Dispatch() overloads depending on EventPassMode. + */ +template <EventPassMode Mode, typename... As> +class Listener : public ListenerBase { +public: + virtual ~Listener() {} + virtual void Dispatch(const As&... aEvents) = 0; +}; + +template <typename... As> +class Listener<EventPassMode::Move, As...> : public ListenerBase { +public: + virtual ~Listener() {} + virtual void Dispatch(As... aEvents) = 0; +}; + +/** + * Store the registered target thread and function so it knows where and to + * whom to send the event data. + */ +template <DispatchPolicy Dp, typename Target, + typename Function, EventPassMode, typename... As> +class ListenerImpl : public Listener<EventPassMode::Copy, As...> { +public: + ListenerImpl(Target* aTarget, const Function& aFunction) + : mHelper(ListenerBase::Token(), aTarget, aFunction) {} + void Dispatch(const As&... aEvents) override { + mHelper.Dispatch(aEvents...); + } +private: + ListenerHelper<Dp, Target, Function> mHelper; +}; + +template <DispatchPolicy Dp, typename Target, typename Function, typename... As> +class ListenerImpl<Dp, Target, Function, EventPassMode::Move, As...> + : public Listener<EventPassMode::Move, As...> { +public: + ListenerImpl(Target* aTarget, const Function& aFunction) + : mHelper(ListenerBase::Token(), aTarget, aFunction) {} + void Dispatch(As... aEvents) override { + mHelper.Dispatch(Move(aEvents)...); + } +private: + ListenerHelper<Dp, Target, Function> mHelper; +}; + +/** + * Select EventPassMode based on ListenerPolicy. + * + * @Copy Selected when ListenerPolicy is NonExclusive because each listener + * must get a copy. + * + * @Move Selected when ListenerPolicy is Exclusive. All types passed to + * MediaEventProducer::Notify() must be movable. + */ +template <ListenerPolicy Lp> +struct PassModePicker { + static const EventPassMode Value = + Lp == ListenerPolicy::NonExclusive ? + EventPassMode::Copy : EventPassMode::Move; +}; + +/** + * Return true if any type is a reference type. + */ +template <typename Head, typename... Tails> +struct IsAnyReference { + static const bool value = IsReference<Head>::value || + IsAnyReference<Tails...>::value; +}; + +template <typename T> +struct IsAnyReference<T> { + static const bool value = IsReference<T>::value; +}; + +} // namespace detail + +template <DispatchPolicy, ListenerPolicy, typename... Ts> +class MediaEventSourceImpl; + +/** + * Not thread-safe since this is not meant to be shared and therefore only + * move constructor is provided. Used to hold the result of + * MediaEventSource<T>::Connect() and call Disconnect() to disconnect the + * listener from an event source. + */ +class MediaEventListener { + template <DispatchPolicy, ListenerPolicy, typename... Ts> + friend class MediaEventSourceImpl; + +public: + MediaEventListener() {} + + MediaEventListener(MediaEventListener&& aOther) + : mToken(Move(aOther.mToken)) {} + + MediaEventListener& operator=(MediaEventListener&& aOther) { + MOZ_ASSERT(!mToken, "Must disconnect the listener."); + mToken = Move(aOther.mToken); + return *this; + } + + ~MediaEventListener() { + MOZ_ASSERT(!mToken, "Must disconnect the listener."); + } + + void Disconnect() { + mToken->Revoke(); + mToken = nullptr; + } + + void DisconnectIfExists() { + if (mToken) { + Disconnect(); + } + } + +private: + // Avoid exposing RevocableToken directly to the client code so that + // listeners can be disconnected in a controlled manner. + explicit MediaEventListener(RevocableToken* aToken) : mToken(aToken) {} + RefPtr<RevocableToken> mToken; +}; + +/** + * A generic and thread-safe class to implement the observer pattern. + */ +template <DispatchPolicy Dp, ListenerPolicy Lp, typename... Es> +class MediaEventSourceImpl { + static_assert(!detail::IsAnyReference<Es...>::value, + "Ref-type not supported!"); + + template <typename T> + using ArgType = typename detail::EventTypeTraits<T>::ArgType; + + static const detail::EventPassMode PassMode = + detail::PassModePicker<Lp>::Value; + + typedef detail::Listener<PassMode, ArgType<Es>...> Listener; + + template<typename Target, typename Func> + using ListenerImpl = + detail::ListenerImpl<Dp, Target, Func, PassMode, ArgType<Es>...>; + + template <typename Method> + using TakeArgs = detail::TakeArgs<Method>; + + void PruneListeners() { + int32_t last = static_cast<int32_t>(mListeners.Length()) - 1; + for (int32_t i = last; i >= 0; --i) { + if (mListeners[i]->Token()->IsRevoked()) { + mListeners.RemoveElementAt(i); + } + } + } + + template<typename Target, typename Function> + MediaEventListener + ConnectInternal(Target* aTarget, const Function& aFunction) { + MutexAutoLock lock(mMutex); + PruneListeners(); + MOZ_ASSERT(Lp == ListenerPolicy::NonExclusive || mListeners.IsEmpty()); + auto l = mListeners.AppendElement(); + l->reset(new ListenerImpl<Target, Function>(aTarget, aFunction)); + return MediaEventListener((*l)->Token()); + } + + // |Method| takes one or more arguments. + template <typename Target, typename This, typename Method> + typename EnableIf<TakeArgs<Method>::value, MediaEventListener>::Type + ConnectInternal(Target* aTarget, This* aThis, Method aMethod) { + detail::RawPtr<This> thiz(aThis); + auto f = [=] (ArgType<Es>&&... aEvents) { + (thiz.get()->*aMethod)(Move(aEvents)...); + }; + return ConnectInternal(aTarget, f); + } + + // |Method| takes no arguments. Don't bother passing the event data. + template <typename Target, typename This, typename Method> + typename EnableIf<!TakeArgs<Method>::value, MediaEventListener>::Type + ConnectInternal(Target* aTarget, This* aThis, Method aMethod) { + detail::RawPtr<This> thiz(aThis); + auto f = [=] () { + (thiz.get()->*aMethod)(); + }; + return ConnectInternal(aTarget, f); + } + +public: + /** + * Register a function to receive notifications from the event source. + * + * @param aTarget The target thread on which the function will run. + * @param aFunction A function to be called on the target thread. The function + * parameter must be convertible from |EventType|. + * @return An object used to disconnect from the event source. + */ + template<typename Function> + MediaEventListener + Connect(AbstractThread* aTarget, const Function& aFunction) { + return ConnectInternal(aTarget, aFunction); + } + + template<typename Function> + MediaEventListener + Connect(nsIEventTarget* aTarget, const Function& aFunction) { + return ConnectInternal(aTarget, aFunction); + } + + /** + * As above. + * + * Note we deliberately keep a weak reference to |aThis| in order not to + * change its lifetime. This is because notifications are dispatched + * asynchronously and removing a listener doesn't always break the reference + * cycle for the pending event could still hold a reference to |aThis|. + * + * The caller must call MediaEventListener::Disconnect() to avoid dangling + * pointers. + */ + template <typename This, typename Method> + MediaEventListener + Connect(AbstractThread* aTarget, This* aThis, Method aMethod) { + return ConnectInternal(aTarget, aThis, aMethod); + } + + template <typename This, typename Method> + MediaEventListener + Connect(nsIEventTarget* aTarget, This* aThis, Method aMethod) { + return ConnectInternal(aTarget, aThis, aMethod); + } + +protected: + MediaEventSourceImpl() : mMutex("MediaEventSourceImpl::mMutex") {} + + template <DispatchPolicy P, typename... Ts> + typename EnableIf<P == DispatchPolicy::Async, void>::Type + NotifyInternal(IntegralConstant<DispatchPolicy, P>, Ts&&... aEvents) { + MutexAutoLock lock(mMutex); + int32_t last = static_cast<int32_t>(mListeners.Length()) - 1; + for (int32_t i = last; i >= 0; --i) { + auto&& l = mListeners[i]; + // Remove disconnected listeners. + // It is not optimal but is simple and works well. + if (l->Token()->IsRevoked()) { + mListeners.RemoveElementAt(i); + continue; + } + l->Dispatch(Forward<Ts>(aEvents)...); + } + } + + template <DispatchPolicy P, typename... Ts> + typename EnableIf<P == DispatchPolicy::Sync, void>::Type + NotifyInternal(IntegralConstant<DispatchPolicy, P>, Ts&&... aEvents) { + // Move |mListeners| to a new container before iteration to prevent + // |mListeners| from being disrupted if the listener calls Connect() to + // modify |mListeners| in the callback function. + nsTArray<UniquePtr<Listener>> listeners; + listeners.SwapElements(mListeners); + for (auto&& l : listeners) { + l->Dispatch(Forward<Ts>(aEvents)...); + } + PruneListeners(); + // Move remaining listeners back to |mListeners|. + for (auto&& l : listeners) { + if (!l->Token()->IsRevoked()) { + mListeners.AppendElement(Move(l)); + } + } + // Perform sanity checks. + MOZ_ASSERT(Lp == ListenerPolicy::NonExclusive || mListeners.Length() <= 1); + } + + template <typename... Ts> + void Notify(Ts&&... aEvents) { + NotifyInternal(IntegralConstant<DispatchPolicy, Dp>(), + Forward<Ts>(aEvents)...); + } + +private: + Mutex mMutex; + nsTArray<UniquePtr<Listener>> mListeners; +}; + +template <typename... Es> +using MediaEventSource = + MediaEventSourceImpl<DispatchPolicy::Async, + ListenerPolicy::NonExclusive, Es...>; + +template <typename... Es> +using MediaEventSourceExc = + MediaEventSourceImpl<DispatchPolicy::Async, ListenerPolicy::Exclusive, Es...>; + +/** + * A class to separate the interface of event subject (MediaEventSource) + * and event publisher. Mostly used as a member variable to publish events + * to the listeners. + */ +template <typename... Es> +class MediaEventProducer : public MediaEventSource<Es...> { +public: + using MediaEventSource<Es...>::Notify; +}; + +/** + * Specialization for void type. A dummy bool is passed to NotifyInternal + * since there is no way to pass a void value. + */ +template <> +class MediaEventProducer<void> : public MediaEventSource<void> { +public: + void Notify() { + MediaEventSource<void>::Notify(false /* dummy */); + } +}; + +/** + * A producer allowing at most one listener. + */ +template <typename... Es> +class MediaEventProducerExc : public MediaEventSourceExc<Es...> { +public: + using MediaEventSourceExc<Es...>::Notify; +}; + +/** + * Events are passed directly to the callback function of the listeners without + * dispatching. Note this class is not thread-safe. Both Connect() and Notify() + * must be called on the same thread. + */ +template <typename... Es> +class MediaCallback + : public MediaEventSourceImpl<DispatchPolicy::Sync, + ListenerPolicy::NonExclusive, Es...> { +public: + using MediaEventSourceImpl<DispatchPolicy::Sync, + ListenerPolicy::NonExclusive, Es...>::Notify; +}; + +/** + * A special version of MediaCallback which allows at most one listener. + */ +template <typename... Es> +class MediaCallbackExc + : public MediaEventSourceImpl<DispatchPolicy::Sync, + ListenerPolicy::Exclusive, Es...> { +public: + using MediaEventSourceImpl<DispatchPolicy::Sync, + ListenerPolicy::Exclusive, Es...>::Notify; +}; + +} // namespace mozilla + +#endif //MediaEventSource_h_ |