GMPServiceParent.cpp (67959B)
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 "GMPServiceParent.h" 7 8 #include "GMPDecoderModule.h" 9 #include "GMPLog.h" 10 #include "GMPParent.h" 11 #include "GMPVideoDecoderParent.h" 12 #include "base/task.h" 13 #include "mozilla/ClearOnShutdown.h" 14 #include "mozilla/Logging.h" 15 #include "mozilla/Preferences.h" 16 #include "mozilla/SchedulerGroup.h" 17 #include "mozilla/dom/ContentParent.h" 18 #include "mozilla/ipc/Endpoint.h" 19 #include "mozilla/ipc/GeckoChildProcessHost.h" 20 #include "nsFmtString.h" 21 #include "nsThreadUtils.h" 22 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) 23 # include "mozilla/SandboxInfo.h" 24 #endif 25 #include "VideoUtils.h" 26 #include "mozilla/AppShutdown.h" 27 #include "mozilla/Services.h" 28 #include "mozilla/SpinEventLoopUntil.h" 29 #include "mozilla/StaticPrefs_media.h" 30 #include "mozilla/SyncRunnable.h" 31 #if defined(XP_WIN) 32 # include "mozilla/UntrustedModulesData.h" 33 #endif 34 #include "nsAppDirectoryServiceDefs.h" 35 #include "nsComponentManagerUtils.h" 36 #include "nsDirectoryServiceDefs.h" 37 #include "nsDirectoryServiceUtils.h" 38 #include "nsHashKeys.h" 39 #include "nsIFile.h" 40 #include "nsIObserverService.h" 41 #include "nsIXULRuntime.h" 42 #include "nsNativeCharsetUtils.h" 43 #include "nsNetUtil.h" 44 #include "nsXPCOMPrivate.h" 45 #include "prio.h" 46 #include "runnable_utils.h" 47 48 #ifdef DEBUG 49 # include "mozilla/dom/MediaKeys.h" // MediaKeys::kMediaKeysRequestTopic 50 #endif 51 52 #ifdef MOZ_WMF_CDM 53 # include "mozilla/MFCDMParent.h" 54 #endif 55 56 namespace mozilla::gmp { 57 58 #ifdef __CLASS__ 59 # undef __CLASS__ 60 #endif 61 #define __CLASS__ "GMPServiceParent" 62 63 static const uint32_t NodeIdSaltLength = 32; 64 65 already_AddRefed<GeckoMediaPluginServiceParent> 66 GeckoMediaPluginServiceParent::GetSingleton() { 67 MOZ_ASSERT(XRE_IsParentProcess()); 68 RefPtr<GeckoMediaPluginService> service( 69 GeckoMediaPluginServiceParent::GetGeckoMediaPluginService()); 70 #ifdef DEBUG 71 if (service) { 72 nsCOMPtr<mozIGeckoMediaPluginChromeService> chromeService; 73 CallQueryInterface(service.get(), getter_AddRefs(chromeService)); 74 MOZ_ASSERT(chromeService); 75 } 76 #endif 77 return service.forget().downcast<GeckoMediaPluginServiceParent>(); 78 } 79 80 NS_IMPL_ISUPPORTS_INHERITED(GeckoMediaPluginServiceParent, 81 GeckoMediaPluginService, 82 mozIGeckoMediaPluginChromeService) 83 84 GeckoMediaPluginServiceParent::GeckoMediaPluginServiceParent() 85 : mScannedPluginOnDisk(false), 86 mShuttingDown(false), 87 mWaitingForPluginsSyncShutdown(false), 88 mInitPromiseMonitor("GeckoMediaPluginServiceParent::mInitPromiseMonitor"), 89 mInitPromise(&mInitPromiseMonitor), 90 mLoadPluginsFromDiskComplete(false) { 91 MOZ_ASSERT(NS_IsMainThread()); 92 } 93 94 GeckoMediaPluginServiceParent::~GeckoMediaPluginServiceParent() { 95 MOZ_ASSERT(mPlugins.IsEmpty()); 96 } 97 98 nsresult GeckoMediaPluginServiceParent::Init() { 99 MOZ_ASSERT(NS_IsMainThread()); 100 101 if (AppShutdown::GetCurrentShutdownPhase() != ShutdownPhase::NotInShutdown) { 102 return NS_OK; 103 } 104 105 nsCOMPtr<nsIObserverService> obsService = 106 mozilla::services::GetObserverService(); 107 MOZ_ASSERT(obsService); 108 109 MOZ_ALWAYS_SUCCEEDS( 110 obsService->AddObserver(this, "profile-change-teardown", false)); 111 MOZ_ALWAYS_SUCCEEDS( 112 obsService->AddObserver(this, "last-pb-context-exited", false)); 113 MOZ_ALWAYS_SUCCEEDS( 114 obsService->AddObserver(this, "browser:purge-session-history", false)); 115 MOZ_ALWAYS_SUCCEEDS( 116 obsService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false)); 117 MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, "nsPref:changed", false)); 118 119 #ifdef DEBUG 120 MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver( 121 this, dom::MediaKeys::kMediaKeysRequestTopic, false)); 122 #endif 123 124 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); 125 if (prefs) { 126 prefs->AddObserver("media.gmp.plugin.crash", this, false); 127 } 128 129 nsresult rv = InitStorage(); 130 if (NS_WARN_IF(NS_FAILED(rv))) { 131 return rv; 132 } 133 134 // Kick off scanning for plugins 135 nsCOMPtr<nsIThread> thread; 136 rv = GetThread(getter_AddRefs(thread)); 137 if (NS_WARN_IF(NS_FAILED(rv))) { 138 return rv; 139 } 140 141 // Detect if GMP storage has an incompatible version, and if so nuke it. 142 int32_t version = 143 Preferences::GetInt("media.gmp.storage.version.observed", 0); 144 int32_t expected = 145 Preferences::GetInt("media.gmp.storage.version.expected", 0); 146 if (version != expected) { 147 Preferences::SetInt("media.gmp.storage.version.observed", expected); 148 return GMPDispatch( 149 NewRunnableMethod("gmp::GeckoMediaPluginServiceParent::ClearStorage", 150 this, &GeckoMediaPluginServiceParent::ClearStorage)); 151 } 152 return NS_OK; 153 } 154 155 already_AddRefed<nsIFile> CloneAndAppend(nsIFile* aFile, 156 const nsAString& aDir) { 157 nsCOMPtr<nsIFile> f; 158 nsresult rv = aFile->Clone(getter_AddRefs(f)); 159 if (NS_WARN_IF(NS_FAILED(rv))) { 160 return nullptr; 161 } 162 163 rv = f->Append(aDir); 164 if (NS_WARN_IF(NS_FAILED(rv))) { 165 return nullptr; 166 } 167 return f.forget(); 168 } 169 170 static nsresult GMPPlatformString(nsAString& aOutPlatform) { 171 // Append the OS and arch so that we don't reuse the storage if the profile is 172 // copied or used under a different bit-ness, or copied to another platform. 173 nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1"); 174 if (!runtime) { 175 return NS_ERROR_FAILURE; 176 } 177 178 nsAutoCString OS; 179 nsresult rv = runtime->GetOS(OS); 180 if (NS_WARN_IF(NS_FAILED(rv))) { 181 return rv; 182 } 183 184 nsAutoCString arch; 185 rv = runtime->GetXPCOMABI(arch); 186 if (NS_WARN_IF(NS_FAILED(rv))) { 187 return rv; 188 } 189 190 nsCString platform; 191 platform.Append(OS); 192 platform.AppendLiteral("_"); 193 platform.Append(arch); 194 195 CopyUTF8toUTF16(platform, aOutPlatform); 196 197 return NS_OK; 198 } 199 200 nsresult GeckoMediaPluginServiceParent::InitStorage() { 201 MOZ_ASSERT(NS_IsMainThread()); 202 203 // GMP storage should be used in the chrome process only. 204 if (!XRE_IsParentProcess()) { 205 return NS_OK; 206 } 207 208 // Directory service is main thread only, so cache the profile dir here 209 // so that we can use it off main thread. 210 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 211 getter_AddRefs(mStorageBaseDir)); 212 213 if (NS_WARN_IF(NS_FAILED(rv))) { 214 return rv; 215 } 216 217 rv = mStorageBaseDir->AppendNative("gmp"_ns); 218 if (NS_WARN_IF(NS_FAILED(rv))) { 219 return rv; 220 } 221 222 nsAutoString platform; 223 rv = GMPPlatformString(platform); 224 if (NS_WARN_IF(NS_FAILED(rv))) { 225 return rv; 226 } 227 228 rv = mStorageBaseDir->Append(platform); 229 if (NS_WARN_IF(NS_FAILED(rv))) { 230 return rv; 231 } 232 233 return GeckoMediaPluginService::Init(); 234 } 235 236 NS_IMETHODIMP 237 GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject, 238 const char* aTopic, 239 const char16_t* aSomeData) { 240 GMP_LOG_DEBUG("%s::%s topic='%s' data='%s'", __CLASS__, __FUNCTION__, aTopic, 241 NS_ConvertUTF16toUTF8(aSomeData).get()); 242 if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { 243 nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(aSubject)); 244 if (branch) { 245 bool crashNow = false; 246 if (u"media.gmp.plugin.crash"_ns.Equals(aSomeData)) { 247 branch->GetBoolPref("media.gmp.plugin.crash", &crashNow); 248 } 249 if (crashNow) { 250 nsCOMPtr<nsIThread> gmpThread; 251 { 252 MutexAutoLock lock(mMutex); 253 gmpThread = mGMPThread; 254 } 255 if (gmpThread) { 256 // Note: the GeckoMediaPluginServiceParent singleton is kept alive by 257 // a static refptr that is only cleared in the final stage of shutdown 258 // after everything else is shutdown, so this RefPtr<> is not strictly 259 // necessary so long as that is true, but it's safer. 260 gmpThread->Dispatch( 261 WrapRunnable(RefPtr<GeckoMediaPluginServiceParent>(this), 262 &GeckoMediaPluginServiceParent::CrashPlugins), 263 NS_DISPATCH_NORMAL); 264 } 265 } 266 } 267 } else if (!strcmp("profile-change-teardown", aTopic)) { 268 mWaitingForPluginsSyncShutdown = true; 269 270 nsCOMPtr<nsIThread> gmpThread; 271 DebugOnly<bool> plugins_empty; 272 { 273 MutexAutoLock lock(mMutex); 274 MOZ_ASSERT(!mShuttingDown); 275 mShuttingDown = true; 276 gmpThread = mGMPThread; 277 #ifdef DEBUG 278 plugins_empty = mPlugins.IsEmpty(); 279 #endif 280 } 281 282 if (gmpThread) { 283 GMP_LOG_DEBUG( 284 "%s::%s Starting to unload plugins, waiting for sync shutdown...", 285 __CLASS__, __FUNCTION__); 286 gmpThread->Dispatch( 287 NewRunnableMethod("gmp::GeckoMediaPluginServiceParent::UnloadPlugins", 288 this, 289 &GeckoMediaPluginServiceParent::UnloadPlugins), 290 NS_DISPATCH_NORMAL); 291 292 // Wait for UnloadPlugins() to do sync shutdown... 293 SpinEventLoopUntil( 294 "GeckoMediaPluginServiceParent::Observe " 295 "WaitingForPluginsSyncShutdown"_ns, 296 [&]() { return !mWaitingForPluginsSyncShutdown; }); 297 } else { 298 // GMP thread has already shutdown. 299 MOZ_ASSERT(plugins_empty); 300 mWaitingForPluginsSyncShutdown = false; 301 } 302 303 } else if (!strcmp(NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, aTopic)) { 304 #ifdef DEBUG 305 MOZ_ASSERT(mShuttingDown); 306 #endif 307 ShutdownGMPThread(); 308 } else if (!strcmp(NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, aTopic)) { 309 mXPCOMWillShutdown = true; 310 } else if (!strcmp("last-pb-context-exited", aTopic)) { 311 GMP_LOG_DEBUG( 312 "Received 'last-pb-context-exited', clearing temporary node and " 313 "storage"); 314 // When Private Browsing mode exits, we need to clear node Ids and storage. 315 // After dropping all the node ids we've cached in memory for PB 316 // origin-pairs, if we try to open an origin-pair for non-PB mode, we'll get 317 // the NodeId salt stored on-disk, and if we try to open a PB mode 318 // origin-pair, we'll re-generate new salt. 319 mTempNodeIds.Clear(); 320 mTempGMPStorage.Clear(); 321 } else if (!strcmp("browser:purge-session-history", aTopic)) { 322 GMP_LOG_DEBUG( 323 "Received 'browser:purge-session-history', clearing everything"); 324 // Clear everything! 325 if (!aSomeData || nsDependentString(aSomeData).IsEmpty()) { 326 return GMPDispatch(NewRunnableMethod( 327 "gmp::GeckoMediaPluginServiceParent::ClearStorage", this, 328 &GeckoMediaPluginServiceParent::ClearStorage)); 329 } 330 331 // Clear nodeIds/records modified after |t|. 332 nsresult rv; 333 PRTime t = nsDependentString(aSomeData).ToInteger64(&rv, 10); 334 if (NS_WARN_IF(NS_FAILED(rv))) { 335 return rv; 336 } 337 return GMPDispatch(NewRunnableMethod<PRTime>( 338 "gmp::GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread", 339 this, &GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread, 340 t)); 341 } else if (!strcmp("nsPref:changed", aTopic)) { 342 bool hasProcesses = false; 343 { 344 MutexAutoLock lock(mMutex); 345 for (const auto& plugin : mPlugins) { 346 if (plugin->State() == GMPState::Loaded) { 347 hasProcesses = true; 348 break; 349 } 350 } 351 } 352 353 if (hasProcesses) { 354 // We know prefs are ASCII here. 355 NS_LossyConvertUTF16toASCII strData(aSomeData); 356 mozilla::dom::Pref pref(strData, /* isLocked */ false, 357 /* isSanitized */ false, Nothing(), Nothing()); 358 Preferences::GetPreference(&pref, GeckoProcessType_GMPlugin, 359 /* remoteType */ ""_ns); 360 return GMPDispatch(NewRunnableMethod<mozilla::dom::Pref&&>( 361 "gmp::GeckoMediaPluginServiceParent::OnPreferenceChanged", this, 362 &GeckoMediaPluginServiceParent::OnPreferenceChanged, 363 std::move(pref))); 364 } 365 } 366 367 return NS_OK; 368 } 369 370 void GeckoMediaPluginServiceParent::OnPreferenceChanged( 371 mozilla::dom::Pref&& aPref) { 372 AssertOnGMPThread(); 373 374 MutexAutoLock lock(mMutex); 375 for (const auto& plugin : mPlugins) { 376 plugin->OnPreferenceChange(aPref); 377 } 378 } 379 380 RefPtr<GenericPromise> GeckoMediaPluginServiceParent::EnsureInitialized() { 381 MonitorAutoLock lock(mInitPromiseMonitor); 382 if (mLoadPluginsFromDiskComplete) { 383 return GenericPromise::CreateAndResolve(true, __func__); 384 } 385 // We should have an init promise in flight. 386 MOZ_ASSERT(!mInitPromise.IsEmpty()); 387 return mInitPromise.Ensure(__func__); 388 } 389 390 RefPtr<GetGMPContentParentPromise> 391 GeckoMediaPluginServiceParent::GetContentParent( 392 GMPCrashHelper* aHelper, const NodeIdVariant& aNodeIdVariant, 393 const nsACString& aAPI, const nsTArray<nsCString>& aTags) { 394 AssertOnGMPThread(); 395 396 nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); 397 if (!thread) { 398 MOZ_ASSERT_UNREACHABLE( 399 "We should always be called on GMP thread, so it should be live"); 400 return GetGMPContentParentPromise::CreateAndReject(NS_ERROR_FAILURE, 401 __func__); 402 } 403 404 nsCString nodeIdString; 405 nsresult rv = GetNodeId(aNodeIdVariant, nodeIdString); 406 if (NS_WARN_IF(NS_FAILED(rv))) { 407 return GetGMPContentParentPromise::CreateAndReject(NS_ERROR_FAILURE, 408 __func__); 409 } 410 411 auto holder = MakeUnique<MozPromiseHolder<GetGMPContentParentPromise>>(); 412 RefPtr<GetGMPContentParentPromise> promise = holder->Ensure(__func__); 413 EnsureInitialized()->Then( 414 thread, __func__, 415 [self = RefPtr<GeckoMediaPluginServiceParent>(this), 416 nodeIdString = std::move(nodeIdString), api = nsCString(aAPI), 417 tags = aTags.Clone(), helper = RefPtr<GMPCrashHelper>(aHelper), 418 holder = std::move(holder)]( 419 const GenericPromise::ResolveOrRejectValue& aValue) mutable -> void { 420 if (aValue.IsReject()) { 421 NS_WARNING("GMPService::EnsureInitialized failed."); 422 holder->Reject(NS_ERROR_FAILURE, __func__); 423 return; 424 } 425 RefPtr<GMPParent> gmp = 426 self->SelectPluginForAPI(nodeIdString, api, tags); 427 GMP_LOG_DEBUG("%s: %p returning %p for api %s", __FUNCTION__, 428 self.get(), gmp.get(), api.get()); 429 if (!gmp) { 430 NS_WARNING( 431 "GeckoMediaPluginServiceParent::GetContentParentFrom failed"); 432 holder->Reject(NS_ERROR_FAILURE, __func__); 433 return; 434 } 435 self->ConnectCrashHelper(gmp->GetPluginId(), helper); 436 gmp->GetGMPContentParent(std::move(holder)); 437 }); 438 439 return promise; 440 } 441 442 void GeckoMediaPluginServiceParent::InitializePlugins( 443 nsISerialEventTarget* aGMPThread) { 444 MOZ_ASSERT(aGMPThread); 445 MonitorAutoLock lock(mInitPromiseMonitor); 446 if (mLoadPluginsFromDiskComplete) { 447 return; 448 } 449 450 RefPtr<GeckoMediaPluginServiceParent> self(this); 451 RefPtr<GenericPromise> p = mInitPromise.Ensure(__func__); 452 InvokeAsync(aGMPThread, this, __func__, 453 &GeckoMediaPluginServiceParent::LoadFromEnvironment) 454 ->Then( 455 aGMPThread, __func__, 456 [self]() -> void { 457 MonitorAutoLock lock(self->mInitPromiseMonitor); 458 self->mLoadPluginsFromDiskComplete = true; 459 self->mInitPromise.Resolve(true, __func__); 460 }, 461 [self]() -> void { 462 MonitorAutoLock lock(self->mInitPromiseMonitor); 463 self->mLoadPluginsFromDiskComplete = true; 464 self->mInitPromise.Reject(NS_ERROR_FAILURE, __func__); 465 }); 466 } 467 468 void GeckoMediaPluginServiceParent::NotifySyncShutdownComplete() { 469 MOZ_ASSERT(NS_IsMainThread()); 470 mWaitingForPluginsSyncShutdown = false; 471 } 472 473 bool GeckoMediaPluginServiceParent::IsShuttingDown() { 474 AssertOnGMPThread(); 475 return mShuttingDownOnGMPThread; 476 } 477 478 void GeckoMediaPluginServiceParent::UnloadPlugins() { 479 AssertOnGMPThread(); 480 MOZ_ASSERT(!mShuttingDownOnGMPThread); 481 mShuttingDownOnGMPThread = true; 482 483 nsTArray<RefPtr<GMPParent>> plugins; 484 { 485 MutexAutoLock lock(mMutex); 486 // Move all plugins references to a local array. This way mMutex won't be 487 // locked when calling CloseActive (to avoid inter-locking). 488 std::swap(plugins, mPlugins); 489 490 for (GMPServiceParent* parent : mServiceParents) { 491 (void)parent->SendBeginShutdown(); 492 } 493 494 GMP_LOG_DEBUG("%s::%s plugins:%zu", __CLASS__, __FUNCTION__, 495 plugins.Length()); 496 #ifdef DEBUG 497 for (const auto& plugin : plugins) { 498 GMP_LOG_DEBUG("%s::%s plugin: '%s'", __CLASS__, __FUNCTION__, 499 plugin->GetDisplayName().get()); 500 } 501 #endif 502 } 503 504 // Note: CloseActive may be async; it could actually finish 505 // shutting down when all the plugins have unloaded. 506 for (const auto& plugin : plugins) { 507 plugin->CloseActive(true); 508 } 509 510 nsCOMPtr<nsIRunnable> task = NewRunnableMethod( 511 "GeckoMediaPluginServiceParent::NotifySyncShutdownComplete", this, 512 &GeckoMediaPluginServiceParent::NotifySyncShutdownComplete); 513 mMainThread->Dispatch(task.forget()); 514 } 515 516 void GeckoMediaPluginServiceParent::CrashPlugins() { 517 GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__); 518 AssertOnGMPThread(); 519 520 MutexAutoLock lock(mMutex); 521 for (size_t i = 0; i < mPlugins.Length(); i++) { 522 mPlugins[i]->Crash(); 523 } 524 } 525 526 RefPtr<GenericPromise> GeckoMediaPluginServiceParent::LoadFromEnvironment() { 527 AssertOnGMPThread(); 528 nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); 529 if (!thread) { 530 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 531 } 532 533 #ifdef MOZ_WIDGET_ANDROID 534 if (RefPtr<GMPParent> clearkeyGmp = CreateGMPParent()) { 535 clearkeyGmp->InitForClearkey(this); 536 537 { 538 MutexAutoLock lock(mMutex); 539 mPlugins.AppendElement(std::move(clearkeyGmp)); 540 } 541 542 UpdateContentProcessGMPCapabilities(); 543 } 544 #endif 545 546 const char* env = PR_GetEnv("MOZ_GMP_PATH"); 547 if (!env || !*env) { 548 return GenericPromise::CreateAndResolve(true, __func__); 549 } 550 551 nsString allpaths; 552 if (NS_WARN_IF(NS_FAILED( 553 NS_CopyNativeToUnicode(nsDependentCString(env), allpaths)))) { 554 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 555 } 556 557 nsTArray<RefPtr<GenericPromise>> promises; 558 uint32_t pos = 0; 559 while (pos < allpaths.Length()) { 560 // Loop over multiple path entries separated by colons (*nix) or 561 // semicolons (Windows) 562 int32_t next = allpaths.FindChar(XPCOM_ENV_PATH_SEPARATOR[0], pos); 563 if (next == -1) { 564 promises.AppendElement( 565 AddOnGMPThread(nsString(Substring(allpaths, pos)))); 566 break; 567 } else { 568 promises.AppendElement( 569 AddOnGMPThread(nsString(Substring(allpaths, pos, next - pos)))); 570 pos = next + 1; 571 } 572 } 573 574 mScannedPluginOnDisk = true; 575 return GenericPromise::All(thread, promises) 576 ->Then( 577 thread, __func__, 578 []() { return GenericPromise::CreateAndResolve(true, __func__); }, 579 []() { 580 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 581 }); 582 } 583 584 class NotifyObserversTask final : public mozilla::Runnable { 585 public: 586 explicit NotifyObserversTask(const char* aTopic, nsString aData = u""_ns) 587 : Runnable(aTopic), mTopic(aTopic), mData(aData) {} 588 NS_IMETHOD Run() override { 589 MOZ_ASSERT(NS_IsMainThread()); 590 nsCOMPtr<nsIObserverService> obsService = 591 mozilla::services::GetObserverService(); 592 MOZ_ASSERT(obsService); 593 if (obsService) { 594 obsService->NotifyObservers(nullptr, mTopic, mData.get()); 595 } 596 return NS_OK; 597 } 598 599 private: 600 ~NotifyObserversTask() = default; 601 const char* mTopic; 602 const nsString mData; 603 }; 604 605 NS_IMETHODIMP 606 GeckoMediaPluginServiceParent::PathRunnable::Run() { 607 mService->RemoveOnGMPThread(mPath, mOperation == REMOVE_AND_DELETE_FROM_DISK, 608 mDefer); 609 610 mService->UpdateContentProcessGMPCapabilities(); 611 return NS_OK; 612 } 613 614 void GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities( 615 ContentParent* aContentProcess) { 616 if (!NS_IsMainThread()) { 617 nsCOMPtr<nsIRunnable> task = NewRunnableMethod<ContentParent*>( 618 "GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities", 619 this, 620 &GeckoMediaPluginServiceParent::UpdateContentProcessGMPCapabilities, 621 aContentProcess); 622 mMainThread->Dispatch(task.forget()); 623 return; 624 } 625 626 typedef mozilla::dom::GMPCapabilityData GMPCapabilityData; 627 typedef mozilla::dom::GMPAPITags GMPAPITags; 628 typedef mozilla::dom::ContentParent ContentParent; 629 630 nsTArray<GMPCapabilityData> caps; 631 { 632 MutexAutoLock lock(mMutex); 633 for (const RefPtr<GMPParent>& gmp : mPlugins) { 634 // We have multiple instances of a GMPParent for a given GMP in the 635 // list, one per origin. So filter the list so that we don't include 636 // the same GMP's capabilities twice. 637 NS_ConvertUTF16toUTF8 name(gmp->GetPluginBaseName()); 638 bool found = false; 639 for (const GMPCapabilityData& cap : caps) { 640 if (cap.name().Equals(name)) { 641 found = true; 642 break; 643 } 644 } 645 if (found) { 646 continue; 647 } 648 GMPCapabilityData x; 649 x.name() = name; 650 x.version() = gmp->GetVersion(); 651 for (const GMPCapability& tag : gmp->GetCapabilities()) { 652 x.capabilities().AppendElement(GMPAPITags(tag.mAPIName, tag.mAPITags)); 653 } 654 #ifdef MOZ_WMF_CDM 655 if (name.Equals("gmp-widevinecdm-l1")) { 656 if (nsCOMPtr<nsIFile> pluginFile = gmp->GetDirectory()) { 657 MFCDMService::UpdateWidevineL1Path(pluginFile); 658 } else { 659 MOZ_ASSERT_UNREACHABLE("Missing directory for Widevine L1 plugin!"); 660 } 661 } 662 #endif 663 caps.AppendElement(std::move(x)); 664 } 665 } 666 667 if (aContentProcess) { 668 (void)aContentProcess->SendGMPsChanged(caps); 669 return; 670 } 671 672 for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) { 673 (void)cp->SendGMPsChanged(caps); 674 } 675 676 // For non-e10s, we must fire a notification so that any MediaKeySystemAccess 677 // requests waiting on a CDM to download will retry. 678 nsCOMPtr<nsIObserverService> obsService = 679 mozilla::services::GetObserverService(); 680 MOZ_ASSERT(obsService); 681 if (obsService) { 682 obsService->NotifyObservers(nullptr, "gmp-changed", nullptr); 683 } 684 } 685 686 void GeckoMediaPluginServiceParent::SendFlushFOGData( 687 nsTArray<RefPtr<FlushFOGDataPromise>>& promises) { 688 MOZ_ASSERT(NS_IsMainThread()); 689 MutexAutoLock lock(mMutex); 690 691 for (const RefPtr<GMPParent>& gmp : mPlugins) { 692 if (gmp->State() != GMPState::Loaded) { 693 // Plugins that are not in the Loaded state have no process attached to 694 // them, and any IPC we would attempt to send them would be ignored (or 695 // result in a warning on debug builds). 696 continue; 697 } 698 RefPtr<FlushFOGDataPromise::Private> promise = 699 new FlushFOGDataPromise::Private(__func__); 700 // Direct dispatch will resolve the promise on the same thread, which is 701 // faster; FOGIPC will move execution back to the main thread. 702 promise->UseDirectTaskDispatch(__func__); 703 promises.EmplaceBack(promise); 704 705 mGMPThread->Dispatch( 706 NewRunnableMethod<ipc::ResolveCallback<ipc::ByteBuf>&&, 707 ipc::RejectCallback&&>( 708 "GMPParent::SendFlushFOGData", gmp, 709 static_cast<void (GMPParent::*)( 710 mozilla::ipc::ResolveCallback<ipc::ByteBuf>&& aResolve, 711 mozilla::ipc::RejectCallback&& aReject)>( 712 &GMPParent::SendFlushFOGData), 713 714 [promise](ipc::ByteBuf&& aValue) { 715 promise->Resolve(std::move(aValue), __func__); 716 }, 717 [promise](ipc::ResponseRejectReason&& aReason) { 718 promise->Reject(std::move(aReason), __func__); 719 }), 720 NS_DISPATCH_NORMAL); 721 } 722 } 723 724 #if defined(XP_WIN) 725 void GeckoMediaPluginServiceParent::SendGetUntrustedModulesData( 726 nsTArray<RefPtr<GetUntrustedModulesDataPromise>>& promises) { 727 MOZ_ASSERT(NS_IsMainThread()); 728 MutexAutoLock lock(mMutex); 729 730 for (const RefPtr<GMPParent>& gmp : mPlugins) { 731 if (gmp->State() != GMPState::Loaded) { 732 // Plugins that are not in the Loaded state have no process attached to 733 // them, and any IPC we would attempt to send them would be ignored (or 734 // result in a warning on debug builds). 735 continue; 736 } 737 RefPtr<GetUntrustedModulesDataPromise::Private> promise = 738 new GetUntrustedModulesDataPromise::Private(__func__); 739 // Direct dispatch will resolve the promise on the same thread, which is 740 // faster; IPC will move execution back to the main thread. 741 promise->UseDirectTaskDispatch(__func__); 742 promises.EmplaceBack(promise); 743 744 mGMPThread->Dispatch( 745 NewRunnableMethod<ipc::ResolveCallback<Maybe<UntrustedModulesData>>&&, 746 ipc::RejectCallback&&>( 747 "GMPParent::SendGetUntrustedModulesData", gmp, 748 static_cast<void (GMPParent::*)( 749 mozilla::ipc::ResolveCallback<Maybe<UntrustedModulesData>>&& 750 aResolve, 751 mozilla::ipc::RejectCallback&& aReject)>( 752 &GMPParent::SendGetUntrustedModulesData), 753 754 [promise](Maybe<UntrustedModulesData>&& aValue) { 755 promise->Resolve(std::move(aValue), __func__); 756 }, 757 [promise](ipc::ResponseRejectReason&& aReason) { 758 promise->Reject(std::move(aReason), __func__); 759 }), 760 NS_DISPATCH_NORMAL); 761 } 762 } 763 764 void GeckoMediaPluginServiceParent::SendUnblockUntrustedModulesThread() { 765 MOZ_ASSERT(NS_IsMainThread()); 766 MutexAutoLock lock(mMutex); 767 768 for (const RefPtr<GMPParent>& gmp : mPlugins) { 769 if (gmp->State() != GMPState::Loaded) { 770 // Plugins that are not in the Loaded state have no process attached to 771 // them, and any IPC we would attempt to send them would be ignored (or 772 // result in a warning on debug builds). 773 continue; 774 } 775 776 mGMPThread->Dispatch( 777 NewRunnableMethod<>("GMPParent::SendUnblockUntrustedModulesThread", gmp, 778 static_cast<bool (GMPParent::*)()>( 779 &GMPParent::SendUnblockUntrustedModulesThread)), 780 NS_DISPATCH_NORMAL); 781 } 782 } 783 #endif 784 785 RefPtr<PGMPParent::TestTriggerMetricsPromise> 786 GeckoMediaPluginServiceParent::TestTriggerMetrics() { 787 MOZ_ASSERT(NS_IsMainThread()); 788 { 789 MutexAutoLock lock(mMutex); 790 for (const RefPtr<GMPParent>& gmp : mPlugins) { 791 if (gmp->State() != GMPState::Loaded) { 792 // Plugins that are not in the Loaded state have no process attached to 793 // them, and any IPC we would attempt to send them would be ignored (or 794 // result in a warning on debug builds). 795 continue; 796 } 797 798 RefPtr<PGMPParent::TestTriggerMetricsPromise::Private> promise = 799 new PGMPParent::TestTriggerMetricsPromise::Private(__func__); 800 // Direct dispatch will resolve the promise on the same thread, which is 801 // faster; FOGIPC will move execution back to the main thread. 802 promise->UseDirectTaskDispatch(__func__); 803 804 mGMPThread->Dispatch( 805 NewRunnableMethod<ipc::ResolveCallback<bool>&&, 806 ipc::RejectCallback&&>( 807 "GMPParent::SendTestTriggerMetrics", gmp, 808 static_cast<void (GMPParent::*)( 809 mozilla::ipc::ResolveCallback<bool>&& aResolve, 810 mozilla::ipc::RejectCallback&& aReject)>( 811 &PGMPParent::SendTestTriggerMetrics), 812 813 [promise](bool aValue) { 814 promise->Resolve(std::move(aValue), __func__); 815 }, 816 [promise](ipc::ResponseRejectReason&& aReason) { 817 promise->Reject(std::move(aReason), __func__); 818 }), 819 NS_DISPATCH_NORMAL); 820 821 return promise; 822 } 823 } 824 825 return PGMPParent::TestTriggerMetricsPromise::CreateAndReject( 826 ipc::ResponseRejectReason::SendError, __func__); 827 } 828 829 RefPtr<GenericPromise> GeckoMediaPluginServiceParent::AsyncAddPluginDirectory( 830 const nsAString& aDirectory) { 831 nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); 832 if (!thread) { 833 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 834 } 835 836 mDirectoriesAdded++; 837 mDirectoriesInProgress++; 838 839 nsString dir(aDirectory); 840 RefPtr<GeckoMediaPluginServiceParent> self = this; 841 return InvokeAsync(thread, this, __func__, 842 &GeckoMediaPluginServiceParent::AddOnGMPThread, dir) 843 ->Then( 844 mMainThread, __func__, 845 [dir, self](bool aVal) { 846 GMP_LOG_DEBUG( 847 "GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s " 848 "succeeded", 849 NS_ConvertUTF16toUTF8(dir).get()); 850 MOZ_ASSERT(NS_IsMainThread()); 851 self->mDirectoriesInProgress--; 852 self->UpdateContentProcessGMPCapabilities(); 853 return GenericPromise::CreateAndResolve(aVal, __func__); 854 }, 855 [dir, self](nsresult aResult) { 856 GMP_LOG_DEBUG( 857 "GeckoMediaPluginServiceParent::AsyncAddPluginDirectory %s " 858 "failed", 859 NS_ConvertUTF16toUTF8(dir).get()); 860 self->mDirectoriesInProgress--; 861 return GenericPromise::CreateAndReject(aResult, __func__); 862 }); 863 } 864 865 NS_IMETHODIMP 866 GeckoMediaPluginServiceParent::AddPluginDirectory(const nsAString& aDirectory) { 867 MOZ_ASSERT(NS_IsMainThread()); 868 RefPtr<GenericPromise> p = AsyncAddPluginDirectory(aDirectory); 869 (void)p; 870 return NS_OK; 871 } 872 873 NS_IMETHODIMP 874 GeckoMediaPluginServiceParent::RemovePluginDirectory( 875 const nsAString& aDirectory) { 876 MOZ_ASSERT(NS_IsMainThread()); 877 return GMPDispatch( 878 new PathRunnable(this, aDirectory, PathRunnable::EOperation::REMOVE)); 879 } 880 881 NS_IMETHODIMP 882 GeckoMediaPluginServiceParent::RemoveAndDeletePluginDirectory( 883 const nsAString& aDirectory, const bool aDefer) { 884 MOZ_ASSERT(NS_IsMainThread()); 885 return GMPDispatch(new PathRunnable( 886 this, aDirectory, PathRunnable::EOperation::REMOVE_AND_DELETE_FROM_DISK, 887 aDefer)); 888 } 889 890 NS_IMETHODIMP 891 GeckoMediaPluginServiceParent::HasPluginForAPI(const nsACString& aAPI, 892 const nsTArray<nsCString>& aTags, 893 bool* aHasPlugin) { 894 NS_ENSURE_ARG(!aTags.IsEmpty()); 895 NS_ENSURE_ARG(aHasPlugin); 896 897 nsresult rv = EnsurePluginsOnDiskScanned(); 898 if (NS_FAILED(rv)) { 899 NS_WARNING("Failed to load GMPs from disk."); 900 return rv; 901 } 902 903 { 904 MutexAutoLock lock(mMutex); 905 nsCString api(aAPI); 906 size_t index = 0; 907 RefPtr<GMPParent> gmp = FindPluginForAPIFrom(index, api, aTags, &index); 908 *aHasPlugin = !!gmp; 909 } 910 911 return NS_OK; 912 } 913 914 NS_IMETHODIMP 915 GeckoMediaPluginServiceParent::FindPluginDirectoryForAPI( 916 const nsACString& aAPI, const nsTArray<nsCString>& aTags, 917 nsIFile** aDirectory) { 918 NS_ENSURE_ARG(!aTags.IsEmpty()); 919 NS_ENSURE_ARG(aDirectory); 920 921 nsresult rv = EnsurePluginsOnDiskScanned(); 922 if (NS_FAILED(rv)) { 923 NS_WARNING("Failed to load GMPs from disk."); 924 return rv; 925 } 926 927 { 928 MutexAutoLock lock(mMutex); 929 nsCString api(aAPI); 930 size_t index = 0; 931 RefPtr<GMPParent> gmp = FindPluginForAPIFrom(index, api, aTags, &index); 932 if (gmp) { 933 if (nsCOMPtr<nsIFile> dir = gmp->GetDirectory()) { 934 dir.forget(aDirectory); 935 } else { 936 NS_WARNING("Found plugin but missing directory."); 937 return NS_ERROR_FAILURE; 938 } 939 } 940 } 941 942 return NS_OK; 943 } 944 945 nsresult GeckoMediaPluginServiceParent::EnsurePluginsOnDiskScanned() { 946 const char* env = nullptr; 947 if (!mScannedPluginOnDisk && (env = PR_GetEnv("MOZ_GMP_PATH")) && *env) { 948 // We have a MOZ_GMP_PATH environment variable which may specify the 949 // location of plugins to load, and we haven't yet scanned the disk to 950 // see if there are plugins there. Get the GMP thread, which will 951 // cause an event to be dispatched to which scans for plugins. We 952 // dispatch a sync event to the GMP thread here in order to wait until 953 // after the GMP thread has scanned any paths in MOZ_GMP_PATH. 954 nsCOMPtr<nsIThread> thread; 955 nsresult rv = GetThread(getter_AddRefs(thread)); 956 NS_ENSURE_SUCCESS(rv, rv); 957 rv = NS_DispatchAndSpinEventLoopUntilComplete( 958 "GeckoMediaPluginServiceParent::EnsurePluginsOnDiskScanned"_ns, thread, 959 MakeAndAddRef<mozilla::Runnable>("GMPDummyRunnable")); 960 NS_ENSURE_SUCCESS(rv, rv); 961 MOZ_ASSERT(mScannedPluginOnDisk, "Should have scanned MOZ_GMP_PATH by now"); 962 } 963 964 return NS_OK; 965 } 966 967 already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::FindPluginForAPIFrom( 968 size_t aSearchStartIndex, const nsACString& aAPI, 969 const nsTArray<nsCString>& aTags, size_t* aOutPluginIndex) { 970 mMutex.AssertCurrentThreadOwns(); 971 for (size_t i = aSearchStartIndex; i < mPlugins.Length(); i++) { 972 RefPtr<GMPParent> gmp = mPlugins[i]; 973 if (!GMPCapability::Supports(gmp->GetCapabilities(), aAPI, aTags)) { 974 continue; 975 } 976 if (aOutPluginIndex) { 977 *aOutPluginIndex = i; 978 } 979 return gmp.forget(); 980 } 981 return nullptr; 982 } 983 984 already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::SelectPluginForAPI( 985 const nsACString& aNodeId, const nsACString& aAPI, 986 const nsTArray<nsCString>& aTags) { 987 AssertOnGMPThread(); 988 989 GMPParent* gmpToClone = nullptr; 990 { 991 MutexAutoLock lock(mMutex); 992 size_t index = 0; 993 RefPtr<GMPParent> gmp; 994 while ((gmp = FindPluginForAPIFrom(index, aAPI, aTags, &index))) { 995 if (aNodeId.IsEmpty()) { 996 if (gmp->CanBeSharedCrossNodeIds()) { 997 return gmp.forget(); 998 } 999 } else if (gmp->CanBeUsedFrom(aNodeId)) { 1000 return gmp.forget(); 1001 } 1002 1003 if (!gmpToClone || 1004 (gmpToClone->IsMarkedForDeletion() && !gmp->IsMarkedForDeletion())) { 1005 // This GMP has the correct type but has the wrong nodeId; hold on to it 1006 // in case we need to clone it. 1007 // Prefer GMPs in-use for the case where an upgraded plugin version is 1008 // waiting for the old one to die. If the old plugin is in use, we 1009 // should continue using it so that any persistent state remains 1010 // consistent. Otherwise, just check that the plugin isn't scheduled 1011 // for deletion. 1012 gmpToClone = gmp; 1013 } 1014 // Loop around and try the next plugin; it may be usable from aNodeId. 1015 index++; 1016 } 1017 } 1018 1019 // Plugin exists, but we can't use it due to cross-origin separation. Create a 1020 // new one. 1021 if (gmpToClone) { 1022 RefPtr<GMPParent> clone = ClonePlugin(gmpToClone); 1023 { 1024 MutexAutoLock lock(mMutex); 1025 mPlugins.AppendElement(clone); 1026 } 1027 if (!aNodeId.IsEmpty()) { 1028 clone->SetNodeId(aNodeId); 1029 } 1030 return clone.forget(); 1031 } 1032 1033 return nullptr; 1034 } 1035 1036 already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::CreateGMPParent() { 1037 // Should run on the GMP thread. 1038 #if defined(XP_LINUX) && defined(MOZ_SANDBOX) 1039 if (!SandboxInfo::Get().CanSandboxMedia()) { 1040 if (!StaticPrefs::media_gmp_insecure_allow()) { 1041 NS_WARNING("Denying media plugin load due to lack of sandboxing."); 1042 return nullptr; 1043 } 1044 NS_WARNING("Loading media plugin despite lack of sandboxing."); 1045 } 1046 #endif 1047 RefPtr<GMPParent> gmpParent = new GMPParent(); 1048 return gmpParent.forget(); 1049 } 1050 1051 already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::ClonePlugin( 1052 const GMPParent* aOriginal) { 1053 AssertOnGMPThread(); 1054 MOZ_ASSERT(aOriginal); 1055 1056 RefPtr<GMPParent> gmp = CreateGMPParent(); 1057 if (!gmp) { 1058 NS_WARNING("Can't Create GMPParent"); 1059 return nullptr; 1060 } 1061 1062 gmp->CloneFrom(aOriginal); 1063 return gmp.forget(); 1064 } 1065 1066 RefPtr<GenericPromise> GeckoMediaPluginServiceParent::AddOnGMPThread( 1067 nsString aDirectory) { 1068 #ifdef XP_WIN 1069 // On Windows our various test harnesses often pass paths with UNIX dir 1070 // separators, or a mix of dir separators. NS_NewLocalFile() can't handle 1071 // that, so fixup to match the platform's expected format. This makes us 1072 // more robust in the face of bad input and test harnesses changing... 1073 std::replace(aDirectory.BeginWriting(), aDirectory.EndWriting(), '/', '\\'); 1074 #endif 1075 1076 AssertOnGMPThread(); 1077 nsCString dir = NS_ConvertUTF16toUTF8(aDirectory); 1078 nsCOMPtr<nsISerialEventTarget> thread(GetGMPThread()); 1079 if (!thread) { 1080 GMP_LOG_DEBUG("%s::%s: %s No GMP Thread", __CLASS__, __FUNCTION__, 1081 dir.get()); 1082 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 1083 } 1084 GMP_LOG_DEBUG("%s::%s: %s", __CLASS__, __FUNCTION__, dir.get()); 1085 1086 nsCOMPtr<nsIFile> directory; 1087 nsresult rv = NS_NewLocalFile(aDirectory, getter_AddRefs(directory)); 1088 if (NS_WARN_IF(NS_FAILED(rv))) { 1089 GMP_LOG_DEBUG("%s::%s: failed to create nsIFile for dir=%s rv=%" PRIx32, 1090 __CLASS__, __FUNCTION__, dir.get(), 1091 static_cast<uint32_t>(rv)); 1092 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 1093 } 1094 1095 RefPtr<GMPParent> gmp = CreateGMPParent(); 1096 if (!gmp) { 1097 NS_WARNING("Can't Create GMPParent"); 1098 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 1099 } 1100 1101 RefPtr<GeckoMediaPluginServiceParent> self(this); 1102 return gmp->Init(this, directory) 1103 ->Then( 1104 thread, __func__, 1105 [gmp, self, dir](bool aVal) { 1106 GMP_LOG_DEBUG("%s::%s: %s Succeeded", __CLASS__, __FUNCTION__, 1107 dir.get()); 1108 { 1109 MutexAutoLock lock(self->mMutex); 1110 self->mPlugins.AppendElement(gmp); 1111 } 1112 return GenericPromise::CreateAndResolve(aVal, __func__); 1113 }, 1114 [dir](nsresult aResult) { 1115 GMP_LOG_DEBUG("%s::%s: %s Failed", __CLASS__, __FUNCTION__, 1116 dir.get()); 1117 return GenericPromise::CreateAndReject(aResult, __func__); 1118 }); 1119 } 1120 1121 void GeckoMediaPluginServiceParent::RemoveOnGMPThread( 1122 const nsAString& aDirectory, const bool aDeleteFromDisk, 1123 const bool aCanDefer) { 1124 AssertOnGMPThread(); 1125 GMP_LOG_DEBUG("%s::%s: %s", __CLASS__, __FUNCTION__, 1126 NS_LossyConvertUTF16toASCII(aDirectory).get()); 1127 1128 nsCOMPtr<nsIFile> directory; 1129 nsresult rv = NS_NewLocalFile(aDirectory, getter_AddRefs(directory)); 1130 if (NS_WARN_IF(NS_FAILED(rv))) { 1131 return; 1132 } 1133 1134 // Plugin destruction can modify |mPlugins|. Put them aside for now and 1135 // destroy them once we're done with |mPlugins|. 1136 nsTArray<RefPtr<GMPParent>> deadPlugins; 1137 1138 bool inUse = false; 1139 MutexAutoLock lock(mMutex); 1140 for (size_t i = mPlugins.Length(); i-- > 0;) { 1141 nsCOMPtr<nsIFile> pluginpath = mPlugins[i]->GetDirectory(); 1142 bool equals; 1143 if (!pluginpath || NS_FAILED(directory->Equals(pluginpath, &equals)) || 1144 !equals) { 1145 continue; 1146 } 1147 1148 RefPtr<GMPParent> gmp = mPlugins[i]; 1149 if (aDeleteFromDisk && gmp->State() != GMPState::NotLoaded) { 1150 // We have to wait for the child process to release its lib handle 1151 // before we can delete the GMP. 1152 inUse = true; 1153 gmp->MarkForDeletion(); 1154 1155 if (!mPluginsWaitingForDeletion.Contains(aDirectory)) { 1156 mPluginsWaitingForDeletion.AppendElement(aDirectory); 1157 } 1158 } 1159 1160 if (gmp->State() == GMPState::NotLoaded || !aCanDefer) { 1161 // GMP not in use or shutdown is being forced; can shut it down now. 1162 deadPlugins.AppendElement(gmp); 1163 mPlugins.RemoveElementAt(i); 1164 } 1165 } 1166 1167 { 1168 MutexAutoUnlock unlock(mMutex); 1169 for (auto& gmp : deadPlugins) { 1170 gmp->CloseActive(true); 1171 } 1172 } 1173 1174 if (aDeleteFromDisk && !inUse) { 1175 // Ensure the GMP dir and all files in it are writable, so we have 1176 // permission to delete them. 1177 directory->SetPermissions(0700); 1178 DirectoryEnumerator iter(directory, DirectoryEnumerator::FilesAndDirs); 1179 for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) { 1180 dirEntry->SetPermissions(0700); 1181 } 1182 if (NS_SUCCEEDED(directory->Remove(true))) { 1183 mPluginsWaitingForDeletion.RemoveElement(aDirectory); 1184 nsCOMPtr<nsIRunnable> task = new NotifyObserversTask( 1185 "gmp-directory-deleted", nsString(aDirectory)); 1186 mMainThread->Dispatch(task.forget()); 1187 } 1188 } 1189 } // Ignore mutex not held; MutexAutoUnlock is used above 1190 1191 // May remove when Bug 1043671 is fixed 1192 static void Dummy(RefPtr<GMPParent> aOnDeathsDoor) { 1193 // exists solely to do nothing and let the Runnable kill the GMPParent 1194 // when done. 1195 } 1196 1197 void GeckoMediaPluginServiceParent::PluginTerminated( 1198 const RefPtr<GMPParent>& aPlugin) { 1199 AssertOnGMPThread(); 1200 1201 if (aPlugin->IsMarkedForDeletion()) { 1202 nsString path; 1203 if (RefPtr<nsIFile> dir = aPlugin->GetDirectory()) { 1204 nsresult rv = dir->GetPath(path); 1205 NS_ENSURE_SUCCESS_VOID(rv); 1206 if (mPluginsWaitingForDeletion.Contains(path)) { 1207 RemoveOnGMPThread(path, true /* delete */, true /* can defer */); 1208 } 1209 } else { 1210 MOZ_ASSERT_UNREACHABLE("Plugin without directory marked for deletion?"); 1211 } 1212 } 1213 } 1214 1215 void GeckoMediaPluginServiceParent::ReAddOnGMPThread( 1216 const RefPtr<GMPParent>& aOld) { 1217 AssertOnGMPThread(); 1218 GMP_LOG_DEBUG("%s::%s: %p", __CLASS__, __FUNCTION__, (void*)aOld); 1219 1220 RefPtr<GMPParent> gmp; 1221 if (!mShuttingDownOnGMPThread) { 1222 // We're not shutting down, so replace the old plugin in the list with a 1223 // clone which is in a pristine state. Note: We place the plugin in 1224 // the same slot in the array as a hack to ensure if we re-request with 1225 // the same capabilities we get an instance of the same plugin. 1226 gmp = ClonePlugin(aOld); 1227 MutexAutoLock lock(mMutex); 1228 MOZ_ASSERT(mPlugins.Contains(aOld)); 1229 if (mPlugins.Contains(aOld)) { 1230 mPlugins[mPlugins.IndexOf(aOld)] = gmp; 1231 } 1232 } else { 1233 // We're shutting down; don't re-add plugin, let the old plugin die. 1234 MutexAutoLock lock(mMutex); 1235 mPlugins.RemoveElement(aOld); 1236 } 1237 // Schedule aOld to be destroyed. We can't destroy it from here since we 1238 // may be inside ActorDestroyed() for it. 1239 NS_DispatchToCurrentThread(WrapRunnableNM(&Dummy, aOld)); 1240 } 1241 1242 NS_IMETHODIMP 1243 GeckoMediaPluginServiceParent::GetStorageDir(nsIFile** aOutFile) { 1244 if (NS_WARN_IF(!mStorageBaseDir)) { 1245 return NS_ERROR_FAILURE; 1246 } 1247 return mStorageBaseDir->Clone(aOutFile); 1248 } 1249 1250 nsresult WriteToFile(nsIFile* aPath, const nsACString& aFileName, 1251 const nsACString& aData) { 1252 nsCOMPtr<nsIFile> path; 1253 nsresult rv = aPath->Clone(getter_AddRefs(path)); 1254 if (NS_WARN_IF(NS_FAILED(rv))) { 1255 return rv; 1256 } 1257 1258 rv = path->AppendNative(aFileName); 1259 if (NS_WARN_IF(NS_FAILED(rv))) { 1260 return rv; 1261 } 1262 1263 PRFileDesc* f = nullptr; 1264 rv = path->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, PR_IRWXU, &f); 1265 if (NS_WARN_IF(NS_FAILED(rv))) { 1266 return rv; 1267 } 1268 1269 int32_t len = PR_Write(f, aData.BeginReading(), aData.Length()); 1270 PR_Close(f); 1271 if (NS_WARN_IF(len < 0 || (size_t)len != aData.Length())) { 1272 return NS_ERROR_FAILURE; 1273 } 1274 1275 return NS_OK; 1276 } 1277 1278 static nsresult ReadFromFile(nsIFile* aPath, const nsACString& aFileName, 1279 nsACString& aOutData, int32_t aMaxLength) { 1280 nsCOMPtr<nsIFile> path; 1281 nsresult rv = aPath->Clone(getter_AddRefs(path)); 1282 if (NS_WARN_IF(NS_FAILED(rv))) { 1283 return rv; 1284 } 1285 1286 rv = path->AppendNative(aFileName); 1287 if (NS_WARN_IF(NS_FAILED(rv))) { 1288 return rv; 1289 } 1290 1291 PRFileDesc* f = nullptr; 1292 rv = path->OpenNSPRFileDesc(PR_RDONLY | PR_CREATE_FILE, PR_IRWXU, &f); 1293 if (NS_WARN_IF(NS_FAILED(rv))) { 1294 return rv; 1295 } 1296 1297 auto size = PR_Seek(f, 0, PR_SEEK_END); 1298 PR_Seek(f, 0, PR_SEEK_SET); 1299 1300 if (size > aMaxLength) { 1301 return NS_ERROR_FAILURE; 1302 } 1303 aOutData.SetLength(size); 1304 1305 auto len = PR_Read(f, aOutData.BeginWriting(), size); 1306 PR_Close(f); 1307 if (NS_WARN_IF(len != size)) { 1308 return NS_ERROR_FAILURE; 1309 } 1310 1311 return NS_OK; 1312 } 1313 1314 nsresult ReadSalt(nsIFile* aPath, nsACString& aOutData) { 1315 return ReadFromFile(aPath, "salt"_ns, aOutData, NodeIdSaltLength); 1316 } 1317 1318 already_AddRefed<GMPStorage> GeckoMediaPluginServiceParent::GetMemoryStorageFor( 1319 const nsACString& aNodeId, const nsAString& aGMPName) { 1320 return do_AddRef(mTempGMPStorage.LookupOrInsertWith( 1321 aNodeId, [&] { return CreateGMPMemoryStorage(aNodeId, aGMPName); })); 1322 } 1323 1324 NS_IMETHODIMP 1325 GeckoMediaPluginServiceParent::IsPersistentStorageAllowed( 1326 const nsACString& aNodeId, bool* aOutAllowed) { 1327 AssertOnGMPThread(); 1328 NS_ENSURE_ARG(aOutAllowed); 1329 // We disallow persistent storage for the NodeId used for shared GMP 1330 // decoding, to prevent GMP decoding being used to track what a user 1331 // watches somehow. 1332 *aOutAllowed = !aNodeId.Equals(SHARED_GMP_DECODING_NODE_ID) && 1333 mPersistentStorageAllowed.Get(aNodeId); 1334 return NS_OK; 1335 } 1336 1337 nsresult GeckoMediaPluginServiceParent::GetNodeId( 1338 const nsAString& aOrigin, const nsAString& aTopLevelOrigin, 1339 const nsAString& aGMPName, nsACString& aOutId) { 1340 AssertOnGMPThread(); 1341 GMP_LOG_DEBUG("%s::%s: (%s, %s)", __CLASS__, __FUNCTION__, 1342 NS_ConvertUTF16toUTF8(aOrigin).get(), 1343 NS_ConvertUTF16toUTF8(aTopLevelOrigin).get()); 1344 1345 nsresult rv; 1346 1347 if (aOrigin.EqualsLiteral("null") || aOrigin.IsEmpty() || 1348 aTopLevelOrigin.EqualsLiteral("null") || aTopLevelOrigin.IsEmpty()) { 1349 // (origin, topLevelOrigin) is null or empty; this is for an anonymous 1350 // origin, probably a local file, for which we don't provide persistent 1351 // storage. Generate a random node id, and don't store it so that the GMP's 1352 // storage is temporary and the process for this GMP is not shared with GMP 1353 // instances that have the same nodeId. 1354 nsAutoCString salt; 1355 rv = GenerateRandomPathName(salt, NodeIdSaltLength); 1356 if (NS_WARN_IF(NS_FAILED(rv))) { 1357 return rv; 1358 } 1359 aOutId = salt; 1360 mPersistentStorageAllowed.InsertOrUpdate(salt, false); 1361 return NS_OK; 1362 } 1363 1364 const uint32_t hash = 1365 AddToHash(HashString(aOrigin), HashString(aTopLevelOrigin)); 1366 1367 if (OriginAttributes::IsPrivateBrowsing(NS_ConvertUTF16toUTF8(aOrigin))) { 1368 // For PB mode, we store the node id, indexed by the origin pair and GMP 1369 // name, so that if the same origin pair is opened for the same GMP in this 1370 // session, it gets the same node id. 1371 const uint32_t pbHash = AddToHash(HashString(aGMPName), hash); 1372 return mTempNodeIds.WithEntryHandle(pbHash, [&](auto&& entry) { 1373 if (!entry) { 1374 // No salt stored, generate and temporarily store some for this id. 1375 nsAutoCString newSalt; 1376 rv = GenerateRandomPathName(newSalt, NodeIdSaltLength); 1377 if (NS_WARN_IF(NS_FAILED(rv))) { 1378 return rv; 1379 } 1380 auto salt = MakeUnique<nsCString>(newSalt); 1381 1382 mPersistentStorageAllowed.InsertOrUpdate(*salt, false); 1383 entry.Insert(std::move(salt)); 1384 } 1385 1386 aOutId = *entry.Data(); 1387 return NS_OK; 1388 }); 1389 } 1390 1391 // Otherwise, try to see if we've previously generated and stored salt 1392 // for this origin pair. 1393 nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/ 1394 rv = GetStorageDir(getter_AddRefs(path)); 1395 if (NS_WARN_IF(NS_FAILED(rv))) { 1396 return rv; 1397 } 1398 1399 // $profileDir/gmp/$platform/$gmpName/ 1400 rv = path->Append(aGMPName); 1401 if (NS_WARN_IF(NS_FAILED(rv))) { 1402 return rv; 1403 } 1404 1405 // $profileDir/gmp/$platform/$gmpName/id/ 1406 rv = path->AppendNative("id"_ns); 1407 if (NS_WARN_IF(NS_FAILED(rv))) { 1408 return rv; 1409 } 1410 1411 nsAutoCString hashStr; 1412 hashStr.AppendInt((int64_t)hash); 1413 1414 // $profileDir/gmp/$platform/$gmpName/id/$hash 1415 rv = path->AppendNative(hashStr); 1416 if (NS_WARN_IF(NS_FAILED(rv))) { 1417 return rv; 1418 } 1419 1420 rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700); 1421 if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) { 1422 return rv; 1423 } 1424 1425 nsCOMPtr<nsIFile> saltFile; 1426 rv = path->Clone(getter_AddRefs(saltFile)); 1427 if (NS_WARN_IF(NS_FAILED(rv))) { 1428 return rv; 1429 } 1430 1431 rv = saltFile->AppendNative("salt"_ns); 1432 if (NS_WARN_IF(NS_FAILED(rv))) { 1433 return rv; 1434 } 1435 1436 nsAutoCString salt; 1437 bool exists = false; 1438 rv = saltFile->Exists(&exists); 1439 if (NS_WARN_IF(NS_FAILED(rv))) { 1440 return rv; 1441 } 1442 if (!exists) { 1443 // No stored salt for this origin. Generate salt, and store it and 1444 // the origin on disk. 1445 nsresult rv = GenerateRandomPathName(salt, NodeIdSaltLength); 1446 if (NS_WARN_IF(NS_FAILED(rv))) { 1447 return rv; 1448 } 1449 MOZ_ASSERT(salt.Length() == NodeIdSaltLength); 1450 1451 // $profileDir/gmp/$platform/$gmpName/id/$hash/salt 1452 rv = WriteToFile(path, "salt"_ns, salt); 1453 if (NS_WARN_IF(NS_FAILED(rv))) { 1454 return rv; 1455 } 1456 1457 // $profileDir/gmp/$platform/$gmpName/id/$hash/origin 1458 rv = WriteToFile(path, "origin"_ns, NS_ConvertUTF16toUTF8(aOrigin)); 1459 if (NS_WARN_IF(NS_FAILED(rv))) { 1460 return rv; 1461 } 1462 1463 // $profileDir/gmp/$platform/$gmpName/id/$hash/topLevelOrigin 1464 rv = WriteToFile(path, "topLevelOrigin"_ns, 1465 NS_ConvertUTF16toUTF8(aTopLevelOrigin)); 1466 if (NS_WARN_IF(NS_FAILED(rv))) { 1467 return rv; 1468 } 1469 1470 } else { 1471 rv = ReadSalt(path, salt); 1472 if (NS_WARN_IF(NS_FAILED(rv))) { 1473 return rv; 1474 } 1475 } 1476 1477 aOutId = salt; 1478 mPersistentStorageAllowed.InsertOrUpdate(salt, true); 1479 1480 return NS_OK; 1481 } 1482 1483 NS_IMETHODIMP 1484 GeckoMediaPluginServiceParent::GetNodeId( 1485 const nsAString& aOrigin, const nsAString& aTopLevelOrigin, 1486 const nsAString& aGMPName, UniquePtr<GetNodeIdCallback>&& aCallback) { 1487 nsCString nodeId; 1488 nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, nodeId); 1489 aCallback->Done(rv, nodeId); 1490 return rv; 1491 } 1492 1493 nsresult GeckoMediaPluginServiceParent::GetNodeId( 1494 const NodeIdVariant& aNodeIdVariant, nsACString& aOutId) { 1495 if (aNodeIdVariant.type() == NodeIdVariant::TnsCString) { 1496 // The union already contains a node ID as a string, use that. 1497 aOutId = aNodeIdVariant.get_nsCString(); 1498 return NS_OK; 1499 } 1500 // The union contains a NodeIdParts, convert it to a node ID string. 1501 NodeIdParts nodeIdParts{aNodeIdVariant.get_NodeIdParts()}; 1502 return GetNodeId(nodeIdParts.mOrigin(), nodeIdParts.mTopLevelOrigin(), 1503 nodeIdParts.mGMPName(), aOutId); 1504 } 1505 1506 static bool ExtractHostName(const nsACString& aOrigin, nsACString& aOutData) { 1507 nsCString str; 1508 str.Assign(aOrigin); 1509 int begin = str.Find("://"); 1510 // The scheme is missing! 1511 if (begin == -1) { 1512 return false; 1513 } 1514 1515 int end = str.RFind(":"); 1516 // Remove the port number 1517 if (end != begin) { 1518 str.SetLength(end); 1519 } 1520 1521 nsDependentCSubstring host(str, begin + 3); 1522 aOutData.Assign(host); 1523 return true; 1524 } 1525 1526 constexpr uint32_t kMaxDomainLength = 253; 1527 // http://en.wikipedia.org/wiki/Domain_Name_System#Domain_name_syntax 1528 1529 constexpr std::array<nsLiteralCString, 2> kFileNames = {"origin"_ns, 1530 "topLevelOrigin"_ns}; 1531 1532 bool MatchOrigin(nsIFile* aPath, const nsACString& aSite, 1533 const mozilla::OriginAttributesPattern& aPattern) { 1534 nsresult rv; 1535 nsCString str; 1536 nsCString originNoSuffix; 1537 for (const auto& fileName : kFileNames) { 1538 rv = ReadFromFile(aPath, fileName, str, kMaxDomainLength); 1539 mozilla::OriginAttributes originAttributes; 1540 if (NS_FAILED(rv) || 1541 !originAttributes.PopulateFromOrigin(str, originNoSuffix)) { 1542 // Fails on parsing the originAttributes, treat this as a non-match. 1543 return false; 1544 } 1545 1546 if (ExtractHostName(originNoSuffix, str) && str.Equals(aSite) && 1547 aPattern.Matches(originAttributes)) { 1548 return true; 1549 } 1550 } 1551 return false; 1552 } 1553 1554 bool MatchBaseDomain(nsIFile* aPath, const nsACString& aBaseDomain) { 1555 nsresult rv; 1556 nsCString fileContent; 1557 nsCString originNoSuffix; 1558 for (const auto& fileName : kFileNames) { 1559 rv = ReadFromFile(aPath, fileName, fileContent, kMaxDomainLength); 1560 mozilla::OriginAttributes originAttributes; 1561 if (NS_FAILED(rv) || 1562 !originAttributes.PopulateFromOrigin(fileContent, originNoSuffix)) { 1563 // Fails on parsing the originAttributes, treat this as a non-match. 1564 return false; 1565 } 1566 nsCString originHostname; 1567 if (!ExtractHostName(originNoSuffix, originHostname)) { 1568 return false; 1569 } 1570 bool success; 1571 rv = net::HasRootDomain(originHostname, aBaseDomain, &success); 1572 if (NS_SUCCEEDED(rv) && success) { 1573 return true; 1574 } 1575 } 1576 return false; 1577 } 1578 1579 template <typename T> 1580 static void KillPlugins(const nsTArray<RefPtr<GMPParent>>& aPlugins, 1581 Mutex& aMutex, T&& aFilter) { 1582 // Shutdown the plugins when |aFilter| evaluates to true. 1583 // After we clear storage data, node IDs will become invalid and shouldn't be 1584 // used anymore. We need to kill plugins with such nodeIDs. 1585 // Note: we can't shut them down while holding the lock, 1586 // as the lock is not re-entrant and shutdown requires taking the lock. 1587 // The plugin list is only edited on the GMP thread, so this should be OK. 1588 nsTArray<RefPtr<GMPParent>> pluginsToKill; 1589 { 1590 MutexAutoLock lock(aMutex); 1591 for (size_t i = 0; i < aPlugins.Length(); i++) { 1592 RefPtr<GMPParent> parent(aPlugins[i]); 1593 if (aFilter(parent)) { 1594 pluginsToKill.AppendElement(parent); 1595 } 1596 } 1597 } 1598 1599 for (size_t i = 0; i < pluginsToKill.Length(); i++) { 1600 pluginsToKill[i]->CloseActive(false); 1601 } 1602 } 1603 1604 struct NodeFilter { 1605 explicit NodeFilter(const nsTArray<nsCString>& nodeIDs) : mNodeIDs(nodeIDs) {} 1606 bool operator()(GMPParent* aParent) { 1607 return mNodeIDs.Contains(aParent->GetNodeId()); 1608 } 1609 1610 private: 1611 const nsTArray<nsCString>& mNodeIDs; 1612 }; 1613 1614 void GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin( 1615 DirectoryFilter& aFilter) { 1616 // $profileDir/gmp/$platform/ 1617 nsCOMPtr<nsIFile> path; 1618 nsresult rv = GetStorageDir(getter_AddRefs(path)); 1619 if (NS_WARN_IF(NS_FAILED(rv))) { 1620 return; 1621 } 1622 1623 // Iterate all sub-folders of $profileDir/gmp/$platform/, i.e. the dirs in 1624 // which specific GMPs store their data. 1625 DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly); 1626 for (nsCOMPtr<nsIFile> pluginDir; (pluginDir = iter.Next()) != nullptr;) { 1627 ClearNodeIdAndPlugin(pluginDir, aFilter); 1628 } 1629 } 1630 1631 void GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin( 1632 nsIFile* aPluginStorageDir, DirectoryFilter& aFilter) { 1633 // $profileDir/gmp/$platform/$gmpName/id/ 1634 nsCOMPtr<nsIFile> path = CloneAndAppend(aPluginStorageDir, u"id"_ns); 1635 if (!path) { 1636 return; 1637 } 1638 1639 // Iterate all sub-folders of $profileDir/gmp/$platform/$gmpName/id/ 1640 nsTArray<nsCString> nodeIDsToClear; 1641 DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly); 1642 for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) { 1643 // dirEntry is the hash of origins, i.e.: 1644 // $profileDir/gmp/$platform/$gmpName/id/$originHash/ 1645 if (!aFilter(dirEntry)) { 1646 continue; 1647 } 1648 nsAutoCString salt; 1649 if (NS_SUCCEEDED(ReadSalt(dirEntry, salt))) { 1650 // Keep node IDs to clear data/plugins associated with them later. 1651 nodeIDsToClear.AppendElement(salt); 1652 // Also remove node IDs from the table. 1653 mPersistentStorageAllowed.Remove(salt); 1654 } 1655 // Now we can remove the directory for the origin pair. 1656 if (NS_FAILED(dirEntry->Remove(true))) { 1657 NS_WARNING("Failed to delete the directory for the origin pair"); 1658 } 1659 } 1660 1661 // Kill plugin instances that have node IDs being cleared. 1662 MOZ_PUSH_IGNORE_THREAD_SAFETY 1663 KillPlugins(mPlugins, mMutex, NodeFilter(nodeIDsToClear)); 1664 MOZ_POP_THREAD_SAFETY 1665 1666 // Clear all storage in $profileDir/gmp/$platform/$gmpName/storage/$nodeId/ 1667 path = CloneAndAppend(aPluginStorageDir, u"storage"_ns); 1668 if (!path) { 1669 return; 1670 } 1671 1672 for (const nsACString& nodeId : nodeIDsToClear) { 1673 nsCOMPtr<nsIFile> dirEntry; 1674 nsresult rv = path->Clone(getter_AddRefs(dirEntry)); 1675 if (NS_WARN_IF(NS_FAILED(rv))) { 1676 continue; 1677 } 1678 1679 rv = dirEntry->AppendNative(nodeId); 1680 if (NS_WARN_IF(NS_FAILED(rv))) { 1681 continue; 1682 } 1683 1684 if (NS_FAILED(dirEntry->Remove(true))) { 1685 NS_WARNING("Failed to delete GMP storage directory for the node"); 1686 } 1687 } 1688 } 1689 1690 void GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread( 1691 const nsACString& aSite, const mozilla::OriginAttributesPattern& aPattern) { 1692 AssertOnGMPThread(); 1693 GMP_LOG_DEBUG("%s::%s: origin=%s", __CLASS__, __FUNCTION__, aSite.Data()); 1694 1695 struct OriginFilter : public DirectoryFilter { 1696 explicit OriginFilter(const nsACString& aSite, 1697 const mozilla::OriginAttributesPattern& aPattern) 1698 : mSite(aSite), mPattern(aPattern) {} 1699 bool operator()(nsIFile* aPath) override { 1700 return MatchOrigin(aPath, mSite, mPattern); 1701 } 1702 1703 private: 1704 const nsACString& mSite; 1705 const mozilla::OriginAttributesPattern& mPattern; 1706 } filter(aSite, aPattern); 1707 1708 ClearNodeIdAndPlugin(filter); 1709 } 1710 1711 void GeckoMediaPluginServiceParent::ForgetThisBaseDomainOnGMPThread( 1712 const nsACString& aBaseDomain) { 1713 AssertOnGMPThread(); 1714 GMP_LOG_DEBUG("%s::%s: baseDomain=%s", __CLASS__, __FUNCTION__, 1715 aBaseDomain.Data()); 1716 1717 struct BaseDomainFilter : public DirectoryFilter { 1718 explicit BaseDomainFilter(const nsACString& aBaseDomain) 1719 : mBaseDomain(aBaseDomain) {} 1720 bool operator()(nsIFile* aPath) override { 1721 return MatchBaseDomain(aPath, mBaseDomain); 1722 } 1723 1724 private: 1725 const nsACString& mBaseDomain; 1726 } filter(aBaseDomain); 1727 1728 ClearNodeIdAndPlugin(filter); 1729 } 1730 1731 void GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread( 1732 PRTime aSince) { 1733 AssertOnGMPThread(); 1734 GMP_LOG_DEBUG("%s::%s: since=%" PRId64, __CLASS__, __FUNCTION__, 1735 (int64_t)aSince); 1736 1737 struct MTimeFilter : public DirectoryFilter { 1738 explicit MTimeFilter(PRTime aSince) : mSince(aSince) {} 1739 1740 // Return true if any files under aPath is modified after |mSince|. 1741 bool IsModifiedAfter(nsIFile* aPath) { 1742 PRTime lastModified; 1743 nsresult rv = aPath->GetLastModifiedTime(&lastModified); 1744 if (NS_SUCCEEDED(rv) && lastModified >= mSince) { 1745 return true; 1746 } 1747 DirectoryEnumerator iter(aPath, DirectoryEnumerator::FilesAndDirs); 1748 for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) { 1749 if (IsModifiedAfter(dirEntry)) { 1750 return true; 1751 } 1752 } 1753 return false; 1754 } 1755 1756 // |aPath| is $profileDir/gmp/$platform/$gmpName/id/$originHash/ 1757 bool operator()(nsIFile* aPath) override { 1758 if (IsModifiedAfter(aPath)) { 1759 return true; 1760 } 1761 1762 nsAutoCString salt; 1763 if (NS_WARN_IF(NS_FAILED(ReadSalt(aPath, salt)))) { 1764 return false; 1765 } 1766 1767 // $profileDir/gmp/$platform/$gmpName/id/ 1768 nsCOMPtr<nsIFile> idDir; 1769 if (NS_WARN_IF(NS_FAILED(aPath->GetParent(getter_AddRefs(idDir))))) { 1770 return false; 1771 } 1772 // $profileDir/gmp/$platform/$gmpName/ 1773 nsCOMPtr<nsIFile> temp; 1774 if (NS_WARN_IF(NS_FAILED(idDir->GetParent(getter_AddRefs(temp))))) { 1775 return false; 1776 } 1777 1778 // $profileDir/gmp/$platform/$gmpName/storage/ 1779 if (NS_WARN_IF(NS_FAILED(temp->Append(u"storage"_ns)))) { 1780 return false; 1781 } 1782 // $profileDir/gmp/$platform/$gmpName/storage/$originSalt 1783 return NS_SUCCEEDED(temp->AppendNative(salt)) && IsModifiedAfter(temp); 1784 } 1785 1786 private: 1787 const PRTime mSince; 1788 } filter(aSince); 1789 1790 ClearNodeIdAndPlugin(filter); 1791 1792 nsCOMPtr<nsIRunnable> task = 1793 new NotifyObserversTask("gmp-clear-storage-complete"); 1794 mMainThread->Dispatch(task.forget()); 1795 } 1796 1797 NS_IMETHODIMP 1798 GeckoMediaPluginServiceParent::ForgetThisSite(const nsAString& aSite, 1799 const nsAString& aPattern) { 1800 MOZ_ASSERT(NS_IsMainThread()); 1801 1802 mozilla::OriginAttributesPattern pattern; 1803 1804 if (!pattern.Init(aPattern)) { 1805 return NS_ERROR_INVALID_ARG; 1806 } 1807 1808 return ForgetThisSiteNative(aSite, pattern); 1809 } 1810 1811 nsresult GeckoMediaPluginServiceParent::ForgetThisSiteNative( 1812 const nsAString& aSite, const mozilla::OriginAttributesPattern& aPattern) { 1813 MOZ_ASSERT(NS_IsMainThread()); 1814 1815 return GMPDispatch( 1816 NewRunnableMethod<nsCString, mozilla::OriginAttributesPattern>( 1817 "gmp::GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread", this, 1818 &GeckoMediaPluginServiceParent::ForgetThisSiteOnGMPThread, 1819 NS_ConvertUTF16toUTF8(aSite), aPattern)); 1820 } 1821 1822 NS_IMETHODIMP 1823 GeckoMediaPluginServiceParent::ForgetThisBaseDomain( 1824 const nsAString& aBaseDomain) { 1825 MOZ_ASSERT(NS_IsMainThread()); 1826 1827 return ForgetThisBaseDomainNative(aBaseDomain); 1828 } 1829 1830 nsresult GeckoMediaPluginServiceParent::ForgetThisBaseDomainNative( 1831 const nsAString& aBaseDomain) { 1832 MOZ_ASSERT(NS_IsMainThread()); 1833 1834 return GMPDispatch(NewRunnableMethod<nsCString>( 1835 "gmp::GeckoMediaPluginServiceParent::ForgetThisBaseDomainOnGMPThread", 1836 this, &GeckoMediaPluginServiceParent::ForgetThisBaseDomainOnGMPThread, 1837 NS_ConvertUTF16toUTF8(aBaseDomain))); 1838 } 1839 1840 static bool IsNodeIdValid(GMPParent* aParent) { 1841 return !aParent->GetNodeId().IsEmpty(); 1842 } 1843 1844 // Called from GMPServiceParent::Create() which holds the lock 1845 void GeckoMediaPluginServiceParent::ServiceUserCreated( 1846 GMPServiceParent* aServiceParent) { 1847 MOZ_ASSERT(NS_IsMainThread()); 1848 mMutex.AssertCurrentThreadOwns(); 1849 1850 MOZ_ASSERT(!mServiceParents.Contains(aServiceParent)); 1851 mServiceParents.AppendElement(aServiceParent); 1852 } 1853 1854 void GeckoMediaPluginServiceParent::ServiceUserDestroyed( 1855 GMPServiceParent* aServiceParent) { 1856 MOZ_ASSERT(NS_IsMainThread()); 1857 MutexAutoLock lock(mMutex); 1858 MOZ_ASSERT(mServiceParents.Length() > 0); 1859 MOZ_ASSERT(mServiceParents.Contains(aServiceParent)); 1860 mServiceParents.RemoveElement(aServiceParent); 1861 } 1862 1863 void GeckoMediaPluginServiceParent::ClearStorage() { 1864 AssertOnGMPThread(); 1865 GMP_LOG_DEBUG("%s::%s", __CLASS__, __FUNCTION__); 1866 1867 // Kill plugins with valid nodeIDs. 1868 MOZ_PUSH_IGNORE_THREAD_SAFETY 1869 KillPlugins(mPlugins, mMutex, &IsNodeIdValid); 1870 MOZ_POP_THREAD_SAFETY 1871 1872 nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/ 1873 nsresult rv = GetStorageDir(getter_AddRefs(path)); 1874 if (NS_WARN_IF(NS_FAILED(rv))) { 1875 return; 1876 } 1877 1878 if (NS_FAILED(path->Remove(true))) { 1879 NS_WARNING("Failed to delete GMP storage directory"); 1880 } 1881 1882 // Clear private-browsing storage. 1883 mTempGMPStorage.Clear(); 1884 1885 nsCOMPtr<nsIRunnable> task = 1886 new NotifyObserversTask("gmp-clear-storage-complete"); 1887 mMainThread->Dispatch(task.forget()); 1888 } 1889 1890 already_AddRefed<GMPParent> GeckoMediaPluginServiceParent::GetById( 1891 uint32_t aPluginId) { 1892 MutexAutoLock lock(mMutex); 1893 for (const RefPtr<GMPParent>& gmp : mPlugins) { 1894 if (gmp->GetPluginId() == aPluginId) { 1895 return do_AddRef(gmp); 1896 } 1897 } 1898 return nullptr; 1899 } 1900 1901 GMPServiceParent::GMPServiceParent(GeckoMediaPluginServiceParent* aService) 1902 : mService(aService), mShutdownBlocker([](GMPServiceParent* aThis) { 1903 nsFmtString name(u"GMPServiceParent {}", static_cast<void*>(aThis)); 1904 return media::ShutdownBlockingTicket::Create( 1905 name, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__); 1906 }(this)) { 1907 MOZ_ASSERT(NS_IsMainThread(), "Should be constructed on the main thread"); 1908 MOZ_ASSERT(mService); 1909 MOZ_RELEASE_ASSERT(mShutdownBlocker); 1910 mService->ServiceUserCreated(this); 1911 } 1912 1913 GMPServiceParent::~GMPServiceParent() { 1914 MOZ_ASSERT(NS_IsMainThread(), "Should be destroyted on the main thread"); 1915 mService->ServiceUserDestroyed(this); 1916 } 1917 1918 mozilla::ipc::IPCResult GMPServiceParent::RecvLaunchGMP( 1919 const NodeIdVariant& aNodeIdVariant, const nsACString& aAPI, 1920 nsTArray<nsCString>&& aTags, nsTArray<ProcessId>&& aAlreadyBridgedTo, 1921 LaunchGMPResolver&& aResolve) { 1922 GMPLaunchResult result; 1923 1924 if (mService->IsShuttingDown()) { 1925 result.pluginId() = 0; 1926 result.pluginType() = GMPPluginType::Unknown; 1927 result.pid() = base::kInvalidProcessId; 1928 result.result() = NS_ERROR_ILLEGAL_DURING_SHUTDOWN; 1929 result.errorDescription() = "Service is shutting down."_ns; 1930 aResolve(std::move(result)); 1931 return IPC_OK(); 1932 } 1933 1934 nsCString nodeIdString; 1935 nsresult rv = mService->GetNodeId(aNodeIdVariant, nodeIdString); 1936 if (NS_WARN_IF(NS_FAILED(rv))) { 1937 result.pluginId() = 0; 1938 result.pluginType() = GMPPluginType::Unknown; 1939 result.pid() = base::kInvalidProcessId; 1940 result.result() = rv; 1941 result.errorDescription() = "GetNodeId failed."_ns; 1942 aResolve(std::move(result)); 1943 return IPC_OK(); 1944 } 1945 1946 RefPtr<GMPParent> gmp = 1947 mService->SelectPluginForAPI(nodeIdString, aAPI, aTags); 1948 if (gmp) { 1949 result.pluginId() = gmp->GetPluginId(); 1950 result.pluginType() = gmp->GetPluginType(); 1951 } else { 1952 result.pluginId() = 0; 1953 result.pluginType() = GMPPluginType::Unknown; 1954 result.pid() = base::kInvalidProcessId; 1955 result.result() = NS_ERROR_FAILURE; 1956 result.errorDescription() = "SelectPluginForAPI returns nullptr."_ns; 1957 aResolve(std::move(result)); 1958 return IPC_OK(); 1959 } 1960 1961 if (!gmp->EnsureProcessLoaded(&result.pid())) { 1962 result.pid() = base::kInvalidProcessId; 1963 result.result() = NS_ERROR_FAILURE; 1964 result.errorDescription() = "Process has not loaded."_ns; 1965 aResolve(std::move(result)); 1966 return IPC_OK(); 1967 } 1968 1969 MOZ_ASSERT(result.pid() != base::kInvalidProcessId); 1970 1971 result.displayName() = gmp->GetDisplayName(); 1972 1973 if (aAlreadyBridgedTo.Contains(result.pid())) { 1974 result.result() = NS_OK; 1975 aResolve(std::move(result)); 1976 return IPC_OK(); 1977 } 1978 1979 Endpoint<PGMPContentParent> parent; 1980 Endpoint<PGMPContentChild> child; 1981 rv = PGMPContent::CreateEndpoints( 1982 OtherEndpointProcInfo(), gmp->OtherEndpointProcInfo(), &parent, &child); 1983 if (NS_WARN_IF(NS_FAILED(rv))) { 1984 result.result() = rv; 1985 result.errorDescription() = "PGMPContent::CreateEndpoints failed."_ns; 1986 aResolve(std::move(result)); 1987 return IPC_OK(); 1988 } 1989 1990 if (!gmp->SendInitGMPContentChild(std::move(child))) { 1991 result.result() = NS_ERROR_FAILURE; 1992 result.errorDescription() = "SendInitGMPContentChild failed."_ns; 1993 return IPC_OK(); 1994 } 1995 1996 gmp->IncrementGMPContentChildCount(); 1997 1998 result.result() = NS_OK; 1999 result.endpoint() = std::move(parent); 2000 aResolve(std::move(result)); 2001 return IPC_OK(); 2002 } 2003 2004 mozilla::ipc::IPCResult GMPServiceParent::RecvGetGMPNodeId( 2005 const nsAString& aOrigin, const nsAString& aTopLevelOrigin, 2006 const nsAString& aGMPName, GetGMPNodeIdResolver&& aResolve) { 2007 nsCString id; 2008 nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, id); 2009 aResolve(id); 2010 if (!NS_SUCCEEDED(rv)) { 2011 return IPC_FAIL( 2012 this, 2013 "GMPServiceParent::RecvGetGMPNodeId: mService->GetNodeId failed."); 2014 } 2015 return IPC_OK(); 2016 } 2017 2018 class OpenPGMPServiceParent : public mozilla::Runnable { 2019 public: 2020 OpenPGMPServiceParent(RefPtr<GMPServiceParent>&& aGMPServiceParent, 2021 ipc::Endpoint<PGMPServiceParent>&& aEndpoint, 2022 bool* aResult) 2023 : Runnable("gmp::OpenPGMPServiceParent"), 2024 mGMPServiceParent(aGMPServiceParent), 2025 mEndpoint(std::move(aEndpoint)), 2026 mResult(aResult) {} 2027 2028 NS_IMETHOD Run() override { 2029 *mResult = mEndpoint.Bind(mGMPServiceParent); 2030 return NS_OK; 2031 } 2032 2033 private: 2034 RefPtr<GMPServiceParent> mGMPServiceParent; 2035 ipc::Endpoint<PGMPServiceParent> mEndpoint; 2036 bool* mResult; 2037 }; 2038 2039 /* static */ 2040 bool GMPServiceParent::Create(Endpoint<PGMPServiceParent>&& aGMPService) { 2041 RefPtr<GeckoMediaPluginServiceParent> gmp = 2042 GeckoMediaPluginServiceParent::GetSingleton(); 2043 2044 if (!gmp || gmp->mShuttingDown) { 2045 // Shutdown is initiated. There is no point creating a new actor. 2046 return false; 2047 } 2048 2049 nsCOMPtr<nsIThread> gmpThread; 2050 RefPtr<GMPServiceParent> serviceParent; 2051 { 2052 MutexAutoLock lock(gmp->mMutex); 2053 nsresult rv = gmp->GetThreadLocked(getter_AddRefs(gmpThread)); 2054 NS_ENSURE_SUCCESS(rv, false); 2055 serviceParent = new GMPServiceParent(gmp); 2056 } 2057 bool ok; 2058 nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete( 2059 "GMPServiceParent::Create"_ns, gmpThread, 2060 do_AddRef(new OpenPGMPServiceParent(std::move(serviceParent), 2061 std::move(aGMPService), &ok))); 2062 2063 if (NS_WARN_IF(NS_FAILED(rv) || !ok)) { 2064 return false; 2065 } 2066 2067 // Now that the service parent is set up, it will be released by IPC 2068 // refcounting, so we don't need to hold any more references here. 2069 2070 return true; 2071 } 2072 2073 } // namespace mozilla::gmp 2074 2075 #undef __CLASS__