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_