gecko-dev/dom/media/eme/MediaKeySystemAccessManager...

384 строки
15 KiB
C++

/* 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"
#include "nsContentUtils.h"
#include "nsIScriptError.h"
#include "mozilla/Unused.h"
#include "nsDataHashtable.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)) {
// 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 &&
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;
}
nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
nsDataHashtable<nsCharPtrHashKey, bool> warnings;
std::function<void(const char*)> deprecationWarningLogFn =
[&](const char* aMsgName) {
EME_LOG("Logging deprecation warning '%s' to WebConsole.", aMsgName);
warnings.Put(aMsgName, true);
nsString uri;
if (doc) {
Unused << doc->GetDocumentURI(uri);
}
const char16_t* params[] = { uri.get() };
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_LITERAL_CSTRING("Media"),
doc,
nsContentUtils::eDOM_PROPERTIES,
aMsgName,
params,
ArrayLength(params));
};
bool isPrivateBrowsing =
mWindow->GetExtantDoc() &&
mWindow->GetExtantDoc()->NodePrincipal()->GetPrivateBrowsingId() > 0;
MediaKeySystemConfiguration config;
if (MediaKeySystemAccess::GetSupportedConfig(
aKeySystem, aConfigs, config, &diagnostics, isPrivateBrowsing, deprecationWarningLogFn)) {
RefPtr<MediaKeySystemAccess> access(
new MediaKeySystemAccess(mWindow, aKeySystem, config));
aPromise->MaybeResolve(access);
diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
aKeySystem, true, __func__);
// Accumulate telemetry to report whether we hit deprecation warnings.
if (warnings.Get("MediaEMENoCapabilitiesDeprecatedWarning")) {
Telemetry::Accumulate(
Telemetry::HistogramID::MEDIA_EME_REQUEST_DEPRECATED_WARNINGS, 1);
EME_LOG("MEDIA_EME_REQUEST_DEPRECATED_WARNINGS "
"MediaEMENoCapabilitiesDeprecatedWarning");
} else if (warnings.Get("MediaEMENoCodecsDeprecatedWarning")) {
Telemetry::Accumulate(
Telemetry::HistogramID::MEDIA_EME_REQUEST_DEPRECATED_WARNINGS, 2);
EME_LOG("MEDIA_EME_REQUEST_DEPRECATED_WARNINGS "
"MediaEMENoCodecsDeprecatedWarning");
} else {
Telemetry::Accumulate(
Telemetry::HistogramID::MEDIA_EME_REQUEST_DEPRECATED_WARNINGS, 0);
EME_LOG("MEDIA_EME_REQUEST_DEPRECATED_WARNINGS No warnings");
}
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