/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "MediaKeySystemAccessManager.h" #include "DecoderDoctorDiagnostics.h" #include "MediaKeySystemAccessPermissionRequest.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/Document.h" #include "mozilla/DetailedPromise.h" #include "mozilla/EMEUtils.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_media.h" #include "mozilla/Unused.h" #ifdef XP_WIN # include "mozilla/WindowsVersion.h" #endif #ifdef XP_MACOSX # include "nsCocoaFeatures.h" #endif #include "nsComponentManagerUtils.h" #include "nsContentUtils.h" #include "nsDataHashtable.h" #include "nsIObserverService.h" #include "nsIScriptError.h" #include "nsPrintfCString.h" #include "nsServiceManagerUtils.h" namespace mozilla::dom { MediaKeySystemAccessManager::PendingRequest::PendingRequest( DetailedPromise* aPromise, const nsAString& aKeySystem, const Sequence& aConfigs) : mPromise(aPromise), mKeySystem(aKeySystem), mConfigs(aConfigs) { MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest); } MediaKeySystemAccessManager::PendingRequest::~PendingRequest() { MOZ_COUNT_DTOR(MediaKeySystemAccessManager::PendingRequest); } void MediaKeySystemAccessManager::PendingRequest::CancelTimer() { if (mTimer) { mTimer->Cancel(); mTimer = nullptr; } } void MediaKeySystemAccessManager::PendingRequest:: RejectPromiseWithInvalidAccessError(const nsACString& aReason) { if (mPromise) { mPromise->MaybeRejectWithInvalidAccessError(aReason); } } void MediaKeySystemAccessManager::PendingRequest:: RejectPromiseWithNotSupportedError(const nsACString& aReason) { if (mPromise) { mPromise->MaybeRejectWithNotSupportedError(aReason); } } void MediaKeySystemAccessManager::PendingRequest::RejectPromiseWithTypeError( const nsACString& aReason) { if (mPromise) { mPromise->MaybeRejectWithTypeError(aReason); } } NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccessManager) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccessManager) NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccessManager) NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeySystemAccessManager) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeySystemAccessManager) NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) for (size_t i = 0; i < tmp->mPendingInstallRequests.Length(); i++) { tmp->mPendingInstallRequests[i]->CancelTimer(); tmp->mPendingInstallRequests[i]->RejectPromiseWithInvalidAccessError( nsLiteralCString( "Promise still outstanding at MediaKeySystemAccessManager GC")); NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingInstallRequests[i]->mPromise) } tmp->mPendingInstallRequests.Clear(); for (size_t i = 0; i < tmp->mPendingAppApprovalRequests.Length(); i++) { tmp->mPendingAppApprovalRequests[i]->RejectPromiseWithInvalidAccessError( nsLiteralCString( "Promise still outstanding at MediaKeySystemAccessManager GC")); NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingAppApprovalRequests[i]->mPromise) } tmp->mPendingAppApprovalRequests.Clear(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeySystemAccessManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) for (size_t i = 0; i < tmp->mPendingInstallRequests.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingInstallRequests[i]->mPromise) } for (size_t i = 0; i < tmp->mPendingAppApprovalRequests.Length(); i++) { NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingAppApprovalRequests[i]->mPromise) } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END #define MKSAM_LOG_DEBUG(msg, ...) \ EME_LOG("MediaKeySystemAccessManager::%s " msg, __func__, ##__VA_ARGS__) MediaKeySystemAccessManager::MediaKeySystemAccessManager( nsPIDOMWindowInner* aWindow) : mWindow(aWindow) { MOZ_ASSERT(NS_IsMainThread()); } MediaKeySystemAccessManager::~MediaKeySystemAccessManager() { MOZ_ASSERT(NS_IsMainThread()); Shutdown(); } void MediaKeySystemAccessManager::Request( DetailedPromise* aPromise, const nsAString& aKeySystem, const Sequence& aConfigs) { MOZ_ASSERT(NS_IsMainThread()); CheckDoesWindowSupportProtectedMedia( MakeUnique(aPromise, aKeySystem, aConfigs)); } void MediaKeySystemAccessManager::CheckDoesWindowSupportProtectedMedia( UniquePtr aRequest) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRequest); MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); // In Windows OS, some Firefox windows that host content cannot support // protected content, so check the status of support for this window. // On other platforms windows should always support protected media. #ifdef XP_WIN RefPtr browser(BrowserChild::GetFrom(mWindow)); if (!browser) { if (!XRE_IsParentProcess() || XRE_IsE10sParentProcess()) { // In this case, there is no browser because the Navigator object has // been disconnected from its window. Thus, reject the promise. aRequest->mPromise->MaybeRejectWithTypeError( "Browsing context is no longer available"); } else { // In this case, there is no browser because e10s is off. Proceed with // the request with support since this scenario is always supported. MKSAM_LOG_DEBUG("Allowing protected media on Windows with e10s off."); OnDoesWindowSupportProtectedMedia(true, std::move(aRequest)); } return; } RefPtr self(this); MKSAM_LOG_DEBUG( "Checking with browser if this window supports protected media."); browser->DoesWindowSupportProtectedMedia()->Then( GetCurrentSerialEventTarget(), __func__, [self, request = std::move(aRequest)]( const BrowserChild::IsWindowSupportingProtectedMediaPromise:: ResolveOrRejectValue& value) mutable { if (value.IsResolve()) { self->OnDoesWindowSupportProtectedMedia(value.ResolveValue(), std::move(request)); } else { EME_LOG( "MediaKeySystemAccessManager::DoesWindowSupportProtectedMedia-" "ResolveOrRejectLambda Failed to make IPC call to " "IsWindowSupportingProtectedMedia: " "reason=%d", static_cast(value.RejectValue())); // Treat as failure. self->OnDoesWindowSupportProtectedMedia(false, std::move(request)); } }); #else // Non-Windows OS windows always support protected media. MKSAM_LOG_DEBUG( "Allowing protected media because all non-Windows OS windows support " "protected media."); OnDoesWindowSupportProtectedMedia(true, std::move(aRequest)); #endif } void MediaKeySystemAccessManager::OnDoesWindowSupportProtectedMedia( bool aIsSupportedInWindow, UniquePtr aRequest) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRequest); MKSAM_LOG_DEBUG("aIsSupportedInWindow=%s aRequest->mKeySystem=%s", aIsSupportedInWindow ? "true" : "false", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); if (!aIsSupportedInWindow) { aRequest->RejectPromiseWithNotSupportedError( "EME is not supported in this window"_ns); return; } RequestMediaKeySystemAccess(std::move(aRequest)); } void MediaKeySystemAccessManager::CheckDoesAppAllowProtectedMedia( UniquePtr aRequest) { // At time of writing, only GeckoView is expected to leverage the need to // approve EME requests from the application. However, this functionality // can be tested on all platforms by manipulating the // media.eme.require-app-approval + test prefs associated with // MediaKeySystemPermissionRequest. MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRequest); MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); if (!StaticPrefs::media_eme_require_app_approval()) { MKSAM_LOG_DEBUG( "media.eme.require-app-approval is false, allowing request."); // We don't require app approval as the pref is not set. Treat as app // approving by passing true to the handler. OnDoesAppAllowProtectedMedia(true, std::move(aRequest)); return; } if (mAppAllowsProtectedMediaPromiseRequest.Exists()) { // We already have a pending permission request, we don't need to fire // another. Just wait for the existing permission request to be handled // and the result from that will be used to handle the current request. MKSAM_LOG_DEBUG( "mAppAllowsProtectedMediaPromiseRequest already exists. aRequest " "addded to queue and will be handled when exising permission request " "is serviced."); mPendingAppApprovalRequests.AppendElement(std::move(aRequest)); return; } RefPtr appApprovalRequest = MediaKeySystemAccessPermissionRequest::Create(mWindow); if (!appApprovalRequest) { MKSAM_LOG_DEBUG( "Failed to create app approval request! Blocking eme request as " "fallback."); aRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString( "Failed to create approval request to send to app embedding Gecko.")); return; } // If we're not using testing prefs (which take precedence over cached // values) and have a cached value, handle based on the cached value. if (appApprovalRequest->CheckPromptPrefs() == MediaKeySystemAccessPermissionRequest::PromptResult::Pending && mAppAllowsProtectedMedia) { MKSAM_LOG_DEBUG( "Short circuiting based on mAppAllowsProtectedMedia cached value"); OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia, std::move(aRequest)); return; } // Store the approval request, it will be handled when we get a response // from the app. mPendingAppApprovalRequests.AppendElement(std::move(aRequest)); RefPtr p = appApprovalRequest->GetPromise(); p->Then( GetCurrentSerialEventTarget(), __func__, // Allow callback [this, self = RefPtr(this)](bool aRequestResult) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRequestResult, "Result should be true on allow callback!"); mAppAllowsProtectedMediaPromiseRequest.Complete(); // Cache result. mAppAllowsProtectedMedia = Some(aRequestResult); // For each pending request, handle it based on the app's response. for (UniquePtr& approvalRequest : mPendingAppApprovalRequests) { OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia, std::move(approvalRequest)); } self->mPendingAppApprovalRequests.Clear(); }, // Cancel callback [this, self = RefPtr(this)](bool aRequestResult) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aRequestResult, "Result should be false on cancel callback!"); mAppAllowsProtectedMediaPromiseRequest.Complete(); // Cache result. mAppAllowsProtectedMedia = Some(aRequestResult); // For each pending request, handle it based on the app's response. for (UniquePtr& approvalRequest : mPendingAppApprovalRequests) { OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia, std::move(approvalRequest)); } self->mPendingAppApprovalRequests.Clear(); }) ->Track(mAppAllowsProtectedMediaPromiseRequest); // Prefs not causing short circuit, no cached value, go ahead and request // permission. MKSAM_LOG_DEBUG("Dispatching async request for app approval"); if (NS_FAILED(appApprovalRequest->Start())) { // This shouldn't happen unless we're shutting down or similar edge cases. // If this is regularly being hit then something is wrong and should be // investigated. MKSAM_LOG_DEBUG( "Failed to start app approval request! Eme approval will be left in " "limbo!"); } } void MediaKeySystemAccessManager::OnDoesAppAllowProtectedMedia( bool aIsAllowed, UniquePtr aRequest) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRequest); MKSAM_LOG_DEBUG("aIsAllowed=%s aRequest->mKeySystem=%s", aIsAllowed ? "true" : "false", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); if (!aIsAllowed) { aRequest->RejectPromiseWithNotSupportedError( nsLiteralCString("The application embedding this user agent has " "blocked MediaKeySystemAccess")); return; } ProvideAccess(std::move(aRequest)); } void MediaKeySystemAccessManager::RequestMediaKeySystemAccess( UniquePtr aRequest) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRequest); MKSAM_LOG_DEBUG("aIsSupportedInWindow=%s", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); // 1. If keySystem is the empty string, return a promise rejected with a newly // created TypeError. if (aRequest->mKeySystem.IsEmpty()) { aRequest->mPromise->MaybeRejectWithTypeError("Key system string is empty"); // Don't notify DecoderDoctor, as there's nothing we or the user can // do to fix this situation; the site is using the API wrong. return; } // 2. If supportedConfigurations is empty, return a promise rejected with a // newly created TypeError. if (aRequest->mConfigs.IsEmpty()) { aRequest->mPromise->MaybeRejectWithTypeError( "Candidate MediaKeySystemConfigs is empty"); // Don't notify DecoderDoctor, as there's nothing we or the user can // do to fix this situation; the site is using the API wrong. return; } // 3. Let document be the calling context's Document. // 4. Let origin be the origin of document. // 5. Let promise be a new promise. // 6. Run the following steps in parallel: DecoderDoctorDiagnostics diagnostics; // 1. If keySystem is not one of the Key Systems supported by the user // agent, reject promise with a NotSupportedError. String comparison is // case-sensitive. if (!IsWidevineKeySystem(aRequest->mKeySystem) && !IsClearkeyKeySystem(aRequest->mKeySystem)) { // Not to inform user, because nothing to do if the keySystem is not // supported. aRequest->RejectPromiseWithNotSupportedError( "Key system is unsupported"_ns); diagnostics.StoreMediaKeySystemAccess( mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__); return; } if (!StaticPrefs::media_eme_enabled() && !IsClearkeyKeySystem(aRequest->mKeySystem)) { // EME disabled by user, send notification to chrome so UI can inform user. // Clearkey is allowed even when EME is disabled because we want the pref // "media.eme.enabled" only taking effect on proprietary DRMs. // We don't show the notification if the pref is locked. if (!Preferences::IsLocked("media.eme.enabled")) { MediaKeySystemAccess::NotifyObservers(mWindow, aRequest->mKeySystem, MediaKeySystemStatus::Api_disabled); } aRequest->RejectPromiseWithNotSupportedError("EME has been preffed off"_ns); diagnostics.StoreMediaKeySystemAccess( mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__); return; } nsAutoCString message; MediaKeySystemStatus status = MediaKeySystemAccess::GetKeySystemStatus(aRequest->mKeySystem, message); nsPrintfCString msg( "MediaKeySystemAccess::GetKeySystemStatus(%s) " "result=%s msg='%s'", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get(), nsCString(MediaKeySystemStatusValues::GetString(status)).get(), message.get()); LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg)); // We may need to install the CDM to continue. if (status == MediaKeySystemStatus::Cdm_not_installed && IsWidevineKeySystem(aRequest->mKeySystem)) { // These are cases which could be resolved by downloading a new(er) CDM. // When we send the status to chrome, chrome's GMPProvider will attempt to // download or update the CDM. In AwaitInstall() we add listeners to wait // for the update to complete, and we'll call this function again with // aType==Subsequent once the download has completed and the GMPService // has had a new plugin added. AwaitInstall() sets a timer to fail if the // update/download takes too long or fails. if (aRequest->mRequestType != PendingRequest::RequestType::Initial) { MOZ_ASSERT(aRequest->mRequestType == PendingRequest::RequestType::Subsequent); // CDM is not installed, but this is a subsequent request. We've waited, // but can't service this request! Give up. Chrome will still be showing a // "I can't play, updating" notification. aRequest->RejectPromiseWithNotSupportedError( "Timed out while waiting for a CDM update"_ns); diagnostics.StoreMediaKeySystemAccess( mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__); return; } const nsString keySystem = aRequest->mKeySystem; if (AwaitInstall(std::move(aRequest))) { // Notify chrome that we're going to wait for the CDM to download/update. MediaKeySystemAccess::NotifyObservers(mWindow, keySystem, status); } else { // Failed to await the install. Log failure and give up trying to service // this request. diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(), keySystem, false, __func__); } return; } if (status != MediaKeySystemStatus::Available) { // Failed due to user disabling something, send a notification to // chrome, so we can show some UI to explain how the user can rectify // the situation. MediaKeySystemAccess::NotifyObservers(mWindow, aRequest->mKeySystem, status); aRequest->RejectPromiseWithNotSupportedError(message); return; } nsCOMPtr doc = mWindow->GetExtantDoc(); nsDataHashtable warnings; std::function deprecationWarningLogFn = [&](const char* aMsgName) { EME_LOG( "MediaKeySystemAccessManager::DeprecationWarningLambda Logging " "deprecation warning '%s' to WebConsole.", aMsgName); warnings.Put(aMsgName, true); AutoTArray params; nsString& uri = *params.AppendElement(); if (doc) { Unused << doc->GetDocumentURI(uri); } nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Media"_ns, doc, nsContentUtils::eDOM_PROPERTIES, aMsgName, params); }; bool isPrivateBrowsing = mWindow->GetExtantDoc() && mWindow->GetExtantDoc()->NodePrincipal()->GetPrivateBrowsingId() > 0; // 2. Let implementation be the implementation of keySystem. // 3. For each value in supportedConfigurations: // 1. Let candidate configuration be the value. // 2. Let supported configuration be the result of executing the Get // Supported Configuration algorithm on implementation, candidate // configuration, and origin. // 3. If supported configuration is not NotSupported, run the following // steps: // 1. Let access be a new MediaKeySystemAccess object, and initialize it // as follows: // 1. Set the keySystem attribute to keySystem. // 2. Let the configuration value be supported configuration. // 3. Let the cdm implementation value be implementation. // 2. Resolve promise with access and abort the parallel steps of this // algorithm. MediaKeySystemConfiguration config; if (MediaKeySystemAccess::GetSupportedConfig( aRequest->mKeySystem, aRequest->mConfigs, config, &diagnostics, isPrivateBrowsing, deprecationWarningLogFn)) { aRequest->mSupportedConfig = Some(config); // The app gets the final say on if we provide access or not. CheckDoesAppAllowProtectedMedia(std::move(aRequest)); return; } // 4. Reject promise with a NotSupportedError. // Not to inform user, because nothing to do if the corresponding keySystem // configuration is not supported. aRequest->RejectPromiseWithNotSupportedError( "Key system configuration is not supported"_ns); diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(), aRequest->mKeySystem, false, __func__); } void MediaKeySystemAccessManager::ProvideAccess( UniquePtr aRequest) { MOZ_ASSERT(aRequest); MOZ_ASSERT( aRequest->mSupportedConfig, "The request needs a supported config if we're going to provide access!"); MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); DecoderDoctorDiagnostics diagnostics; RefPtr access(new MediaKeySystemAccess( mWindow, aRequest->mKeySystem, aRequest->mSupportedConfig.ref())); aRequest->mPromise->MaybeResolve(access); diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(), aRequest->mKeySystem, true, __func__); } bool MediaKeySystemAccessManager::AwaitInstall( UniquePtr aRequest) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRequest); MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); if (!EnsureObserversAdded()) { NS_WARNING("Failed to add pref observer"); aRequest->RejectPromiseWithNotSupportedError(nsLiteralCString( "Failed trying to setup CDM update: failed adding observers")); return false; } nsCOMPtr timer; NS_NewTimerWithObserver(getter_AddRefs(timer), this, 60 * 1000, nsITimer::TYPE_ONE_SHOT); if (!timer) { NS_WARNING("Failed to create timer to await CDM install."); aRequest->RejectPromiseWithNotSupportedError(nsLiteralCString( "Failed trying to setup CDM update: failed timer creation")); return false; } MOZ_DIAGNOSTIC_ASSERT( aRequest->mTimer == nullptr, "Timer should not already be set on a request we're about to await"); aRequest->mTimer = timer; mPendingInstallRequests.AppendElement(std::move(aRequest)); return true; } void MediaKeySystemAccessManager::RetryRequest( UniquePtr aRequest) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRequest); MKSAM_LOG_DEBUG("aRequest->mKeySystem=%s", NS_ConvertUTF16toUTF8(aRequest->mKeySystem).get()); // Cancel and null timer if it exists. aRequest->CancelTimer(); // Indicate that this is a request that's being retried. aRequest->mRequestType = PendingRequest::RequestType::Subsequent; RequestMediaKeySystemAccess(std::move(aRequest)); } nsresult MediaKeySystemAccessManager::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { MOZ_ASSERT(NS_IsMainThread()); MKSAM_LOG_DEBUG("%s", aTopic); if (!strcmp(aTopic, "gmp-changed")) { // Filter out the requests where the CDM's install-status is no longer // "unavailable". This will be the CDMs which have downloaded since the // initial request. // Note: We don't have a way to communicate from chrome that the CDM has // failed to download, so we'll just let the timeout fail us in that case. nsTArray> requests; for (size_t i = mPendingInstallRequests.Length(); i-- > 0;) { nsAutoCString message; MediaKeySystemStatus status = MediaKeySystemAccess::GetKeySystemStatus( mPendingInstallRequests[i]->mKeySystem, message); if (status == MediaKeySystemStatus::Cdm_not_installed) { // Not yet installed, don't retry. Keep waiting until timeout. continue; } // Status has changed, retry request. requests.AppendElement(std::move(mPendingInstallRequests[i])); mPendingInstallRequests.RemoveElementAt(i); } // Retry all pending requests, but this time fail if the CDM is not // installed. for (size_t i = requests.Length(); i-- > 0;) { RetryRequest(std::move(requests[i])); } } else if (!strcmp(aTopic, "timer-callback")) { // Find the timer that expired and re-run the request for it. nsCOMPtr timer(do_QueryInterface(aSubject)); for (size_t i = 0; i < mPendingInstallRequests.Length(); i++) { if (mPendingInstallRequests[i]->mTimer == timer) { EME_LOG("MediaKeySystemAccessManager::AwaitInstall resuming request"); UniquePtr request = std::move(mPendingInstallRequests[i]); mPendingInstallRequests.RemoveElementAt(i); RetryRequest(std::move(request)); break; } } } return NS_OK; } bool MediaKeySystemAccessManager::EnsureObserversAdded() { MOZ_ASSERT(NS_IsMainThread()); if (mAddedObservers) { return true; } nsCOMPtr obsService = mozilla::services::GetObserverService(); if (NS_WARN_IF(!obsService)) { return false; } mAddedObservers = NS_SUCCEEDED(obsService->AddObserver(this, "gmp-changed", false)); return mAddedObservers; } void MediaKeySystemAccessManager::Shutdown() { MOZ_ASSERT(NS_IsMainThread()); MKSAM_LOG_DEBUG(""); for (const UniquePtr& installRequest : mPendingInstallRequests) { // Cancel all requests; we're shutting down. installRequest->CancelTimer(); installRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString( "Promise still outstanding at MediaKeySystemAccessManager shutdown")); } mPendingInstallRequests.Clear(); for (const UniquePtr& approvalRequest : mPendingAppApprovalRequests) { approvalRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString( "Promise still outstanding at MediaKeySystemAccessManager shutdown")); } mPendingAppApprovalRequests.Clear(); mAppAllowsProtectedMediaPromiseRequest.DisconnectIfExists(); if (mAddedObservers) { nsCOMPtr obsService = mozilla::services::GetObserverService(); if (obsService) { obsService->RemoveObserver(this, "gmp-changed"); mAddedObservers = false; } } } } // namespace mozilla::dom #undef MKSAM_LOG_DEBUG