MediaUtils.cpp (9355B)
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 #include "MediaUtils.h" 8 9 #include "mozilla/AppShutdown.h" 10 #include "mozilla/Preferences.h" 11 #include "mozilla/Services.h" 12 #include "mozilla/dom/WorkerCommon.h" 13 #include "mozilla/dom/WorkerRef.h" 14 #include "nsIObserver.h" 15 #include "nsIObserverService.h" 16 #include "nsNetUtil.h" 17 18 namespace mozilla::media { 19 20 bool HostnameInPref(const char* aPref, const nsCString& aHostName) { 21 auto HostInDomain = [](const nsCString& aHost, const nsCString& aPattern) { 22 int32_t patternOffset = 0; 23 int32_t hostOffset = 0; 24 25 // Act on '*.' wildcard in the left-most position in a domain pattern. 26 if (StringBeginsWith(aPattern, nsCString("*."))) { 27 patternOffset = 2; 28 29 // Ignore the lowest level sub-domain for the hostname. 30 hostOffset = aHost.FindChar('.') + 1; 31 32 if (hostOffset <= 1) { 33 // Reject a match between a wildcard and a TLD or '.foo' form. 34 return false; 35 } 36 } 37 38 nsDependentCString hostRoot(aHost, hostOffset); 39 return hostRoot.EqualsIgnoreCase(aPattern.BeginReading() + patternOffset); 40 }; 41 42 nsCString domainList; 43 nsresult rv = Preferences::GetCString(aPref, domainList); 44 45 if (NS_FAILED(rv)) { 46 return false; 47 } 48 49 domainList.StripWhitespace(); 50 51 if (domainList.IsEmpty() || aHostName.IsEmpty()) { 52 return false; 53 } 54 55 // Test each domain name in the comma separated list 56 // after converting from UTF8 to ASCII. Each domain 57 // must match exactly or have a single leading '*.' wildcard. 58 for (const nsACString& each : domainList.Split(',')) { 59 nsCString domainPattern; 60 rv = NS_DomainToASCIIAllowAnyGlyphfulASCII(each, domainPattern); 61 if (NS_SUCCEEDED(rv)) { 62 if (HostInDomain(aHostName, domainPattern)) { 63 return true; 64 } 65 } else { 66 NS_WARNING("Failed to convert UTF-8 host to ASCII"); 67 } 68 } 69 return false; 70 } 71 72 nsCOMPtr<nsIAsyncShutdownClient> GetShutdownBarrier() { 73 nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService(); 74 if (!svc) { 75 // We can fail to get the shutdown service if we're already shutting down. 76 return nullptr; 77 } 78 79 nsCOMPtr<nsIAsyncShutdownClient> barrier; 80 nsresult rv = svc->GetProfileBeforeChange(getter_AddRefs(barrier)); 81 if (!barrier) { 82 // We are probably in a content process. We need to do cleanup at 83 // XPCOM shutdown in leakchecking builds. 84 rv = svc->GetXpcomWillShutdown(getter_AddRefs(barrier)); 85 } 86 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); 87 MOZ_RELEASE_ASSERT(barrier); 88 return barrier; 89 } 90 91 nsCOMPtr<nsIAsyncShutdownClient> MustGetShutdownBarrier() { 92 nsCOMPtr<nsIAsyncShutdownClient> barrier = GetShutdownBarrier(); 93 MOZ_RELEASE_ASSERT(barrier); 94 return barrier; 95 } 96 97 NS_IMPL_ISUPPORTS(ShutdownBlocker, nsIAsyncShutdownBlocker) 98 99 namespace { 100 class TicketBlocker : public ShutdownBlocker { 101 using ShutdownMozPromise = ShutdownBlockingTicket::ShutdownMozPromise; 102 103 public: 104 explicit TicketBlocker(const nsAString& aName) 105 : ShutdownBlocker(aName), mPromise(mHolder.Ensure(__func__)) {} 106 107 NS_IMETHOD 108 BlockShutdown(nsIAsyncShutdownClient* aProfileBeforeChange) override { 109 mHolder.Resolve(true, __func__); 110 return NS_OK; 111 } 112 113 void RejectIfExists() { mHolder.RejectIfExists(false, __func__); } 114 115 ShutdownMozPromise* ShutdownPromise() { return mPromise; } 116 117 private: 118 ~TicketBlocker() = default; 119 120 MozPromiseHolder<ShutdownMozPromise> mHolder; 121 const RefPtr<ShutdownMozPromise> mPromise; 122 }; 123 124 class ShutdownBlockingTicketImpl : public ShutdownBlockingTicket { 125 private: 126 RefPtr<TicketBlocker> mBlocker; 127 128 public: 129 explicit ShutdownBlockingTicketImpl(RefPtr<TicketBlocker> aBlocker) 130 : mBlocker(std::move(aBlocker)) {} 131 132 static UniquePtr<ShutdownBlockingTicket> Create(const nsAString& aName, 133 const nsAString& aFileName, 134 int32_t aLineNr) { 135 auto blocker = MakeRefPtr<TicketBlocker>(aName); 136 NS_DispatchToMainThread(NS_NewRunnableFunction( 137 "ShutdownBlockingTicketImpl::AddBlocker", 138 [blocker, file = nsString(aFileName), aLineNr] { 139 MustGetShutdownBarrier()->AddBlocker(blocker, file, aLineNr, u""_ns); 140 })); 141 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdown)) { 142 // Adding a blocker is not guaranteed to succeed. Remove the blocker in 143 // case it succeeded anyway, and bail. 144 NS_DispatchToMainThread(NS_NewRunnableFunction( 145 "ShutdownBlockingTicketImpl::RemoveBlocker", [blocker] { 146 MustGetShutdownBarrier()->RemoveBlocker(blocker); 147 blocker->RejectIfExists(); 148 })); 149 return nullptr; 150 } 151 152 // Adding a blocker is now guaranteed to succeed: 153 // - If AppShutdown::IsInOrBeyond(AppShutdown) returned false, 154 // - then the AddBlocker main thread task was queued before AppShutdown's 155 // sCurrentShutdownPhase is set to ShutdownPhase::AppShutdown, 156 // - which is before AppShutdown will drain the (main thread) event queue to 157 // run the AddBlocker task, if not already run, 158 // - which is before profile-before-change (the earliest barrier we'd add a 159 // blocker to, see GetShutdownBarrier()) is notified, 160 // - which is when AsyncShutdown prevents further conditions (blockers) 161 // being added to the profile-before-change barrier. 162 return MakeUnique<ShutdownBlockingTicketImpl>(std::move(blocker)); 163 } 164 165 ~ShutdownBlockingTicketImpl() { 166 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread( 167 NS_NewRunnableFunction(__func__, [blocker = std::move(mBlocker)] { 168 GetShutdownBarrier()->RemoveBlocker(blocker); 169 blocker->RejectIfExists(); 170 }))); 171 } 172 173 ShutdownMozPromise* ShutdownPromise() override { 174 return mBlocker->ShutdownPromise(); 175 } 176 }; 177 } // namespace 178 179 UniquePtr<ShutdownBlockingTicket> ShutdownBlockingTicket::Create( 180 const nsAString& aName, const nsAString& aFileName, int32_t aLineNr) { 181 return ShutdownBlockingTicketImpl::Create(aName, aFileName, aLineNr); 182 } 183 184 class MainShutdownWatcher final : public ShutdownWatcher, public nsIObserver { 185 public: 186 NS_DECL_ISUPPORTS 187 188 explicit MainShutdownWatcher(ShutdownConsumer* aConsumer) 189 : ShutdownWatcher(aConsumer) {} 190 191 bool Initialize() { 192 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 193 mConsumer = nullptr; 194 return false; 195 } 196 197 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); 198 if (NS_WARN_IF(!obsService)) { 199 mConsumer = nullptr; 200 return false; 201 } 202 203 if (NS_WARN_IF(NS_FAILED(obsService->AddObserver( 204 this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false)))) { 205 mConsumer = nullptr; 206 return false; 207 } 208 209 mRegistered = true; 210 return true; 211 } 212 213 void Destroy() override { 214 if (!mRegistered) { 215 return; 216 } 217 218 mRegistered = false; 219 mConsumer = nullptr; 220 221 if (nsCOMPtr<nsIObserverService> obsService = 222 services::GetObserverService()) { 223 obsService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); 224 } 225 } 226 227 NS_IMETHODIMP Observe(nsISupports* aSubject, const char* aTopic, 228 const char16_t* aData) override { 229 MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0); 230 if (mConsumer) { 231 mConsumer->OnShutdown(); 232 } 233 Destroy(); 234 return NS_OK; 235 } 236 237 private: 238 ~MainShutdownWatcher() override { Destroy(); } 239 240 bool mRegistered = false; 241 }; 242 243 NS_IMPL_ISUPPORTS(MainShutdownWatcher, nsIObserver); 244 245 class WorkerShutdownWatcher final : public ShutdownWatcher { 246 public: 247 NS_DECL_ISUPPORTS 248 249 explicit WorkerShutdownWatcher(ShutdownConsumer* aConsumer) 250 : ShutdownWatcher(aConsumer) {} 251 252 bool Initialize(dom::WorkerPrivate* aWorkerPrivate) { 253 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 254 mConsumer = nullptr; 255 return false; 256 } 257 258 mWorkerRef = dom::WeakWorkerRef::Create( 259 aWorkerPrivate, [self = RefPtr{this}] { self->OnShutdown(); }); 260 if (NS_WARN_IF(!mWorkerRef)) { 261 mConsumer = nullptr; 262 return false; 263 } 264 265 return true; 266 } 267 268 void OnShutdown() { 269 if (mConsumer) { 270 mConsumer->OnShutdown(); 271 } 272 Destroy(); 273 } 274 275 void Destroy() override { 276 mWorkerRef = nullptr; 277 mConsumer = nullptr; 278 } 279 280 private: 281 ~WorkerShutdownWatcher() override { Destroy(); } 282 283 RefPtr<dom::WeakWorkerRef> mWorkerRef; 284 }; 285 286 NS_IMPL_ISUPPORTS0(WorkerShutdownWatcher); 287 288 /* static */ 289 already_AddRefed<ShutdownWatcher> ShutdownWatcher::Create( 290 ShutdownConsumer* aConsumer) { 291 if (NS_IsMainThread()) { 292 auto watcher = MakeRefPtr<MainShutdownWatcher>(aConsumer); 293 if (watcher->Initialize()) { 294 return watcher.forget().downcast<ShutdownWatcher>(); 295 } 296 } else if (dom::WorkerPrivate* workerPrivate = 297 dom::GetCurrentThreadWorkerPrivate()) { 298 auto watcher = MakeRefPtr<WorkerShutdownWatcher>(aConsumer); 299 if (watcher->Initialize(workerPrivate)) { 300 return watcher.forget().downcast<ShutdownWatcher>(); 301 } 302 } 303 304 return nullptr; 305 } 306 307 } // namespace mozilla::media