no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / media / MediaEventSource.h
blobcb0a851ffe9f6c95491b288aec47bad5abdb970a
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>
11 #include <utility>
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"
21 #include "nsTArray.h"
22 #include "nsThreadUtils.h"
24 namespace mozilla {
26 /**
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);
38 public:
39 RevocableToken() = default;
41 virtual void Revoke() = 0;
42 virtual bool IsRevoked() const = 0;
44 protected:
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.
53 Exclusive,
54 // Allow multiple listeners. Event data will always be copied when passed
55 // to the listeners.
56 NonExclusive
59 namespace detail {
61 /**
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.
65 template <typename T>
66 struct EventTypeTraits {
67 typedef T ArgType;
70 template <>
71 struct EventTypeTraits<void> {
72 typedef bool ArgType;
75 /**
76 * Test if a method function or lambda accepts one or more arguments.
78 template <typename T>
79 class TakeArgsHelper {
80 template <typename C>
81 static std::false_type test(void (C::*)(), int);
82 template <typename C>
83 static std::false_type test(void (C::*)() const, int);
84 template <typename C>
85 static std::false_type test(void (C::*)() volatile, int);
86 template <typename C>
87 static std::false_type test(void (C::*)() const volatile, int);
88 template <typename F>
89 static std::false_type test(F&&, decltype(std::declval<F>()(), 0));
90 static std::true_type test(...);
92 public:
93 typedef decltype(test(std::declval<T>(), 0)) type;
96 template <typename T>
97 struct TakeArgs : public TakeArgsHelper<T>::type {};
99 template <typename T>
100 struct EventTarget;
102 template <>
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) {
109 bool rv;
110 aTarget->IsOnCurrentThread(&rv);
111 return rv;
115 template <>
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) {
122 bool rv;
123 aTarget->IsOnCurrentThread(&rv);
124 return rv;
129 * Encapsulate a raw pointer to be captured by a lambda without causing
130 * static-analysis errors.
132 template <typename T>
133 class RawPtr {
134 public:
135 explicit RawPtr(T* aPtr) : mPtr(aPtr) {}
136 T* get() const { return mPtr; }
138 private:
139 T* const mPtr;
142 template <typename... As>
143 class Listener : public RevocableToken {
144 public:
145 template <typename... Ts>
146 void Dispatch(Ts&&... aEvents) {
147 if (CanTakeArgs()) {
148 DispatchTask(NewRunnableMethod<std::decay_t<Ts>&&...>(
149 "detail::Listener::ApplyWithArgs", this, &Listener::ApplyWithArgs,
150 std::forward<Ts>(aEvents)...));
151 } else {
152 DispatchTask(NewRunnableMethod("detail::Listener::ApplyWithNoArgs", this,
153 &Listener::ApplyWithNoArgs));
157 private:
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...>;
180 public:
181 ListenerImpl(Target* aTarget, Function&& aFunction)
182 : mData(MakeRefPtr<Data>(aTarget, std::forward<Function>(aFunction)),
183 "MediaEvent ListenerImpl::mData") {
184 MOZ_DIAGNOSTIC_ASSERT(aTarget);
187 protected:
188 virtual ~ListenerImpl() {
189 MOZ_ASSERT(IsRevoked(), "Must disconnect the listener.");
192 private:
193 void DispatchTask(already_AddRefed<nsIRunnable> aTask) override {
194 RefPtr<Data> data;
196 auto d = mData.Lock();
197 data = *d;
199 if (NS_WARN_IF(!data)) {
200 // already_AddRefed doesn't allow releasing the ref, so transfer it first.
201 RefPtr<nsIRunnable> temp(aTask);
202 return;
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.
227 RefPtr<Data> data;
229 auto d = mData.Lock();
230 data = *d;
232 if (!data) {
233 return;
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));
251 aFunc();
254 void ApplyWithNoArgs() override {
255 MOZ_RELEASE_ASSERT(!TakeArgs<Function>::value);
256 // Don't call the listener if it is disconnected.
257 RefPtr<Data> data;
259 auto d = mData.Lock();
260 data = *d;
262 if (!data) {
263 return;
265 MOZ_DIAGNOSTIC_ASSERT(EventTarget<Target>::IsOnTargetThread(data->mTarget));
266 ApplyWithNoArgsImpl(data->mTarget, data->mFunction);
269 void Revoke() override {
271 auto data = mData.Lock();
272 *data = nullptr;
276 bool IsRevoked() const override {
277 auto data = mData.Lock();
278 return !*data;
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)
285 protected:
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;
329 public:
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);
338 return *this;
341 ~MediaEventListener() {
342 MOZ_ASSERT(!mToken, "Must disconnect the listener.");
345 void Disconnect() {
346 mToken->Revoke();
347 mToken = nullptr;
350 void DisconnectIfExists() {
351 if (mToken) {
352 Disconnect();
356 private:
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);
390 PruneListeners();
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)(); });
416 public:
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));
436 * As above.
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
444 * pointers.
446 template <typename This, typename Method>
447 MediaEventListener Connect(AbstractThread* aTarget, This* aThis,
448 Method aMethod) {
449 return ConnectInternal(aTarget, aThis, aMethod);
452 template <typename This, typename Method>
453 MediaEventListener Connect(nsIEventTarget* aTarget, This* aThis,
454 Method aMethod) {
455 return ConnectInternal(aTarget, aThis, aMethod);
458 protected:
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);
471 continue;
473 l->Dispatch(std::forward<Ts>(aEvents)...);
477 private:
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
493 * to the listeners.
495 template <typename... Es>
496 class MediaEventProducer : public MediaEventSource<Es...> {
497 public:
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.
509 template <>
510 class MediaEventProducer<void> : public MediaEventSource<void> {
511 public:
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...> {
520 public:
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
535 * threadsafe manner.
537 template <typename... Es>
538 class MediaEventForwarder : public MediaEventSource<Es...> {
539 public:
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)...);
564 }));
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) {
573 if (!func()) {
574 return;
576 this->NotifyInternal(std::forward<ArgType<Es>...>(aEvents)...);
577 }));
580 void DisconnectAll() {
581 for (auto& l : mListeners) {
582 l.Disconnect();
584 mListeners.Clear();
587 private:
588 const nsCOMPtr<nsISerialEventTarget> mEventTarget;
589 nsTArray<MediaEventListener> mListeners;
592 } // namespace mozilla
594 #endif // MediaEventSource_h_