gecko/dom/media/MediaEventSource.h
Sebastian Hengst 77867db976 Backed out changesets d0eee19c83cd, 0c54ee53678f, 5b202008a431, 81291b4e6dc3, acbc0d7e21cd, a7ceb6162a63 (bug 1194112) for Linux x64 Hazard failure. r=backout
Backed out changeset d0eee19c83cd (bug 1194112)
Backed out changeset 0c54ee53678f (bug 1194112)
Backed out changeset 5b202008a431 (bug 1194112)
Backed out changeset 81291b4e6dc3 (bug 1194112)
Backed out changeset acbc0d7e21cd (bug 1194112)
Backed out changeset a7ceb6162a63 (bug 1194112)
2015-08-18 11:11:38 +02:00

382 lines
11 KiB
C++

/* -*- 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/Mutex.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 ListenerMode : int8_t {
// Allow at most one listener. Move will be used when possible
// to pass the event data to save copy.
Exclusive,
// This is the default. Event data will always be copied when passed
// to the listeners.
NonExclusive
};
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 TakeArgs {
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 EventTarget;
template <>
struct EventTarget<nsIEventTarget> {
static void
Dispatch(nsIEventTarget* aTarget, already_AddRefed<nsIRunnable>&& aTask) {
aTarget->Dispatch(Move(aTask), NS_DISPATCH_NORMAL);
}
};
template <>
struct EventTarget<AbstractThread> {
static void
Dispatch(AbstractThread* aTarget, already_AddRefed<nsIRunnable>&& aTask) {
aTarget->Dispatch(Move(aTask));
}
};
/**
* 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;
};
} // namespace detail
template <typename T, ListenerMode> class MediaEventSource;
/**
* 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 <typename T, ListenerMode>
friend class MediaEventSource;
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;
}
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) {}
nsRefPtr<RevocableToken> mToken;
};
/**
* A generic and thread-safe class to implement the observer pattern.
*/
template <typename EventType, ListenerMode Mode = ListenerMode::NonExclusive>
class MediaEventSource {
static_assert(!IsReference<EventType>::value, "Ref-type not supported!");
typedef typename detail::EventTypeTraits<EventType>::ArgType ArgType;
/**
* Stored by MediaEventSource to send notifications to the listener.
*/
class Listener {
public:
Listener() : mToken(new RevocableToken()) {}
virtual ~Listener() {
MOZ_ASSERT(Token()->IsRevoked(), "Must disconnect the listener.");
}
virtual void Dispatch(const ArgType& aEvent) = 0;
RevocableToken* Token() const {
return mToken;
}
private:
const nsRefPtr<RevocableToken> mToken;
};
/**
* Store the registered target thread and function so it knows where and to
* whom to send the event data.
*/
template<typename Target, typename Function>
class ListenerImpl : public Listener {
public:
explicit ListenerImpl(Target* aTarget, const Function& aFunction)
: mTarget(aTarget), mFunction(aFunction) {}
// |Function| takes one argument.
void Dispatch(const ArgType& aEvent, TrueType) {
// 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.
class R : public nsRunnable {
public:
R(RevocableToken* aToken,
const Function& aFunction, const ArgType& aEvent)
: mToken(aToken), mFunction(aFunction), mEvent(aEvent) {}
NS_IMETHOD Run() override {
// Don't call the listener if it is disconnected.
if (!mToken->IsRevoked()) {
// Enable move whenever possible since mEvent won't be used anymore.
mFunction(Move(mEvent));
}
return NS_OK;
}
private:
nsRefPtr<RevocableToken> mToken;
Function mFunction;
ArgType mEvent;
};
nsCOMPtr<nsIRunnable> r = new R(this->Token(), mFunction, aEvent);
detail::EventTarget<Target>::Dispatch(mTarget.get(), r.forget());
}
// |Function| takes no arguments. Don't bother passing aEvent.
void Dispatch(const ArgType& aEvent, FalseType) {
nsRefPtr<RevocableToken> token = this->Token();
const Function& function = mFunction;
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([=] () {
// Don't call the listener if it is disconnected.
if (!token->IsRevoked()) {
function();
}
});
detail::EventTarget<Target>::Dispatch(mTarget.get(), r.forget());
}
void Dispatch(const ArgType& aEvent) override {
Dispatch(aEvent, typename detail::TakeArgs<Function>::Type());
}
private:
const nsRefPtr<Target> mTarget;
Function mFunction;
};
template<typename Target, typename Function>
MediaEventListener
ConnectInternal(Target* aTarget, const Function& aFunction) {
MutexAutoLock lock(mMutex);
MOZ_ASSERT(Mode == ListenerMode::NonExclusive || mListeners.IsEmpty());
auto l = mListeners.AppendElement();
l->reset(new ListenerImpl<Target, Function>(aTarget, aFunction));
return MediaEventListener((*l)->Token());
}
// |Method| takes one argument.
template <typename Target, typename This, typename Method>
MediaEventListener
ConnectInternal(Target* aTarget, This* aThis, Method aMethod, TrueType) {
detail::RawPtr<This> thiz(aThis);
auto f = [=] (ArgType&& aEvent) {
(thiz.get()->*aMethod)(Move(aEvent));
};
return ConnectInternal(aTarget, f);
}
// |Method| takes no arguments. Don't bother passing the event data.
template <typename Target, typename This, typename Method>
MediaEventListener
ConnectInternal(Target* aTarget, This* aThis, Method aMethod, FalseType) {
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,
typename detail::TakeArgs<Method>::Type());
}
template <typename This, typename Method>
MediaEventListener
Connect(nsIEventTarget* aTarget, This* aThis, Method aMethod) {
return ConnectInternal(aTarget, aThis, aMethod,
typename detail::TakeArgs<Method>::Type());
}
protected:
MediaEventSource() : mMutex("MediaEventSource::mMutex") {}
void NotifyInternal(const ArgType& aEvent) {
MutexAutoLock lock(mMutex);
for (int32_t i = mListeners.Length() - 1; 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(aEvent);
}
}
private:
Mutex mMutex;
nsTArray<UniquePtr<Listener>> mListeners;
};
/**
* 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 EventType, ListenerMode Mode = ListenerMode::NonExclusive>
class MediaEventProducer : public MediaEventSource<EventType, Mode> {
public:
void Notify(const EventType& aEvent) {
this->NotifyInternal(aEvent);
}
};
/**
* 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() {
this->NotifyInternal(true /* dummy */);
}
};
} // namespace mozilla
#endif //MediaEventSource_h_