1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef MediaEventSource_h_
8 #define MediaEventSource_h_
10 #include <type_traits>
13 #include "mozilla/AbstractThread.h"
14 #include "mozilla/Atomics.h"
15 #include "mozilla/DataMutex.h"
16 #include "mozilla/Mutex.h"
18 #include "mozilla/Unused.h"
20 #include "nsISupportsImpl.h"
22 #include "nsThreadUtils.h"
27 * A thread-safe tool to communicate "revocation" across threads. It is used to
28 * disconnect a listener from the event source to prevent future notifications
29 * from coming. Revoke() can be called on any thread. However, it is recommended
30 * to be called on the target thread to avoid race condition.
32 * RevocableToken is not exposed to the client code directly.
33 * Use MediaEventListener below to do the job.
35 class RevocableToken
{
36 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RevocableToken
);
39 RevocableToken() = default;
41 virtual void Revoke() = 0;
42 virtual bool IsRevoked() const = 0;
45 // Virtual destructor is required since we might delete a Listener object
46 // through its base type pointer.
47 virtual ~RevocableToken() = default;
50 enum class ListenerPolicy
: int8_t {
51 // Allow at most one listener. Move will be used when possible
52 // to pass the event data to save copy.
54 // Allow multiple listeners. Event data will always be copied when passed
62 * Define how an event type is passed internally in MediaEventSource and to the
63 * listeners. Specialized for the void type to pass a dummy bool instead.
66 struct EventTypeTraits
{
71 struct EventTypeTraits
<void> {
76 * Test if a method function or lambda accepts one or more arguments.
79 class TakeArgsHelper
{
81 static std::false_type
test(void (C::*)(), int);
83 static std::false_type
test(void (C::*)() const, int);
85 static std::false_type
test(void (C::*)() volatile, int);
87 static std::false_type
test(void (C::*)() const volatile, int);
89 static std::false_type
test(F
&&, decltype(std::declval
<F
>()(), 0));
90 static std::true_type
test(...);
93 typedef decltype(test(std::declval
<T
>(), 0)) type
;
97 struct TakeArgs
: public TakeArgsHelper
<T
>::type
{};
103 struct EventTarget
<nsIEventTarget
> {
104 static void Dispatch(nsIEventTarget
* aTarget
,
105 already_AddRefed
<nsIRunnable
> aTask
) {
106 aTarget
->Dispatch(std::move(aTask
), NS_DISPATCH_NORMAL
);
108 static bool IsOnTargetThread(nsIEventTarget
* aTarget
) {
110 aTarget
->IsOnCurrentThread(&rv
);
116 struct EventTarget
<AbstractThread
> {
117 static void Dispatch(AbstractThread
* aTarget
,
118 already_AddRefed
<nsIRunnable
> aTask
) {
119 aTarget
->Dispatch(std::move(aTask
));
121 static bool IsOnTargetThread(AbstractThread
* aTarget
) {
123 aTarget
->IsOnCurrentThread(&rv
);
129 * Encapsulate a raw pointer to be captured by a lambda without causing
130 * static-analysis errors.
132 template <typename T
>
135 explicit RawPtr(T
* aPtr
) : mPtr(aPtr
) {}
136 T
* get() const { return mPtr
; }
142 template <typename
... As
>
143 class Listener
: public RevocableToken
{
145 template <typename
... Ts
>
146 void Dispatch(Ts
&&... aEvents
) {
148 DispatchTask(NewRunnableMethod
<std::decay_t
<Ts
>&&...>(
149 "detail::Listener::ApplyWithArgs", this, &Listener::ApplyWithArgs
,
150 std::forward
<Ts
>(aEvents
)...));
152 DispatchTask(NewRunnableMethod("detail::Listener::ApplyWithNoArgs", this,
153 &Listener::ApplyWithNoArgs
));
158 virtual void DispatchTask(already_AddRefed
<nsIRunnable
> aTask
) = 0;
160 // True if the underlying listener function takes non-zero arguments.
161 virtual bool CanTakeArgs() const = 0;
162 // Pass the event data to the underlying listener function. Should be called
163 // only when CanTakeArgs() returns true.
164 virtual void ApplyWithArgs(As
&&... aEvents
) = 0;
165 // Invoke the underlying listener function. Should be called only when
166 // CanTakeArgs() returns false.
167 virtual void ApplyWithNoArgs() = 0;
171 * Store the registered target thread and function so it knows where and to
172 * whom to send the event data.
174 template <typename Target
, typename Function
, typename
... As
>
175 class ListenerImpl
: public Listener
<As
...> {
176 // Strip CV and reference from Function.
177 using FunctionStorage
= std::decay_t
<Function
>;
178 using SelfType
= ListenerImpl
<Target
, Function
, As
...>;
181 ListenerImpl(Target
* aTarget
, Function
&& aFunction
)
182 : mData(MakeRefPtr
<Data
>(aTarget
, std::forward
<Function
>(aFunction
)),
183 "MediaEvent ListenerImpl::mData") {
184 MOZ_DIAGNOSTIC_ASSERT(aTarget
);
188 virtual ~ListenerImpl() {
189 MOZ_ASSERT(IsRevoked(), "Must disconnect the listener.");
193 void DispatchTask(already_AddRefed
<nsIRunnable
> aTask
) override
{
196 auto d
= mData
.Lock();
199 if (NS_WARN_IF(!data
)) {
200 // already_AddRefed doesn't allow releasing the ref, so transfer it first.
201 RefPtr
<nsIRunnable
> temp(aTask
);
204 EventTarget
<Target
>::Dispatch(data
->mTarget
, std::move(aTask
));
207 bool CanTakeArgs() const override
{ return TakeArgs
<FunctionStorage
>::value
; }
209 // |F| takes one or more arguments.
210 template <typename F
>
211 std::enable_if_t
<TakeArgs
<F
>::value
, void> ApplyWithArgsImpl(
212 Target
* aTarget
, const F
& aFunc
, As
&&... aEvents
) {
213 MOZ_DIAGNOSTIC_ASSERT(EventTarget
<Target
>::IsOnTargetThread(aTarget
));
214 aFunc(std::move(aEvents
)...);
217 // |F| takes no arguments.
218 template <typename F
>
219 std::enable_if_t
<!TakeArgs
<F
>::value
, void> ApplyWithArgsImpl(
220 Target
* aTarget
, const F
& aFunc
, As
&&... aEvents
) {
221 MOZ_CRASH("Call ApplyWithNoArgs instead.");
224 void ApplyWithArgs(As
&&... aEvents
) override
{
225 MOZ_RELEASE_ASSERT(TakeArgs
<Function
>::value
);
226 // Don't call the listener if it is disconnected.
229 auto d
= mData
.Lock();
235 MOZ_DIAGNOSTIC_ASSERT(EventTarget
<Target
>::IsOnTargetThread(data
->mTarget
));
236 ApplyWithArgsImpl(data
->mTarget
, data
->mFunction
, std::move(aEvents
)...);
239 // |F| takes one or more arguments.
240 template <typename F
>
241 std::enable_if_t
<TakeArgs
<F
>::value
, void> ApplyWithNoArgsImpl(
242 Target
* aTarget
, const F
& aFunc
) {
243 MOZ_CRASH("Call ApplyWithArgs instead.");
246 // |F| takes no arguments.
247 template <typename F
>
248 std::enable_if_t
<!TakeArgs
<F
>::value
, void> ApplyWithNoArgsImpl(
249 Target
* aTarget
, const F
& aFunc
) {
250 MOZ_DIAGNOSTIC_ASSERT(EventTarget
<Target
>::IsOnTargetThread(aTarget
));
254 void ApplyWithNoArgs() override
{
255 MOZ_RELEASE_ASSERT(!TakeArgs
<Function
>::value
);
256 // Don't call the listener if it is disconnected.
259 auto d
= mData
.Lock();
265 MOZ_DIAGNOSTIC_ASSERT(EventTarget
<Target
>::IsOnTargetThread(data
->mTarget
));
266 ApplyWithNoArgsImpl(data
->mTarget
, data
->mFunction
);
269 void Revoke() override
{
271 auto data
= mData
.Lock();
276 bool IsRevoked() const override
{
277 auto data
= mData
.Lock();
281 struct RefCountedMediaEventListenerData
{
282 // Keep ref-counting here since Data holds a template member, leading to
283 // instances of varying size, which the memory leak logging system dislikes.
284 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMediaEventListenerData
)
286 virtual ~RefCountedMediaEventListenerData() = default;
288 struct Data
: public RefCountedMediaEventListenerData
{
289 Data(RefPtr
<Target
> aTarget
, Function
&& aFunction
)
290 : mTarget(std::move(aTarget
)),
291 mFunction(std::forward
<Function
>(aFunction
)) {}
292 const RefPtr
<Target
> mTarget
;
293 FunctionStorage mFunction
;
296 // Storage for target and function. Also used to track revocation.
297 mutable DataMutex
<RefPtr
<Data
>> mData
;
301 * Return true if any type is a reference type.
303 template <typename Head
, typename
... Tails
>
304 struct IsAnyReference
{
305 static const bool value
=
306 std::is_reference_v
<Head
> || IsAnyReference
<Tails
...>::value
;
309 template <typename T
>
310 struct IsAnyReference
<T
> {
311 static const bool value
= std::is_reference_v
<T
>;
314 } // namespace detail
316 template <ListenerPolicy
, typename
... Ts
>
317 class MediaEventSourceImpl
;
320 * Not thread-safe since this is not meant to be shared and therefore only
321 * move constructor is provided. Used to hold the result of
322 * MediaEventSource<T>::Connect() and call Disconnect() to disconnect the
323 * listener from an event source.
325 class MediaEventListener
{
326 template <ListenerPolicy
, typename
... Ts
>
327 friend class MediaEventSourceImpl
;
330 MediaEventListener() = default;
332 MediaEventListener(MediaEventListener
&& aOther
)
333 : mToken(std::move(aOther
.mToken
)) {}
335 MediaEventListener
& operator=(MediaEventListener
&& aOther
) {
336 MOZ_ASSERT(!mToken
, "Must disconnect the listener.");
337 mToken
= std::move(aOther
.mToken
);
341 ~MediaEventListener() {
342 MOZ_ASSERT(!mToken
, "Must disconnect the listener.");
350 void DisconnectIfExists() {
357 // Avoid exposing RevocableToken directly to the client code so that
358 // listeners can be disconnected in a controlled manner.
359 explicit MediaEventListener(RevocableToken
* aToken
) : mToken(aToken
) {}
360 RefPtr
<RevocableToken
> mToken
;
364 * A generic and thread-safe class to implement the observer pattern.
366 template <ListenerPolicy Lp
, typename
... Es
>
367 class MediaEventSourceImpl
{
368 static_assert(!detail::IsAnyReference
<Es
...>::value
,
369 "Ref-type not supported!");
371 template <typename T
>
372 using ArgType
= typename
detail::EventTypeTraits
<T
>::ArgType
;
374 typedef detail::Listener
<ArgType
<Es
>...> Listener
;
376 template <typename Target
, typename Func
>
377 using ListenerImpl
= detail::ListenerImpl
<Target
, Func
, ArgType
<Es
>...>;
379 template <typename Method
>
380 using TakeArgs
= detail::TakeArgs
<Method
>;
382 void PruneListeners() {
383 mListeners
.RemoveElementsBy(
384 [](const auto& listener
) { return listener
->IsRevoked(); });
387 template <typename Target
, typename Function
>
388 MediaEventListener
ConnectInternal(Target
* aTarget
, Function
&& aFunction
) {
389 MutexAutoLock
lock(mMutex
);
391 MOZ_ASSERT(Lp
== ListenerPolicy::NonExclusive
|| mListeners
.IsEmpty());
392 auto l
= mListeners
.AppendElement();
393 *l
= new ListenerImpl
<Target
, Function
>(aTarget
,
394 std::forward
<Function
>(aFunction
));
395 return MediaEventListener(*l
);
398 // |Method| takes one or more arguments.
399 template <typename Target
, typename This
, typename Method
>
400 std::enable_if_t
<TakeArgs
<Method
>::value
, MediaEventListener
> ConnectInternal(
401 Target
* aTarget
, This
* aThis
, Method aMethod
) {
402 detail::RawPtr
<This
> thiz(aThis
);
403 return ConnectInternal(aTarget
, [=](ArgType
<Es
>&&... aEvents
) {
404 (thiz
.get()->*aMethod
)(std::move(aEvents
)...);
408 // |Method| takes no arguments. Don't bother passing the event data.
409 template <typename Target
, typename This
, typename Method
>
410 std::enable_if_t
<!TakeArgs
<Method
>::value
, MediaEventListener
>
411 ConnectInternal(Target
* aTarget
, This
* aThis
, Method aMethod
) {
412 detail::RawPtr
<This
> thiz(aThis
);
413 return ConnectInternal(aTarget
, [=]() { (thiz
.get()->*aMethod
)(); });
418 * Register a function to receive notifications from the event source.
420 * @param aTarget The target thread on which the function will run.
421 * @param aFunction A function to be called on the target thread. The function
422 * parameter must be convertible from |EventType|.
423 * @return An object used to disconnect from the event source.
425 template <typename Function
>
426 MediaEventListener
Connect(AbstractThread
* aTarget
, Function
&& aFunction
) {
427 return ConnectInternal(aTarget
, std::forward
<Function
>(aFunction
));
430 template <typename Function
>
431 MediaEventListener
Connect(nsIEventTarget
* aTarget
, Function
&& aFunction
) {
432 return ConnectInternal(aTarget
, std::forward
<Function
>(aFunction
));
438 * Note we deliberately keep a weak reference to |aThis| in order not to
439 * change its lifetime. This is because notifications are dispatched
440 * asynchronously and removing a listener doesn't always break the reference
441 * cycle for the pending event could still hold a reference to |aThis|.
443 * The caller must call MediaEventListener::Disconnect() to avoid dangling
446 template <typename This
, typename Method
>
447 MediaEventListener
Connect(AbstractThread
* aTarget
, This
* aThis
,
449 return ConnectInternal(aTarget
, aThis
, aMethod
);
452 template <typename This
, typename Method
>
453 MediaEventListener
Connect(nsIEventTarget
* aTarget
, This
* aThis
,
455 return ConnectInternal(aTarget
, aThis
, aMethod
);
459 MediaEventSourceImpl() : mMutex("MediaEventSourceImpl::mMutex") {}
461 template <typename
... Ts
>
462 void NotifyInternal(Ts
&&... aEvents
) {
463 MutexAutoLock
lock(mMutex
);
464 int32_t last
= static_cast<int32_t>(mListeners
.Length()) - 1;
465 for (int32_t i
= last
; i
>= 0; --i
) {
466 auto&& l
= mListeners
[i
];
467 // Remove disconnected listeners.
468 // It is not optimal but is simple and works well.
469 if (l
->IsRevoked()) {
470 mListeners
.RemoveElementAt(i
);
473 l
->Dispatch(std::forward
<Ts
>(aEvents
)...);
478 Mutex mMutex MOZ_UNANNOTATED
;
479 nsTArray
<RefPtr
<Listener
>> mListeners
;
482 template <typename
... Es
>
483 using MediaEventSource
=
484 MediaEventSourceImpl
<ListenerPolicy::NonExclusive
, Es
...>;
486 template <typename
... Es
>
487 using MediaEventSourceExc
=
488 MediaEventSourceImpl
<ListenerPolicy::Exclusive
, Es
...>;
491 * A class to separate the interface of event subject (MediaEventSource)
492 * and event publisher. Mostly used as a member variable to publish events
495 template <typename
... Es
>
496 class MediaEventProducer
: public MediaEventSource
<Es
...> {
498 template <typename
... Ts
>
499 void Notify(Ts
&&... aEvents
) {
500 // Pass lvalues to prevent move in NonExclusive mode.
501 this->NotifyInternal(aEvents
...);
506 * Specialization for void type. A dummy bool is passed to NotifyInternal
507 * since there is no way to pass a void value.
510 class MediaEventProducer
<void> : public MediaEventSource
<void> {
512 void Notify() { this->NotifyInternal(true /* dummy */); }
516 * A producer allowing at most one listener.
518 template <typename
... Es
>
519 class MediaEventProducerExc
: public MediaEventSourceExc
<Es
...> {
521 template <typename
... Ts
>
522 void Notify(Ts
&&... aEvents
) {
523 this->NotifyInternal(std::forward
<Ts
>(aEvents
)...);
528 * A class that facilitates forwarding MediaEvents from multiple sources of the
529 * same type into a single source.
531 * Lifetimes are convenient. A forwarded source is disconnected either by
532 * the source itself going away, or the forwarder being destroyed.
534 * Not threadsafe. The caller is responsible for calling Forward() in a
537 template <typename
... Es
>
538 class MediaEventForwarder
: public MediaEventSource
<Es
...> {
540 template <typename T
>
541 using ArgType
= typename
detail::EventTypeTraits
<T
>::ArgType
;
543 explicit MediaEventForwarder(nsCOMPtr
<nsISerialEventTarget
> aEventTarget
)
544 : mEventTarget(std::move(aEventTarget
)) {}
546 MediaEventForwarder(MediaEventForwarder
&& aOther
)
547 : mEventTarget(aOther
.mEventTarget
),
548 mListeners(std::move(aOther
.mListeners
)) {}
550 ~MediaEventForwarder() { MOZ_ASSERT(mListeners
.IsEmpty()); }
552 MediaEventForwarder
& operator=(MediaEventForwarder
&& aOther
) {
553 MOZ_RELEASE_ASSERT(mEventTarget
== aOther
.mEventTarget
);
554 MOZ_ASSERT(mListeners
.IsEmpty());
555 mListeners
= std::move(aOther
.mListeners
);
558 void Forward(MediaEventSource
<Es
...>& aSource
) {
559 // Forwarding a rawptr `this` here is fine, since DisconnectAll disconnect
560 // all mListeners synchronously and prevents this handler from running.
561 mListeners
.AppendElement(
562 aSource
.Connect(mEventTarget
, [this](ArgType
<Es
>&&... aEvents
) {
563 this->NotifyInternal(std::forward
<ArgType
<Es
>...>(aEvents
)...);
567 template <typename Function
>
568 void ForwardIf(MediaEventSource
<Es
...>& aSource
, Function
&& aFunction
) {
569 // Forwarding a rawptr `this` here is fine, since DisconnectAll disconnect
570 // all mListeners synchronously and prevents this handler from running.
571 mListeners
.AppendElement(aSource
.Connect(
572 mEventTarget
, [this, func
= aFunction
](ArgType
<Es
>&&... aEvents
) {
576 this->NotifyInternal(std::forward
<ArgType
<Es
>...>(aEvents
)...);
580 void DisconnectAll() {
581 for (auto& l
: mListeners
) {
588 const nsCOMPtr
<nsISerialEventTarget
> mEventTarget
;
589 nsTArray
<MediaEventListener
> mListeners
;
592 } // namespace mozilla
594 #endif // MediaEventSource_h_