зеркало из https://github.com/mozilla/gecko-dev.git
384 строки
15 KiB
C++
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
|