Line data Source code
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 "mozilla/AbstractThread.h"
11 : #include "mozilla/Atomics.h"
12 : #include "mozilla/IndexSequence.h"
13 : #include "mozilla/Mutex.h"
14 : #include "mozilla/Tuple.h"
15 : #include "mozilla/TypeTraits.h"
16 :
17 : #include "nsISupportsImpl.h"
18 : #include "nsTArray.h"
19 : #include "nsThreadUtils.h"
20 :
21 : namespace mozilla {
22 :
23 : /**
24 : * A thread-safe tool to communicate "revocation" across threads. It is used to
25 : * disconnect a listener from the event source to prevent future notifications
26 : * from coming. Revoke() can be called on any thread. However, it is recommended
27 : * to be called on the target thread to avoid race condition.
28 : *
29 : * RevocableToken is not exposed to the client code directly.
30 : * Use MediaEventListener below to do the job.
31 : */
32 : class RevocableToken {
33 0 : NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RevocableToken);
34 :
35 : public:
36 0 : RevocableToken() : mRevoked(false) {}
37 :
38 0 : void Revoke() {
39 0 : mRevoked = true;
40 0 : }
41 :
42 0 : bool IsRevoked() const {
43 0 : return mRevoked;
44 : }
45 :
46 : protected:
47 : // Virtual destructor is required since we might delete a Listener object
48 : // through its base type pointer.
49 0 : virtual ~RevocableToken() { }
50 :
51 : private:
52 : Atomic<bool> mRevoked;
53 : };
54 :
55 : enum class ListenerPolicy : int8_t {
56 : // Allow at most one listener. Move will be used when possible
57 : // to pass the event data to save copy.
58 : Exclusive,
59 : // Allow multiple listeners. Event data will always be copied when passed
60 : // to the listeners.
61 : NonExclusive
62 : };
63 :
64 : namespace detail {
65 :
66 : /**
67 : * Define how an event type is passed internally in MediaEventSource and to the
68 : * listeners. Specialized for the void type to pass a dummy bool instead.
69 : */
70 : template <typename T>
71 : struct EventTypeTraits {
72 : typedef T ArgType;
73 : };
74 :
75 : template <>
76 : struct EventTypeTraits<void> {
77 : typedef bool ArgType;
78 : };
79 :
80 : /**
81 : * Test if a method function or lambda accepts one or more arguments.
82 : */
83 : template <typename T>
84 : class TakeArgsHelper {
85 : template <typename C> static FalseType test(void(C::*)(), int);
86 : template <typename C> static FalseType test(void(C::*)() const, int);
87 : template <typename C> static FalseType test(void(C::*)() volatile, int);
88 : template <typename C> static FalseType test(void(C::*)() const volatile, int);
89 : template <typename F> static FalseType test(F&&, decltype(DeclVal<F>()(), 0));
90 : static TrueType test(...);
91 : public:
92 : typedef decltype(test(DeclVal<T>(), 0)) Type;
93 : };
94 :
95 : template <typename T>
96 : struct TakeArgs : public TakeArgsHelper<T>::Type {};
97 :
98 : template <typename T> struct EventTarget;
99 :
100 : template <>
101 : struct EventTarget<nsIEventTarget> {
102 : static void
103 : Dispatch(nsIEventTarget* aTarget, already_AddRefed<nsIRunnable> aTask) {
104 : aTarget->Dispatch(Move(aTask), NS_DISPATCH_NORMAL);
105 : }
106 : };
107 :
108 : template <>
109 : struct EventTarget<AbstractThread> {
110 : static void
111 0 : Dispatch(AbstractThread* aTarget, already_AddRefed<nsIRunnable> aTask) {
112 0 : aTarget->Dispatch(Move(aTask), AbstractThread::DontAssertDispatchSuccess);
113 0 : }
114 : };
115 :
116 : /**
117 : * Encapsulate a raw pointer to be captured by a lambda without causing
118 : * static-analysis errors.
119 : */
120 : template <typename T>
121 : class RawPtr {
122 : public:
123 0 : explicit RawPtr(T* aPtr) : mPtr(aPtr) {}
124 0 : T* get() const { return mPtr; }
125 : private:
126 : T* const mPtr;
127 : };
128 :
129 : template <typename... As>
130 0 : class Listener : public RevocableToken
131 : {
132 : public:
133 : template <typename... Ts>
134 0 : void Dispatch(Ts&&... aEvents)
135 : {
136 0 : if (CanTakeArgs()) {
137 0 : DispatchTask(NewRunnableMethod<typename Decay<Ts>::Type&&...>(
138 : "detail::Listener::ApplyWithArgs",
139 : this,
140 : &Listener::ApplyWithArgs,
141 : Forward<Ts>(aEvents)...));
142 : } else {
143 0 : DispatchTask(NewRunnableMethod(
144 : "detail::Listener::ApplyWithNoArgs", this, &Listener::ApplyWithNoArgs));
145 : }
146 0 : }
147 :
148 : protected:
149 0 : virtual ~Listener()
150 : {
151 0 : MOZ_ASSERT(IsRevoked(), "Must disconnect the listener.");
152 0 : }
153 :
154 : private:
155 : virtual void DispatchTask(already_AddRefed<nsIRunnable> aTask) = 0;
156 :
157 : // True if the underlying listener function takes non-zero arguments.
158 : virtual bool CanTakeArgs() const = 0;
159 : // Pass the event data to the underlying listener function. Should be called
160 : // only when CanTakeArgs() returns true.
161 : virtual void ApplyWithArgs(As&&... aEvents) = 0;
162 : // Invoke the underlying listener function. Should be called only when
163 : // CanTakeArgs() returns false.
164 : virtual void ApplyWithNoArgs() = 0;
165 : };
166 :
167 : /**
168 : * Store the registered target thread and function so it knows where and to
169 : * whom to send the event data.
170 : */
171 : template <typename Target, typename Function, typename... As>
172 0 : class ListenerImpl : public Listener<As...>
173 : {
174 : // Strip CV and reference from Function.
175 : using FunctionStorage = typename Decay<Function>::Type;
176 :
177 : public:
178 : template <typename F>
179 0 : ListenerImpl(Target* aTarget, F&& aFunction)
180 : : mTarget(aTarget)
181 0 : , mFunction(Forward<F>(aFunction))
182 : {
183 0 : }
184 :
185 : private:
186 0 : void DispatchTask(already_AddRefed<nsIRunnable> aTask) override
187 : {
188 0 : EventTarget<Target>::Dispatch(mTarget.get(), Move(aTask));
189 0 : }
190 :
191 0 : bool CanTakeArgs() const override
192 : {
193 0 : return TakeArgs<FunctionStorage>::value;
194 : }
195 :
196 : // |F| takes one or more arguments.
197 : template <typename F>
198 : typename EnableIf<TakeArgs<F>::value, void>::Type
199 0 : ApplyWithArgsImpl(const F& aFunc, As&&... aEvents)
200 : {
201 0 : aFunc(Move(aEvents)...);
202 0 : }
203 :
204 : // |F| takes no arguments.
205 : template <typename F>
206 : typename EnableIf<!TakeArgs<F>::value, void>::Type
207 : ApplyWithArgsImpl(const F& aFunc, As&&... aEvents)
208 : {
209 : MOZ_CRASH("Call ApplyWithNoArgs instead.");
210 : }
211 :
212 0 : void ApplyWithArgs(As&&... aEvents) override
213 : {
214 0 : MOZ_RELEASE_ASSERT(TakeArgs<Function>::value);
215 : // Don't call the listener if it is disconnected.
216 0 : if (!RevocableToken::IsRevoked()) {
217 0 : ApplyWithArgsImpl(mFunction, Move(aEvents)...);
218 : }
219 0 : }
220 :
221 : // |F| takes one or more arguments.
222 : template <typename F>
223 : typename EnableIf<TakeArgs<F>::value, void>::Type
224 : ApplyWithNoArgsImpl(const F& aFunc)
225 : {
226 : MOZ_CRASH("Call ApplyWithArgs instead.");
227 : }
228 :
229 : // |F| takes no arguments.
230 : template <typename F>
231 : typename EnableIf<!TakeArgs<F>::value, void>::Type
232 0 : ApplyWithNoArgsImpl(const F& aFunc)
233 : {
234 0 : aFunc();
235 0 : }
236 :
237 0 : virtual void ApplyWithNoArgs() override
238 : {
239 0 : MOZ_RELEASE_ASSERT(!TakeArgs<Function>::value);
240 : // Don't call the listener if it is disconnected.
241 0 : if (!RevocableToken::IsRevoked()) {
242 0 : ApplyWithNoArgsImpl(mFunction);
243 : }
244 0 : }
245 :
246 : const RefPtr<Target> mTarget;
247 : FunctionStorage mFunction;
248 : };
249 :
250 : /**
251 : * Return true if any type is a reference type.
252 : */
253 : template <typename Head, typename... Tails>
254 : struct IsAnyReference {
255 : static const bool value = IsReference<Head>::value ||
256 : IsAnyReference<Tails...>::value;
257 : };
258 :
259 : template <typename T>
260 : struct IsAnyReference<T> {
261 : static const bool value = IsReference<T>::value;
262 : };
263 :
264 : } // namespace detail
265 :
266 : template <ListenerPolicy, typename... Ts> class MediaEventSourceImpl;
267 :
268 : /**
269 : * Not thread-safe since this is not meant to be shared and therefore only
270 : * move constructor is provided. Used to hold the result of
271 : * MediaEventSource<T>::Connect() and call Disconnect() to disconnect the
272 : * listener from an event source.
273 : */
274 : class MediaEventListener {
275 : template <ListenerPolicy, typename... Ts>
276 : friend class MediaEventSourceImpl;
277 :
278 : public:
279 1 : MediaEventListener() {}
280 :
281 : MediaEventListener(MediaEventListener&& aOther)
282 : : mToken(Move(aOther.mToken)) {}
283 :
284 0 : MediaEventListener& operator=(MediaEventListener&& aOther) {
285 0 : MOZ_ASSERT(!mToken, "Must disconnect the listener.");
286 0 : mToken = Move(aOther.mToken);
287 0 : return *this;
288 : }
289 :
290 0 : ~MediaEventListener() {
291 0 : MOZ_ASSERT(!mToken, "Must disconnect the listener.");
292 0 : }
293 :
294 0 : void Disconnect() {
295 0 : mToken->Revoke();
296 0 : mToken = nullptr;
297 0 : }
298 :
299 0 : void DisconnectIfExists() {
300 0 : if (mToken) {
301 0 : Disconnect();
302 : }
303 0 : }
304 :
305 : private:
306 : // Avoid exposing RevocableToken directly to the client code so that
307 : // listeners can be disconnected in a controlled manner.
308 0 : explicit MediaEventListener(RevocableToken* aToken) : mToken(aToken) {}
309 : RefPtr<RevocableToken> mToken;
310 : };
311 :
312 : /**
313 : * A generic and thread-safe class to implement the observer pattern.
314 : */
315 : template <ListenerPolicy Lp, typename... Es>
316 0 : class MediaEventSourceImpl {
317 : static_assert(!detail::IsAnyReference<Es...>::value,
318 : "Ref-type not supported!");
319 :
320 : template <typename T>
321 : using ArgType = typename detail::EventTypeTraits<T>::ArgType;
322 :
323 : typedef detail::Listener<ArgType<Es>...> Listener;
324 :
325 : template<typename Target, typename Func>
326 : using ListenerImpl = detail::ListenerImpl<Target, Func, ArgType<Es>...>;
327 :
328 : template <typename Method>
329 : using TakeArgs = detail::TakeArgs<Method>;
330 :
331 0 : void PruneListeners() {
332 0 : int32_t last = static_cast<int32_t>(mListeners.Length()) - 1;
333 0 : for (int32_t i = last; i >= 0; --i) {
334 0 : if (mListeners[i]->IsRevoked()) {
335 0 : mListeners.RemoveElementAt(i);
336 : }
337 : }
338 0 : }
339 :
340 : template<typename Target, typename Function>
341 : MediaEventListener
342 0 : ConnectInternal(Target* aTarget, Function&& aFunction) {
343 0 : MutexAutoLock lock(mMutex);
344 0 : PruneListeners();
345 0 : MOZ_ASSERT(Lp == ListenerPolicy::NonExclusive || mListeners.IsEmpty());
346 0 : auto l = mListeners.AppendElement();
347 0 : *l = new ListenerImpl<Target, Function>(
348 : aTarget, Forward<Function>(aFunction));
349 0 : return MediaEventListener(*l);
350 : }
351 :
352 : // |Method| takes one or more arguments.
353 : template <typename Target, typename This, typename Method>
354 : typename EnableIf<TakeArgs<Method>::value, MediaEventListener>::Type
355 0 : ConnectInternal(Target* aTarget, This* aThis, Method aMethod) {
356 0 : detail::RawPtr<This> thiz(aThis);
357 : return ConnectInternal(aTarget,
358 0 : [=](ArgType<Es>&&... aEvents) {
359 0 : (thiz.get()->*aMethod)(Move(aEvents)...);
360 0 : });
361 : }
362 :
363 : // |Method| takes no arguments. Don't bother passing the event data.
364 : template <typename Target, typename This, typename Method>
365 : typename EnableIf<!TakeArgs<Method>::value, MediaEventListener>::Type
366 0 : ConnectInternal(Target* aTarget, This* aThis, Method aMethod) {
367 0 : detail::RawPtr<This> thiz(aThis);
368 : return ConnectInternal(aTarget,
369 0 : [=]() {
370 0 : (thiz.get()->*aMethod)();
371 0 : });
372 : }
373 :
374 : public:
375 : /**
376 : * Register a function to receive notifications from the event source.
377 : *
378 : * @param aTarget The target thread on which the function will run.
379 : * @param aFunction A function to be called on the target thread. The function
380 : * parameter must be convertible from |EventType|.
381 : * @return An object used to disconnect from the event source.
382 : */
383 : template<typename Function>
384 : MediaEventListener
385 0 : Connect(AbstractThread* aTarget, Function&& aFunction) {
386 0 : return ConnectInternal(aTarget, Forward<Function>(aFunction));
387 : }
388 :
389 : template<typename Function>
390 : MediaEventListener
391 : Connect(nsIEventTarget* aTarget, Function&& aFunction) {
392 : return ConnectInternal(aTarget, Forward<Function>(aFunction));
393 : }
394 :
395 : /**
396 : * As above.
397 : *
398 : * Note we deliberately keep a weak reference to |aThis| in order not to
399 : * change its lifetime. This is because notifications are dispatched
400 : * asynchronously and removing a listener doesn't always break the reference
401 : * cycle for the pending event could still hold a reference to |aThis|.
402 : *
403 : * The caller must call MediaEventListener::Disconnect() to avoid dangling
404 : * pointers.
405 : */
406 : template <typename This, typename Method>
407 : MediaEventListener
408 0 : Connect(AbstractThread* aTarget, This* aThis, Method aMethod) {
409 0 : return ConnectInternal(aTarget, aThis, aMethod);
410 : }
411 :
412 : template <typename This, typename Method>
413 : MediaEventListener
414 : Connect(nsIEventTarget* aTarget, This* aThis, Method aMethod) {
415 : return ConnectInternal(aTarget, aThis, aMethod);
416 : }
417 :
418 : protected:
419 0 : MediaEventSourceImpl() : mMutex("MediaEventSourceImpl::mMutex") {}
420 :
421 : template <typename... Ts>
422 0 : void NotifyInternal(Ts&&... aEvents) {
423 0 : MutexAutoLock lock(mMutex);
424 0 : int32_t last = static_cast<int32_t>(mListeners.Length()) - 1;
425 0 : for (int32_t i = last; i >= 0; --i) {
426 0 : auto&& l = mListeners[i];
427 : // Remove disconnected listeners.
428 : // It is not optimal but is simple and works well.
429 0 : if (l->IsRevoked()) {
430 0 : mListeners.RemoveElementAt(i);
431 0 : continue;
432 : }
433 0 : l->Dispatch(Forward<Ts>(aEvents)...);
434 : }
435 0 : }
436 :
437 : private:
438 : Mutex mMutex;
439 : nsTArray<RefPtr<Listener>> mListeners;
440 : };
441 :
442 : template <typename... Es>
443 : using MediaEventSource =
444 : MediaEventSourceImpl<ListenerPolicy::NonExclusive, Es...>;
445 :
446 : template <typename... Es>
447 : using MediaEventSourceExc =
448 : MediaEventSourceImpl<ListenerPolicy::Exclusive, Es...>;
449 :
450 : /**
451 : * A class to separate the interface of event subject (MediaEventSource)
452 : * and event publisher. Mostly used as a member variable to publish events
453 : * to the listeners.
454 : */
455 : template <typename... Es>
456 0 : class MediaEventProducer : public MediaEventSource<Es...> {
457 : public:
458 : template <typename... Ts>
459 0 : void Notify(Ts&&... aEvents) {
460 : // Pass lvalues to prevent move in NonExclusive mode.
461 0 : this->NotifyInternal(aEvents...);
462 0 : }
463 : };
464 :
465 : /**
466 : * Specialization for void type. A dummy bool is passed to NotifyInternal
467 : * since there is no way to pass a void value.
468 : */
469 : template <>
470 0 : class MediaEventProducer<void> : public MediaEventSource<void> {
471 : public:
472 0 : void Notify() {
473 0 : this->NotifyInternal(true /* dummy */);
474 0 : }
475 : };
476 :
477 : /**
478 : * A producer allowing at most one listener.
479 : */
480 : template <typename... Es>
481 0 : class MediaEventProducerExc : public MediaEventSourceExc<Es...> {
482 : public:
483 : template <typename... Ts>
484 0 : void Notify(Ts&&... aEvents) {
485 0 : this->NotifyInternal(Forward<Ts>(aEvents)...);
486 0 : }
487 : };
488 :
489 : } // namespace mozilla
490 :
491 : #endif //MediaEventSource_h_
|