Bug 1846848 - part5 : display MFCDM capabilites in `about:support`. r=fluent-reviewers,gerard-majax,jolin,niklas

This patch implements retrieving the capabilities from MFCDMs. I will
add non-MFCDM support in following patches, eg. Widevine L3, and
ClearKey.

Differential Revision: https://phabricator.services.mozilla.com/D194837
This commit is contained in:
alwu 2023-12-05 01:13:48 +00:00
Родитель f115f2ea3e
Коммит 999157ad9b
13 изменённых файлов: 311 добавлений и 44 удалений

Просмотреть файл

@ -52,6 +52,8 @@
#include "mozilla/ipc/UtilityProcessManager.h"
#include "mozilla/ipc/UtilityProcessHost.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "mozilla/RemoteDecoderManagerChild.h"
#include "mozilla/KeySystemConfig.h"
#include "mozilla/WheelHandlingHelper.h"
#include "IOActivityMonitor.h"
#include "nsNativeTheme.h"
@ -74,6 +76,10 @@
# endif
#endif
#ifdef MOZ_WMF_CDM
# include "mozilla/MFCDMParent.h"
#endif
namespace mozilla::dom {
/* static */
@ -1922,4 +1928,20 @@ void ChromeUtils::NotifyDevToolsClosed(GlobalObject& aGlobal) {
ChromeUtils::sDevToolsOpenedCount--;
}
#ifdef MOZ_WMF_CDM
/* static */
already_AddRefed<Promise> ChromeUtils::GetWMFContentDecryptionModuleInformation(
GlobalObject& aGlobal, ErrorResult& aRv) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
MOZ_ASSERT(global);
RefPtr<Promise> domPromise = Promise::Create(global, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
MOZ_ASSERT(domPromise);
MFCDMCapabilities::GetAllKeySystemsCapabilities(domPromise);
return domPromise.forget();
}
#endif
} // namespace mozilla::dom

Просмотреть файл

@ -308,6 +308,11 @@ class ChromeUtils {
GlobalObject& aGlobal, JSRFPTarget aTarget,
const Nullable<uint64_t>& aOverriddenFingerprintingSettings);
#ifdef MOZ_WMF_CDM
static already_AddRefed<Promise> GetWMFContentDecryptionModuleInformation(
GlobalObject& aGlobal, ErrorResult& aRv);
#endif
private:
// Number of DevTools session debugging the current process
static std::atomic<uint32_t> sDevToolsOpenedCount;

Просмотреть файл

@ -297,6 +297,15 @@ namespace ChromeUtils {
LibcConstants getLibcConstants();
#endif
#ifdef MOZ_WMF_CDM
/**
* Returns the information about all Media Foundation based content decryption
* modules, which would include key system names and their capabilities.
*/
[NewObject]
Promise<sequence<CDMInformation>> getWMFContentDecryptionModuleInformation();
#endif
/**
* IF YOU ADD NEW METHODS HERE, MAKE SURE THEY ARE THREAD-SAFE.
*/
@ -1079,3 +1088,8 @@ dictionary LibcConstants {
#endif
};
#endif
dictionary CDMInformation {
required DOMString keySystemName;
required DOMString capabilities;
};

Просмотреть файл

@ -12,6 +12,11 @@
#include "mozilla/dom/KeySystemNames.h"
#include "mozilla/dom/UnionTypes.h"
#ifdef MOZ_WMF_CDM
# include "mozilla/PMFCDM.h"
# include "KeySystemConfig.h"
#endif
namespace mozilla {
LogModule* GetEMELog() {
@ -140,4 +145,45 @@ const char* EncryptionSchemeStr(const CryptoScheme& aScheme) {
}
}
#ifdef MOZ_WMF_CDM
void MFCDMCapabilitiesIPDLToKeySystemConfig(
const MFCDMCapabilitiesIPDL& aCDMConfig,
KeySystemConfig& aKeySystemConfig) {
aKeySystemConfig.mKeySystem = aCDMConfig.keySystem();
for (const auto& type : aCDMConfig.initDataTypes()) {
aKeySystemConfig.mInitDataTypes.AppendElement(type);
}
for (const auto& type : aCDMConfig.sessionTypes()) {
aKeySystemConfig.mSessionTypes.AppendElement(type);
}
for (const auto& c : aCDMConfig.videoCapabilities()) {
if (!c.robustness().IsEmpty() &&
!aKeySystemConfig.mVideoRobustness.Contains(c.robustness())) {
aKeySystemConfig.mVideoRobustness.AppendElement(c.robustness());
}
aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
NS_ConvertUTF16toUTF8(c.contentType()));
}
for (const auto& c : aCDMConfig.audioCapabilities()) {
if (!c.robustness().IsEmpty() &&
!aKeySystemConfig.mAudioRobustness.Contains(c.robustness())) {
aKeySystemConfig.mAudioRobustness.AppendElement(c.robustness());
}
aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
NS_ConvertUTF16toUTF8(c.contentType()));
}
aKeySystemConfig.mPersistentState = aCDMConfig.persistentState();
aKeySystemConfig.mDistinctiveIdentifier = aCDMConfig.distinctiveID();
for (const auto& scheme : aCDMConfig.encryptionSchemes()) {
aKeySystemConfig.mEncryptionSchemes.AppendElement(
NS_ConvertUTF8toUTF16(EncryptionSchemeStr(scheme)));
}
EME_LOG("New Capabilities=%s",
NS_ConvertUTF16toUTF8(aKeySystemConfig.GetDebugInfo()).get());
}
#endif
} // namespace mozilla

Просмотреть файл

@ -16,6 +16,10 @@
namespace mozilla {
enum class CryptoScheme : uint8_t;
#ifdef MOZ_WMF_CDM
class MFCDMCapabilitiesIPDL;
#endif
struct KeySystemConfig;
namespace dom {
class ArrayBufferViewOrArrayBuffer;
@ -82,6 +86,11 @@ bool IsHardwareDecryptionSupported(
const char* EncryptionSchemeStr(const CryptoScheme& aScheme);
#ifdef MOZ_WMF_CDM
void MFCDMCapabilitiesIPDLToKeySystemConfig(
const MFCDMCapabilitiesIPDL& aCDMConfig, KeySystemConfig& aKeySystemConfig);
#endif
} // namespace mozilla
#endif // EME_LOG_H_

Просмотреть файл

@ -29,47 +29,6 @@ bool WMFCDMImpl::Supports(const nsAString& aKeySystem) {
return s;
}
static void MFCDMCapabilitiesIPDLToKeySystemConfig(
const MFCDMCapabilitiesIPDL& aCDMConfig,
KeySystemConfig& aKeySystemConfig) {
aKeySystemConfig.mKeySystem = aCDMConfig.keySystem();
for (const auto& type : aCDMConfig.initDataTypes()) {
aKeySystemConfig.mInitDataTypes.AppendElement(type);
}
for (const auto& type : aCDMConfig.sessionTypes()) {
aKeySystemConfig.mSessionTypes.AppendElement(type);
}
for (const auto& c : aCDMConfig.videoCapabilities()) {
if (!c.robustness().IsEmpty() &&
!aKeySystemConfig.mVideoRobustness.Contains(c.robustness())) {
aKeySystemConfig.mVideoRobustness.AppendElement(c.robustness());
}
aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
NS_ConvertUTF16toUTF8(c.contentType()));
}
for (const auto& c : aCDMConfig.audioCapabilities()) {
if (!c.robustness().IsEmpty() &&
!aKeySystemConfig.mAudioRobustness.Contains(c.robustness())) {
aKeySystemConfig.mAudioRobustness.AppendElement(c.robustness());
}
aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
NS_ConvertUTF16toUTF8(c.contentType()));
}
aKeySystemConfig.mPersistentState = aCDMConfig.persistentState();
aKeySystemConfig.mDistinctiveIdentifier = aCDMConfig.distinctiveID();
for (const auto& scheme : aCDMConfig.encryptionSchemes()) {
aKeySystemConfig.mEncryptionSchemes.AppendElement(
NS_ConvertUTF8toUTF16(CryptoSchemeToString(scheme)));
}
EME_LOG("New Capabilities=%s",
NS_ConvertUTF16toUTF8(aKeySystemConfig.GetDebugInfo()).get());
}
bool WMFCDMImpl::GetCapabilities(nsTArray<KeySystemConfig>& aOutConfigs) {
nsCOMPtr<nsISerialEventTarget> backgroundTaskQueue;
NS_CreateBackgroundTaskQueue(__func__, getter_AddRefs(backgroundTaskQueue));

Просмотреть файл

@ -10,7 +10,11 @@
#include <propkeydef.h> // For DEFINE_PROPERTYKEY() definition
#include <propvarutil.h> // For InitPropVariantFrom*()
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/KeySystemNames.h"
#include "mozilla/ipc/UtilityAudioDecoderChild.h"
#include "mozilla/ipc/UtilityProcessManager.h"
#include "mozilla/ipc/UtilityProcessParent.h"
#include "mozilla/EMEUtils.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/StaticPrefs_media.h"
@ -1067,6 +1071,76 @@ already_AddRefed<MFCDMProxy> MFCDMParent::GetMFCDMProxy() {
return proxy.forget();
}
/* static */
void MFCDMCapabilities::GetAllKeySystemsCapabilities(dom::Promise* aPromise) {
const static auto kSandboxKind = ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM;
LaunchMFCDMProcessIfNeeded(kSandboxKind)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[promise = RefPtr(aPromise)]() {
RefPtr<ipc::UtilityAudioDecoderChild> uadc =
ipc::UtilityAudioDecoderChild::GetSingleton(kSandboxKind);
if (NS_WARN_IF(!uadc)) {
promise->MaybeReject(NS_ERROR_FAILURE);
return;
}
uadc->GetKeySystemCapabilities(promise);
},
[promise = RefPtr(aPromise)](nsresult aError) {
promise->MaybeReject(NS_ERROR_FAILURE);
});
}
/* static */
RefPtr<GenericNonExclusivePromise>
MFCDMCapabilities::LaunchMFCDMProcessIfNeeded(ipc::SandboxingKind aSandbox) {
MOZ_ASSERT(aSandbox == ipc::SandboxingKind::MF_MEDIA_ENGINE_CDM);
RefPtr<ipc::UtilityProcessManager> utilityProc =
ipc::UtilityProcessManager::GetSingleton();
if (NS_WARN_IF(!utilityProc)) {
NS_WARNING("Failed to get UtilityProcessManager");
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
// Check if the MFCDM process exists or not. If not, launch it.
if (utilityProc->Process(aSandbox)) {
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
}
RefPtr<ipc::UtilityAudioDecoderChild> uadc =
ipc::UtilityAudioDecoderChild::GetSingleton(aSandbox);
if (NS_WARN_IF(!uadc)) {
NS_WARNING("Failed to get UtilityAudioDecoderChild");
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
return utilityProc->StartUtility(uadc, aSandbox)
->Then(
GetMainThreadSerialEventTarget(), __func__,
[uadc, utilityProc, aSandbox]() {
RefPtr<ipc::UtilityProcessParent> parent =
utilityProc->GetProcessParent(aSandbox);
if (!parent) {
NS_WARNING("UtilityAudioDecoderParent lost in the middle");
return GenericNonExclusivePromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
}
if (!uadc->CanSend()) {
NS_WARNING("UtilityAudioDecoderChild lost in the middle");
return GenericNonExclusivePromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
}
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
},
[](nsresult aError) {
NS_WARNING("Failed to start the MFCDM process!");
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
});
}
#undef MFCDM_REJECT_IF_FAILED
#undef MFCDM_REJECT_IF
#undef MFCDM_RETURN_IF_FAILED

Просмотреть файл

@ -140,6 +140,16 @@ class MFCDMParent final : public PMFCDMParent {
MediaEventListener mExpirationListener;
};
// A helper class to display CDM capabilites in `about:support`.
class MFCDMCapabilities {
public:
static void GetAllKeySystemsCapabilities(dom::Promise* aPromise);
private:
static RefPtr<GenericNonExclusivePromise> LaunchMFCDMProcessIfNeeded(
ipc::SandboxingKind aSandbox);
};
} // namespace mozilla
#endif // DOM_MEDIA_IPC_MFCDMPARENT_H_

Просмотреть файл

@ -20,6 +20,7 @@
#endif
#ifdef MOZ_WMF_CDM
# include "mozilla/dom/Promise.h"
# include "mozilla/EMEUtils.h"
# include "mozilla/PMFCDM.h"
#endif
@ -179,11 +180,14 @@ bool UtilityAudioDecoderChild::CreateVideoBridge() {
#endif
#ifdef MOZ_WMF_CDM
void UtilityAudioDecoderChild::GetKeySystemCapabilities() {
void UtilityAudioDecoderChild::GetKeySystemCapabilities(
dom::Promise* aPromise) {
EME_LOG("Ask capabilities for all supported CDMs");
SendGetKeySystemCapabilities()->Then(
NS_GetCurrentThread(), __func__,
[](CopyableTArray<MFCDMCapabilitiesIPDL>&& result) {
[promise = RefPtr<dom::Promise>(aPromise)](
CopyableTArray<MFCDMCapabilitiesIPDL>&& result) {
FallibleTArray<dom::CDMInformation> cdmInfo;
for (const auto& capabilities : result) {
EME_LOG("Received capabilities for %s",
NS_ConvertUTF16toUTF8(capabilities.keySystem()).get());
@ -199,7 +203,18 @@ void UtilityAudioDecoderChild::GetKeySystemCapabilities() {
EME_LOG(" capabilities: encryptionScheme=%s",
EncryptionSchemeStr(e));
}
auto* info = cdmInfo.AppendElement(fallible);
if (!info) {
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
return;
}
info->mKeySystemName = capabilities.keySystem();
KeySystemConfig config;
MFCDMCapabilitiesIPDLToKeySystemConfig(capabilities, config);
info->mCapabilities = config.GetDebugInfo();
}
promise->MaybeResolve(cdmInfo);
},
[](const mozilla::ipc::ResponseRejectReason& aReason) {
EME_LOG("IPC failure for GetKeySystemCapabilities!");

Просмотреть файл

@ -108,7 +108,7 @@ class UtilityAudioDecoderChild final : public PUtilityAudioDecoderChild
#endif
#ifdef MOZ_WMF_CDM
void GetKeySystemCapabilities();
void GetKeySystemCapabilities(dom::Promise* aPromise);
#endif
private:

Просмотреть файл

@ -1105,6 +1105,94 @@ var snapshotFormatters = {
.catch(e => {});
}
function createCDMInfoRow(cdmInfo) {
function findElementInArray(array, name) {
const rv = array.find(element => element.includes(name));
return rv ? rv.split("=")[1] : "Unknown";
}
function getAudioRobustness(array) {
return findElementInArray(array, "audio-robustness");
}
function getVideoRobustness(array) {
return findElementInArray(array, "video-robustness");
}
function getSupportedCodecs(array) {
const mp4Content = findElementInArray(array, "MP4");
const webContent = findElementInArray(array, "WEBM");
const mp4DecodingAndDecryptingCodecs = mp4Content
.match(/decoding-and-decrypting:\[([^\]]*)\]/)[1]
.split(",");
const webmDecodingAndDecryptingCodecs = webContent
.match(/decoding-and-decrypting:\[([^\]]*)\]/)[1]
.split(",");
const mp4DecryptingOnlyCodecs = mp4Content
.match(/decrypting-only:\[([^\]]*)\]/)[1]
.split(",");
const webmDecryptingOnlyCodecs = webContent
.match(/decrypting-only:\[([^\]]*)\]/)[1]
.split(",");
// Combine and get unique codecs for decoding-and-decrypting (always)
// and decrypting-only (only set when it's not empty)
let rv = {};
rv.decodingAndDecrypting = [
...new Set(
[
...mp4DecodingAndDecryptingCodecs,
...webmDecodingAndDecryptingCodecs,
].filter(Boolean)
),
];
let temp = [
...new Set(
[...mp4DecryptingOnlyCodecs, ...webmDecryptingOnlyCodecs].filter(
Boolean
)
),
];
if (temp.length) {
rv.decryptingOnly = temp;
}
return rv;
}
function getCapabilities(array) {
let capabilities = {};
capabilities.persistent = findElementInArray(array, "persistent");
capabilities.distinctive = findElementInArray(array, "distinctive");
capabilities.sessionType = findElementInArray(array, "sessionType");
capabilities.scheme = findElementInArray(array, "scheme");
capabilities.codec = getSupportedCodecs(array);
return JSON.stringify(capabilities);
}
const rvArray = cdmInfo.capabilities.split(" ");
return $.new("tr", [
$.new("td", cdmInfo.keySystemName),
$.new("td", getVideoRobustness(rvArray)),
$.new("td", getAudioRobustness(rvArray)),
$.new("td", getCapabilities(rvArray)),
]);
}
async function insertContentDecryptionModuleInfo() {
let rows = [];
// Retrieve information from WMFCDM, only works when MOZ_WMF_CDM is true
if (ChromeUtils.getWMFContentDecryptionModuleInformation !== undefined) {
const cdmInfo =
await ChromeUtils.getWMFContentDecryptionModuleInformation();
for (let info of cdmInfo) {
rows.push(createCDMInfoRow(info));
}
}
$.append($("media-content-decryption-modules-tbody"), rows);
}
// Basic information
insertBasicInfo("audio-backend", data.currentAudioBackend);
insertBasicInfo("max-audio-channels", data.currentMaxAudioChannels);
@ -1258,6 +1346,9 @@ var snapshotFormatters = {
if (["win", "macosx", "linux", "android"].includes(AppConstants.platform)) {
insertBasicInfo("media-codec-support-info", supportInfo);
}
// CDM info
insertContentDecryptionModuleInfo();
},
remoteAgent(data) {

Просмотреть файл

@ -565,6 +565,18 @@
</tr>
</tbody>
<tbody id="media-content-decryption-modules-tbody">
<tr>
<th colspan="4" class="title-column" data-l10n-id="media-content-decryption-modules-title"/>
</tr>
<tr>
<th data-l10n-id="media-key-system-name"/>
<th data-l10n-id="media-video-robustness"/>
<th data-l10n-id="media-audio-robustness"/>
<th data-l10n-id="media-cdm-capabilities"/>
</tr>
</tbody>
</table>
<!-- - - - - - - - - - - - - - - - - - - - - -->

Просмотреть файл

@ -192,6 +192,16 @@ media-codec-support-unsupported = Unsupported
media-codec-support-error = Codec support information unavailable. Try again after playing back a media file.
media-codec-support-lack-of-extension = Install extension
## Media Content Decryption Modules (CDM)
## See EME Spec for more explanation for following technical terms
## https://w3c.github.io/encrypted-media/
media-content-decryption-modules-title = Content Decryption Modules Information
media-key-system-name = Key System Name
media-video-robustness = Video Robustness
media-audio-robustness = Audio Robustness
media-cdm-capabilities = Capabilities
##
intl-title = Internationalization & Localization