diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/eme/MediaKeySystemAccessManager.cpp | 684 |
1 files changed, 684 insertions, 0 deletions
diff --git a/dom/media/eme/MediaKeySystemAccessManager.cpp b/dom/media/eme/MediaKeySystemAccessManager.cpp new file mode 100644 index 0000000000..e6febfef54 --- /dev/null +++ b/dom/media/eme/MediaKeySystemAccessManager.cpp @@ -0,0 +1,684 @@ +/* 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 "VideoUtils.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 "nsTHashMap.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<MediaKeySystemConfiguration>& 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_AMBIGUOUS(nsISupports, nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsINamed) +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<MediaKeySystemConfiguration>& aConfigs) { + MOZ_ASSERT(NS_IsMainThread()); + CheckDoesWindowSupportProtectedMedia( + MakeUnique<PendingRequest>(aPromise, aKeySystem, aConfigs)); +} + +void MediaKeySystemAccessManager::CheckDoesWindowSupportProtectedMedia( + UniquePtr<PendingRequest> 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<BrowserChild> 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<MediaKeySystemAccessManager> 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<int>(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<PendingRequest> 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<PendingRequest> 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<MediaKeySystemAccessPermissionRequest> 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<MediaKeySystemAccessPermissionRequest::RequestPromise> p = + appApprovalRequest->GetPromise(); + p->Then( + GetCurrentSerialEventTarget(), __func__, + // Allow callback + [this, + self = RefPtr<MediaKeySystemAccessManager>(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<PendingRequest>& approvalRequest : + mPendingAppApprovalRequests) { + OnDoesAppAllowProtectedMedia(*mAppAllowsProtectedMedia, + std::move(approvalRequest)); + } + self->mPendingAppApprovalRequests.Clear(); + }, + // Cancel callback + [this, + self = RefPtr<MediaKeySystemAccessManager>(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<PendingRequest>& 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<PendingRequest> 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<PendingRequest> 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) && +#ifdef MOZ_WMF_CDM + !IsPlayReadyKeySystemAndSupported(aRequest->mKeySystem) && +#endif + !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<Document> doc = mWindow->GetExtantDoc(); + nsTHashMap<nsCharPtrHashKey, bool> warnings; + std::function<void(const char*)> deprecationWarningLogFn = + [&](const char* aMsgName) { + EME_LOG( + "MediaKeySystemAccessManager::DeprecationWarningLambda Logging " + "deprecation warning '%s' to WebConsole.", + aMsgName); + warnings.InsertOrUpdate(aMsgName, true); + AutoTArray<nsString, 1> 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<PendingRequest> 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<MediaKeySystemAccess> 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<PendingRequest> 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<nsITimer> 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<PendingRequest> 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<UniquePtr<PendingRequest>> 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<nsITimer> 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<PendingRequest> request = + std::move(mPendingInstallRequests[i]); + mPendingInstallRequests.RemoveElementAt(i); + RetryRequest(std::move(request)); + break; + } + } + } + return NS_OK; +} + +nsresult MediaKeySystemAccessManager::GetName(nsACString& aName) { + aName.AssignLiteral("MediaKeySystemAccessManager"); + return NS_OK; +} + +bool MediaKeySystemAccessManager::EnsureObserversAdded() { + MOZ_ASSERT(NS_IsMainThread()); + if (mAddedObservers) { + return true; + } + + nsCOMPtr<nsIObserverService> 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<PendingRequest>& 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<PendingRequest>& approvalRequest : + mPendingAppApprovalRequests) { + approvalRequest->RejectPromiseWithInvalidAccessError(nsLiteralCString( + "Promise still outstanding at MediaKeySystemAccessManager shutdown")); + } + mPendingAppApprovalRequests.Clear(); + mAppAllowsProtectedMediaPromiseRequest.DisconnectIfExists(); + if (mAddedObservers) { + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) { + obsService->RemoveObserver(this, "gmp-changed"); + mAddedObservers = false; + } + } +} + +} // namespace mozilla::dom + +#undef MKSAM_LOG_DEBUG |