GMPService.cpp (17937B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "GMPService.h" 7 8 #include "ChromiumCDMParent.h" 9 #include "GMPLog.h" 10 #include "GMPParent.h" 11 #include "GMPProcessParent.h" 12 #include "GMPServiceChild.h" 13 #include "GMPServiceParent.h" 14 #include "GMPVideoDecoderParent.h" 15 #include "mozilla/ClearOnShutdown.h" 16 #include "mozilla/EventDispatcher.h" 17 #include "mozilla/dom/Document.h" 18 #include "mozilla/dom/PluginCrashedEvent.h" 19 #include "mozilla/ipc/GeckoChildProcessHost.h" 20 #include "nsThreadUtils.h" 21 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) 22 # include "mozilla/SandboxInfo.h" 23 #endif 24 #include "VideoUtils.h" 25 #include "mozilla/Services.h" 26 #include "mozilla/SyncRunnable.h" 27 #include "nsAppDirectoryServiceDefs.h" 28 #include "nsComponentManagerUtils.h" 29 #include "nsDirectoryServiceDefs.h" 30 #include "nsDirectoryServiceUtils.h" 31 #include "nsGlobalWindowInner.h" 32 #include "nsHashKeys.h" 33 #include "nsIObserverService.h" 34 #include "nsIXULAppInfo.h" 35 #include "nsNativeCharsetUtils.h" 36 #include "nsXPCOMPrivate.h" 37 #include "prio.h" 38 #include "runnable_utils.h" 39 40 namespace mozilla { 41 42 LogModule* GetGMPLog() { 43 static LazyLogModule sLog("GMP"); 44 return sLog; 45 } 46 47 LogModule* GetGMPLibraryLog() { 48 static LazyLogModule sLog("GMPLibrary"); 49 return sLog; 50 } 51 52 GMPLogLevel GetGMPLibraryLogLevel() { 53 switch (GetGMPLibraryLog()->Level()) { 54 case LogLevel::Disabled: 55 return kGMPLogQuiet; 56 case LogLevel::Error: 57 return kGMPLogError; 58 case LogLevel::Warning: 59 return kGMPLogWarning; 60 case LogLevel::Info: 61 return kGMPLogInfo; 62 case LogLevel::Debug: 63 return kGMPLogDebug; 64 case LogLevel::Verbose: 65 return kGMPLogDetail; 66 } 67 return kGMPLogInvalid; 68 } 69 70 #ifdef __CLASS__ 71 # undef __CLASS__ 72 #endif 73 #define __CLASS__ "GMPService" 74 75 namespace gmp { 76 77 static StaticRefPtr<GeckoMediaPluginService> sSingletonService; 78 79 class GMPServiceCreateHelper final : public mozilla::Runnable { 80 RefPtr<GeckoMediaPluginService> mService; 81 82 public: 83 static already_AddRefed<GeckoMediaPluginService> GetOrCreate() { 84 RefPtr<GeckoMediaPluginService> service; 85 86 if (NS_IsMainThread()) { 87 service = GetOrCreateOnMainThread(); 88 } else { 89 RefPtr<GMPServiceCreateHelper> createHelper = 90 new GMPServiceCreateHelper(); 91 92 mozilla::SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), 93 createHelper, true); 94 95 service = std::move(createHelper->mService); 96 } 97 98 return service.forget(); 99 } 100 101 private: 102 GMPServiceCreateHelper() : Runnable("GMPServiceCreateHelper") {} 103 104 ~GMPServiceCreateHelper() { MOZ_ASSERT(!mService); } 105 106 static already_AddRefed<GeckoMediaPluginService> GetOrCreateOnMainThread() { 107 MOZ_ASSERT(NS_IsMainThread()); 108 109 if (!sSingletonService) { 110 if (XRE_IsParentProcess()) { 111 RefPtr<GeckoMediaPluginServiceParent> service = 112 new GeckoMediaPluginServiceParent(); 113 if (NS_WARN_IF(NS_FAILED(service->Init()))) { 114 return nullptr; 115 } 116 sSingletonService = service; 117 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX) 118 // GMPProcessParent should only be instantiated in the parent 119 // so initialization only needs to be done in the parent. 120 GMPProcessParent::InitStaticMainThread(); 121 #endif 122 } else { 123 RefPtr<GeckoMediaPluginServiceChild> service = 124 new GeckoMediaPluginServiceChild(); 125 if (NS_WARN_IF(NS_FAILED(service->Init()))) { 126 return nullptr; 127 } 128 sSingletonService = service; 129 } 130 ClearOnShutdown(&sSingletonService); 131 } 132 133 RefPtr<GeckoMediaPluginService> service = sSingletonService.get(); 134 return service.forget(); 135 } 136 137 NS_IMETHOD 138 Run() override { 139 MOZ_ASSERT(NS_IsMainThread()); 140 141 mService = GetOrCreateOnMainThread(); 142 return NS_OK; 143 } 144 }; 145 146 already_AddRefed<GeckoMediaPluginService> 147 GeckoMediaPluginService::GetGeckoMediaPluginService() { 148 return GMPServiceCreateHelper::GetOrCreate(); 149 } 150 151 NS_IMPL_ISUPPORTS(GeckoMediaPluginService, mozIGeckoMediaPluginService, 152 nsIObserver) 153 154 GeckoMediaPluginService::GeckoMediaPluginService() 155 : mMutex("GeckoMediaPluginService::mMutex"), 156 mMainThread(GetMainThreadSerialEventTarget()), 157 mGMPThreadShutdown(false), 158 mShuttingDownOnGMPThread(false), 159 mXPCOMWillShutdown(false) { 160 MOZ_ASSERT(NS_IsMainThread()); 161 162 nsCOMPtr<nsIXULAppInfo> appInfo = 163 do_GetService("@mozilla.org/xre/app-info;1"); 164 if (appInfo) { 165 nsAutoCString version; 166 nsAutoCString buildID; 167 if (NS_SUCCEEDED(appInfo->GetVersion(version)) && 168 NS_SUCCEEDED(appInfo->GetAppBuildID(buildID))) { 169 GMP_LOG_DEBUG( 170 "GeckoMediaPluginService created; Gecko version=%s buildID=%s", 171 version.get(), buildID.get()); 172 } 173 } 174 } 175 176 GeckoMediaPluginService::~GeckoMediaPluginService() = default; 177 178 NS_IMETHODIMP 179 GeckoMediaPluginService::RunPluginCrashCallbacks( 180 uint32_t aPluginId, const nsACString& aPluginName) { 181 MOZ_ASSERT(NS_IsMainThread()); 182 GMP_LOG_DEBUG("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId); 183 184 mozilla::UniquePtr<nsTArray<RefPtr<GMPCrashHelper>>> helpers; 185 { 186 MutexAutoLock lock(mMutex); 187 mPluginCrashHelpers.Remove(aPluginId, &helpers); 188 } 189 if (!helpers) { 190 GMP_LOG_DEBUG("%s::%s(%i) No crash helpers, not handling crash.", __CLASS__, 191 __FUNCTION__, aPluginId); 192 return NS_OK; 193 } 194 195 for (const auto& helper : *helpers) { 196 nsCOMPtr<nsPIDOMWindowInner> window = helper->GetPluginCrashedEventTarget(); 197 if (NS_WARN_IF(!window)) { 198 continue; 199 } 200 RefPtr<dom::Document> document(window->GetExtantDoc()); 201 if (NS_WARN_IF(!document)) { 202 continue; 203 } 204 205 dom::PluginCrashedEventInit init; 206 init.mPluginID = aPluginId; 207 init.mBubbles = true; 208 init.mCancelable = true; 209 init.mGmpPlugin = true; 210 CopyUTF8toUTF16(aPluginName, init.mPluginName); 211 init.mSubmittedCrashReport = false; 212 RefPtr<dom::PluginCrashedEvent> event = 213 dom::PluginCrashedEvent::Constructor(document, u"PluginCrashed"_ns, 214 init); 215 event->SetTrusted(true); 216 event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; 217 218 // MOZ_KnownLive due to bug 1506441 219 EventDispatcher::DispatchDOMEvent( 220 MOZ_KnownLive(nsGlobalWindowInner::Cast(window)), nullptr, event, 221 nullptr, nullptr); 222 } 223 224 return NS_OK; 225 } 226 227 nsresult GeckoMediaPluginService::Init() { 228 MOZ_ASSERT(NS_IsMainThread()); 229 230 nsCOMPtr<nsIObserverService> obsService = 231 mozilla::services::GetObserverService(); 232 MOZ_ASSERT(obsService); 233 MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver( 234 this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false)); 235 236 // Kick off scanning for plugins 237 nsCOMPtr<nsIThread> thread; 238 return GetThread(getter_AddRefs(thread)); 239 } 240 241 RefPtr<GetCDMParentPromise> GeckoMediaPluginService::GetCDM( 242 const NodeIdParts& aNodeIdParts, const nsACString& aKeySystem, 243 GMPCrashHelper* aHelper) { 244 AssertOnGMPThread(); 245 246 if (mShuttingDownOnGMPThread || aKeySystem.IsEmpty()) { 247 nsPrintfCString reason( 248 "%s::%s failed, aKeySystem.IsEmpty() = %d, mShuttingDownOnGMPThread = " 249 "%d.", 250 __CLASS__, __FUNCTION__, aKeySystem.IsEmpty(), 251 mShuttingDownOnGMPThread); 252 return GetCDMParentPromise::CreateAndReject( 253 MediaResult(NS_ERROR_FAILURE, reason.get()), __func__); 254 } 255 256 typedef MozPromiseHolder<GetCDMParentPromise> PromiseHolder; 257 PromiseHolder* rawHolder(new PromiseHolder()); 258 RefPtr<GetCDMParentPromise> promise = rawHolder->Ensure(__func__); 259 nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); 260 RefPtr<GMPCrashHelper> helper(aHelper); 261 nsTArray<nsCString> tags{nsCString{aKeySystem}}; 262 GetContentParent(aHelper, NodeIdVariant{aNodeIdParts}, 263 nsLiteralCString(CHROMIUM_CDM_API), tags) 264 ->Then( 265 thread, __func__, 266 [rawHolder, helper, keySystem = nsCString{aKeySystem}]( 267 const RefPtr<GMPContentParentCloseBlocker>& wrapper) { 268 RefPtr<GMPContentParent> parent = wrapper->mParent; 269 MOZ_ASSERT( 270 parent, 271 "Wrapper should wrap a valid parent if we're in this path."); 272 UniquePtr<PromiseHolder> holder(rawHolder); 273 RefPtr<ChromiumCDMParent> cdm = parent->GetChromiumCDM(keySystem); 274 if (!cdm) { 275 nsPrintfCString reason( 276 "%s::%s failed since GetChromiumCDM returns nullptr.", 277 __CLASS__, __FUNCTION__); 278 holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), 279 __func__); 280 return; 281 } 282 if (helper) { 283 cdm->SetCrashHelper(helper); 284 } 285 holder->Resolve(cdm, __func__); 286 }, 287 [rawHolder](MediaResult result) { 288 nsPrintfCString reason( 289 "%s::%s failed since GetContentParent rejects the promise with " 290 "reason %s.", 291 __CLASS__, __FUNCTION__, result.Description().get()); 292 UniquePtr<PromiseHolder> holder(rawHolder); 293 holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), 294 __func__); 295 }); 296 297 return promise; 298 } 299 300 #if defined(MOZ_SANDBOX) && defined(MOZ_DEBUG) && defined(ENABLE_TESTS) 301 RefPtr<GetGMPContentParentPromise> 302 GeckoMediaPluginService::GetContentParentForTest() { 303 AssertOnGMPThread(); 304 305 nsTArray<nsCString> tags; 306 tags.AppendElement("fake"_ns); 307 308 const nsString origin1 = u"http://example1.com"_ns; 309 const nsString origin2 = u"http://example2.org"_ns; 310 const nsString gmpName = u"gmp-fake"_ns; 311 312 NodeIdParts nodeIdParts = NodeIdParts{origin1, origin2, gmpName}; 313 314 if (mShuttingDownOnGMPThread) { 315 nsPrintfCString reason("%s::%s failed, mShuttingDownOnGMPThread = %d.", 316 __CLASS__, __FUNCTION__, mShuttingDownOnGMPThread); 317 return GetGMPContentParentPromise::CreateAndReject( 318 MediaResult(NS_ERROR_FAILURE, reason.get()), __func__); 319 } 320 321 using PromiseHolder = MozPromiseHolder<GetGMPContentParentPromise>; 322 PromiseHolder* rawHolder(new PromiseHolder()); 323 RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__); 324 nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); 325 GetContentParent(nullptr, NodeIdVariant{nodeIdParts}, 326 nsLiteralCString(CHROMIUM_CDM_API), tags) 327 ->Then( 328 thread, __func__, 329 [rawHolder](const RefPtr<GMPContentParentCloseBlocker>& wrapper) { 330 RefPtr<GMPContentParent> parent = wrapper->mParent; 331 MOZ_ASSERT( 332 parent, 333 "Wrapper should wrap a valid parent if we're in this path."); 334 UniquePtr<PromiseHolder> holder(rawHolder); 335 if (!parent) { 336 nsPrintfCString reason("%s::%s failed since no GMPContentParent.", 337 __CLASS__, __FUNCTION__); 338 holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), 339 __func__); 340 return; 341 } 342 holder->Resolve(wrapper, __func__); 343 }, 344 [rawHolder](const MediaResult& result) { 345 nsPrintfCString reason( 346 "%s::%s failed since GetContentParent rejects the promise with " 347 "reason %s.", 348 __CLASS__, __FUNCTION__, result.Description().get()); 349 UniquePtr<PromiseHolder> holder(rawHolder); 350 holder->Reject(MediaResult(NS_ERROR_FAILURE, reason.get()), 351 __func__); 352 }); 353 354 return promise; 355 } 356 #endif 357 358 void GeckoMediaPluginService::ShutdownGMPThread() { 359 GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__); 360 nsCOMPtr<nsIThread> gmpThread; 361 { 362 MutexAutoLock lock(mMutex); 363 mGMPThreadShutdown = true; 364 mGMPThread.swap(gmpThread); 365 } 366 367 if (gmpThread) { 368 gmpThread->Shutdown(); 369 } 370 } 371 372 nsresult GeckoMediaPluginService::GMPDispatch( 373 nsIRunnable* event, nsIEventTarget::DispatchFlags flags) { 374 return GMPDispatch(do_AddRef(event), flags); 375 } 376 377 nsresult GeckoMediaPluginService::GMPDispatch( 378 already_AddRefed<nsIRunnable> event, nsIEventTarget::DispatchFlags flags) { 379 // NOTE: This method always releases `event` on failure, rather than leaking 380 // it, even if `NS_DISPATCH_FALLIBLE` is not specified. 381 nsCOMPtr<nsIRunnable> r(event); 382 nsCOMPtr<nsIThread> thread; 383 nsresult rv = GetThread(getter_AddRefs(thread)); 384 if (NS_WARN_IF(NS_FAILED(rv))) { 385 return rv; 386 } 387 return thread->Dispatch(r.forget(), flags | NS_DISPATCH_FALLIBLE); 388 } 389 390 // always call with getter_AddRefs, because it does 391 NS_IMETHODIMP 392 GeckoMediaPluginService::GetThread(nsIThread** aThread) { 393 MOZ_ASSERT(aThread); 394 395 // This can be called from any thread. 396 MutexAutoLock lock(mMutex); 397 398 return GetThreadLocked(aThread); 399 } 400 401 // always call with getter_AddRefs, because it does 402 nsresult GeckoMediaPluginService::GetThreadLocked(nsIThread** aThread) { 403 MOZ_ASSERT(aThread); 404 405 mMutex.AssertCurrentThreadOwns(); 406 407 if (!mGMPThread) { 408 // Don't allow the thread to be created after shutdown has started. 409 if (mGMPThreadShutdown) { 410 return NS_ERROR_FAILURE; 411 } 412 413 nsresult rv = NS_NewNamedThread("GMPThread", getter_AddRefs(mGMPThread)); 414 if (NS_WARN_IF(NS_FAILED(rv))) { 415 return rv; 416 } 417 418 // Tell the thread to initialize plugins 419 InitializePlugins(mGMPThread); 420 } 421 422 nsCOMPtr<nsIThread> copy = mGMPThread; 423 copy.forget(aThread); 424 425 return NS_OK; 426 } 427 428 already_AddRefed<nsISerialEventTarget> GeckoMediaPluginService::GetGMPThread() { 429 nsCOMPtr<nsISerialEventTarget> thread; 430 { 431 MutexAutoLock lock(mMutex); 432 thread = mGMPThread; 433 } 434 return thread.forget(); 435 } 436 437 NS_IMETHODIMP 438 GeckoMediaPluginService::GetGMPVideoDecoder( 439 GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags, 440 const nsACString& aNodeId, 441 UniquePtr<GetGMPVideoDecoderCallback>&& aCallback) { 442 AssertOnGMPThread(); 443 NS_ENSURE_ARG(aTags && aTags->Length() > 0); 444 NS_ENSURE_ARG(aCallback); 445 446 if (mShuttingDownOnGMPThread) { 447 return NS_ERROR_FAILURE; 448 } 449 450 GetGMPVideoDecoderCallback* rawCallback = aCallback.release(); 451 nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); 452 RefPtr<GMPCrashHelper> helper(aHelper); 453 GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)}, 454 nsLiteralCString(GMP_API_VIDEO_DECODER), *aTags) 455 ->Then( 456 thread, __func__, 457 [rawCallback, 458 helper](const RefPtr<GMPContentParentCloseBlocker>& wrapper) { 459 RefPtr<GMPContentParent> parent = wrapper->mParent; 460 UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback); 461 GMPVideoDecoderParent* actor = nullptr; 462 GMPVideoHostImpl* host = nullptr; 463 if (parent && NS_SUCCEEDED(parent->GetGMPVideoDecoder(&actor))) { 464 host = &(actor->Host()); 465 actor->SetCrashHelper(helper); 466 } 467 callback->Done(actor, host); 468 }, 469 [rawCallback] { 470 UniquePtr<GetGMPVideoDecoderCallback> callback(rawCallback); 471 callback->Done(nullptr, nullptr); 472 }); 473 474 return NS_OK; 475 } 476 477 NS_IMETHODIMP 478 GeckoMediaPluginService::GetGMPVideoEncoder( 479 GMPCrashHelper* aHelper, nsTArray<nsCString>* aTags, 480 const nsACString& aNodeId, 481 UniquePtr<GetGMPVideoEncoderCallback>&& aCallback) { 482 AssertOnGMPThread(); 483 NS_ENSURE_ARG(aTags && aTags->Length() > 0); 484 NS_ENSURE_ARG(aCallback); 485 486 if (mShuttingDownOnGMPThread) { 487 return NS_ERROR_FAILURE; 488 } 489 490 GetGMPVideoEncoderCallback* rawCallback = aCallback.release(); 491 nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); 492 RefPtr<GMPCrashHelper> helper(aHelper); 493 GetContentParent(aHelper, NodeIdVariant{nsCString(aNodeId)}, 494 nsLiteralCString(GMP_API_VIDEO_ENCODER), *aTags) 495 ->Then( 496 thread, __func__, 497 [rawCallback, 498 helper](const RefPtr<GMPContentParentCloseBlocker>& wrapper) { 499 RefPtr<GMPContentParent> parent = wrapper->mParent; 500 UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback); 501 GMPVideoEncoderParent* actor = nullptr; 502 GMPVideoHostImpl* host = nullptr; 503 if (parent && NS_SUCCEEDED(parent->GetGMPVideoEncoder(&actor))) { 504 host = &(actor->Host()); 505 actor->SetCrashHelper(helper); 506 } 507 callback->Done(actor, host); 508 }, 509 [rawCallback] { 510 UniquePtr<GetGMPVideoEncoderCallback> callback(rawCallback); 511 callback->Done(nullptr, nullptr); 512 }); 513 514 return NS_OK; 515 } 516 517 void GeckoMediaPluginService::ConnectCrashHelper(uint32_t aPluginId, 518 GMPCrashHelper* aHelper) { 519 if (!aHelper) { 520 return; 521 } 522 523 MutexAutoLock lock(mMutex); 524 mPluginCrashHelpers.WithEntryHandle(aPluginId, [&](auto&& entry) { 525 if (!entry) { 526 entry.Insert(MakeUnique<nsTArray<RefPtr<GMPCrashHelper>>>()); 527 } else if (entry.Data()->Contains(aHelper)) { 528 return; 529 } 530 entry.Data()->AppendElement(aHelper); 531 }); 532 } 533 534 void GeckoMediaPluginService::DisconnectCrashHelper(GMPCrashHelper* aHelper) { 535 if (!aHelper) { 536 return; 537 } 538 MutexAutoLock lock(mMutex); 539 for (auto iter = mPluginCrashHelpers.Iter(); !iter.Done(); iter.Next()) { 540 nsTArray<RefPtr<GMPCrashHelper>>* helpers = iter.UserData(); 541 if (!helpers->Contains(aHelper)) { 542 continue; 543 } 544 helpers->RemoveElement(aHelper); 545 MOZ_ASSERT(!helpers->Contains(aHelper)); // Ensure there aren't duplicates. 546 if (helpers->IsEmpty()) { 547 iter.Remove(); 548 } 549 } 550 } 551 552 } // namespace gmp 553 } // namespace mozilla 554 555 #undef __CLASS__