tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MediaEventSource.h (27710B)


      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/. */
      6 
      7 #ifndef MediaEventSource_h_
      8 #define MediaEventSource_h_
      9 
     10 #include <type_traits>
     11 #include <utility>
     12 
     13 #include "mozilla/DataMutex.h"
     14 #include "mozilla/Mutex.h"
     15 #include "nsISupportsImpl.h"
     16 #include "nsTArray.h"
     17 #include "nsThreadUtils.h"
     18 
     19 namespace mozilla {
     20 
     21 /**
     22 * A thread-safe tool to communicate "revocation" across threads. It is used to
     23 * disconnect a listener from the event source to prevent future notifications
     24 * from coming. Revoke() can be called on any thread. However, it is recommended
     25 * to be called on the target thread to avoid race condition.
     26 *
     27 * RevocableToken is not exposed to the client code directly.
     28 * Use MediaEventListener below to do the job.
     29 */
     30 class RevocableToken {
     31  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RevocableToken);
     32 
     33 public:
     34  RevocableToken() = default;
     35 
     36  virtual void Revoke() = 0;
     37  virtual bool IsRevoked() const = 0;
     38 
     39 protected:
     40  // Virtual destructor is required since we might delete a Listener object
     41  // through its base type pointer.
     42  virtual ~RevocableToken() = default;
     43 };
     44 
     45 enum class ListenerPolicy : int8_t {
     46  // Allow at most one listener. Move will be used when possible
     47  // to pass the event data to save copy.
     48  Exclusive,
     49  // Allow multiple listeners, which will be given thread-scoped refs of
     50  // the event data. For N targets/threads, this results in N-1 copies.
     51  OneCopyPerThread,
     52  // Allow multiple listeners, which will all be given a const& to the same
     53  // event data.
     54  NonExclusive
     55 };
     56 
     57 namespace detail {
     58 
     59 /**
     60 * Define how an event type is passed internally in MediaEventSource and to the
     61 * listeners. Specialized for the void type to pass a dummy bool instead.
     62 */
     63 template <typename T>
     64 struct EventTypeTraits {
     65  typedef T ArgType;
     66 };
     67 
     68 template <>
     69 struct EventTypeTraits<void> {
     70  typedef bool ArgType;
     71 };
     72 
     73 /**
     74 * Test if a method function or lambda accepts one or more arguments.
     75 */
     76 template <typename T>
     77 class TakeArgsHelper {
     78  template <typename C>
     79  static std::false_type test(void (C::*)(), int);
     80  template <typename C>
     81  static std::false_type test(void (C::*)() const, int);
     82  template <typename C>
     83  static std::false_type test(void (C::*)() volatile, int);
     84  template <typename C>
     85  static std::false_type test(void (C::*)() const volatile, int);
     86  template <typename F>
     87  static std::false_type test(F&&, decltype(std::declval<F>()(), 0));
     88  static std::true_type test(...);
     89 
     90 public:
     91  using type = decltype(test(std::declval<T>(), 0));
     92 };
     93 
     94 template <typename T>
     95 struct TakeArgs : public TakeArgsHelper<T>::type {};
     96 
     97 /**
     98 * Encapsulate a raw pointer to be captured by a lambda without causing
     99 * static-analysis errors.
    100 */
    101 template <typename T>
    102 class RawPtr {
    103 public:
    104  explicit RawPtr(T* aPtr) : mPtr(aPtr) {}
    105  T* get() const { return mPtr; }
    106 
    107 private:
    108  T* const mPtr;
    109 };
    110 
    111 // A list of listeners with some helper functions. Used to batch notifications
    112 // for a single thread/target.
    113 template <typename Listener>
    114 class ListenerBatch {
    115 public:
    116  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ListenerBatch);
    117 
    118  explicit ListenerBatch(nsCOMPtr<nsIEventTarget>&& aTarget)
    119      : mTarget(std::move(aTarget)) {}
    120 
    121  bool MaybeAddListener(const RefPtr<Listener>& aListener) {
    122    auto target = aListener->GetTarget();
    123    // It does not matter what batch disconnected listeners go in.
    124    if (!target) {
    125      // It _also_ does not matter if we actually add a disconnected listener.
    126      // Pretend we did, but don't.
    127      return true;
    128    }
    129    if (target != mTarget) {
    130      return false;
    131    }
    132    mListeners.AppendElement(aListener);
    133    return true;
    134  }
    135 
    136  bool CanTakeArgs() const {
    137    for (auto& listener : mListeners) {
    138      if (listener->CanTakeArgs()) {
    139        return true;
    140      }
    141    }
    142    return false;
    143  }
    144 
    145  template <typename Storage>
    146  void ApplyWithArgsTuple(Storage&& aStorage) {
    147    std::apply(
    148        [&](auto&&... aArgs) mutable {
    149          for (auto& listener : mListeners) {
    150            if (listener->CanTakeArgs()) {
    151              listener->ApplyWithArgsImpl(
    152                  std::forward<decltype(aArgs)>(aArgs)...);
    153            } else {
    154              listener->ApplyWithNoArgs();
    155            }
    156          }
    157        },
    158        std::forward<Storage>(aStorage));
    159  }
    160 
    161  void ApplyWithNoArgs() {
    162    for (auto& listener : mListeners) {
    163      listener->ApplyWithNoArgs();
    164    }
    165  }
    166 
    167  void DispatchTask(already_AddRefed<nsIRunnable> aTask) {
    168    // Every listener might or might not have disconnected, so find the
    169    // first one that can actually perform the dispatch. If all of them are
    170    // disconnected, this is a no-op, which is fine.
    171    nsCOMPtr task = aTask;
    172    for (auto& listener : mListeners) {
    173      if (listener->TryDispatchTask(do_AddRef(task))) {
    174        return;
    175      }
    176    }
    177  }
    178 
    179  size_t Length() const { return mListeners.Length(); }
    180 
    181 private:
    182  ~ListenerBatch() = default;
    183  nsTArray<RefPtr<Listener>> mListeners;
    184  nsCOMPtr<nsIEventTarget> mTarget;
    185 };
    186 
    187 template <ListenerPolicy, typename Listener>
    188 class NotificationPolicy;
    189 
    190 template <typename Listener>
    191 class NotificationPolicy<ListenerPolicy::Exclusive, Listener> {
    192 public:
    193  using ListenerBatch = typename detail::ListenerBatch<Listener>;
    194 
    195  template <typename... Ts>
    196  static void DispatchNotifications(
    197      const nsTArray<RefPtr<ListenerBatch>>& aListenerBatches,
    198      Ts&&... aEvents) {
    199    using Storage = std::tuple<std::decay_t<Ts>...>;
    200    // Should we let extra argless listeners slide?
    201    MOZ_ASSERT(aListenerBatches.Length() == 1);
    202    auto& batch = aListenerBatches[0];
    203    if (batch->CanTakeArgs()) {
    204      Storage storage(std::move(aEvents)...);
    205      batch->DispatchTask(NS_NewRunnableFunction(
    206          "ListenerBatch::DispatchTask(with args)",
    207          [batch, storage = std::move(storage)]() mutable {
    208            batch->ApplyWithArgsTuple(std::move(storage));
    209          }));
    210    } else {
    211      batch->DispatchTask(
    212          NewRunnableMethod("ListenerBatch::DispatchTask(without args)", batch,
    213                            &ListenerBatch::ApplyWithNoArgs));
    214    }
    215  }
    216 };
    217 
    218 template <typename Listener>
    219 class NotificationPolicy<ListenerPolicy::OneCopyPerThread, Listener> {
    220 public:
    221  using ListenerBatch = typename detail::ListenerBatch<Listener>;
    222 
    223  template <typename... Ts>
    224  static void DispatchNotifications(
    225      const nsTArray<RefPtr<ListenerBatch>>& aListenerBatches,
    226      Ts&&... aEvents) {
    227    using Storage = std::tuple<std::decay_t<Ts>...>;
    228 
    229    // Find the last batch that takes args, and remember the index; that
    230    // batch will get the original copy (aEvents).
    231    Maybe<size_t> lastBatchWithArgs;
    232    for (size_t i = 0; i < aListenerBatches.Length(); ++i) {
    233      if (aListenerBatches[i]->CanTakeArgs()) {
    234        lastBatchWithArgs = Some(i);
    235      }
    236    }
    237 
    238    Storage storage(std::move(aEvents)...);
    239    for (size_t i = 0; i < aListenerBatches.Length(); ++i) {
    240      auto& batch = aListenerBatches[i];
    241      if (batch->CanTakeArgs()) {
    242        if (i != *lastBatchWithArgs) {
    243          // Copy |storage| into everything but the last args-taking dispatch
    244          batch->DispatchTask(
    245              NS_NewRunnableFunction("ListenerBatch::DispatchTask(with args)",
    246                                     [batch, storage]() mutable {
    247                                       batch->ApplyWithArgsTuple(storage);
    248                                     }));
    249        } else {
    250          // Move |storage| into the last args-taking dispatch
    251          batch->DispatchTask(NS_NewRunnableFunction(
    252              "ListenerBatch::DispatchTask(with args)",
    253              [batch, storage = std::move(storage)]() mutable {
    254                batch->ApplyWithArgsTuple(storage);
    255              }));
    256        }
    257      } else {
    258        batch->DispatchTask(
    259            NewRunnableMethod("ListenerBatch::DispatchTask(without args)",
    260                              batch, &ListenerBatch::ApplyWithNoArgs));
    261      }
    262    }
    263  }
    264 };
    265 
    266 template <typename Listener>
    267 class NotificationPolicy<ListenerPolicy::NonExclusive, Listener> {
    268 public:
    269  using ListenerBatch = typename detail::ListenerBatch<Listener>;
    270 
    271  // This base class exists solely to keep the refcount logging code happy :(
    272  // It cannot handle templates inside the _THREADSAFE_REFCOUNTING macro
    273  // properly; it is all keyed off string matching, and that string would end
    274  // up being "SharedArgs<As>" verbatim, which is the same regardless of what
    275  // |As| is.
    276  class SharedArgsBase {
    277   public:
    278    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedArgsBase);
    279 
    280   protected:
    281    virtual ~SharedArgsBase() = default;
    282  };
    283  template <typename... As>
    284  class SharedArgs : public SharedArgsBase {
    285   public:
    286    using Storage = std::tuple<std::decay_t<As>...>;
    287    explicit SharedArgs(As&&... aArgs) : mStorage(std::forward<As>(aArgs)...) {}
    288    // We should not ever be copying this, it is refcounted
    289    SharedArgs(const SharedArgs& aOrig) = delete;
    290 
    291    void ApplyWithArgs(ListenerBatch* aBatch) {
    292      aBatch->ApplyWithArgsTuple(mStorage);
    293    }
    294 
    295   private:
    296    const Storage mStorage;
    297  };
    298 
    299  template <typename... Ts>
    300  static void DispatchNotifications(
    301      const nsTArray<RefPtr<ListenerBatch>>& aListenerBatches,
    302      Ts&&... aEvents) {
    303    // Lazy initted when we see the first args-taking batch
    304    RefPtr<SharedArgs<Ts...>> args;
    305 
    306    for (auto& batch : aListenerBatches) {
    307      if (batch->CanTakeArgs()) {
    308        if (!args) {
    309          // Lazy init
    310          args = MakeRefPtr<SharedArgs<Ts...>>(std::forward<Ts>(aEvents)...);
    311        }
    312        batch->DispatchTask(NewRunnableMethod<RefPtr<ListenerBatch>>(
    313            "ListenerBatch::DispatchTask(with args)", args,
    314            &SharedArgs<Ts...>::ApplyWithArgs, batch));
    315      } else {
    316        batch->DispatchTask(
    317            NewRunnableMethod("ListenerBatch::DispatchTask(without args)",
    318                              batch, &ListenerBatch::ApplyWithNoArgs));
    319      }
    320    }
    321  }
    322 };
    323 
    324 // Bottom-level base class for Listeners. Declares virtual functions that are
    325 // always present, regardless of template parameters. This is where we handle
    326 // the fact that different listeners have different targets, and even different
    327 // ways of dispatching to those targets.
    328 class ListenerBase : public RevocableToken {
    329 public:
    330  virtual bool TryDispatchTask(already_AddRefed<nsIRunnable> aTask) = 0;
    331 
    332  // True if the underlying listener function takes non-zero arguments.
    333  virtual bool CanTakeArgs() const = 0;
    334  // Invoke the underlying listener function. Should be called only when
    335  // CanTakeArgs() returns false.
    336  virtual void ApplyWithNoArgs() = 0;
    337 
    338  virtual nsCOMPtr<nsIEventTarget> GetTarget() const = 0;
    339 };
    340 
    341 // This is where we handle the fact that listeners will typically have
    342 // different function signatures on their callbacks, and also the fact that
    343 // different policies require different function signatures when perfect
    344 // forwarding.
    345 // We cannot simply have a single virtual ApplyWithArgs function in our
    346 // superclass, because it is not possible to have a templated virtual function,
    347 // and the policies all have different params that are passed (eg; NonExclusive
    348 // passes const refs, which is not compatible with passing by rvalue ref or
    349 // non-const lvalue ref)
    350 template <ListenerPolicy, typename...>
    351 class Listener;
    352 
    353 template <typename... As>
    354 class Listener<ListenerPolicy::Exclusive, As...> : public ListenerBase {
    355 public:
    356  virtual void ApplyWithArgsImpl(As&&... aEvents) = 0;
    357 };
    358 
    359 template <typename... As>
    360 class Listener<ListenerPolicy::OneCopyPerThread, As...> : public ListenerBase {
    361 public:
    362  virtual void ApplyWithArgsImpl(As&... aEvents) = 0;
    363 };
    364 
    365 template <typename... As>
    366 class Listener<ListenerPolicy::NonExclusive, As...> : public ListenerBase {
    367 public:
    368  virtual void ApplyWithArgsImpl(const As&... aEvents) = 0;
    369 };
    370 
    371 /**
    372 * Store the registered event target and function so it knows where and to
    373 * whom to send the event data.
    374 * The only way to make the existence of a virtual function override contingent
    375 * on a template parameter is to use template specialization. So, this
    376 * implements everything but ApplyWithArgs, which will be handled by yet
    377 * another subclass.
    378 */
    379 template <ListenerPolicy Policy, typename Function, typename... As>
    380 class ListenerImpl : public Listener<Policy, As...> {
    381  // Strip CV and reference from Function.
    382  using FunctionStorage = std::decay_t<Function>;
    383  using SelfType = ListenerImpl<Policy, Function, As...>;
    384 
    385 public:
    386  ListenerImpl(nsCOMPtr<nsIEventTarget>&& aTarget, Function&& aFunction)
    387      : mData(MakeRefPtr<Data>(std::move(aTarget),
    388                               std::forward<Function>(aFunction)),
    389              "MediaEvent ListenerImpl::mData") {}
    390 
    391 protected:
    392  virtual ~ListenerImpl() {
    393    MOZ_ASSERT(IsRevoked(), "Must disconnect the listener.");
    394  }
    395 
    396  nsCOMPtr<nsIEventTarget> GetTarget() const override {
    397    auto d = mData.Lock();
    398    if (d.ref()) {
    399      return d.ref()->mTarget;
    400    }
    401    return nullptr;
    402  }
    403 
    404  bool TryDispatchTask(already_AddRefed<nsIRunnable> aTask) override {
    405    nsCOMPtr task = aTask;
    406    RefPtr<Data> data;
    407    {
    408      auto d = mData.Lock();
    409      data = *d;
    410    }
    411    if (!data) {
    412      return false;
    413    }
    414    data->mTarget->Dispatch(task.forget());
    415    return true;
    416  }
    417 
    418  bool CanTakeArgs() const override { return TakeArgs<FunctionStorage>::value; }
    419 
    420  template <typename... Ts>
    421  void ApplyWithArgs(Ts&&... aEvents) {
    422    if constexpr (TakeArgs<Function>::value) {
    423      // Don't call the listener if it is disconnected.
    424      RefPtr<Data> data;
    425      {
    426        auto d = mData.Lock();
    427        data = *d;
    428      }
    429      if (!data) {
    430        return;
    431      }
    432      MOZ_DIAGNOSTIC_ASSERT(data->mTarget->IsOnCurrentThread());
    433      data->mFunction(std::forward<Ts>(aEvents)...);
    434    } else {
    435      MOZ_CRASH(
    436          "Don't use ApplyWithArgsImpl on listeners that don't take args! Use "
    437          "ApplyWithNoArgsImpl instead.");
    438    }
    439  }
    440 
    441  // |F| takes no arguments.
    442  void ApplyWithNoArgs() override {
    443    if constexpr (!TakeArgs<Function>::value) {
    444      // Don't call the listener if it is disconnected.
    445      RefPtr<Data> data;
    446      {
    447        auto d = mData.Lock();
    448        data = *d;
    449      }
    450      if (!data) {
    451        return;
    452      }
    453      MOZ_DIAGNOSTIC_ASSERT(data->mTarget->IsOnCurrentThread());
    454      data->mFunction();
    455    } else {
    456      MOZ_CRASH(
    457          "Don't use ApplyWithNoArgsImpl on listeners that take args! Use "
    458          "ApplyWithArgsImpl instead.");
    459    }
    460  }
    461 
    462  void Revoke() override {
    463    {
    464      auto data = mData.Lock();
    465      *data = nullptr;
    466    }
    467  }
    468 
    469  bool IsRevoked() const override {
    470    auto data = mData.Lock();
    471    return !*data;
    472  }
    473 
    474  struct RefCountedMediaEventListenerData {
    475    // Keep ref-counting here since Data holds a template member, leading to
    476    // instances of varying size, which the memory leak logging system dislikes.
    477    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefCountedMediaEventListenerData)
    478   protected:
    479    virtual ~RefCountedMediaEventListenerData() = default;
    480  };
    481  struct Data : public RefCountedMediaEventListenerData {
    482    Data(nsCOMPtr<nsIEventTarget>&& aTarget, Function&& aFunction)
    483        : mTarget(std::move(aTarget)),
    484          mFunction(std::forward<Function>(aFunction)) {
    485      MOZ_DIAGNOSTIC_ASSERT(mTarget);
    486    }
    487    const nsCOMPtr<nsIEventTarget> mTarget;
    488    FunctionStorage mFunction;
    489  };
    490 
    491  // Storage for target and function. Also used to track revocation.
    492  mutable DataMutex<RefPtr<Data>> mData;
    493 };
    494 
    495 // We're finally at the end of the class hierarchy. The implementation code
    496 // within ApplyWithArgs is a one-liner that is mostly identical for all
    497 // policies; this could have all been a single template function if these
    498 // weren't virtual functions.
    499 template <ListenerPolicy, typename, typename...>
    500 class ListenerImplFinal;
    501 
    502 template <typename Function, typename... As>
    503 class ListenerImplFinal<ListenerPolicy::Exclusive, Function, As...> final
    504    : public ListenerImpl<ListenerPolicy::Exclusive, Function, As...> {
    505 public:
    506  using BaseType = ListenerImpl<ListenerPolicy::Exclusive, Function, As...>;
    507  ListenerImplFinal(nsIEventTarget* aTarget, Function&& aFunction)
    508      : BaseType(aTarget, std::forward<Function>(aFunction)) {}
    509 
    510  void ApplyWithArgsImpl(As&&... aEvents) override {
    511    BaseType::ApplyWithArgs(std::move(aEvents)...);
    512  }
    513 };
    514 
    515 template <typename Function, typename... As>
    516 class ListenerImplFinal<ListenerPolicy::OneCopyPerThread, Function, As...> final
    517    : public ListenerImpl<ListenerPolicy::OneCopyPerThread, Function, As...> {
    518 public:
    519  using BaseType =
    520      ListenerImpl<ListenerPolicy::OneCopyPerThread, Function, As...>;
    521  ListenerImplFinal(nsIEventTarget* aTarget, Function&& aFunction)
    522      : BaseType(aTarget, std::forward<Function>(aFunction)) {}
    523 
    524  void ApplyWithArgsImpl(As&... aEvents) override {
    525    BaseType::ApplyWithArgs(aEvents...);
    526  }
    527 };
    528 
    529 template <typename Function, typename... As>
    530 class ListenerImplFinal<ListenerPolicy::NonExclusive, Function, As...> final
    531    : public ListenerImpl<ListenerPolicy::NonExclusive, Function, As...> {
    532 public:
    533  using BaseType = ListenerImpl<ListenerPolicy::NonExclusive, Function, As...>;
    534  ListenerImplFinal(nsIEventTarget* aTarget, Function&& aFunction)
    535      : BaseType(aTarget, std::forward<Function>(aFunction)) {}
    536 
    537  void ApplyWithArgsImpl(const As&... aEvents) override {
    538    BaseType::ApplyWithArgs(aEvents...);
    539  }
    540 };
    541 
    542 /**
    543 * Return true if any type is a reference type.
    544 */
    545 template <typename Head, typename... Tails>
    546 struct IsAnyReference {
    547  static const bool value =
    548      std::is_reference_v<Head> || IsAnyReference<Tails...>::value;
    549 };
    550 
    551 template <typename T>
    552 struct IsAnyReference<T> {
    553  static const bool value = std::is_reference_v<T>;
    554 };
    555 
    556 }  // namespace detail
    557 
    558 template <ListenerPolicy, typename... Ts>
    559 class MediaEventSourceImpl;
    560 
    561 /**
    562 * Not thread-safe since this is not meant to be shared and therefore only
    563 * move constructor is provided. Used to hold the result of
    564 * MediaEventSource<T>::Connect() and call Disconnect() to disconnect the
    565 * listener from an event source.
    566 */
    567 class MediaEventListener {
    568  template <ListenerPolicy, typename... Ts>
    569  friend class MediaEventSourceImpl;
    570 
    571 public:
    572  MediaEventListener() = default;
    573 
    574  MediaEventListener(MediaEventListener&& aOther)
    575      : mToken(std::move(aOther.mToken)) {}
    576 
    577  MediaEventListener& operator=(MediaEventListener&& aOther) {
    578    MOZ_ASSERT(!mToken, "Must disconnect the listener.");
    579    mToken = std::move(aOther.mToken);
    580    return *this;
    581  }
    582 
    583  ~MediaEventListener() {
    584    MOZ_ASSERT(!mToken, "Must disconnect the listener.");
    585  }
    586 
    587  void Disconnect() {
    588    mToken->Revoke();
    589    mToken = nullptr;
    590  }
    591 
    592  void DisconnectIfExists() {
    593    if (mToken) {
    594      Disconnect();
    595    }
    596  }
    597 
    598 private:
    599  // Avoid exposing RevocableToken directly to the client code so that
    600  // listeners can be disconnected in a controlled manner.
    601  explicit MediaEventListener(RevocableToken* aToken) : mToken(aToken) {}
    602  RefPtr<RevocableToken> mToken;
    603 };
    604 
    605 /**
    606 * A generic and thread-safe class to implement the observer pattern.
    607 */
    608 template <ListenerPolicy Lp, typename... Es>
    609 class MediaEventSourceImpl {
    610  static_assert(!detail::IsAnyReference<Es...>::value,
    611                "Ref-type not supported!");
    612 
    613  template <typename T>
    614  using ArgType = typename detail::EventTypeTraits<T>::ArgType;
    615 
    616  using Listener = detail::Listener<Lp, ArgType<Es>...>;
    617 
    618  template <typename Func>
    619  using ListenerImpl = detail::ListenerImplFinal<Lp, Func, ArgType<Es>...>;
    620 
    621  using ListenerBatch = typename detail::ListenerBatch<Listener>;
    622 
    623  template <typename Method>
    624  using TakeArgs = detail::TakeArgs<Method>;
    625 
    626  void PruneListeners() {
    627    mListeners.RemoveElementsBy(
    628        [](const auto& listener) { return listener->IsRevoked(); });
    629  }
    630 
    631  template <typename Function>
    632  MediaEventListener ConnectInternal(nsIEventTarget* aTarget,
    633                                     Function&& aFunction) {
    634    MutexAutoLock lock(mMutex);
    635    PruneListeners();
    636    MOZ_ASSERT(Lp != ListenerPolicy::Exclusive || mListeners.IsEmpty());
    637    auto l = mListeners.AppendElement();
    638    *l = new ListenerImpl<Function>(aTarget, std::forward<Function>(aFunction));
    639    return MediaEventListener(*l);
    640  }
    641 
    642 public:
    643  /**
    644   * Register a function to receive notifications from the event source.
    645   *
    646   * @param aTarget The event target on which the function will run.
    647   * @param aFunction A function to be called on the target thread. The function
    648   *                  parameter must be convertible from |EventType|.
    649   * @return An object used to disconnect from the event source.
    650   */
    651  template <typename Function>
    652  MediaEventListener Connect(nsIEventTarget* aTarget, Function&& aFunction) {
    653    return ConnectInternal(aTarget, std::forward<Function>(aFunction));
    654  }
    655 
    656  /**
    657   * As above.
    658   *
    659   * Note we deliberately keep a weak reference to |aThis| in order not to
    660   * change its lifetime. This is because notifications are dispatched
    661   * asynchronously and removing a listener doesn't always break the reference
    662   * cycle for the pending event could still hold a reference to |aThis|.
    663   *
    664   * The caller must call MediaEventListener::Disconnect() to avoid dangling
    665   * pointers.
    666   */
    667  template <typename This, typename Method>
    668  MediaEventListener Connect(nsIEventTarget* aTarget, This* aThis,
    669                             Method aMethod) {
    670    if constexpr (TakeArgs<Method>::value) {
    671      detail::RawPtr<This> thiz(aThis);
    672      if constexpr (Lp == ListenerPolicy::Exclusive) {
    673        return ConnectInternal(aTarget, [=](ArgType<Es>&&... aEvents) {
    674          (thiz.get()->*aMethod)(std::move(aEvents)...);
    675        });
    676      } else if constexpr (Lp == ListenerPolicy::OneCopyPerThread) {
    677        return ConnectInternal(aTarget, [=](ArgType<Es>&... aEvents) {
    678          (thiz.get()->*aMethod)(aEvents...);
    679        });
    680      } else if constexpr (Lp == ListenerPolicy::NonExclusive) {
    681        return ConnectInternal(aTarget, [=](const ArgType<Es>&... aEvents) {
    682          (thiz.get()->*aMethod)(aEvents...);
    683        });
    684      }
    685    } else {
    686      detail::RawPtr<This> thiz(aThis);
    687      return ConnectInternal(aTarget, [=]() { (thiz.get()->*aMethod)(); });
    688    }
    689  }
    690 
    691 protected:
    692  MediaEventSourceImpl() : mMutex("MediaEventSourceImpl::mMutex") {}
    693 
    694  template <typename... Ts>
    695  void NotifyInternal(Ts&&... aEvents) {
    696    MutexAutoLock lock(mMutex);
    697    nsTArray<RefPtr<ListenerBatch>> listenerBatches;
    698    for (size_t i = 0; i < mListeners.Length();) {
    699      auto& l = mListeners[i];
    700      // Remove disconnected listeners.
    701      // It is not optimal but is simple and works well.
    702      nsCOMPtr<nsIEventTarget> target = l->GetTarget();
    703      if (!target) {
    704        mListeners.RemoveElementAt(i);
    705        continue;
    706      }
    707 
    708      ++i;
    709 
    710      // Find a batch (or create one) for this listener's target, and add the
    711      // listener to it.
    712      bool added = false;
    713      for (auto& batch : listenerBatches) {
    714        if (batch->MaybeAddListener(l)) {
    715          added = true;
    716          break;
    717        }
    718      }
    719 
    720      if (!added) {
    721        // The listener might not have a target anymore, but we still place it
    722        // in a Batch with the target we observed up top.
    723        listenerBatches.AppendElement(new ListenerBatch(nsCOMPtr(target)));
    724        (void)listenerBatches.LastElement()->MaybeAddListener(l);
    725      }
    726    }
    727 
    728    if (listenerBatches.Length()) {
    729      detail::NotificationPolicy<Lp, Listener>::DispatchNotifications(
    730          listenerBatches, std::forward<Ts>(aEvents)...);
    731    }
    732  }
    733 
    734  using Listeners = nsTArray<RefPtr<Listener>>;
    735 
    736 private:
    737  Mutex mMutex MOZ_UNANNOTATED;
    738  nsTArray<RefPtr<Listener>> mListeners;
    739 };
    740 
    741 template <typename... Es>
    742 using MediaEventSource =
    743    MediaEventSourceImpl<ListenerPolicy::NonExclusive, Es...>;
    744 
    745 template <typename... Es>
    746 using MediaEventSourceExc =
    747    MediaEventSourceImpl<ListenerPolicy::Exclusive, Es...>;
    748 
    749 template <typename... Es>
    750 using MediaEventSourceOneCopyPerThread =
    751    MediaEventSourceImpl<ListenerPolicy::OneCopyPerThread, Es...>;
    752 
    753 /**
    754 * A class to separate the interface of event subject (MediaEventSource)
    755 * and event publisher. Mostly used as a member variable to publish events
    756 * to the listeners.
    757 */
    758 template <typename... Es>
    759 class MediaEventProducer : public MediaEventSource<Es...> {
    760 public:
    761  template <typename... Ts>
    762  void Notify(Ts&&... aEvents) {
    763    this->NotifyInternal(std::forward<Ts>(aEvents)...);
    764  }
    765 };
    766 
    767 /**
    768 * Specialization for void type. A dummy bool is passed to NotifyInternal
    769 * since there is no way to pass a void value.
    770 */
    771 template <>
    772 class MediaEventProducer<void> : public MediaEventSource<void> {
    773 public:
    774  void Notify() { this->NotifyInternal(true /* dummy */); }
    775 };
    776 
    777 /**
    778 * A producer allowing at most one listener.
    779 */
    780 template <typename... Es>
    781 class MediaEventProducerExc : public MediaEventSourceExc<Es...> {
    782 public:
    783  template <typename... Ts>
    784  void Notify(Ts&&... aEvents) {
    785    this->NotifyInternal(std::forward<Ts>(aEvents)...);
    786  }
    787 };
    788 
    789 template <typename... Es>
    790 class MediaEventProducerOneCopyPerThread
    791    : public MediaEventSourceOneCopyPerThread<Es...> {
    792 public:
    793  template <typename... Ts>
    794  void Notify(Ts&&... aEvents) {
    795    this->NotifyInternal(std::forward<Ts>(aEvents)...);
    796  }
    797 };
    798 
    799 /**
    800 * A class that facilitates forwarding MediaEvents from multiple sources of the
    801 * same type into a single source.
    802 *
    803 * Lifetimes are convenient. A forwarded source is disconnected either by
    804 * the source itself going away, or the forwarder being destroyed.
    805 *
    806 * Not threadsafe. The caller is responsible for calling Forward() in a
    807 * threadsafe manner.
    808 */
    809 template <typename... Es>
    810 class MediaEventForwarder : public MediaEventSource<Es...> {
    811 public:
    812  template <typename T>
    813  using ArgType = typename detail::EventTypeTraits<T>::ArgType;
    814 
    815  explicit MediaEventForwarder(nsCOMPtr<nsISerialEventTarget> aEventTarget)
    816      : mEventTarget(std::move(aEventTarget)) {}
    817 
    818  MediaEventForwarder(MediaEventForwarder&& aOther)
    819      : mEventTarget(aOther.mEventTarget),
    820        mListeners(std::move(aOther.mListeners)) {}
    821 
    822  ~MediaEventForwarder() { MOZ_ASSERT(mListeners.IsEmpty()); }
    823 
    824  MediaEventForwarder& operator=(MediaEventForwarder&& aOther) {
    825    MOZ_RELEASE_ASSERT(mEventTarget == aOther.mEventTarget);
    826    MOZ_ASSERT(mListeners.IsEmpty());
    827    mListeners = std::move(aOther.mListeners);
    828  }
    829 
    830  void Forward(MediaEventSource<Es...>& aSource) {
    831    // Forwarding a rawptr `this` here is fine, since DisconnectAll disconnect
    832    // all mListeners synchronously and prevents this handler from running.
    833    mListeners.AppendElement(
    834        aSource.Connect(mEventTarget, [this](const ArgType<Es>&... aEvents) {
    835          this->NotifyInternal(aEvents...);
    836        }));
    837  }
    838 
    839  template <typename Function>
    840  void ForwardIf(MediaEventSource<Es...>& aSource, Function&& aFunction) {
    841    // Forwarding a rawptr `this` here is fine, since DisconnectAll disconnect
    842    // all mListeners synchronously and prevents this handler from running.
    843    mListeners.AppendElement(aSource.Connect(
    844        mEventTarget, [this, func = aFunction](const ArgType<Es>&... aEvents) {
    845          if (!func()) {
    846            return;
    847          }
    848          this->NotifyInternal(aEvents...);
    849        }));
    850  }
    851 
    852  void DisconnectAll() {
    853    for (auto& l : mListeners) {
    854      l.Disconnect();
    855    }
    856    mListeners.Clear();
    857  }
    858 
    859 private:
    860  const nsCOMPtr<nsISerialEventTarget> mEventTarget;
    861  nsTArray<MediaEventListener> mListeners;
    862 };
    863 
    864 }  // namespace mozilla
    865 
    866 #endif  // MediaEventSource_h_