summaryrefslogtreecommitdiffstats
path: root/dom/media/eme/MediaKeySystemAccess.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/eme/MediaKeySystemAccess.cpp')
-rw-r--r--dom/media/eme/MediaKeySystemAccess.cpp1086
1 files changed, 1086 insertions, 0 deletions
diff --git a/dom/media/eme/MediaKeySystemAccess.cpp b/dom/media/eme/MediaKeySystemAccess.cpp
new file mode 100644
index 0000000000..0d61226eb5
--- /dev/null
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -0,0 +1,1086 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/dom/MediaKeySystemAccess.h"
+
+#include <functional>
+
+#include "DecoderDoctorDiagnostics.h"
+#include "DecoderTraits.h"
+#include "KeySystemConfig.h"
+#include "MediaContainerType.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/KeySystemNames.h"
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/MediaSource.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "nsDOMString.h"
+#include "nsIObserverService.h"
+#include "nsMimeTypes.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "WebMDecoder.h"
+
+#ifdef XP_WIN
+# include "WMFDecoderModule.h"
+#endif
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+static nsCString ToCString(const MediaKeySystemConfiguration& aConfig);
+
+MediaKeySystemAccess::MediaKeySystemAccess(
+ nsPIDOMWindowInner* aParent, const nsAString& aKeySystem,
+ const MediaKeySystemConfiguration& aConfig)
+ : mParent(aParent), mKeySystem(aKeySystem), mConfig(aConfig) {
+ EME_LOG("Created MediaKeySystemAccess for keysystem=%s config=%s",
+ NS_ConvertUTF16toUTF8(mKeySystem).get(),
+ mozilla::dom::ToCString(mConfig).get());
+}
+
+MediaKeySystemAccess::~MediaKeySystemAccess() = default;
+
+JSObject* MediaKeySystemAccess::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return MediaKeySystemAccess_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* MediaKeySystemAccess::GetParentObject() const {
+ return mParent;
+}
+
+void MediaKeySystemAccess::GetKeySystem(nsString& aOutKeySystem) const {
+ aOutKeySystem.Assign(mKeySystem);
+}
+
+void MediaKeySystemAccess::GetConfiguration(
+ MediaKeySystemConfiguration& aConfig) {
+ aConfig = mConfig;
+}
+
+already_AddRefed<Promise> MediaKeySystemAccess::CreateMediaKeys(
+ ErrorResult& aRv) {
+ RefPtr<MediaKeys> keys(new MediaKeys(mParent, mKeySystem, mConfig));
+ return keys->Init(aRv);
+}
+
+static MediaKeySystemStatus EnsureCDMInstalled(const nsAString& aKeySystem,
+ nsACString& aOutMessage) {
+ if (!KeySystemConfig::Supports(aKeySystem)) {
+ aOutMessage = "CDM is not installed"_ns;
+ return MediaKeySystemStatus::Cdm_not_installed;
+ }
+
+ return MediaKeySystemStatus::Available;
+}
+
+/* static */
+MediaKeySystemStatus MediaKeySystemAccess::GetKeySystemStatus(
+ const nsAString& aKeySystem, nsACString& aOutMessage) {
+ MOZ_ASSERT(StaticPrefs::media_eme_enabled() ||
+ IsClearkeyKeySystem(aKeySystem));
+
+ if (IsClearkeyKeySystem(aKeySystem)) {
+ return EnsureCDMInstalled(aKeySystem, aOutMessage);
+ }
+
+ if (IsWidevineKeySystem(aKeySystem)) {
+ if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
+ if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
+ aOutMessage = "Widevine EME disabled"_ns;
+ return MediaKeySystemStatus::Cdm_disabled;
+ }
+ return EnsureCDMInstalled(aKeySystem, aOutMessage);
+#ifdef MOZ_WIDGET_ANDROID
+ } else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible",
+ false)) {
+ nsCString keySystem = NS_ConvertUTF16toUTF8(aKeySystem);
+ bool supported =
+ mozilla::java::MediaDrmProxy::IsSchemeSupported(keySystem);
+ if (!supported) {
+ aOutMessage = nsLiteralCString(
+ "KeySystem or Minimum API level not met for Widevine EME");
+ return MediaKeySystemStatus::Cdm_not_supported;
+ }
+ return MediaKeySystemStatus::Available;
+#endif
+ }
+ }
+
+#ifdef MOZ_WMF_CDM
+ if (IsPlayReadyKeySystemAndSupported(aKeySystem) &&
+ KeySystemConfig::Supports(aKeySystem)) {
+ return MediaKeySystemStatus::Available;
+ }
+#endif
+
+ return MediaKeySystemStatus::Cdm_not_supported;
+}
+
+static KeySystemConfig::EMECodecString ToEMEAPICodecString(
+ const nsString& aCodec) {
+ if (IsAACCodecString(aCodec)) {
+ return KeySystemConfig::EME_CODEC_AAC;
+ }
+ if (aCodec.EqualsLiteral("opus")) {
+ return KeySystemConfig::EME_CODEC_OPUS;
+ }
+ if (aCodec.EqualsLiteral("vorbis")) {
+ return KeySystemConfig::EME_CODEC_VORBIS;
+ }
+ if (aCodec.EqualsLiteral("flac")) {
+ return KeySystemConfig::EME_CODEC_FLAC;
+ }
+ if (IsH264CodecString(aCodec)) {
+ return KeySystemConfig::EME_CODEC_H264;
+ }
+ if (IsVP8CodecString(aCodec)) {
+ return KeySystemConfig::EME_CODEC_VP8;
+ }
+ if (IsVP9CodecString(aCodec)) {
+ return KeySystemConfig::EME_CODEC_VP9;
+ }
+ return ""_ns;
+}
+
+static nsTArray<KeySystemConfig> GetSupportedKeySystems() {
+ nsTArray<KeySystemConfig> keySystemConfigs;
+
+ const nsTArray<nsString> keySystemNames{
+ NS_ConvertUTF8toUTF16(kClearKeyKeySystemName),
+ NS_ConvertUTF8toUTF16(kWidevineKeySystemName),
+#ifdef MOZ_WMF_CDM
+ NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName),
+ NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware),
+#endif
+ };
+ for (const auto& name : keySystemNames) {
+ KeySystemConfig config;
+ if (KeySystemConfig::GetConfig(name, config)) {
+ if (IsClearkeyKeySystem(name) &&
+ StaticPrefs::media_clearkey_test_key_systems_enabled()) {
+ // Add testing key systems. These offer the same capabilities as the
+ // base clearkey system, so just clone clearkey and change the name.
+ KeySystemConfig clearkeyWithProtectionQuery{config};
+ clearkeyWithProtectionQuery.mKeySystem.AssignLiteral(
+ kClearKeyWithProtectionQueryKeySystemName);
+ keySystemConfigs.AppendElement(std::move(clearkeyWithProtectionQuery));
+ }
+ keySystemConfigs.AppendElement(std::move(config));
+ }
+ }
+
+ return keySystemConfigs;
+}
+
+static bool GetKeySystemConfig(const nsAString& aKeySystem,
+ KeySystemConfig& aOutKeySystemConfig) {
+ for (auto&& config : GetSupportedKeySystems()) {
+ if (config.mKeySystem.Equals(aKeySystem)) {
+ aOutKeySystemConfig = std::move(config);
+ return true;
+ }
+ }
+ // No matching key system found.
+ return false;
+}
+
+/* static */
+bool MediaKeySystemAccess::KeySystemSupportsInitDataType(
+ const nsAString& aKeySystem, const nsAString& aInitDataType) {
+ KeySystemConfig implementation;
+ return GetKeySystemConfig(aKeySystem, implementation) &&
+ implementation.mInitDataTypes.Contains(aInitDataType);
+}
+
+enum CodecType { Audio, Video, Invalid };
+
+static bool CanDecryptAndDecode(
+ const nsString& aKeySystem, const nsString& aContentType,
+ CodecType aCodecType,
+ const KeySystemConfig::ContainerSupport& aContainerSupport,
+ const nsTArray<KeySystemConfig::EMECodecString>& aCodecs,
+ DecoderDoctorDiagnostics* aDiagnostics) {
+ MOZ_ASSERT(aCodecType != Invalid);
+ for (const KeySystemConfig::EMECodecString& codec : aCodecs) {
+ MOZ_ASSERT(!codec.IsEmpty());
+
+ if (aContainerSupport.DecryptsAndDecodes(codec)) {
+ // GMP can decrypt-and-decode this codec.
+ continue;
+ }
+
+ if (aContainerSupport.Decrypts(codec)) {
+ IgnoredErrorResult rv;
+ MediaSource::IsTypeSupported(aContentType, aDiagnostics, rv);
+ if (!rv.Failed()) {
+ // GMP can decrypt and is allowed to return compressed samples to
+ // Gecko to decode, and Gecko has a decoder.
+ continue;
+ }
+ }
+
+ // Neither the GMP nor Gecko can both decrypt and decode. We don't
+ // support this codec.
+
+#if defined(XP_WIN)
+ // Widevine CDM doesn't include an AAC decoder. So if WMF can't
+ // decode AAC, and a codec wasn't specified, be conservative
+ // and reject the MediaKeys request, since we assume Widevine
+ // will be used with AAC.
+ if (codec == KeySystemConfig::EME_CODEC_AAC &&
+ IsWidevineKeySystem(aKeySystem) &&
+ !WMFDecoderModule::CanCreateMFTDecoder(WMFStreamType::AAC)) {
+ if (aDiagnostics) {
+ aDiagnostics->SetKeySystemIssue(
+ DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+ }
+ }
+#endif
+ return false;
+ }
+ return true;
+}
+
+// Returns if an encryption scheme is supported per:
+// https://github.com/WICG/encrypted-media-encryption-scheme/blob/master/explainer.md
+// To be supported the scheme should be one of:
+// - null
+// - missing (which will result in the nsString being set to void and thus null)
+// - one of the schemes supported by the CDM
+// If the pref to enable this behavior is not set, then the value should be
+// empty/null, as the dict member will not be exposed. In this case we will
+// always report support as we would before this feature was implemented.
+static bool SupportsEncryptionScheme(
+ const nsString& aEncryptionScheme,
+ const nsTArray<nsString>& aSupportedEncryptionSchemes) {
+ MOZ_ASSERT(
+ DOMStringIsNull(aEncryptionScheme) ||
+ StaticPrefs::media_eme_encrypted_media_encryption_scheme_enabled(),
+ "Encryption scheme checking support must be preffed on for "
+ "encryptionScheme to be a non-null string");
+ if (DOMStringIsNull(aEncryptionScheme)) {
+ // "A missing or null value indicates that any encryption scheme is
+ // acceptable."
+ return true;
+ }
+ return aSupportedEncryptionSchemes.Contains(aEncryptionScheme);
+}
+
+static bool ToSessionType(const nsAString& aSessionType,
+ MediaKeySessionType& aOutType) {
+ if (aSessionType.Equals(ToString(MediaKeySessionType::Temporary))) {
+ aOutType = MediaKeySessionType::Temporary;
+ return true;
+ }
+ if (aSessionType.Equals(ToString(MediaKeySessionType::Persistent_license))) {
+ aOutType = MediaKeySessionType::Persistent_license;
+ return true;
+ }
+ return false;
+}
+
+// 5.1.1 Is persistent session type?
+static bool IsPersistentSessionType(MediaKeySessionType aSessionType) {
+ return aSessionType == MediaKeySessionType::Persistent_license;
+}
+
+static bool ContainsSessionType(
+ const nsTArray<KeySystemConfig::SessionType>& aTypes,
+ const MediaKeySessionType& aSessionType) {
+ return (aSessionType == MediaKeySessionType::Persistent_license &&
+ aTypes.Contains(KeySystemConfig::SessionType::PersistentLicense)) ||
+ (aSessionType == MediaKeySessionType::Temporary &&
+ aTypes.Contains(KeySystemConfig::SessionType::Temporary));
+}
+
+CodecType GetMajorType(const MediaMIMEType& aMIMEType) {
+ if (aMIMEType.HasAudioMajorType()) {
+ return Audio;
+ }
+ if (aMIMEType.HasVideoMajorType()) {
+ return Video;
+ }
+ return Invalid;
+}
+
+static CodecType GetCodecType(const KeySystemConfig::EMECodecString& aCodec) {
+ if (aCodec.Equals(KeySystemConfig::EME_CODEC_AAC) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_OPUS) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_VORBIS) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_FLAC)) {
+ return Audio;
+ }
+ if (aCodec.Equals(KeySystemConfig::EME_CODEC_H264) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_VP8) ||
+ aCodec.Equals(KeySystemConfig::EME_CODEC_VP9)) {
+ return Video;
+ }
+ return Invalid;
+}
+
+static bool AllCodecsOfType(
+ const nsTArray<KeySystemConfig::EMECodecString>& aCodecs,
+ const CodecType aCodecType) {
+ for (const KeySystemConfig::EMECodecString& codec : aCodecs) {
+ if (GetCodecType(codec) != aCodecType) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool IsParameterUnrecognized(const nsAString& aContentType) {
+ nsAutoString contentType(aContentType);
+ contentType.StripWhitespace();
+
+ nsTArray<nsString> params;
+ nsAString::const_iterator start, end, semicolon, equalSign;
+ contentType.BeginReading(start);
+ contentType.EndReading(end);
+ semicolon = start;
+ // Find any substring between ';' & '='.
+ while (semicolon != end) {
+ if (FindCharInReadable(';', semicolon, end)) {
+ equalSign = ++semicolon;
+ if (FindCharInReadable('=', equalSign, end)) {
+ params.AppendElement(Substring(semicolon, equalSign));
+ semicolon = equalSign;
+ }
+ }
+ }
+
+ for (auto param : params) {
+ if (!param.LowerCaseEqualsLiteral("codecs") &&
+ !param.LowerCaseEqualsLiteral("profiles")) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// 3.1.1.3 Get Supported Capabilities for Audio/Video Type
+static Sequence<MediaKeySystemMediaCapability> GetSupportedCapabilities(
+ const CodecType aCodecType,
+ const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
+ const MediaKeySystemConfiguration& aPartialConfig,
+ const KeySystemConfig& aKeySystem, DecoderDoctorDiagnostics* aDiagnostics,
+ const std::function<void(const char*)>& aDeprecationLogFn) {
+ // Let local accumulated configuration be a local copy of partial
+ // configuration. (Note: It's not necessary for us to maintain a local copy,
+ // as we don't need to test whether capabilites from previous calls to this
+ // algorithm work with the capabilities currently being considered in this
+ // call. )
+
+ // Let supported media capabilities be an empty sequence of
+ // MediaKeySystemMediaCapability dictionaries.
+ Sequence<MediaKeySystemMediaCapability> supportedCapabilities;
+
+ // For each requested media capability in requested media capabilities:
+ for (const MediaKeySystemMediaCapability& capabilities :
+ aRequestedCapabilities) {
+ // Let content type be requested media capability's contentType member.
+ const nsString& contentTypeString = capabilities.mContentType;
+ // Let robustness be requested media capability's robustness member.
+ const nsString& robustness = capabilities.mRobustness;
+ // Optional encryption scheme extension, see
+ // https://github.com/WICG/encrypted-media-encryption-scheme/blob/master/explainer.md
+ // This will only be exposed to JS if
+ // media.eme.encrypted-media-encryption-scheme.enabled is preffed on.
+ const nsString encryptionScheme = capabilities.mEncryptionScheme;
+ // If content type is the empty string, return null.
+ if (contentTypeString.IsEmpty()) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') rejected; "
+ "audio or video capability has empty contentType.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ return Sequence<MediaKeySystemMediaCapability>();
+ }
+ // If content type is an invalid or unrecognized MIME type, continue
+ // to the next iteration.
+ Maybe<MediaContainerType> maybeContainerType =
+ MakeMediaContainerType(contentTypeString);
+ if (!maybeContainerType) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "failed to parse contentTypeString as MIME type.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ const MediaContainerType& containerType = *maybeContainerType;
+ bool invalid = false;
+ nsTArray<KeySystemConfig::EMECodecString> codecs;
+ for (const auto& codecString :
+ containerType.ExtendedType().Codecs().Range()) {
+ KeySystemConfig::EMECodecString emeCodec =
+ ToEMEAPICodecString(nsString(codecString));
+ if (emeCodec.IsEmpty()) {
+ invalid = true;
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "'%s' is an invalid codec string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get(),
+ NS_ConvertUTF16toUTF8(codecString).get());
+ break;
+ }
+ codecs.AppendElement(emeCodec);
+ }
+ if (invalid) {
+ continue;
+ }
+
+ // If the user agent does not support container, continue to the next
+ // iteration. The case-sensitivity of string comparisons is determined by
+ // the appropriate RFC. (Note: Per RFC 6838 [RFC6838], "Both top-level type
+ // and subtype names are case-insensitive."'. We're using
+ // nsContentTypeParser and that is case-insensitive and converts all its
+ // parameter outputs to lower case.)
+ const bool isMP4 =
+ DecoderTraits::IsMP4SupportedType(containerType, aDiagnostics);
+ if (isMP4 && !aKeySystem.mMP4.IsSupported()) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "MP4 requested but unsupported.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ const bool isWebM = WebMDecoder::IsSupportedType(containerType);
+ if (isWebM && !aKeySystem.mWebM.IsSupported()) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s,'%s') unsupported; "
+ "WebM requested but unsupported.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ if (!isMP4 && !isWebM) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "Unsupported or unrecognized container requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+
+ // Let parameters be the RFC 6381[RFC6381] parameters, if any, specified by
+ // content type.
+ // If the user agent does not recognize one or more parameters, continue to
+ // the next iteration.
+ if (IsParameterUnrecognized(contentTypeString)) {
+ continue;
+ }
+
+ // Let media types be the set of codecs and codec constraints specified by
+ // parameters. The case-sensitivity of string comparisons is determined by
+ // the appropriate RFC or other specification.
+ // (Note: codecs array is 'parameter').
+
+ // If media types is empty:
+ if (codecs.IsEmpty()) {
+ // Log deprecation warning to encourage authors to not do this!
+ aDeprecationLogFn("MediaEMENoCodecsDeprecatedWarning");
+ // TODO: Remove this once we're sure it doesn't break the web.
+ // If container normatively implies a specific set of codecs and codec
+ // constraints: Let parameters be that set.
+ if (isMP4) {
+ if (aCodecType == Audio) {
+ codecs.AppendElement(KeySystemConfig::EME_CODEC_AAC);
+ } else if (aCodecType == Video) {
+ codecs.AppendElement(KeySystemConfig::EME_CODEC_H264);
+ }
+ } else if (isWebM) {
+ if (aCodecType == Audio) {
+ codecs.AppendElement(KeySystemConfig::EME_CODEC_VORBIS);
+ } else if (aCodecType == Video) {
+ codecs.AppendElement(KeySystemConfig::EME_CODEC_VP8);
+ }
+ }
+ // Otherwise: Continue to the next iteration.
+ // (Note: all containers we support have implied codecs, so don't continue
+ // here.)
+ }
+
+ // If container type is not strictly a audio/video type, continue to the
+ // next iteration.
+ const auto majorType = GetMajorType(containerType.Type());
+ if (majorType == Invalid) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "MIME type is not an audio or video MIME type.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ if (majorType != aCodecType || !AllCodecsOfType(codecs, aCodecType)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "MIME type mixes audio codecs in video capabilities "
+ "or video codecs in audio capabilities.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ // If robustness is not the empty string and contains an unrecognized
+ // value or a value not supported by implementation, continue to the
+ // next iteration. String comparison is case-sensitive.
+ if (!robustness.IsEmpty()) {
+ if (majorType == Audio &&
+ !aKeySystem.mAudioRobustness.Contains(robustness)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "unsupported robustness string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ if (majorType == Video &&
+ !aKeySystem.mVideoRobustness.Contains(robustness)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "unsupported robustness string.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+ // Note: specified robustness requirements are satisfied.
+ }
+
+ // If preffed on: "In the Get Supported Capabilities for Audio/Video Type
+ // algorithm, implementations must skip capabilities specifying unsupported
+ // encryption schemes."
+ if (!SupportsEncryptionScheme(encryptionScheme,
+ aKeySystem.mEncryptionSchemes)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "encryption scheme unsupported by CDM requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+
+ // If the user agent and implementation definitely support playback of
+ // encrypted media data for the combination of container, media types,
+ // robustness and local accumulated configuration in combination with
+ // restrictions...
+ const auto& containerSupport = isMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM;
+ if (!CanDecryptAndDecode(aKeySystem.mKeySystem, contentTypeString,
+ majorType, containerSupport, codecs,
+ aDiagnostics)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') "
+ "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
+ "codec unsupported by CDM requested.",
+ NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+ NS_ConvertUTF16toUTF8(contentTypeString).get(),
+ NS_ConvertUTF16toUTF8(robustness).get(),
+ NS_ConvertUTF16toUTF8(encryptionScheme).get());
+ continue;
+ }
+
+ // ... add requested media capability to supported media capabilities.
+ if (!supportedCapabilities.AppendElement(capabilities, mozilla::fallible)) {
+ NS_WARNING("GetSupportedCapabilities: Malloc failure");
+ return Sequence<MediaKeySystemMediaCapability>();
+ }
+
+ // Note: omitting steps 3.13.2, our robustness is not sophisticated enough
+ // to require considering all requirements together.
+ }
+ return supportedCapabilities;
+}
+
+// "Get Supported Configuration and Consent" algorithm, steps 4-7 for
+// distinctive identifier, and steps 8-11 for persistent state. The steps
+// are the same for both requirements/features, so we factor them out into
+// a single function.
+static bool CheckRequirement(
+ const MediaKeysRequirement aRequirement,
+ const KeySystemConfig::Requirement aKeySystemRequirement,
+ MediaKeysRequirement& aOutRequirement) {
+ // Let requirement be the value of candidate configuration's member.
+ MediaKeysRequirement requirement = aRequirement;
+ // If requirement is "optional" and feature is not allowed according to
+ // restrictions, set requirement to "not-allowed".
+ if (aRequirement == MediaKeysRequirement::Optional &&
+ aKeySystemRequirement == KeySystemConfig::Requirement::NotAllowed) {
+ requirement = MediaKeysRequirement::Not_allowed;
+ }
+
+ // Follow the steps for requirement from the following list:
+ switch (requirement) {
+ case MediaKeysRequirement::Required: {
+ // If the implementation does not support use of requirement in
+ // combination with accumulated configuration and restrictions, return
+ // NotSupported.
+ if (aKeySystemRequirement == KeySystemConfig::Requirement::NotAllowed) {
+ return false;
+ }
+ break;
+ }
+ case MediaKeysRequirement::Optional: {
+ // Continue with the following steps.
+ break;
+ }
+ case MediaKeysRequirement::Not_allowed: {
+ // If the implementation requires use of feature in combination with
+ // accumulated configuration and restrictions, return NotSupported.
+ if (aKeySystemRequirement == KeySystemConfig::Requirement::Required) {
+ return false;
+ }
+ break;
+ }
+ default: {
+ return false;
+ }
+ }
+
+ // Set the requirement member of accumulated configuration to equal
+ // calculated requirement.
+ aOutRequirement = requirement;
+
+ return true;
+}
+
+// 3.1.1.2, step 12
+// Follow the steps for the first matching condition from the following list:
+// If the sessionTypes member is present in candidate configuration.
+// Let session types be candidate configuration's sessionTypes member.
+// Otherwise let session types be ["temporary"].
+// Note: This returns an empty array on malloc failure.
+static Sequence<nsString> UnboxSessionTypes(
+ const Optional<Sequence<nsString>>& aSessionTypes) {
+ Sequence<nsString> sessionTypes;
+ if (aSessionTypes.WasPassed()) {
+ sessionTypes = aSessionTypes.Value();
+ } else {
+ // Note: fallible. Results in an empty array.
+ (void)sessionTypes.AppendElement(ToString(MediaKeySessionType::Temporary),
+ mozilla::fallible);
+ }
+ return sessionTypes;
+}
+
+// 3.1.1.2 Get Supported Configuration and Consent
+static bool GetSupportedConfig(
+ const KeySystemConfig& aKeySystem,
+ const MediaKeySystemConfiguration& aCandidate,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics, bool aInPrivateBrowsing,
+ const std::function<void(const char*)>& aDeprecationLogFn) {
+ // Let accumulated configuration be a new MediaKeySystemConfiguration
+ // dictionary.
+ MediaKeySystemConfiguration config;
+ // Set the label member of accumulated configuration to equal the label member
+ // of candidate configuration.
+ config.mLabel = aCandidate.mLabel;
+ // If the initDataTypes member of candidate configuration is non-empty, run
+ // the following steps:
+ if (!aCandidate.mInitDataTypes.IsEmpty()) {
+ // Let supported types be an empty sequence of DOMStrings.
+ nsTArray<nsString> supportedTypes;
+ // For each value in candidate configuration's initDataTypes member:
+ for (const nsString& initDataType : aCandidate.mInitDataTypes) {
+ // Let initDataType be the value.
+ // If the implementation supports generating requests based on
+ // initDataType, add initDataType to supported types. String comparison is
+ // case-sensitive. The empty string is never supported.
+ if (aKeySystem.mInitDataTypes.Contains(initDataType)) {
+ supportedTypes.AppendElement(initDataType);
+ }
+ }
+ // If supported types is empty, return NotSupported.
+ if (supportedTypes.IsEmpty()) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "no supported initDataTypes provided.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // Set the initDataTypes member of accumulated configuration to supported
+ // types.
+ if (!config.mInitDataTypes.Assign(supportedTypes)) {
+ return false;
+ }
+ }
+
+ if (!CheckRequirement(aCandidate.mDistinctiveIdentifier,
+ aKeySystem.mDistinctiveIdentifier,
+ config.mDistinctiveIdentifier)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "distinctiveIdentifier requirement not satisfied.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+
+ if (!CheckRequirement(aCandidate.mPersistentState,
+ aKeySystem.mPersistentState, config.mPersistentState)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "persistentState requirement not satisfied.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+
+ if (config.mPersistentState == MediaKeysRequirement::Required &&
+ aInPrivateBrowsing) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "persistentState requested in Private Browsing window.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+
+ Sequence<nsString> sessionTypes(UnboxSessionTypes(aCandidate.mSessionTypes));
+ if (sessionTypes.IsEmpty()) {
+ // Malloc failure.
+ return false;
+ }
+
+ // For each value in session types:
+ for (const auto& sessionTypeString : sessionTypes) {
+ // Let session type be the value.
+ MediaKeySessionType sessionType;
+ if (!ToSessionType(sessionTypeString, sessionType)) {
+ // (Assume invalid sessionType is unsupported as per steps below).
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "invalid session type specified.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // If accumulated configuration's persistentState value is "not-allowed"
+ // and the Is persistent session type? algorithm returns true for session
+ // type return NotSupported.
+ if (config.mPersistentState == MediaKeysRequirement::Not_allowed &&
+ IsPersistentSessionType(sessionType)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "persistent session requested but keysystem doesn't"
+ "support persistent state.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // If the implementation does not support session type in combination
+ // with accumulated configuration and restrictions for other reasons,
+ // return NotSupported.
+ if (!ContainsSessionType(aKeySystem.mSessionTypes, sessionType)) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "session type '%s' unsupported by keySystem.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get(),
+ NS_ConvertUTF16toUTF8(sessionTypeString).get());
+ return false;
+ }
+ // If accumulated configuration's persistentState value is "optional"
+ // and the result of running the Is persistent session type? algorithm
+ // on session type is true, change accumulated configuration's
+ // persistentState value to "required".
+ if (config.mPersistentState == MediaKeysRequirement::Optional &&
+ IsPersistentSessionType(sessionType)) {
+ config.mPersistentState = MediaKeysRequirement::Required;
+ }
+ }
+ // Set the sessionTypes member of accumulated configuration to session types.
+ config.mSessionTypes.Construct(std::move(sessionTypes));
+
+ // If the videoCapabilities and audioCapabilities members in candidate
+ // configuration are both empty, return NotSupported.
+ if (aCandidate.mAudioCapabilities.IsEmpty() &&
+ aCandidate.mVideoCapabilities.IsEmpty()) {
+ // TODO: Most sites using EME still don't pass capabilities, so we
+ // can't reject on it yet without breaking them. So add this later.
+ // Log deprecation warning to encourage authors to not do this!
+ aDeprecationLogFn("MediaEMENoCapabilitiesDeprecatedWarning");
+ }
+
+ // If the videoCapabilities member in candidate configuration is non-empty:
+ if (!aCandidate.mVideoCapabilities.IsEmpty()) {
+ // Let video capabilities be the result of executing the Get Supported
+ // Capabilities for Audio/Video Type algorithm on Video, candidate
+ // configuration's videoCapabilities member, accumulated configuration,
+ // and restrictions.
+ Sequence<MediaKeySystemMediaCapability> caps =
+ GetSupportedCapabilities(Video, aCandidate.mVideoCapabilities, config,
+ aKeySystem, aDiagnostics, aDeprecationLogFn);
+ // If video capabilities is null, return NotSupported.
+ if (caps.IsEmpty()) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "no supported video capabilities.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // Set the videoCapabilities member of accumulated configuration to video
+ // capabilities.
+ config.mVideoCapabilities = std::move(caps);
+ } else {
+ // Otherwise:
+ // Set the videoCapabilities member of accumulated configuration to an empty
+ // sequence.
+ }
+
+ // If the audioCapabilities member in candidate configuration is non-empty:
+ if (!aCandidate.mAudioCapabilities.IsEmpty()) {
+ // Let audio capabilities be the result of executing the Get Supported
+ // Capabilities for Audio/Video Type algorithm on Audio, candidate
+ // configuration's audioCapabilities member, accumulated configuration, and
+ // restrictions.
+ Sequence<MediaKeySystemMediaCapability> caps =
+ GetSupportedCapabilities(Audio, aCandidate.mAudioCapabilities, config,
+ aKeySystem, aDiagnostics, aDeprecationLogFn);
+ // If audio capabilities is null, return NotSupported.
+ if (caps.IsEmpty()) {
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "no supported audio capabilities.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+ // Set the audioCapabilities member of accumulated configuration to audio
+ // capabilities.
+ config.mAudioCapabilities = std::move(caps);
+ } else {
+ // Otherwise:
+ // Set the audioCapabilities member of accumulated configuration to an empty
+ // sequence.
+ }
+
+ // If accumulated configuration's distinctiveIdentifier value is "optional",
+ // follow the steps for the first matching condition from the following list:
+ if (config.mDistinctiveIdentifier == MediaKeysRequirement::Optional) {
+ // If the implementation requires use Distinctive Identifier(s) or
+ // Distinctive Permanent Identifier(s) for any of the combinations
+ // in accumulated configuration
+ if (aKeySystem.mDistinctiveIdentifier ==
+ KeySystemConfig::Requirement::Required) {
+ // Change accumulated configuration's distinctiveIdentifier value to
+ // "required".
+ config.mDistinctiveIdentifier = MediaKeysRequirement::Required;
+ } else {
+ // Otherwise, change accumulated configuration's distinctiveIdentifier
+ // value to "not-allowed".
+ config.mDistinctiveIdentifier = MediaKeysRequirement::Not_allowed;
+ }
+ }
+
+ // If accumulated configuration's persistentState value is "optional", follow
+ // the steps for the first matching condition from the following list:
+ if (config.mPersistentState == MediaKeysRequirement::Optional) {
+ // If the implementation requires persisting state for any of the
+ // combinations in accumulated configuration
+ if (aKeySystem.mPersistentState == KeySystemConfig::Requirement::Required) {
+ // Change accumulated configuration's persistentState value to "required".
+ config.mPersistentState = MediaKeysRequirement::Required;
+ } else {
+ // Otherwise, change accumulated configuration's persistentState
+ // value to "not-allowed".
+ config.mPersistentState = MediaKeysRequirement::Not_allowed;
+ }
+ }
+
+ // Note: Omitting steps 20-22. We don't ask for consent.
+
+#if defined(XP_WIN)
+ // Widevine CDM doesn't include an AAC decoder. So if WMF can't decode AAC,
+ // and a codec wasn't specified, be conservative and reject the MediaKeys
+ // request.
+ if (IsWidevineKeySystem(aKeySystem.mKeySystem) &&
+ (aCandidate.mAudioCapabilities.IsEmpty() ||
+ aCandidate.mVideoCapabilities.IsEmpty()) &&
+ !WMFDecoderModule::CanCreateMFTDecoder(WMFStreamType::AAC)) {
+ if (aDiagnostics) {
+ aDiagnostics->SetKeySystemIssue(
+ DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+ }
+ EME_LOG(
+ "MediaKeySystemConfiguration (label='%s') rejected; "
+ "WMF required for Widevine decoding, but it's not available.",
+ NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+ return false;
+ }
+#endif
+
+ // Return accumulated configuration.
+ aOutConfig = config;
+
+ return true;
+}
+
+/* static */
+bool MediaKeySystemAccess::GetSupportedConfig(
+ const nsAString& aKeySystem,
+ const Sequence<MediaKeySystemConfiguration>& aConfigs,
+ MediaKeySystemConfiguration& aOutConfig,
+ DecoderDoctorDiagnostics* aDiagnostics, bool aIsPrivateBrowsing,
+ const std::function<void(const char*)>& aDeprecationLogFn) {
+ KeySystemConfig implementation;
+ if (!GetKeySystemConfig(aKeySystem, implementation)) {
+ return false;
+ }
+ for (const MediaKeySystemConfiguration& candidate : aConfigs) {
+ if (mozilla::dom::GetSupportedConfig(implementation, candidate, aOutConfig,
+ aDiagnostics, aIsPrivateBrowsing,
+ aDeprecationLogFn)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */
+void MediaKeySystemAccess::NotifyObservers(nsPIDOMWindowInner* aWindow,
+ const nsAString& aKeySystem,
+ MediaKeySystemStatus aStatus) {
+ RequestMediaKeySystemAccessNotification data;
+ data.mKeySystem = aKeySystem;
+ data.mStatus = aStatus;
+ nsAutoString json;
+ data.ToJSON(json);
+ EME_LOG("MediaKeySystemAccess::NotifyObservers() %s",
+ NS_ConvertUTF16toUTF8(json).get());
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(aWindow, MediaKeys::kMediaKeysRequestTopic,
+ json.get());
+ }
+}
+
+static nsCString ToCString(const nsString& aString) {
+ nsCString str("'");
+ str.Append(NS_ConvertUTF16toUTF8(aString));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString ToCString(const MediaKeysRequirement aValue) {
+ nsCString str("'");
+ str.AppendASCII(MediaKeysRequirementValues::GetString(aValue));
+ str.AppendLiteral("'");
+ return str;
+}
+
+static nsCString ToCString(const MediaKeySystemMediaCapability& aValue) {
+ nsCString str;
+ str.AppendLiteral("{contentType=");
+ str.Append(ToCString(aValue.mContentType));
+ str.AppendLiteral(", robustness=");
+ str.Append(ToCString(aValue.mRobustness));
+ str.AppendLiteral(", encryptionScheme=");
+ str.Append(ToCString(aValue.mEncryptionScheme));
+ str.AppendLiteral("}");
+ return str;
+}
+
+template <class Type>
+static nsCString ToCString(const Sequence<Type>& aSequence) {
+ nsCString str;
+ str.AppendLiteral("[");
+ StringJoinAppend(str, ","_ns, aSequence,
+ [](nsACString& dest, const Type& element) {
+ dest.Append(ToCString(element));
+ });
+ str.AppendLiteral("]");
+ return str;
+}
+
+template <class Type>
+static nsCString ToCString(const Optional<Sequence<Type>>& aOptional) {
+ nsCString str;
+ if (aOptional.WasPassed()) {
+ str.Append(ToCString(aOptional.Value()));
+ } else {
+ str.AppendLiteral("[]");
+ }
+ return str;
+}
+
+static nsCString ToCString(const MediaKeySystemConfiguration& aConfig) {
+ nsCString str;
+ str.AppendLiteral("{label=");
+ str.Append(ToCString(aConfig.mLabel));
+
+ str.AppendLiteral(", initDataTypes=");
+ str.Append(ToCString(aConfig.mInitDataTypes));
+
+ str.AppendLiteral(", audioCapabilities=");
+ str.Append(ToCString(aConfig.mAudioCapabilities));
+
+ str.AppendLiteral(", videoCapabilities=");
+ str.Append(ToCString(aConfig.mVideoCapabilities));
+
+ str.AppendLiteral(", distinctiveIdentifier=");
+ str.Append(ToCString(aConfig.mDistinctiveIdentifier));
+
+ str.AppendLiteral(", persistentState=");
+ str.Append(ToCString(aConfig.mPersistentState));
+
+ str.AppendLiteral(", sessionTypes=");
+ str.Append(ToCString(aConfig.mSessionTypes));
+
+ str.AppendLiteral("}");
+
+ return str;
+}
+
+/* static */
+nsCString MediaKeySystemAccess::ToCString(
+ const Sequence<MediaKeySystemConfiguration>& aConfig) {
+ return mozilla::dom::ToCString(aConfig);
+}
+
+} // namespace mozilla::dom