summaryrefslogtreecommitdiffstats
path: root/dom/media/eme/MediaKeySystemAccessManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/eme/MediaKeySystemAccessManager.cpp')
-rw-r--r--dom/media/eme/MediaKeySystemAccessManager.cpp340
1 files changed, 340 insertions, 0 deletions
diff --git a/dom/media/eme/MediaKeySystemAccessManager.cpp b/dom/media/eme/MediaKeySystemAccessManager.cpp
new file mode 100644
index 000000000..8fefc62ec
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -0,0 +1,340 @@
+/* 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 "MediaPrefs.h"
+#include "mozilla/EMEUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/DetailedPromise.h"
+#ifdef XP_WIN
+#include "mozilla/WindowsVersion.h"
+#endif
+#ifdef XP_MACOSX
+#include "nsCocoaFeatures.h"
+#endif
+#include "nsPrintfCString.h"
+
+namespace mozilla {
+namespace dom {
+
+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->mRequests.Length(); i++) {
+ tmp->mRequests[i].RejectPromise(NS_LITERAL_CSTRING("Promise still outstanding at MediaKeySystemAccessManager GC"));
+ tmp->mRequests[i].CancelTimer();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mRequests[i].mPromise)
+ }
+ tmp->mRequests.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->mRequests.Length(); i++) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRequests[i].mPromise)
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+MediaKeySystemAccessManager::MediaKeySystemAccessManager(nsPIDOMWindowInner* aWindow)
+ : mWindow(aWindow)
+ , mAddedObservers(false)
+{
+}
+
+MediaKeySystemAccessManager::~MediaKeySystemAccessManager()
+{
+ Shutdown();
+}
+
+void
+MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+{
+ Request(aPromise, aKeySystem, aConfigs, RequestType::Initial);
+}
+
+void
+MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ RequestType aType)
+{
+ EME_LOG("MediaKeySystemAccessManager::Request %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
+
+ if (aKeySystem.IsEmpty()) {
+ aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("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;
+ }
+ if (aConfigs.IsEmpty()) {
+ aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+ NS_LITERAL_CSTRING("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;
+ }
+
+ DecoderDoctorDiagnostics diagnostics;
+
+ // Ensure keysystem is supported.
+ if (!IsWidevineKeySystem(aKeySystem) &&
+ !IsClearkeyKeySystem(aKeySystem) &&
+ !IsPrimetimeKeySystem(aKeySystem)) {
+ // Not to inform user, because nothing to do if the keySystem is not
+ // supported.
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Key system is unsupported"));
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+ return;
+ }
+
+ if (!MediaPrefs::EMEEnabled() && !IsClearkeyKeySystem(aKeySystem)) {
+ // 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.
+ MediaKeySystemAccess::NotifyObservers(mWindow,
+ aKeySystem,
+ MediaKeySystemStatus::Api_disabled);
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("EME has been preffed off"));
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+ return;
+ }
+
+ nsAutoCString message;
+ MediaKeySystemStatus status =
+ MediaKeySystemAccess::GetKeySystemStatus(aKeySystem, message);
+
+ nsPrintfCString msg("MediaKeySystemAccess::GetKeySystemStatus(%s) "
+ "result=%s msg='%s'",
+ NS_ConvertUTF16toUTF8(aKeySystem).get(),
+ MediaKeySystemStatusValues::strings[(size_t)status].value,
+ message.get());
+ LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
+
+ if (status == MediaKeySystemStatus::Cdm_not_installed &&
+ (IsPrimetimeKeySystem(aKeySystem) || IsWidevineKeySystem(aKeySystem))) {
+ // 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 (aType == RequestType::Initial &&
+ AwaitInstall(aPromise, aKeySystem, aConfigs)) {
+ // Notify chrome that we're going to wait for the CDM to download/update.
+ // Note: If we're re-trying, we don't re-send the notification,
+ // as chrome is already displaying the "we can't play, updating"
+ // notification.
+ MediaKeySystemAccess::NotifyObservers(mWindow, aKeySystem, status);
+ } else {
+ // We waited or can't wait for an update and we still can't service
+ // the request. Give up. Chrome will still be showing a "I can't play,
+ // updating" notification.
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Gave up while waiting for a CDM update"));
+ }
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, 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, aKeySystem, status);
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, message);
+ return;
+ }
+
+ MediaKeySystemConfiguration config;
+ if (MediaKeySystemAccess::GetSupportedConfig(aKeySystem, aConfigs, config, &diagnostics)) {
+ RefPtr<MediaKeySystemAccess> access(
+ new MediaKeySystemAccess(mWindow, aKeySystem, config));
+ aPromise->MaybeResolve(access);
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, true, __func__);
+ return;
+ }
+ // Not to inform user, because nothing to do if the corresponding keySystem
+ // configuration is not supported.
+ aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
+ NS_LITERAL_CSTRING("Key system configuration is not supported"));
+ diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+ aKeySystem, false, __func__);
+}
+
+MediaKeySystemAccessManager::PendingRequest::PendingRequest(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ nsITimer* aTimer)
+ : mPromise(aPromise)
+ , mKeySystem(aKeySystem)
+ , mConfigs(aConfigs)
+ , mTimer(aTimer)
+{
+ MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+MediaKeySystemAccessManager::PendingRequest::PendingRequest(const PendingRequest& aOther)
+ : mPromise(aOther.mPromise)
+ , mKeySystem(aOther.mKeySystem)
+ , mConfigs(aOther.mConfigs)
+ , mTimer(aOther.mTimer)
+{
+ MOZ_COUNT_CTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+MediaKeySystemAccessManager::PendingRequest::~PendingRequest()
+{
+ MOZ_COUNT_DTOR(MediaKeySystemAccessManager::PendingRequest);
+}
+
+void
+MediaKeySystemAccessManager::PendingRequest::CancelTimer()
+{
+ if (mTimer) {
+ mTimer->Cancel();
+ }
+}
+
+void
+MediaKeySystemAccessManager::PendingRequest::RejectPromise(const nsCString& aReason)
+{
+ if (mPromise) {
+ mPromise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR, aReason);
+ }
+}
+
+bool
+MediaKeySystemAccessManager::AwaitInstall(DetailedPromise* aPromise,
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs)
+{
+ EME_LOG("MediaKeySystemAccessManager::AwaitInstall %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
+
+ if (!EnsureObserversAdded()) {
+ NS_WARNING("Failed to add pref observer");
+ return false;
+ }
+
+ nsCOMPtr<nsITimer> timer(do_CreateInstance("@mozilla.org/timer;1"));
+ if (!timer || NS_FAILED(timer->Init(this, 60 * 1000, nsITimer::TYPE_ONE_SHOT))) {
+ NS_WARNING("Failed to create timer to await CDM install.");
+ return false;
+ }
+
+ mRequests.AppendElement(PendingRequest(aPromise, aKeySystem, aConfigs, timer));
+ return true;
+}
+
+void
+MediaKeySystemAccessManager::RetryRequest(PendingRequest& aRequest)
+{
+ aRequest.CancelTimer();
+ Request(aRequest.mPromise, aRequest.mKeySystem, aRequest.mConfigs, RequestType::Subsequent);
+}
+
+nsresult
+MediaKeySystemAccessManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ EME_LOG("MediaKeySystemAccessManager::Observe %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<PendingRequest> requests;
+ for (size_t i = mRequests.Length(); i-- > 0; ) {
+ PendingRequest& request = mRequests[i];
+ nsAutoCString message;
+ MediaKeySystemStatus status =
+ MediaKeySystemAccess::GetKeySystemStatus(request.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(Move(request));
+ mRequests.RemoveElementAt(i);
+ }
+ // Retry all pending requests, but this time fail if the CDM is not installed.
+ for (PendingRequest& request : requests) {
+ RetryRequest(request);
+ }
+ } 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 < mRequests.Length(); i++) {
+ if (mRequests[i].mTimer == timer) {
+ EME_LOG("MediaKeySystemAccessManager::AwaitInstall resuming request");
+ PendingRequest request = mRequests[i];
+ mRequests.RemoveElementAt(i);
+ RetryRequest(request);
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+bool
+MediaKeySystemAccessManager::EnsureObserversAdded()
+{
+ 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()
+{
+ EME_LOG("MediaKeySystemAccessManager::Shutdown");
+ nsTArray<PendingRequest> requests(Move(mRequests));
+ for (PendingRequest& request : requests) {
+ // Cancel all requests; we're shutting down.
+ request.CancelTimer();
+ request.RejectPromise(NS_LITERAL_CSTRING("Promise still outstanding at MediaKeySystemAccessManager shutdown"));
+ }
+ if (mAddedObservers) {
+ nsCOMPtr<nsIObserverService> obsService = mozilla::services::GetObserverService();
+ if (obsService) {
+ obsService->RemoveObserver(this, "gmp-changed");
+ mAddedObservers = false;
+ }
+ }
+}
+
+} // namespace dom
+} // namespace mozilla