MediaUtils.h (11894B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=2 et sw=2 tw=80: */ 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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_MediaUtils_h 8 #define mozilla_MediaUtils_h 9 10 #include "MediaEventSource.h" 11 #include "mozilla/Assertions.h" 12 #include "mozilla/Monitor.h" 13 #include "mozilla/MozPromise.h" 14 #include "mozilla/Mutex.h" 15 #include "mozilla/RefPtr.h" 16 #include "mozilla/SharedThreadPool.h" 17 #include "mozilla/TaskQueue.h" 18 #include "mozilla/UniquePtr.h" 19 #include "nsCOMPtr.h" 20 #include "nsIAsyncShutdown.h" 21 #include "nsISupportsImpl.h" 22 #include "nsProxyRelease.h" 23 #include "nsThreadUtils.h" 24 25 class nsIEventTarget; 26 27 namespace mozilla::media { 28 29 /* Utility function, given a string pref and an URI, returns whether or not 30 * the URI occurs in the pref. Wildcards are supported (e.g. *.example.com) 31 * and multiple hostnames can be present, separated by commas. 32 */ 33 bool HostnameInPref(const char* aPrefList, const nsCString& aHostName); 34 35 /* media::NewRunnableFrom() - Create a Runnable from a lambda. 36 * 37 * Passing variables (closures) to an async function is clunky with Runnable: 38 * 39 * void Foo() 40 * { 41 * class FooRunnable : public Runnable 42 * { 43 * public: 44 * FooRunnable(const Bar &aBar) : mBar(aBar) {} 45 * NS_IMETHOD Run() override 46 * { 47 * // Use mBar 48 * } 49 * private: 50 * RefPtr<Bar> mBar; 51 * }; 52 * 53 * RefPtr<Bar> bar = new Bar(); 54 * NS_DispatchToMainThread(new FooRunnable(bar); 55 * } 56 * 57 * It's worse with more variables. Lambdas have a leg up with variable capture: 58 * 59 * void Foo() 60 * { 61 * RefPtr<Bar> bar = new Bar(); 62 * NS_DispatchToMainThread(media::NewRunnableFrom([bar]() mutable { 63 * // use bar 64 * })); 65 * } 66 * 67 * Capture is by-copy by default, so the nsRefPtr 'bar' is safely copied for 68 * access on the other thread (threadsafe refcounting in bar is assumed). 69 * 70 * The 'mutable' keyword is only needed for non-const access to bar. 71 */ 72 73 template <typename OnRunType> 74 class LambdaRunnable : public Runnable { 75 public: 76 explicit LambdaRunnable(OnRunType&& aOnRun) 77 : Runnable("media::LambdaRunnable"), mOnRun(std::move(aOnRun)) {} 78 79 private: 80 NS_IMETHODIMP 81 Run() override { return mOnRun(); } 82 OnRunType mOnRun; 83 }; 84 85 template <typename OnRunType> 86 already_AddRefed<LambdaRunnable<OnRunType>> NewRunnableFrom( 87 OnRunType&& aOnRun) { 88 typedef LambdaRunnable<OnRunType> LambdaType; 89 RefPtr<LambdaType> lambda = new LambdaType(std::forward<OnRunType>(aOnRun)); 90 return lambda.forget(); 91 } 92 93 /* media::Refcountable - Add threadsafe ref-counting to something that isn't. 94 * 95 * Often, reference counting is the most practical way to share an object with 96 * another thread without imposing lifetime restrictions, even if there's 97 * otherwise no concurrent access happening on the object. For instance, an 98 * algorithm on another thread may find it more expedient to modify a passed-in 99 * object, rather than pass expensive copies back and forth. 100 * 101 * Lists in particular often aren't ref-countable, yet are expensive to copy, 102 * e.g. nsTArray<RefPtr<Foo>>. Refcountable can be used to make such objects 103 * (or owning smart-pointers to such objects) refcountable. 104 * 105 * Technical limitation: A template specialization is needed for types that take 106 * a constructor. Please add below (UniquePtr covers a lot of ground though). 107 */ 108 109 class RefcountableBase { 110 public: 111 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RefcountableBase) 112 protected: 113 virtual ~RefcountableBase() = default; 114 }; 115 116 template <typename T> 117 class Refcountable : public T, public RefcountableBase { 118 public: 119 Refcountable& operator=(T&& aOther) { 120 T::operator=(std::move(aOther)); 121 return *this; 122 } 123 124 Refcountable& operator=(T& aOther) { 125 T::operator=(aOther); 126 return *this; 127 } 128 }; 129 130 template <typename T> 131 class Refcountable<UniquePtr<T>> : public UniquePtr<T>, 132 public RefcountableBase { 133 public: 134 explicit Refcountable(T* aPtr) : UniquePtr<T>(aPtr) {} 135 }; 136 137 template <> 138 class Refcountable<bool> : public RefcountableBase { 139 public: 140 explicit Refcountable(bool aValue) : mValue(aValue) {} 141 142 Refcountable& operator=(bool aOther) { 143 mValue = aOther; 144 return *this; 145 } 146 147 Refcountable& operator=(const Refcountable& aOther) { 148 mValue = aOther.mValue; 149 return *this; 150 } 151 152 explicit operator bool() const { return mValue; } 153 154 private: 155 bool mValue; 156 }; 157 158 /* 159 * Async shutdown helpers 160 */ 161 162 nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier(); 163 164 // Like GetShutdownBarrier but will release assert that the result is not null. 165 nsCOMPtr<nsIAsyncShutdownClient> MustGetShutdownBarrier(); 166 167 class ShutdownBlocker : public nsIAsyncShutdownBlocker { 168 public: 169 ShutdownBlocker(const nsAString& aName) : mName(aName) {} 170 171 NS_IMETHOD 172 BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override = 0; 173 174 NS_IMETHOD GetName(nsAString& aName) override { 175 aName = mName; 176 return NS_OK; 177 } 178 179 NS_IMETHOD GetState(nsIPropertyBag**) override { return NS_OK; } 180 181 NS_DECL_THREADSAFE_ISUPPORTS 182 protected: 183 virtual ~ShutdownBlocker() = default; 184 185 private: 186 const nsString mName; 187 }; 188 189 /** 190 * A convenience class representing a "ticket" that keeps the process from 191 * shutting down until it is destructed. It does this by blocking 192 * xpcom-will-shutdown. Constructed and destroyed on any thread. 193 */ 194 class ShutdownBlockingTicket { 195 public: 196 using ShutdownMozPromise = MozPromise<bool, bool, false>; 197 198 /** 199 * Construct with an arbitrary name, __FILE__ and __LINE__. 200 * Note that __FILE__ needs to be made wide, typically through 201 * NS_LITERAL_STRING_FROM_CSTRING(__FILE__). 202 * Returns nullptr if we are too far in the shutdown sequence to add a 203 * blocker. Any thread. 204 */ 205 static UniquePtr<ShutdownBlockingTicket> Create(const nsAString& aName, 206 const nsAString& aFileName, 207 int32_t aLineNr); 208 209 virtual ~ShutdownBlockingTicket() = default; 210 211 /** 212 * MozPromise that gets resolved upon xpcom-will-shutdown. 213 * Should the ticket get destroyed before the MozPromise has been resolved, 214 * the MozPromise will get rejected. 215 */ 216 virtual ShutdownMozPromise* ShutdownPromise() = 0; 217 }; 218 219 /** 220 * A convenience class intended to be subclassed by DOM objects wanting to be 221 * notified when its owning thread shutdown is about to occur. 222 */ 223 class ShutdownConsumer { 224 public: 225 /** 226 * On the main thread, this is called with the xpcom-will-shutdown event. 227 * On a worker thread, this is called by the WeakWorkerRef callback. 228 */ 229 virtual void OnShutdown() = 0; 230 }; 231 232 /** 233 * A convenience class intended to be held by DOM objects wanting to be notified 234 * when its owning thread shutdown is about to occur. 235 */ 236 class ShutdownWatcher : public nsISupports { 237 public: 238 /** 239 * Create a shutdown watcher for the given consumer. 240 */ 241 static already_AddRefed<ShutdownWatcher> Create(ShutdownConsumer* aConsumer); 242 243 /** 244 * Destroy a shutdown watcher. Must be called by the owning object prior to 245 * clearing its reference. 246 */ 247 virtual void Destroy() = 0; 248 249 protected: 250 explicit ShutdownWatcher(ShutdownConsumer* aConsumer) : mConsumer(aConsumer) { 251 MOZ_ASSERT(aConsumer); 252 } 253 254 virtual ~ShutdownWatcher() { MOZ_ASSERT(!mConsumer); } 255 256 ShutdownConsumer* mConsumer; 257 }; 258 259 /** 260 * Await convenience methods to block until the promise has been resolved or 261 * rejected. The Resolve/Reject functions, while called on a different thread, 262 * would be running just as on the current thread thanks to the memory barrier 263 * provided by the monitor. 264 * For now Await can only be used with an exclusive MozPromise if passed a 265 * Resolve/Reject function. 266 * Await() can *NOT* be called from a task queue/nsISerialEventTarget used for 267 * resolving/rejecting aPromise, otherwise things will deadlock. 268 */ 269 template <typename ResolveValueType, typename RejectValueType, 270 typename ResolveFunction, typename RejectFunction> 271 void Await(already_AddRefed<nsIEventTarget> aPool, 272 RefPtr<MozPromise<ResolveValueType, RejectValueType, true>> aPromise, 273 ResolveFunction&& aResolveFunction, 274 RejectFunction&& aRejectFunction) { 275 RefPtr<TaskQueue> taskQueue = 276 TaskQueue::Create(std::move(aPool), "MozPromiseAwait"); 277 Monitor mon MOZ_UNANNOTATED(__func__); 278 bool done = false; 279 280 aPromise->Then( 281 taskQueue, __func__, 282 [&](ResolveValueType&& aResolveValue) { 283 MonitorAutoLock lock(mon); 284 aResolveFunction(std::forward<ResolveValueType>(aResolveValue)); 285 done = true; 286 mon.Notify(); 287 }, 288 [&](RejectValueType&& aRejectValue) { 289 MonitorAutoLock lock(mon); 290 aRejectFunction(std::forward<RejectValueType>(aRejectValue)); 291 done = true; 292 mon.Notify(); 293 }); 294 295 MonitorAutoLock lock(mon); 296 while (!done) { 297 mon.Wait(); 298 } 299 } 300 301 template <typename ResolveValueType, typename RejectValueType, bool Excl> 302 typename MozPromise<ResolveValueType, RejectValueType, 303 Excl>::ResolveOrRejectValue 304 Await(already_AddRefed<nsIEventTarget> aPool, 305 RefPtr<MozPromise<ResolveValueType, RejectValueType, Excl>> aPromise) { 306 RefPtr<TaskQueue> taskQueue = 307 TaskQueue::Create(std::move(aPool), "MozPromiseAwait"); 308 Monitor mon MOZ_UNANNOTATED(__func__); 309 bool done = false; 310 311 typename MozPromise<ResolveValueType, RejectValueType, 312 Excl>::ResolveOrRejectValue val; 313 aPromise->Then( 314 taskQueue, __func__, 315 [&](ResolveValueType aResolveValue) { 316 val.SetResolve(std::move(aResolveValue)); 317 MonitorAutoLock lock(mon); 318 done = true; 319 mon.Notify(); 320 }, 321 [&](RejectValueType aRejectValue) { 322 val.SetReject(std::move(aRejectValue)); 323 MonitorAutoLock lock(mon); 324 done = true; 325 mon.Notify(); 326 }); 327 328 MonitorAutoLock lock(mon); 329 while (!done) { 330 mon.Wait(); 331 } 332 333 return val; 334 } 335 336 /** 337 * Similar to Await, takes an array of promises of the same type. 338 * MozPromise::All is used to handle the resolution/rejection of the promises. 339 */ 340 template <typename ResolveValueType, typename RejectValueType, 341 typename ResolveFunction, typename RejectFunction> 342 void AwaitAll( 343 already_AddRefed<nsIEventTarget> aPool, 344 nsTArray<RefPtr<MozPromise<ResolveValueType, RejectValueType, true>>>& 345 aPromises, 346 ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction) { 347 typedef MozPromise<ResolveValueType, RejectValueType, true> Promise; 348 RefPtr<nsIEventTarget> pool = aPool; 349 RefPtr<TaskQueue> taskQueue = 350 TaskQueue::Create(do_AddRef(pool), "MozPromiseAwaitAll"); 351 RefPtr<typename Promise::AllPromiseType> p = 352 Promise::All(taskQueue, aPromises); 353 Await(pool.forget(), p, std::move(aResolveFunction), 354 std::move(aRejectFunction)); 355 } 356 357 // Note: only works with exclusive MozPromise, as Promise::All would attempt 358 // to perform copy of nsTArrays which are disallowed. 359 template <typename ResolveValueType, typename RejectValueType> 360 typename MozPromise<ResolveValueType, RejectValueType, 361 true>::AllPromiseType::ResolveOrRejectValue 362 AwaitAll(already_AddRefed<nsIEventTarget> aPool, 363 nsTArray<RefPtr<MozPromise<ResolveValueType, RejectValueType, true>>>& 364 aPromises) { 365 typedef MozPromise<ResolveValueType, RejectValueType, true> Promise; 366 RefPtr<nsIEventTarget> pool = aPool; 367 RefPtr<TaskQueue> taskQueue = 368 TaskQueue::Create(do_AddRef(pool), "MozPromiseAwaitAll"); 369 RefPtr<typename Promise::AllPromiseType> p = 370 Promise::All(taskQueue, aPromises); 371 return Await(pool.forget(), p); 372 } 373 374 } // namespace mozilla::media 375 376 #endif // mozilla_MediaUtils_h