tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MediaKeySystemAccess.cpp (52427B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/MediaKeySystemAccess.h"
      8 
      9 #include <functional>
     10 
     11 #include "DecoderDoctorDiagnostics.h"
     12 #include "DecoderTraits.h"
     13 #include "MP4Decoder.h"
     14 #include "MediaContainerType.h"
     15 #include "WebMDecoder.h"
     16 #include "mozilla/ClearOnShutdown.h"
     17 #include "mozilla/EMEUtils.h"
     18 #include "mozilla/Preferences.h"
     19 #include "mozilla/Services.h"
     20 #include "mozilla/StaticPrefs_media.h"
     21 #include "mozilla/dom/Document.h"
     22 #include "mozilla/dom/KeySystemNames.h"
     23 #include "mozilla/dom/MediaKeySession.h"
     24 #include "mozilla/dom/MediaKeySystemAccessBinding.h"
     25 #include "mozilla/dom/MediaKeySystemAccessManager.h"
     26 #include "mozilla/dom/MediaSource.h"
     27 #include "mozilla/dom/PContent.h"
     28 #include "nsDOMString.h"
     29 #include "nsIObserverService.h"
     30 #include "nsMimeTypes.h"
     31 #include "nsReadableUtils.h"
     32 #include "nsServiceManagerUtils.h"
     33 #include "nsUnicharUtils.h"
     34 
     35 #ifdef XP_WIN
     36 #  include "PDMFactory.h"
     37 #  include "WMFDecoderModule.h"
     38 #endif
     39 #ifdef MOZ_WIDGET_ANDROID
     40 #  include "mozilla/java/MediaDrmProxyWrappers.h"
     41 #endif
     42 
     43 namespace mozilla::dom {
     44 
     45 #ifdef MOZ_WMF_CDM
     46 #  include "nsIWindowsMediaFoundationCDMOriginsListService.h"
     47 
     48 constinit static nsTArray<IPCOriginStatusEntry> sOriginStatusEntries;
     49 #endif
     50 
     51 #define LOG(msg, ...) \
     52  EME_LOG("MediaKeySystemAccess::%s " msg, __func__, ##__VA_ARGS__)
     53 
     54 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess, mParent)
     55 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
     56 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
     57 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeySystemAccess)
     58  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
     59  NS_INTERFACE_MAP_ENTRY(nsISupports)
     60 NS_INTERFACE_MAP_END
     61 
     62 MediaKeySystemAccess::MediaKeySystemAccess(
     63    nsPIDOMWindowInner* aParent, const nsAString& aKeySystem,
     64    const MediaKeySystemConfiguration& aConfig)
     65    : mParent(aParent), mKeySystem(aKeySystem), mConfig(aConfig) {
     66  LOG("Created MediaKeySystemAccess for keysystem=%s config=%s",
     67      NS_ConvertUTF16toUTF8(mKeySystem).get(), ToCString(mConfig).get());
     68 }
     69 
     70 MediaKeySystemAccess::~MediaKeySystemAccess() = default;
     71 
     72 JSObject* MediaKeySystemAccess::WrapObject(JSContext* aCx,
     73                                           JS::Handle<JSObject*> aGivenProto) {
     74  return MediaKeySystemAccess_Binding::Wrap(aCx, this, aGivenProto);
     75 }
     76 
     77 nsPIDOMWindowInner* MediaKeySystemAccess::GetParentObject() const {
     78  return mParent;
     79 }
     80 
     81 void MediaKeySystemAccess::GetKeySystem(nsString& aOutKeySystem) const {
     82  aOutKeySystem.Assign(mKeySystem);
     83 }
     84 
     85 void MediaKeySystemAccess::GetConfiguration(
     86    MediaKeySystemConfiguration& aConfig) {
     87  aConfig = mConfig;
     88 }
     89 
     90 already_AddRefed<Promise> MediaKeySystemAccess::CreateMediaKeys(
     91    ErrorResult& aRv) {
     92  RefPtr<MediaKeys> keys(new MediaKeys(mParent, mKeySystem, mConfig));
     93  return keys->Init(aRv);
     94 }
     95 
     96 enum class SecureLevel {
     97  Software,
     98  Hardware,
     99 };
    100 
    101 static MediaKeySystemStatus EnsureCDMInstalled(const nsAString& aKeySystem,
    102                                               const SecureLevel aSecure,
    103                                               nsACString& aOutMessage) {
    104  if (aSecure == SecureLevel::Software &&
    105      !KeySystemConfig::Supports(aKeySystem)) {
    106    aOutMessage = "CDM is not installed"_ns;
    107    return MediaKeySystemStatus::Cdm_not_installed;
    108  }
    109 
    110 #ifdef MOZ_WMF_CDM
    111  if (aSecure == SecureLevel::Hardware) {
    112    // Ensure we check the hardware key system name.
    113    nsAutoString hardwareKeySystem;
    114    if (IsWidevineKeySystem(aKeySystem) ||
    115        IsWidevineExperimentKeySystemAndSupported(aKeySystem)) {
    116      hardwareKeySystem =
    117          NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName);
    118    } else if (IsPlayReadyKeySystemAndSupported(aKeySystem)) {
    119      hardwareKeySystem = NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware);
    120    } else {
    121      MOZ_ASSERT_UNREACHABLE("Not supported key system for HWDRM!");
    122    }
    123    if (!KeySystemConfig::Supports(hardwareKeySystem)) {
    124      aOutMessage = "CDM is not installed"_ns;
    125      return MediaKeySystemStatus::Cdm_not_installed;
    126    }
    127  }
    128 #endif
    129 
    130  return MediaKeySystemStatus::Available;
    131 }
    132 
    133 /* static */
    134 MediaKeySystemStatus MediaKeySystemAccess::GetKeySystemStatus(
    135    const MediaKeySystemAccessRequest& aRequest, nsACString& aOutMessage) {
    136  const nsString& keySystem = aRequest.mKeySystem;
    137 
    138  MOZ_ASSERT(StaticPrefs::media_eme_enabled() ||
    139             IsClearkeyKeySystem(keySystem));
    140 
    141  LOG("checking if CDM is installed or disabled for %s",
    142      NS_ConvertUTF16toUTF8(keySystem).get());
    143  if (IsClearkeyKeySystem(keySystem)) {
    144    return EnsureCDMInstalled(keySystem, SecureLevel::Software, aOutMessage);
    145  }
    146 
    147  // This is used to determine if we need to download Widevine L1.
    148  bool shouldCheckL1Installation = false;
    149 #ifdef MOZ_WMF_CDM
    150  if (StaticPrefs::media_eme_widevine_experiment_enabled()) {
    151    shouldCheckL1Installation =
    152        CheckIfHarewareDRMConfigExists(aRequest.mConfigs) ||
    153        IsWidevineExperimentKeySystemAndSupported(keySystem);
    154  }
    155 #endif
    156 
    157  // Check Widevine L3
    158  if (IsWidevineKeySystem(keySystem) && !shouldCheckL1Installation) {
    159    if (Preferences::GetBool("media.gmp-widevinecdm.visible", false)) {
    160      if (!Preferences::GetBool("media.gmp-widevinecdm.enabled", false)) {
    161        aOutMessage = "Widevine EME disabled"_ns;
    162        return MediaKeySystemStatus::Cdm_disabled;
    163      }
    164      return EnsureCDMInstalled(keySystem, SecureLevel::Software, aOutMessage);
    165 #ifdef MOZ_WIDGET_ANDROID
    166    } else if (Preferences::GetBool("media.mediadrm-widevinecdm.visible",
    167                                    false)) {
    168      bool supported =
    169          mozilla::java::MediaDrmProxy::IsSchemeSupported(keySystem);
    170      if (!supported) {
    171        aOutMessage = nsLiteralCString(
    172            "KeySystem or Minimum API level not met for Widevine EME");
    173        return MediaKeySystemStatus::Cdm_not_supported;
    174      }
    175      return MediaKeySystemStatus::Available;
    176 #endif
    177    }
    178  }
    179 
    180 #ifdef MOZ_WMF_CDM
    181  // Check PlayReady, which is a built-in CDM on most Windows versions.
    182  if (IsPlayReadyKeySystemAndSupported(keySystem) &&
    183      KeySystemConfig::Supports(keySystem)) {
    184    return MediaKeySystemStatus::Available;
    185  }
    186 
    187  // Check Widevine L1. This can be a request for experimental key systems, or
    188  // for a normal key system with hardware robustness.
    189  if ((IsWidevineExperimentKeySystemAndSupported(keySystem) ||
    190       IsWidevineKeySystem(keySystem)) &&
    191      shouldCheckL1Installation) {
    192    // TODO : if L3 hasn't been installed as well, should we fallback to install
    193    // L3?
    194    if (!Preferences::GetBool("media.gmp-widevinecdm-l1.enabled", false)) {
    195      aOutMessage = "Widevine L1 EME disabled"_ns;
    196      return MediaKeySystemStatus::Cdm_disabled;
    197    }
    198    return EnsureCDMInstalled(keySystem, SecureLevel::Hardware, aOutMessage);
    199  }
    200 #endif
    201 
    202  return MediaKeySystemStatus::Cdm_not_supported;
    203 }
    204 
    205 static KeySystemConfig::EMECodecString ToEMEAPICodecString(
    206    const nsString& aCodec) {
    207  if (IsAACCodecString(aCodec)) {
    208    return KeySystemConfig::EME_CODEC_AAC;
    209  }
    210  if (aCodec.EqualsLiteral("opus")) {
    211    return KeySystemConfig::EME_CODEC_OPUS;
    212  }
    213  if (aCodec.EqualsLiteral("vorbis")) {
    214    return KeySystemConfig::EME_CODEC_VORBIS;
    215  }
    216  if (aCodec.EqualsLiteral("flac")) {
    217    return KeySystemConfig::EME_CODEC_FLAC;
    218  }
    219  if (IsH264CodecString(aCodec)) {
    220    return KeySystemConfig::EME_CODEC_H264;
    221  }
    222  if (IsAV1CodecString(aCodec)) {
    223    return KeySystemConfig::EME_CODEC_AV1;
    224  }
    225  if (IsVP8CodecString(aCodec)) {
    226    return KeySystemConfig::EME_CODEC_VP8;
    227  }
    228  if (IsVP9CodecString(aCodec)) {
    229    return KeySystemConfig::EME_CODEC_VP9;
    230  }
    231 #ifdef MOZ_WMF
    232  if (IsH265CodecString(aCodec)) {
    233    return KeySystemConfig::EME_CODEC_HEVC;
    234  }
    235 #endif
    236  return ""_ns;
    237 }
    238 
    239 #ifdef MOZ_WMF_CDM
    240 /* static */
    241 void MediaKeySystemAccess::UpdateMFCDMOriginEntries(
    242    const nsTArray<IPCOriginStatusEntry>& aEntries) {
    243  MOZ_ASSERT(NS_IsMainThread());
    244  static bool sXPCOMShutdown = false;
    245  if (sXPCOMShutdown) {
    246    EME_LOG("XPCOM shutdown detected; entry update aborted");
    247    return;
    248  }
    249  sOriginStatusEntries.Clear();
    250  sOriginStatusEntries.AppendElements(aEntries);
    251  EME_LOG("UpdateMFCDMOriginEntries");
    252  for (const auto& entry : sOriginStatusEntries) {
    253    EME_LOG("-- Origin: %s, Status: %d\n", entry.origin().get(),
    254            entry.status());
    255  }
    256  RunOnShutdown([&] {
    257    sOriginStatusEntries.Clear();
    258    sXPCOMShutdown = true;
    259  });
    260 }
    261 
    262 static bool IsMFCDMAllowedByOrigin(const Maybe<nsCString>& aOrigin) {
    263  // TODO: Remove hardcoded allowed and blocked lists once the Remote Settings
    264  // lists are verified to be reliable in bug 1964811.
    265  // 0 : disabled, 1 : enabled allowed list, 2 : enabled blocked list,
    266  // 3 : enabled list status control via Remote Setting
    267  enum Filer : uint32_t {
    268    eDisable = 0,
    269    eAllowedListEnabled = 1,
    270    eBlockedListEnabled = 2,
    271    eAllowedByDefaultRemoteSettings = 3,
    272    eBlockedByDefaultRemoteSettings = 4,
    273  };
    274  const auto prefValue = StaticPrefs::media_eme_mfcdm_origin_filter_enabled();
    275  if (prefValue == Filer::eDisable || !aOrigin ||
    276      !IsMediaFoundationCDMPlaybackEnabled()) {
    277    // No need to check the origin.
    278    return true;
    279  }
    280 
    281  // Check if the origin is allowed to use MFCDM.
    282  if (prefValue == Filer::eAllowedListEnabled) {
    283    static nsTArray<nsCString> kAllowedOrigins({
    284        "https://www.netflix.com"_ns,
    285    });
    286    for (const auto& allowedOrigin : kAllowedOrigins) {
    287      if (FindInReadable(allowedOrigin, *aOrigin)) {
    288        EME_LOG(
    289            "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin "
    290            "(%s) is ALLOWED to use MFCDM",
    291            aOrigin->get());
    292        return true;
    293      }
    294    }
    295    EME_LOG(
    296        "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin (%s) is "
    297        "not allowed to use MFCDM",
    298        aOrigin->get());
    299    return false;
    300  }
    301 
    302  // Check if the origin is blocked to use MFCDM.
    303  if (prefValue == Filer::eBlockedListEnabled) {
    304    static nsTArray<nsCString> kBlockedOrigins({
    305        "https://on.orf.at"_ns,
    306        "https://www.hulu.com"_ns,
    307    });
    308    for (const auto& blockedOrigin : kBlockedOrigins) {
    309      if (FindInReadable(blockedOrigin, *aOrigin)) {
    310        EME_LOG(
    311            "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin (%s) "
    312            "is BLOCKED to use MFCDM",
    313            aOrigin->get());
    314        return false;
    315      }
    316    }
    317    EME_LOG(
    318        "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin (%s) "
    319        "is allowed to use MFCDM",
    320        aOrigin->get());
    321    return true;
    322  }
    323 
    324  // List from Remote Settings. No duplicated origins; suborigins follow the
    325  // main origin's result.
    326  bool isAllowed = prefValue == Filer::eAllowedByDefaultRemoteSettings;
    327  bool isFound = false;
    328  for (const auto& entry : sOriginStatusEntries) {
    329    // Check if the given origin matches the entry, or if it's a suborigin.
    330    if (FindInReadable(entry.origin(), *aOrigin)) {
    331      isAllowed =
    332          entry.status() ==
    333          nsIWindowsMediaFoundationCDMOriginsListService::ORIGIN_ALLOWED;
    334      isFound = true;
    335      break;
    336    }
    337  }
    338  EME_LOG(
    339      "MediaKeySystemAccess::IsMFCDMAllowedByOrigin, origin (%s) "
    340      "is %s to use MFCDM %s(Remote)",
    341      aOrigin->get(), isAllowed ? "ALLOWED" : "BLOCKED",
    342      isFound ? "" : "by default ");
    343  return isAllowed;
    344 }
    345 #endif
    346 
    347 static RefPtr<KeySystemConfig::SupportedConfigsPromise>
    348 GetSupportedKeySystemConfigs(const nsAString& aKeySystem,
    349                             bool aIsHardwareDecryption,
    350                             bool aIsPrivateBrowsing,
    351                             const Maybe<nsCString>& aOrigin) {
    352  using DecryptionInfo = KeySystemConfig::DecryptionInfo;
    353  nsTArray<KeySystemConfigRequest> requests;
    354 
    355  // Software Widevine and Clearkey
    356  if (IsWidevineKeySystem(aKeySystem) || IsClearkeyKeySystem(aKeySystem)) {
    357    requests.AppendElement(KeySystemConfigRequest{
    358        aKeySystem, DecryptionInfo::Software, aIsPrivateBrowsing});
    359  }
    360 #ifdef MOZ_WMF_CDM
    361  if (IsMFCDMAllowedByOrigin(aOrigin)) {
    362    if (IsPlayReadyEnabled()) {
    363      // PlayReady software and hardware
    364      if (aKeySystem.EqualsLiteral(kPlayReadyKeySystemName) ||
    365          aKeySystem.EqualsLiteral(kPlayReadyKeySystemHardware)) {
    366        requests.AppendElement(KeySystemConfigRequest{
    367            NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName),
    368            DecryptionInfo::Software, aIsPrivateBrowsing});
    369        if (aIsHardwareDecryption) {
    370          requests.AppendElement(KeySystemConfigRequest{
    371              NS_ConvertUTF8toUTF16(kPlayReadyKeySystemName),
    372              DecryptionInfo::Hardware, aIsPrivateBrowsing});
    373          requests.AppendElement(KeySystemConfigRequest{
    374              NS_ConvertUTF8toUTF16(kPlayReadyKeySystemHardware),
    375              DecryptionInfo::Hardware, aIsPrivateBrowsing});
    376        }
    377      }
    378      // PlayReady clearlead
    379      if (aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) {
    380        requests.AppendElement(KeySystemConfigRequest{
    381            NS_ConvertUTF8toUTF16(kPlayReadyHardwareClearLeadKeySystemName),
    382            DecryptionInfo::Hardware, aIsPrivateBrowsing});
    383      }
    384    }
    385 
    386    if (IsWidevineHardwareDecryptionEnabled()) {
    387      // Widevine hardware
    388      if (aKeySystem.EqualsLiteral(kWidevineExperimentKeySystemName) ||
    389          (IsWidevineKeySystem(aKeySystem) && aIsHardwareDecryption)) {
    390        requests.AppendElement(KeySystemConfigRequest{
    391            NS_ConvertUTF8toUTF16(kWidevineExperimentKeySystemName),
    392            DecryptionInfo::Hardware, aIsPrivateBrowsing});
    393      }
    394      // Widevine clearlead
    395      if (aKeySystem.EqualsLiteral(kWidevineExperiment2KeySystemName)) {
    396        requests.AppendElement(KeySystemConfigRequest{
    397            NS_ConvertUTF8toUTF16(kWidevineExperiment2KeySystemName),
    398            DecryptionInfo::Hardware, aIsPrivateBrowsing});
    399      }
    400    }
    401  }
    402 #endif
    403  return KeySystemConfig::CreateKeySystemConfigs(requests);
    404 }
    405 
    406 /* static */
    407 RefPtr<GenericPromise> MediaKeySystemAccess::KeySystemSupportsInitDataType(
    408    const nsAString& aKeySystem, const nsAString& aInitDataType,
    409    bool aIsHardwareDecryption, bool aIsPrivateBrowsing) {
    410  RefPtr<GenericPromise::Private> promise =
    411      new GenericPromise::Private(__func__);
    412  GetSupportedKeySystemConfigs(aKeySystem, aIsHardwareDecryption,
    413                               aIsPrivateBrowsing, Nothing())
    414      ->Then(GetMainThreadSerialEventTarget(), __func__,
    415             [promise, initDataType = nsString{std::move(aInitDataType)}](
    416                 const KeySystemConfig::SupportedConfigsPromise::
    417                     ResolveOrRejectValue& aResult) {
    418               if (aResult.IsResolve()) {
    419                 for (const auto& config : aResult.ResolveValue()) {
    420                   if (config.mInitDataTypes.Contains(initDataType)) {
    421                     promise->Resolve(true, __func__);
    422                     return;
    423                   }
    424                 }
    425               }
    426               promise->Reject(NS_ERROR_DOM_MEDIA_CDM_ERR, __func__);
    427             });
    428  return promise.forget();
    429 }
    430 
    431 enum CodecType { Audio, Video, Invalid };
    432 
    433 static bool CanDecryptAndDecode(
    434    const nsString& aKeySystem, const nsString& aContentType,
    435    CodecType aCodecType,
    436    const KeySystemConfig::ContainerSupport& aContainerSupport,
    437    const nsTArray<KeySystemConfig::EMECodecString>& aCodecs,
    438    const Maybe<CryptoScheme>& aScheme, DecoderDoctorDiagnostics* aDiagnostics,
    439    Maybe<bool> aShouldResistFingerprinting) {
    440  MOZ_ASSERT(aCodecType != Invalid);
    441  for (const KeySystemConfig::EMECodecString& codec : aCodecs) {
    442    MOZ_ASSERT(!codec.IsEmpty());
    443 
    444    if (aContainerSupport.DecryptsAndDecodes(codec, aScheme)) {
    445      // GMP can decrypt-and-decode this codec.
    446      continue;
    447    }
    448 
    449    if (aContainerSupport.Decrypts(codec, aScheme)) {
    450      IgnoredErrorResult rv;
    451      MediaSource::IsTypeSupported(aContentType, aDiagnostics, rv,
    452                                   aShouldResistFingerprinting);
    453      if (!rv.Failed()) {
    454        // GMP can decrypt and is allowed to return compressed samples to
    455        // Gecko to decode, and Gecko has a decoder.
    456        continue;
    457      }
    458    }
    459 
    460    // Neither the GMP nor Gecko can both decrypt and decode. We don't
    461    // support this codec.
    462 
    463 #if defined(XP_WIN)
    464    // Widevine CDM doesn't include an AAC decoder. So if WMF can't
    465    // decode AAC, and a codec wasn't specified, be conservative
    466    // and reject the MediaKeys request, since we assume Widevine
    467    // will be used with AAC.
    468    if (codec == KeySystemConfig::EME_CODEC_AAC &&
    469        IsWidevineKeySystem(aKeySystem) && aDiagnostics) {
    470      auto pdmFactory = MakeRefPtr<PDMFactory>();
    471      if (pdmFactory->SupportsMimeType("audio/mp4a-latm"_ns).isEmpty()) {
    472        aDiagnostics->SetKeySystemIssue(
    473            DecoderDoctorDiagnostics::eWidevineWithNoWMF);
    474      }
    475    }
    476 #endif
    477    return false;
    478  }
    479  return true;
    480 }
    481 
    482 // https://w3c.github.io/encrypted-media/#dom-mediakeysystemmediacapability-encryptionscheme
    483 // This convert `encryptionScheme` to the type of CryptoScheme, so that we can
    484 // further check whether the scheme is supported or not in our media pipeline.
    485 Maybe<CryptoScheme> ConvertEncryptionSchemeStrToScheme(
    486    const nsString& aEncryptionScheme) {
    487  if (DOMStringIsNull(aEncryptionScheme)) {
    488    // "A missing or null value indicates that any encryption scheme is
    489    // acceptable."
    490    return Nothing();
    491  }
    492  auto scheme = StringToCryptoScheme(aEncryptionScheme);
    493  return Some(scheme);
    494 }
    495 
    496 static bool ToSessionType(const nsAString& aSessionType,
    497                          MediaKeySessionType& aOutType) {
    498  Maybe<MediaKeySessionType> type =
    499      StringToEnum<MediaKeySessionType>(aSessionType);
    500  if (type.isNothing()) {
    501    return false;
    502  }
    503  aOutType = type.value();
    504  return true;
    505 }
    506 
    507 // 5.1.1 Is persistent session type?
    508 static bool IsPersistentSessionType(MediaKeySessionType aSessionType) {
    509  return aSessionType == MediaKeySessionType::Persistent_license;
    510 }
    511 
    512 static bool ContainsSessionType(
    513    const nsTArray<KeySystemConfig::SessionType>& aTypes,
    514    const MediaKeySessionType& aSessionType) {
    515  return (aSessionType == MediaKeySessionType::Persistent_license &&
    516          aTypes.Contains(KeySystemConfig::SessionType::PersistentLicense)) ||
    517         (aSessionType == MediaKeySessionType::Temporary &&
    518          aTypes.Contains(KeySystemConfig::SessionType::Temporary));
    519 }
    520 
    521 CodecType GetMajorType(const MediaMIMEType& aMIMEType) {
    522  if (aMIMEType.HasAudioMajorType()) {
    523    return Audio;
    524  }
    525  if (aMIMEType.HasVideoMajorType()) {
    526    return Video;
    527  }
    528  return Invalid;
    529 }
    530 
    531 static CodecType GetCodecType(const KeySystemConfig::EMECodecString& aCodec) {
    532  if (aCodec.Equals(KeySystemConfig::EME_CODEC_AAC) ||
    533      aCodec.Equals(KeySystemConfig::EME_CODEC_OPUS) ||
    534      aCodec.Equals(KeySystemConfig::EME_CODEC_VORBIS) ||
    535      aCodec.Equals(KeySystemConfig::EME_CODEC_FLAC)) {
    536    return Audio;
    537  }
    538  if (aCodec.Equals(KeySystemConfig::EME_CODEC_H264) ||
    539      aCodec.Equals(KeySystemConfig::EME_CODEC_AV1) ||
    540      aCodec.Equals(KeySystemConfig::EME_CODEC_VP8) ||
    541      aCodec.Equals(KeySystemConfig::EME_CODEC_VP9) ||
    542      aCodec.Equals(KeySystemConfig::EME_CODEC_HEVC)) {
    543    return Video;
    544  }
    545  return Invalid;
    546 }
    547 
    548 static bool AllCodecsOfType(
    549    const nsTArray<KeySystemConfig::EMECodecString>& aCodecs,
    550    const CodecType aCodecType) {
    551  for (const KeySystemConfig::EMECodecString& codec : aCodecs) {
    552    if (GetCodecType(codec) != aCodecType) {
    553      return false;
    554    }
    555  }
    556  return true;
    557 }
    558 
    559 static bool IsParameterUnrecognized(const nsAString& aContentType) {
    560  nsAutoString contentType(aContentType);
    561  contentType.StripWhitespace();
    562 
    563  nsTArray<nsString> params;
    564  nsAString::const_iterator start, end, semicolon, equalSign;
    565  contentType.BeginReading(start);
    566  contentType.EndReading(end);
    567  semicolon = start;
    568  // Find any substring between ';' & '='.
    569  while (semicolon != end) {
    570    if (FindCharInReadable(';', semicolon, end)) {
    571      equalSign = ++semicolon;
    572      if (FindCharInReadable('=', equalSign, end)) {
    573        params.AppendElement(Substring(semicolon, equalSign));
    574        semicolon = equalSign;
    575      }
    576    }
    577  }
    578 
    579  for (auto param : params) {
    580    if (!param.LowerCaseEqualsLiteral("codecs") &&
    581        !param.LowerCaseEqualsLiteral("profiles")) {
    582      return true;
    583    }
    584  }
    585  return false;
    586 }
    587 
    588 // 3.2.2.3 Get Supported Capabilities for Audio/Video Type
    589 // https://w3c.github.io/encrypted-media/#get-supported-capabilities-for-audio-video-type
    590 static Sequence<MediaKeySystemMediaCapability> GetSupportedCapabilities(
    591    const CodecType aCodecType,
    592    const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
    593    const MediaKeySystemConfiguration& aPartialConfig,
    594    const KeySystemConfig& aKeySystem, DecoderDoctorDiagnostics* aDiagnostics,
    595    const Document* aDocument) {
    596  // Let local accumulated configuration be a local copy of partial
    597  // configuration. (Note: It's not necessary for us to maintain a local copy,
    598  // as we don't need to test whether capabilites from previous calls to this
    599  // algorithm work with the capabilities currently being considered in this
    600  // call. )
    601 
    602  // Let supported media capabilities be an empty sequence of
    603  // MediaKeySystemMediaCapability dictionaries.
    604  Sequence<MediaKeySystemMediaCapability> supportedCapabilities;
    605 
    606  // For each requested media capability in requested media capabilities:
    607  for (const MediaKeySystemMediaCapability& capabilities :
    608       aRequestedCapabilities) {
    609    // Let content type be requested media capability's contentType member.
    610    const nsString& contentTypeString = capabilities.mContentType;
    611    // Let robustness be requested media capability's robustness member.
    612    const nsString& robustness = capabilities.mRobustness;
    613    // Optional encryption scheme extension, see
    614    // https://github.com/WICG/encrypted-media-encryption-scheme/blob/master/explainer.md
    615    // This will only be exposed to JS if
    616    // media.eme.encrypted-media-encryption-scheme.enabled is preffed on.
    617    const nsString encryptionScheme = capabilities.mEncryptionScheme;
    618    // If content type is the empty string, return null.
    619    if (contentTypeString.IsEmpty()) {
    620      EME_LOG(
    621          "MediaKeySystemConfiguration (label='%s') "
    622          "MediaKeySystemMediaCapability('%s','%s','%s') rejected; "
    623          "audio or video capability has empty contentType.",
    624          NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    625          NS_ConvertUTF16toUTF8(contentTypeString).get(),
    626          NS_ConvertUTF16toUTF8(robustness).get(),
    627          NS_ConvertUTF16toUTF8(encryptionScheme).get());
    628      return Sequence<MediaKeySystemMediaCapability>();
    629    }
    630    // If content type is an invalid or unrecognized MIME type, continue
    631    // to the next iteration.
    632    Maybe<MediaContainerType> maybeContainerType =
    633        MakeMediaContainerType(contentTypeString);
    634    if (!maybeContainerType) {
    635      EME_LOG(
    636          "MediaKeySystemConfiguration (label='%s') "
    637          "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    638          "failed to parse contentTypeString as MIME type.",
    639          NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    640          NS_ConvertUTF16toUTF8(contentTypeString).get(),
    641          NS_ConvertUTF16toUTF8(robustness).get(),
    642          NS_ConvertUTF16toUTF8(encryptionScheme).get());
    643      continue;
    644    }
    645    const MediaContainerType& containerType = *maybeContainerType;
    646    bool invalid = false;
    647    nsTArray<KeySystemConfig::EMECodecString> codecs;
    648    for (const auto& codecString :
    649         containerType.ExtendedType().Codecs().Range()) {
    650      KeySystemConfig::EMECodecString emeCodec =
    651          ToEMEAPICodecString(nsString(codecString));
    652      if (emeCodec.IsEmpty()) {
    653        invalid = true;
    654        EME_LOG(
    655            "MediaKeySystemConfiguration (label='%s') "
    656            "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    657            "'%s' is an invalid codec string.",
    658            NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    659            NS_ConvertUTF16toUTF8(contentTypeString).get(),
    660            NS_ConvertUTF16toUTF8(robustness).get(),
    661            NS_ConvertUTF16toUTF8(encryptionScheme).get(),
    662            NS_ConvertUTF16toUTF8(codecString).get());
    663        break;
    664      }
    665      codecs.AppendElement(emeCodec);
    666    }
    667    if (invalid) {
    668      continue;
    669    }
    670 
    671    // If the user agent does not support container, continue to the next
    672    // iteration. The case-sensitivity of string comparisons is determined by
    673    // the appropriate RFC. (Note: Per RFC 6838 [RFC6838], "Both top-level type
    674    // and subtype names are case-insensitive."'. We're using
    675    // nsContentTypeParser and that is case-insensitive and converts all its
    676    // parameter outputs to lower case.)
    677    const bool supportedInMP4 =
    678        MP4Decoder::IsSupportedType(containerType, aDiagnostics);
    679    if (supportedInMP4 && !aKeySystem.mMP4.IsSupported()) {
    680      EME_LOG(
    681          "MediaKeySystemConfiguration (label='%s') "
    682          "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    683          "MP4 requested but unsupported.",
    684          NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    685          NS_ConvertUTF16toUTF8(contentTypeString).get(),
    686          NS_ConvertUTF16toUTF8(robustness).get(),
    687          NS_ConvertUTF16toUTF8(encryptionScheme).get());
    688      continue;
    689    }
    690    const bool isWebM = WebMDecoder::IsSupportedType(containerType);
    691    if (isWebM && !aKeySystem.mWebM.IsSupported()) {
    692      EME_LOG(
    693          "MediaKeySystemConfiguration (label='%s') "
    694          "MediaKeySystemMediaCapability('%s','%s,'%s') unsupported; "
    695          "WebM requested but unsupported.",
    696          NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    697          NS_ConvertUTF16toUTF8(contentTypeString).get(),
    698          NS_ConvertUTF16toUTF8(robustness).get(),
    699          NS_ConvertUTF16toUTF8(encryptionScheme).get());
    700      continue;
    701    }
    702    if (!supportedInMP4 && !isWebM) {
    703      EME_LOG(
    704          "MediaKeySystemConfiguration (label='%s') "
    705          "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    706          "Unsupported or unrecognized container requested.",
    707          NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    708          NS_ConvertUTF16toUTF8(contentTypeString).get(),
    709          NS_ConvertUTF16toUTF8(robustness).get(),
    710          NS_ConvertUTF16toUTF8(encryptionScheme).get());
    711      continue;
    712    }
    713 
    714    // Let parameters be the RFC 6381[RFC6381] parameters, if any, specified by
    715    // content type.
    716    // If the user agent does not recognize one or more parameters, continue to
    717    // the next iteration.
    718    if (IsParameterUnrecognized(contentTypeString)) {
    719      continue;
    720    }
    721 
    722    // Let media types be the set of codecs and codec constraints specified by
    723    // parameters. The case-sensitivity of string comparisons is determined by
    724    // the appropriate RFC or other specification.
    725    // (Note: codecs array is 'parameter').
    726 
    727    // If media types is empty:
    728    if (codecs.IsEmpty()) {
    729      // Log deprecation warning to encourage authors to not do this!
    730      DeprecationWarningLog(aDocument, "MediaEMENoCodecsDeprecatedWarning");
    731      // TODO: Remove this once we're sure it doesn't break the web.
    732      // If container normatively implies a specific set of codecs and codec
    733      // constraints: Let parameters be that set.
    734      if (supportedInMP4) {
    735        if (aCodecType == Audio) {
    736          codecs.AppendElement(KeySystemConfig::EME_CODEC_AAC);
    737        } else if (aCodecType == Video) {
    738          codecs.AppendElement(KeySystemConfig::EME_CODEC_H264);
    739        }
    740      } else if (isWebM) {
    741        if (aCodecType == Audio) {
    742          codecs.AppendElement(KeySystemConfig::EME_CODEC_VORBIS);
    743        } else if (aCodecType == Video) {
    744          codecs.AppendElement(KeySystemConfig::EME_CODEC_VP8);
    745        }
    746      }
    747      // Otherwise: Continue to the next iteration.
    748      // (Note: all containers we support have implied codecs, so don't continue
    749      // here.)
    750    }
    751 
    752    // If container type is not strictly a audio/video type, continue to the
    753    // next iteration.
    754    const auto majorType = GetMajorType(containerType.Type());
    755    if (majorType == Invalid) {
    756      EME_LOG(
    757          "MediaKeySystemConfiguration (label='%s') "
    758          "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    759          "MIME type is not an audio or video MIME type.",
    760          NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    761          NS_ConvertUTF16toUTF8(contentTypeString).get(),
    762          NS_ConvertUTF16toUTF8(robustness).get(),
    763          NS_ConvertUTF16toUTF8(encryptionScheme).get());
    764      continue;
    765    }
    766    if (majorType != aCodecType || !AllCodecsOfType(codecs, aCodecType)) {
    767      EME_LOG(
    768          "MediaKeySystemConfiguration (label='%s') "
    769          "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    770          "MIME type mixes audio codecs in video capabilities "
    771          "or video codecs in audio capabilities.",
    772          NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    773          NS_ConvertUTF16toUTF8(contentTypeString).get(),
    774          NS_ConvertUTF16toUTF8(robustness).get(),
    775          NS_ConvertUTF16toUTF8(encryptionScheme).get());
    776      continue;
    777    }
    778    // If encryption scheme is non-null and is not recognized or not supported
    779    // by implementation, continue to the next iteration.
    780    const auto scheme = ConvertEncryptionSchemeStrToScheme(encryptionScheme);
    781    if (scheme && *scheme == CryptoScheme::None) {
    782      EME_LOG(
    783          "MediaKeySystemConfiguration (label='%s') "
    784          "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    785          "unsupported scheme string.",
    786          NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    787          NS_ConvertUTF16toUTF8(contentTypeString).get(),
    788          NS_ConvertUTF16toUTF8(robustness).get(),
    789          NS_ConvertUTF16toUTF8(encryptionScheme).get());
    790      continue;
    791    }
    792    // If robustness is not the empty string and contains an unrecognized
    793    // value or a value not supported by implementation, continue to the
    794    // next iteration. String comparison is case-sensitive.
    795    if (!robustness.IsEmpty()) {
    796      if (majorType == Audio &&
    797          !aKeySystem.mAudioRobustness.Contains(robustness)) {
    798        EME_LOG(
    799            "MediaKeySystemConfiguration (label='%s') "
    800            "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    801            "unsupported robustness string.",
    802            NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    803            NS_ConvertUTF16toUTF8(contentTypeString).get(),
    804            NS_ConvertUTF16toUTF8(robustness).get(),
    805            NS_ConvertUTF16toUTF8(encryptionScheme).get());
    806        continue;
    807      }
    808      if (majorType == Video &&
    809          !aKeySystem.mVideoRobustness.Contains(robustness)) {
    810        EME_LOG(
    811            "MediaKeySystemConfiguration (label='%s') "
    812            "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    813            "unsupported robustness string.",
    814            NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    815            NS_ConvertUTF16toUTF8(contentTypeString).get(),
    816            NS_ConvertUTF16toUTF8(robustness).get(),
    817            NS_ConvertUTF16toUTF8(encryptionScheme).get());
    818        continue;
    819      }
    820      // Note: specified robustness requirements are satisfied.
    821    }
    822 
    823    // If the user agent and implementation definitely support playback of
    824    // encrypted media data for the combination of container, media types,
    825    // robustness and local accumulated configuration in combination with
    826    // restrictions...
    827    const auto& containerSupport =
    828        supportedInMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM;
    829    Maybe<bool> shouldResistFingerprinting =
    830        aDocument ? Some(aDocument->ShouldResistFingerprinting(
    831                        RFPTarget::MediaCapabilities))
    832                  : Nothing();
    833    if (!CanDecryptAndDecode(aKeySystem.mKeySystem, contentTypeString,
    834                             majorType, containerSupport, codecs, scheme,
    835                             aDiagnostics, shouldResistFingerprinting)) {
    836      EME_LOG(
    837          "MediaKeySystemConfiguration (label='%s') "
    838          "MediaKeySystemMediaCapability('%s','%s','%s') unsupported; "
    839          "codec unsupported by CDM requested.",
    840          NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
    841          NS_ConvertUTF16toUTF8(contentTypeString).get(),
    842          NS_ConvertUTF16toUTF8(robustness).get(),
    843          NS_ConvertUTF16toUTF8(encryptionScheme).get());
    844      continue;
    845    }
    846 
    847    // ... add requested media capability to supported media capabilities.
    848    if (!supportedCapabilities.AppendElement(capabilities, mozilla::fallible)) {
    849      NS_WARNING("GetSupportedCapabilities: Malloc failure");
    850      return Sequence<MediaKeySystemMediaCapability>();
    851    }
    852 
    853    // Note: omitting steps 3.13.2, our robustness is not sophisticated enough
    854    // to require considering all requirements together.
    855  }
    856  return supportedCapabilities;
    857 }
    858 
    859 // "Get Supported Configuration and Consent" algorithm, steps 4-7 for
    860 // distinctive identifier, and steps 8-11 for persistent state. The steps
    861 // are the same for both requirements/features, so we factor them out into
    862 // a single function.
    863 static bool CheckRequirement(
    864    const MediaKeysRequirement aRequirement,
    865    const KeySystemConfig::Requirement aKeySystemRequirement,
    866    MediaKeysRequirement& aOutRequirement) {
    867  // Let requirement be the value of candidate configuration's member.
    868  MediaKeysRequirement requirement = aRequirement;
    869  // If requirement is "optional" and feature is not allowed according to
    870  // restrictions, set requirement to "not-allowed".
    871  if (aRequirement == MediaKeysRequirement::Optional &&
    872      aKeySystemRequirement == KeySystemConfig::Requirement::NotAllowed) {
    873    requirement = MediaKeysRequirement::Not_allowed;
    874  }
    875 
    876  // Follow the steps for requirement from the following list:
    877  switch (requirement) {
    878    case MediaKeysRequirement::Required: {
    879      // If the implementation does not support use of requirement in
    880      // combination with accumulated configuration and restrictions, return
    881      // NotSupported.
    882      if (aKeySystemRequirement == KeySystemConfig::Requirement::NotAllowed) {
    883        return false;
    884      }
    885      break;
    886    }
    887    case MediaKeysRequirement::Optional: {
    888      // Continue with the following steps.
    889      break;
    890    }
    891    case MediaKeysRequirement::Not_allowed: {
    892      // If the implementation requires use of feature in combination with
    893      // accumulated configuration and restrictions, return NotSupported.
    894      if (aKeySystemRequirement == KeySystemConfig::Requirement::Required) {
    895        return false;
    896      }
    897      break;
    898    }
    899    default: {
    900      return false;
    901    }
    902  }
    903 
    904  // Set the requirement member of accumulated configuration to equal
    905  // calculated requirement.
    906  aOutRequirement = requirement;
    907 
    908  return true;
    909 }
    910 
    911 // 3.1.1.2, step 12
    912 // Follow the steps for the first matching condition from the following list:
    913 // If the sessionTypes member is present in candidate configuration.
    914 // Let session types be candidate configuration's sessionTypes member.
    915 // Otherwise let session types be ["temporary"].
    916 // Note: This returns an empty array on malloc failure.
    917 static Sequence<nsString> UnboxSessionTypes(
    918    const Optional<Sequence<nsString>>& aSessionTypes) {
    919  Sequence<nsString> sessionTypes;
    920  if (aSessionTypes.WasPassed()) {
    921    sessionTypes = aSessionTypes.Value();
    922  } else {
    923    // Note: fallible. Results in an empty array.
    924    (void)sessionTypes.AppendElement(ToString(MediaKeySessionType::Temporary),
    925                                     mozilla::fallible);
    926  }
    927  return sessionTypes;
    928 }
    929 
    930 // 3.1.1.2 Get Supported Configuration and Consent
    931 static bool GetSupportedConfig(const KeySystemConfig& aKeySystem,
    932                               const MediaKeySystemConfiguration& aCandidate,
    933                               MediaKeySystemConfiguration& aOutConfig,
    934                               DecoderDoctorDiagnostics* aDiagnostics,
    935                               const Document* aDocument) {
    936  EME_LOG("Compare implementation '%s'\n with request '%s'",
    937          NS_ConvertUTF16toUTF8(aKeySystem.GetDebugInfo()).get(),
    938          MediaKeySystemAccess::ToCString(aCandidate).get());
    939  // Let accumulated configuration be a new MediaKeySystemConfiguration
    940  // dictionary.
    941  MediaKeySystemConfiguration config;
    942  // Set the label member of accumulated configuration to equal the label member
    943  // of candidate configuration.
    944  config.mLabel = aCandidate.mLabel;
    945  // If the initDataTypes member of candidate configuration is non-empty, run
    946  // the following steps:
    947  if (!aCandidate.mInitDataTypes.IsEmpty()) {
    948    // Let supported types be an empty sequence of DOMStrings.
    949    nsTArray<nsString> supportedTypes;
    950    // For each value in candidate configuration's initDataTypes member:
    951    for (const nsString& initDataType : aCandidate.mInitDataTypes) {
    952      // Let initDataType be the value.
    953      // If the implementation supports generating requests based on
    954      // initDataType, add initDataType to supported types. String comparison is
    955      // case-sensitive. The empty string is never supported.
    956      if (aKeySystem.mInitDataTypes.Contains(initDataType)) {
    957        supportedTypes.AppendElement(initDataType);
    958      }
    959    }
    960    // If supported types is empty, return NotSupported.
    961    if (supportedTypes.IsEmpty()) {
    962      EME_LOG(
    963          "MediaKeySystemConfiguration (label='%s') rejected; "
    964          "no supported initDataTypes provided.",
    965          NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
    966      return false;
    967    }
    968    // Set the initDataTypes member of accumulated configuration to supported
    969    // types.
    970    if (!config.mInitDataTypes.Assign(supportedTypes)) {
    971      return false;
    972    }
    973  }
    974 
    975  if (!CheckRequirement(aCandidate.mDistinctiveIdentifier,
    976                        aKeySystem.mDistinctiveIdentifier,
    977                        config.mDistinctiveIdentifier)) {
    978    EME_LOG(
    979        "MediaKeySystemConfiguration (label='%s') rejected; "
    980        "distinctiveIdentifier requirement not satisfied.",
    981        NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
    982    return false;
    983  }
    984 
    985  if (!CheckRequirement(aCandidate.mPersistentState,
    986                        aKeySystem.mPersistentState, config.mPersistentState)) {
    987    EME_LOG(
    988        "MediaKeySystemConfiguration (label='%s') rejected; "
    989        "persistentState requirement not satisfied.",
    990        NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
    991    return false;
    992  }
    993 
    994  Sequence<nsString> sessionTypes(UnboxSessionTypes(aCandidate.mSessionTypes));
    995  if (sessionTypes.IsEmpty()) {
    996    // Malloc failure.
    997    return false;
    998  }
    999 
   1000  // For each value in session types:
   1001  for (const auto& sessionTypeString : sessionTypes) {
   1002    // Let session type be the value.
   1003    MediaKeySessionType sessionType;
   1004    if (!ToSessionType(sessionTypeString, sessionType)) {
   1005      // (Assume invalid sessionType is unsupported as per steps below).
   1006      EME_LOG(
   1007          "MediaKeySystemConfiguration (label='%s') rejected; "
   1008          "invalid session type specified.",
   1009          NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
   1010      return false;
   1011    }
   1012    // If accumulated configuration's persistentState value is "not-allowed"
   1013    // and the Is persistent session type? algorithm returns true for session
   1014    // type return NotSupported.
   1015    if (config.mPersistentState == MediaKeysRequirement::Not_allowed &&
   1016        IsPersistentSessionType(sessionType)) {
   1017      EME_LOG(
   1018          "MediaKeySystemConfiguration (label='%s') rejected; "
   1019          "persistent session requested but keysystem doesn't"
   1020          "support persistent state.",
   1021          NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
   1022      return false;
   1023    }
   1024    // If the implementation does not support session type in combination
   1025    // with accumulated configuration and restrictions for other reasons,
   1026    // return NotSupported.
   1027    if (!ContainsSessionType(aKeySystem.mSessionTypes, sessionType)) {
   1028      EME_LOG(
   1029          "MediaKeySystemConfiguration (label='%s') rejected; "
   1030          "session type '%s' unsupported by keySystem.",
   1031          NS_ConvertUTF16toUTF8(aCandidate.mLabel).get(),
   1032          NS_ConvertUTF16toUTF8(sessionTypeString).get());
   1033      return false;
   1034    }
   1035    // If accumulated configuration's persistentState value is "optional"
   1036    // and the result of running the Is persistent session type? algorithm
   1037    // on session type is true, change accumulated configuration's
   1038    // persistentState value to "required".
   1039    if (config.mPersistentState == MediaKeysRequirement::Optional &&
   1040        IsPersistentSessionType(sessionType)) {
   1041      config.mPersistentState = MediaKeysRequirement::Required;
   1042    }
   1043  }
   1044  // Set the sessionTypes member of accumulated configuration to session types.
   1045  config.mSessionTypes.Construct(std::move(sessionTypes));
   1046 
   1047  // If the videoCapabilities and audioCapabilities members in candidate
   1048  // configuration are both empty, return NotSupported.
   1049  if (aCandidate.mAudioCapabilities.IsEmpty() &&
   1050      aCandidate.mVideoCapabilities.IsEmpty()) {
   1051    // TODO: Most sites using EME still don't pass capabilities, so we
   1052    // can't reject on it yet without breaking them. So add this later.
   1053    // Log deprecation warning to encourage authors to not do this!
   1054    DeprecationWarningLog(aDocument, "MediaEMENoCapabilitiesDeprecatedWarning");
   1055  }
   1056 
   1057  // If the videoCapabilities member in candidate configuration is non-empty:
   1058  if (!aCandidate.mVideoCapabilities.IsEmpty()) {
   1059    // Let video capabilities be the result of executing the Get Supported
   1060    // Capabilities for Audio/Video Type algorithm on Video, candidate
   1061    // configuration's videoCapabilities member, accumulated configuration,
   1062    // and restrictions.
   1063    Sequence<MediaKeySystemMediaCapability> caps =
   1064        GetSupportedCapabilities(Video, aCandidate.mVideoCapabilities, config,
   1065                                 aKeySystem, aDiagnostics, aDocument);
   1066    // If video capabilities is null, return NotSupported.
   1067    if (caps.IsEmpty()) {
   1068      EME_LOG(
   1069          "MediaKeySystemConfiguration (label='%s') rejected; "
   1070          "no supported video capabilities.",
   1071          NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
   1072      return false;
   1073    }
   1074    // Set the videoCapabilities member of accumulated configuration to video
   1075    // capabilities.
   1076    config.mVideoCapabilities = std::move(caps);
   1077  } else {
   1078    // Otherwise:
   1079    // Set the videoCapabilities member of accumulated configuration to an empty
   1080    // sequence.
   1081  }
   1082 
   1083  // If the audioCapabilities member in candidate configuration is non-empty:
   1084  if (!aCandidate.mAudioCapabilities.IsEmpty()) {
   1085    // Let audio capabilities be the result of executing the Get Supported
   1086    // Capabilities for Audio/Video Type algorithm on Audio, candidate
   1087    // configuration's audioCapabilities member, accumulated configuration, and
   1088    // restrictions.
   1089    Sequence<MediaKeySystemMediaCapability> caps =
   1090        GetSupportedCapabilities(Audio, aCandidate.mAudioCapabilities, config,
   1091                                 aKeySystem, aDiagnostics, aDocument);
   1092    // If audio capabilities is null, return NotSupported.
   1093    if (caps.IsEmpty()) {
   1094      EME_LOG(
   1095          "MediaKeySystemConfiguration (label='%s') rejected; "
   1096          "no supported audio capabilities.",
   1097          NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
   1098      return false;
   1099    }
   1100    // Set the audioCapabilities member of accumulated configuration to audio
   1101    // capabilities.
   1102    config.mAudioCapabilities = std::move(caps);
   1103  } else {
   1104    // Otherwise:
   1105    // Set the audioCapabilities member of accumulated configuration to an empty
   1106    // sequence.
   1107  }
   1108 
   1109  // If accumulated configuration's distinctiveIdentifier value is "optional",
   1110  // follow the steps for the first matching condition from the following list:
   1111  if (config.mDistinctiveIdentifier == MediaKeysRequirement::Optional) {
   1112    // If the implementation requires use Distinctive Identifier(s) or
   1113    // Distinctive Permanent Identifier(s) for any of the combinations
   1114    // in accumulated configuration
   1115    if (aKeySystem.mDistinctiveIdentifier ==
   1116        KeySystemConfig::Requirement::Required) {
   1117      // Change accumulated configuration's distinctiveIdentifier value to
   1118      // "required".
   1119      config.mDistinctiveIdentifier = MediaKeysRequirement::Required;
   1120    } else {
   1121      // Otherwise, change accumulated configuration's distinctiveIdentifier
   1122      // value to "not-allowed".
   1123      config.mDistinctiveIdentifier = MediaKeysRequirement::Not_allowed;
   1124    }
   1125  }
   1126 
   1127  // If accumulated configuration's persistentState value is "optional", follow
   1128  // the steps for the first matching condition from the following list:
   1129  if (config.mPersistentState == MediaKeysRequirement::Optional) {
   1130    // If the implementation requires persisting state for any of the
   1131    // combinations in accumulated configuration
   1132    if (aKeySystem.mPersistentState == KeySystemConfig::Requirement::Required) {
   1133      // Change accumulated configuration's persistentState value to "required".
   1134      config.mPersistentState = MediaKeysRequirement::Required;
   1135    } else {
   1136      // Otherwise, change accumulated configuration's persistentState
   1137      // value to "not-allowed".
   1138      config.mPersistentState = MediaKeysRequirement::Not_allowed;
   1139    }
   1140  }
   1141 
   1142  // Note: Omitting steps 20-22. We don't ask for consent.
   1143 
   1144 #if defined(XP_WIN)
   1145  // Widevine CDM doesn't include an AAC decoder. So if WMF can't decode AAC,
   1146  // and a codec wasn't specified, be conservative and reject the MediaKeys
   1147  // request.
   1148  if (IsWidevineKeySystem(aKeySystem.mKeySystem) &&
   1149      (aCandidate.mAudioCapabilities.IsEmpty() ||
   1150       aCandidate.mVideoCapabilities.IsEmpty())) {
   1151    auto pdmFactory = MakeRefPtr<PDMFactory>();
   1152    if (pdmFactory->SupportsMimeType("audio/mp4a-latm"_ns).isEmpty()) {
   1153      if (aDiagnostics) {
   1154        aDiagnostics->SetKeySystemIssue(
   1155            DecoderDoctorDiagnostics::eWidevineWithNoWMF);
   1156      }
   1157      EME_LOG(
   1158          "MediaKeySystemConfiguration (label='%s') rejected; "
   1159          "WMF required for Widevine decoding, but it's not available.",
   1160          NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
   1161      return false;
   1162    }
   1163  }
   1164 #endif
   1165 
   1166  // Return accumulated configuration.
   1167  aOutConfig = config;
   1168 
   1169  return true;
   1170 }
   1171 
   1172 /* static */
   1173 RefPtr<KeySystemConfig::KeySystemConfigPromise>
   1174 MediaKeySystemAccess::GetSupportedConfig(MediaKeySystemAccessRequest* aRequest,
   1175                                         bool aIsPrivateBrowsing,
   1176                                         const Document* aDocument) {
   1177  nsTArray<KeySystemConfig> implementations;
   1178  const bool containsHardwareDecryptionConfig =
   1179      CheckIfHarewareDRMConfigExists(aRequest->mConfigs) ||
   1180      DoesKeySystemSupportHardwareDecryption(aRequest->mKeySystem);
   1181 
   1182  RefPtr<KeySystemConfig::KeySystemConfigPromise::Private> promise =
   1183      new KeySystemConfig::KeySystemConfigPromise::Private(__func__);
   1184  GetSupportedKeySystemConfigs(aRequest->mKeySystem,
   1185                               containsHardwareDecryptionConfig,
   1186                               aIsPrivateBrowsing, GetOrigin(aDocument))
   1187      ->Then(GetMainThreadSerialEventTarget(), __func__,
   1188             [promise, aRequest, document = RefPtr<const Document>{aDocument}](
   1189                 const KeySystemConfig::SupportedConfigsPromise::
   1190                     ResolveOrRejectValue& aResult) {
   1191               if (aResult.IsResolve()) {
   1192                 MediaKeySystemConfiguration outConfig;
   1193                 for (const auto& implementation : aResult.ResolveValue()) {
   1194                   for (const MediaKeySystemConfiguration& candidate :
   1195                        aRequest->mConfigs) {
   1196                     if (mozilla::dom::GetSupportedConfig(
   1197                             implementation, candidate, outConfig,
   1198                             &aRequest->mDiagnostics, document)) {
   1199                       promise->Resolve(std::move(outConfig), __func__);
   1200                       return;
   1201                     }
   1202                   }
   1203                 }
   1204               }
   1205               promise->Reject(false, __func__);
   1206             });
   1207  return promise.forget();
   1208 }
   1209 
   1210 /* static */
   1211 void MediaKeySystemAccess::NotifyObservers(nsPIDOMWindowInner* aWindow,
   1212                                           const nsAString& aKeySystem,
   1213                                           MediaKeySystemStatus aStatus) {
   1214  RequestMediaKeySystemAccessNotification data;
   1215  data.mKeySystem = aKeySystem;
   1216  data.mStatus = aStatus;
   1217  nsAutoString json;
   1218  data.ToJSON(json);
   1219  EME_LOG("MediaKeySystemAccess::NotifyObservers() %s",
   1220          NS_ConvertUTF16toUTF8(json).get());
   1221  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   1222  if (obs) {
   1223    obs->NotifyObservers(aWindow, MediaKeys::kMediaKeysRequestTopic,
   1224                         json.get());
   1225  }
   1226 }
   1227 
   1228 static nsCString ToCString(const nsString& aString) {
   1229  nsCString str("\"");
   1230  str.Append(NS_ConvertUTF16toUTF8(aString));
   1231  str.AppendLiteral("\"");
   1232  return str;
   1233 }
   1234 
   1235 static nsCString ToCString(const MediaKeysRequirement aValue) {
   1236  nsCString str("\"");
   1237  str.AppendASCII(GetEnumString(aValue));
   1238  str.AppendLiteral("\"");
   1239  return str;
   1240 }
   1241 
   1242 static nsCString ToCString(const MediaKeySystemMediaCapability& aValue) {
   1243  nsCString str;
   1244  str.AppendLiteral(R"({"contentType":")");
   1245  // Escape any quotes in the content type
   1246  nsString escapedContentType(aValue.mContentType);
   1247  escapedContentType.ReplaceSubstring(u"\"", u"\\\"");
   1248  str.Append(NS_ConvertUTF16toUTF8(escapedContentType));
   1249  str.AppendLiteral(R"(", "robustness":)");
   1250  str.Append(ToCString(aValue.mRobustness));
   1251  str.AppendLiteral(R"(, "encryptionScheme":)");
   1252  str.Append(ToCString(aValue.mEncryptionScheme));
   1253  str.AppendLiteral("}");
   1254  return str;
   1255 }
   1256 
   1257 template <class Type>
   1258 nsCString ToCString(const Sequence<Type>& aSequence) {
   1259  nsCString str;
   1260  str.AppendLiteral("[");
   1261  StringJoinAppend(str, ","_ns, aSequence,
   1262                   [](nsACString& dest, const Type& element) {
   1263                     dest.Append(ToCString(element));
   1264                   });
   1265  str.AppendLiteral("]");
   1266  return str;
   1267 }
   1268 
   1269 template <>
   1270 nsCString ToCString(const Sequence<MediaKeySystemConfiguration>& aSequence) {
   1271  nsCString str;
   1272  str.AppendLiteral("[");
   1273  StringJoinAppend(
   1274      str, ","_ns, aSequence,
   1275      [](nsACString& dest, const MediaKeySystemConfiguration& element) {
   1276        dest.Append(MediaKeySystemAccess::ToCString(element));
   1277      });
   1278  str.AppendLiteral("]");
   1279  return str;
   1280 }
   1281 
   1282 template <class Type>
   1283 nsCString ToCString(const Optional<Sequence<Type>>& aOptional) {
   1284  nsCString str;
   1285  if (aOptional.WasPassed()) {
   1286    str.Append(ToCString(aOptional.Value()));
   1287  } else {
   1288    str.AppendLiteral("[]");
   1289  }
   1290  return str;
   1291 }
   1292 
   1293 template <>
   1294 nsCString ToCString(
   1295    const Optional<Sequence<MediaKeySystemConfiguration>>& aOptional) {
   1296  nsCString str;
   1297  if (aOptional.WasPassed()) {
   1298    str.Append(MediaKeySystemAccess::ToCString(aOptional.Value()));
   1299  } else {
   1300    str.AppendLiteral("[]");
   1301  }
   1302  return str;
   1303 }
   1304 
   1305 /* static */
   1306 nsCString MediaKeySystemAccess::ToCString(
   1307    const MediaKeySystemConfiguration& aConfig) {
   1308  nsCString str;
   1309  str.AppendLiteral(R"({"label":)");
   1310  str.Append(mozilla::dom::ToCString(aConfig.mLabel));
   1311 
   1312  str.AppendLiteral(R"(, "initDataTypes":)");
   1313  str.Append(mozilla::dom::ToCString(aConfig.mInitDataTypes));
   1314 
   1315  str.AppendLiteral(R"(, "audioCapabilities":)");
   1316  str.Append(mozilla::dom::ToCString(aConfig.mAudioCapabilities));
   1317 
   1318  str.AppendLiteral(R"(, "videoCapabilities":)");
   1319  str.Append(mozilla::dom::ToCString(aConfig.mVideoCapabilities));
   1320 
   1321  str.AppendLiteral(R"(, "distinctiveIdentifier":)");
   1322  str.Append(mozilla::dom::ToCString(aConfig.mDistinctiveIdentifier));
   1323 
   1324  str.AppendLiteral(R"(, "persistentState":)");
   1325  str.Append(mozilla::dom::ToCString(aConfig.mPersistentState));
   1326 
   1327  str.AppendLiteral(R"(, "sessionTypes":)");
   1328  str.Append(mozilla::dom::ToCString(aConfig.mSessionTypes));
   1329 
   1330  str.AppendLiteral("}");
   1331 
   1332  return str;
   1333 }
   1334 
   1335 /* static */
   1336 nsCString MediaKeySystemAccess::ToCString(
   1337    const Sequence<MediaKeySystemConfiguration>& aConfig) {
   1338  return mozilla::dom::ToCString(aConfig);
   1339 }
   1340 
   1341 #undef LOG
   1342 
   1343 }  // namespace mozilla::dom