diff options
Diffstat (limited to 'dom/media/eme/MediaKeySystemAccessManager.cpp')
-rw-r--r-- | dom/media/eme/MediaKeySystemAccessManager.cpp | 340 |
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 |