summaryrefslogtreecommitdiffstats
path: root/dom/media/eme/MediaKeySystemAccessManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.cpp684
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