зеркало из https://github.com/mozilla/gecko-dev.git
675 строки
27 KiB
C++
675 строки
27 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 "MediaKeySystemAccessPermissionRequest.h"
|
|
#include "mozilla/dom/BrowserChild.h"
|
|
#include "mozilla/DetailedPromise.h"
|
|
#include "mozilla/EMEUtils.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 "nsDataHashtable.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
|
|
namespace mozilla {
|
|
namespace 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(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->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) &&
|
|
!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();
|
|
nsDataHashtable<nsCharPtrHashKey, bool> warnings;
|
|
std::function<void(const char*)> deprecationWarningLogFn =
|
|
[&](const char* aMsgName) {
|
|
EME_LOG(
|
|
"MediaKeySystemAccessManager::DeprecationWarningLambda Logging "
|
|
"deprecation warning '%s' to WebConsole.",
|
|
aMsgName);
|
|
warnings.Put(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;
|
|
}
|
|
|
|
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 dom
|
|
} // namespace mozilla
|
|
|
|
#undef MKSAM_LOG_DEBUG
|