Bug 1278198 - Implement "Get Supported Configuration" algorithm in MediaKeySystemAccess. r=gerald
authorChris Pearce <cpearce@mozilla.com>
Fri, 01 Jul 2016 13:36:57 +1200
changeset 348527 d3cb023ed5a88d4f905a91be8fb024be2cc2cae1
parent 348526 2866a18663fc5d4beebff4222b54f300133bb09d
child 348528 6a94e3e85347a9dd76656cf6c1ddd8e7857534dc
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgerald
bugs1278198
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1278198 - Implement "Get Supported Configuration" algorithm in MediaKeySystemAccess. r=gerald MozReview-Commit-ID: KiJMOm5HgHe
dom/media/VideoUtils.cpp
dom/media/VideoUtils.h
dom/media/eme/MediaKeySystemAccess.cpp
dom/media/eme/MediaKeySystemAccessManager.cpp
dom/media/mediasource/MediaSource.cpp
dom/media/mediasource/MediaSource.h
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -402,26 +402,16 @@ LogToBrowserConsole(const nsAString& aMs
     NS_WARNING("Failed to log message to console.");
     return;
   }
   nsAutoString msg(aMsg);
   console->LogStringMessage(msg.get());
 }
 
 bool
-IsAACCodecString(const nsAString& aCodec)
-{
-  return
-    aCodec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
-    aCodec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
-    aCodec.EqualsLiteral("mp4a.67")   || // MPEG2 AAC-LC
-    aCodec.EqualsLiteral("mp4a.40.29");  // MPEG4 HE-AACv2
-}
-
-bool
 ParseCodecsString(const nsAString& aCodecs, nsTArray<nsString>& aOutCodecs)
 {
   aOutCodecs.Clear();
   bool expectMoreTokens = false;
   nsCharSeparatedTokenizer tokenizer(aCodecs, ',');
   while (tokenizer.hasMoreTokens()) {
     const nsSubstring& token = tokenizer.nextToken();
     expectMoreTokens = tokenizer.separatorAfterCurrentToken();
@@ -429,102 +419,57 @@ ParseCodecsString(const nsAString& aCode
   }
   if (expectMoreTokens) {
     // Last codec name was empty
     return false;
   }
   return true;
 }
 
-static bool
-CheckContentType(const nsAString& aContentType,
-                 mozilla::function<bool(const nsAString&)> aSubtypeFilter,
-                 mozilla::function<bool(const nsAString&)> aCodecFilter)
+bool
+ParseMIMETypeString(const nsAString& aMIMEType,
+                    nsString& aOutContainerType,
+                    nsTArray<nsString>& aOutCodecs)
 {
-  nsContentTypeParser parser(aContentType);
-  nsAutoString mimeType;
-  nsresult rv = parser.GetType(mimeType);
-  if (NS_FAILED(rv) || !aSubtypeFilter(mimeType)) {
+  nsContentTypeParser parser(aMIMEType);
+  nsresult rv = parser.GetType(aOutContainerType);
+  if (NS_FAILED(rv)) {
     return false;
   }
 
   nsString codecsStr;
   parser.GetParameter("codecs", codecsStr);
-  nsTArray<nsString> codecs;
-  if (!ParseCodecsString(codecsStr, codecs)) {
-    return false;
-  }
-  for (const nsString& codec : codecs) {
-    if (!aCodecFilter(codec)) {
-      return false;
-    }
-  }
-  return true;
+  return ParseCodecsString(codecsStr, aOutCodecs);
 }
 
 bool
-IsH264ContentType(const nsAString& aContentType)
+IsH264CodecString(const nsAString& aCodec)
 {
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("video/mp4");
-    },
-    [](const nsAString& codec) {
-      int16_t profile = 0;
-      int16_t level = 0;
-      return ExtractH264CodecDetails(codec, profile, level);
-    }
-  );
+  int16_t profile = 0;
+  int16_t level = 0;
+  return ExtractH264CodecDetails(aCodec, profile, level);
 }
 
 bool
-IsAACContentType(const nsAString& aContentType)
+IsAACCodecString(const nsAString& aCodec)
 {
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("audio/mp4") ||
-             type.EqualsLiteral("audio/x-m4a");
-    },
-    [](const nsAString& codec) {
-      return codec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
-             codec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
-             codec.EqualsLiteral("mp4a.67");     // MPEG2 AAC-LC
-    });
+  return
+    aCodec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
+    aCodec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
+    aCodec.EqualsLiteral("mp4a.67") || // MPEG2 AAC-LC
+    aCodec.EqualsLiteral("mp4a.40.29");  // MPEG4 HE-AACv2
 }
 
 bool
-IsVorbisContentType(const nsAString& aContentType)
+IsVP8CodecString(const nsAString& aCodec)
 {
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("audio/webm") ||
-             type.EqualsLiteral("audio/ogg");
-    },
-    [](const nsAString& codec) {
-      return codec.EqualsLiteral("vorbis");
-    });
+  return aCodec.EqualsLiteral("vp8") ||
+         aCodec.EqualsLiteral("vp8.0");
 }
 
 bool
-IsVP8ContentType(const nsAString& aContentType)
+IsVP9CodecString(const nsAString& aCodec)
 {
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("video/webm");
-    },
-    [](const nsAString& codec) {
-      return codec.EqualsLiteral("vp8");
-    });
-}
-
-bool
-IsVP9ContentType(const nsAString& aContentType)
-{
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("video/webm");
-    },
-    [](const nsAString& codec) {
-      return codec.EqualsLiteral("vp9");
-    });
+  return aCodec.EqualsLiteral("vp9") ||
+         aCodec.EqualsLiteral("vp9.0");
 }
 
 } // end namespace mozilla
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -318,27 +318,35 @@ private:
   RefPtr<nsIRunnable> mTask;
   nsCOMPtr<nsITimer> mTimer;
 };
 
 void
 LogToBrowserConsole(const nsAString& aMsg);
 
 bool
+ParseMIMETypeString(const nsAString& aMIMEType,
+                    nsString& aOutContainerType,
+                    nsTArray<nsString>& aOutCodecs);
+
+bool
 ParseCodecsString(const nsAString& aCodecs, nsTArray<nsString>& aOutCodecs);
 
 bool
-IsH264ContentType(const nsAString& aContentType);
-
-bool
-IsAACContentType(const nsAString& aContentType);
+IsH264CodecString(const nsAString& aCodec);
 
 bool
 IsAACCodecString(const nsAString& aCodec);
 
+bool
+IsVP8CodecString(const nsAString& aCodec);
+
+bool
+IsVP9CodecString(const nsAString& aCodec);
+
 template <typename String>
 class StringListRange
 {
   typedef typename String::char_type CharType;
   typedef const CharType* Pointer;
 
 public:
   // Iterator into range, trims items and skips empty items.
@@ -442,20 +450,11 @@ StringListContains(const ListString& aLi
   for (const auto& listItem : MakeStringListRange(aList)) {
     if (listItem.Equals(aItem)) {
       return true;
     }
   }
   return false;
 }
 
-bool
-IsVorbisContentType(const nsAString& aContentType);
-
-bool
-IsVP8ContentType(const nsAString& aContentType);
-
-bool
-IsVP9ContentType(const nsAString& aContentType);
-
 } // end namespace mozilla
 
 #endif
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -28,16 +28,20 @@
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsXULAppAPI.h"
 #include "gmp-audio-decode.h"
 #include "gmp-video-decode.h"
 #include "DecoderDoctorDiagnostics.h"
 #include "WebMDecoder.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/dom/MediaSource.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
@@ -308,262 +312,807 @@ MediaKeySystemAccess::GetKeySystemStatus
       }
       return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
     }
   }
 
   return MediaKeySystemStatus::Cdm_not_supported;
 }
 
-static bool
-GMPDecryptsAndDecodesAAC(mozIGeckoMediaPluginService* aGMPS,
-                         const nsAString& aKeySystem,
-                         DecoderDoctorDiagnostics* aDiagnostics)
+typedef nsCString GMPCodecString;
+
+#define GMP_CODEC_AAC NS_LITERAL_CSTRING("aac")
+#define GMP_CODEC_OPUS NS_LITERAL_CSTRING("opus")
+#define GMP_CODEC_VORBIS NS_LITERAL_CSTRING("vorbis")
+#define GMP_CODEC_H264 NS_LITERAL_CSTRING("h264")
+#define GMP_CODEC_VP8 NS_LITERAL_CSTRING("vp8")
+#define GMP_CODEC_VP9 NS_LITERAL_CSTRING("vp9")
+
+GMPCodecString
+ToGMPAPICodecString(const nsString& aCodec)
+{
+  if (IsAACCodecString(aCodec)) {
+    return GMP_CODEC_AAC;
+  }
+  if (aCodec.EqualsLiteral("opus")) {
+    return GMP_CODEC_OPUS;
+  }
+  if (aCodec.EqualsLiteral("vorbis")) {
+    return GMP_CODEC_VORBIS;
+  }
+  if (IsH264CodecString(aCodec)) {
+    return GMP_CODEC_H264;
+  }
+  if (IsVP8CodecString(aCodec)) {
+    return GMP_CODEC_VP8;
+  }
+  if (IsVP9CodecString(aCodec)) {
+    return GMP_CODEC_VP9;
+  }
+  return EmptyCString();
+}
+
+// A codec can be decrypted-and-decoded by the CDM, or only decrypted
+// by the CDM and decoded by Gecko. Not both.
+struct KeySystemContainerSupport
+{
+  bool IsSupported() const
+  {
+    return !mCodecsDecoded.IsEmpty() || !mCodecsDecrypted.IsEmpty();
+  }
+
+  // CDM decrypts and decodes using a DRM robust decoder, and passes decoded
+  // samples back to Gecko for rendering.
+  bool DecryptsAndDecodes(GMPCodecString aCodec) const
+  {
+    return mCodecsDecoded.Contains(aCodec);
+  }
+
+  // CDM decrypts and passes the decrypted samples back to Gecko for decoding.
+  bool Decrypts(GMPCodecString aCodec) const
+  {
+    return mCodecsDecrypted.Contains(aCodec);
+  }
+
+  void SetCanDecryptAndDecode(GMPCodecString aCodec)
+  {
+    // Can't both decrypt and decrypt-and-decode a codec.
+    MOZ_ASSERT(!Decrypts(aCodec));
+    // Prevent duplicates.
+    MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
+    mCodecsDecoded.AppendElement(aCodec);
+  }
+
+  void SetCanDecrypt(GMPCodecString aCodec)
+  {
+    // Prevent duplicates.
+    MOZ_ASSERT(!Decrypts(aCodec));
+    // Can't both decrypt and decrypt-and-decode a codec.
+    MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
+    mCodecsDecrypted.AppendElement(aCodec);
+  }
+
+private:
+  nsTArray<GMPCodecString> mCodecsDecoded;
+  nsTArray<GMPCodecString> mCodecsDecrypted;
+};
+
+enum class KeySystemFeatureSupport
+{
+  Prohibited = 1,
+  Requestable = 2,
+  Required = 3,
+};
+
+struct KeySystemConfig
 {
-  MOZ_ASSERT(HaveGMPFor(aGMPS,
-                        NS_ConvertUTF16toUTF8(aKeySystem),
-                        NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  return HaveGMPFor(aGMPS,
-                    NS_ConvertUTF16toUTF8(aKeySystem),
-                    NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
-                    NS_LITERAL_CSTRING("aac"));
+  nsString mKeySystem;
+  nsTArray<nsString> mInitDataTypes;
+  KeySystemFeatureSupport mPersistentState = KeySystemFeatureSupport::Prohibited;
+  KeySystemFeatureSupport mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+  nsTArray<MediaKeySessionType> mSessionTypes;
+  nsTArray<nsString> mVideoRobustness;
+  nsTArray<nsString> mAudioRobustness;
+  KeySystemContainerSupport mMP4;
+  KeySystemContainerSupport mWebM;
+};
+
+StaticAutoPtr<nsTArray<KeySystemConfig>> sKeySystemConfigs;
+
+static const nsTArray<KeySystemConfig>&
+GetSupportedKeySystems()
+{
+  if (!sKeySystemConfigs) {
+    sKeySystemConfigs = new nsTArray<KeySystemConfig>();
+    ClearOnShutdown(&sKeySystemConfigs);
+
+    {
+      KeySystemConfig clearkey;
+      clearkey.mKeySystem = NS_LITERAL_STRING("org.w3.clearkey");
+      clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+      clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
+      clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
+      clearkey.mPersistentState = KeySystemFeatureSupport::Requestable;
+      clearkey.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+      clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+      clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license);
+#if defined(XP_WIN)
+      // Clearkey CDM uses WMF decoders on Windows.
+      if (WMFDecoderModule::HasAAC()) {
+        clearkey.mMP4.SetCanDecryptAndDecode(GMP_CODEC_AAC);
+      } else {
+        clearkey.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+      }
+      if (WMFDecoderModule::HasH264()) {
+        clearkey.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
+      } else {
+        clearkey.mMP4.SetCanDecrypt(GMP_CODEC_H264);
+      }
+#else
+      clearkey.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+      clearkey.mMP4.SetCanDecrypt(GMP_CODEC_H264);
+#endif
+      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VORBIS);
+      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_OPUS);
+      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VP8);
+      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VP9);
+      sKeySystemConfigs->AppendElement(Move(clearkey));
+    }
+    {
+      KeySystemConfig widevine;
+      widevine.mKeySystem = NS_LITERAL_STRING("com.widevine.alpha");
+      widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+      widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
+      widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
+      widevine.mPersistentState = KeySystemFeatureSupport::Requestable;
+      widevine.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+      widevine.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+      widevine.mAudioRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_CRYPTO"));
+      widevine.mVideoRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_DECODE"));
+#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 our policy is to prevent
+      //  the Adobe GMP's unencrypted AAC decoding path being used to
+      // decode content decrypted by the Widevine CDM.
+      if (WMFDecoderModule::HasAAC()) {
+        widevine.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+      }
+#else
+      widevine.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+#endif
+      widevine.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
+      // TODO: Enable Widevine/WebM once bug 1279077 lands.
+      //widevine.mWebM.SetCanDecrypt(GMP_CODEC_VORBIS);
+      //widevine.mWebM.SetCanDecrypt(GMP_CODEC_OPUS);
+      //widevine.mWebM.SetCanDecryptAndDecode(GMP_CODEC_VP8);
+      //widevine.mWebM.SetCanDecryptAndDecode(GMP_CODEC_VP9);
+      sKeySystemConfigs->AppendElement(Move(widevine));
+    }
+    {
+      KeySystemConfig primetime;
+      primetime.mKeySystem = NS_LITERAL_STRING("com.adobe.primetime");
+      primetime.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+      primetime.mPersistentState = KeySystemFeatureSupport::Required;
+      primetime.mDistinctiveIdentifier = KeySystemFeatureSupport::Required;
+      primetime.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+      primetime.mMP4.SetCanDecryptAndDecode(GMP_CODEC_AAC);
+      primetime.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
+      sKeySystemConfigs->AppendElement(Move(primetime));
+    }
+  }
+  return *sKeySystemConfigs;
 }
 
+static const KeySystemConfig*
+GetKeySystemConfig(const nsAString& aKeySystem)
+{
+  for (const KeySystemConfig& config : GetSupportedKeySystems()) {
+    if (config.mKeySystem.Equals(aKeySystem)) {
+      return &config;
+    }
+  }
+  return nullptr;
+}
+
+enum CodecType
+{
+  Audio,
+  Video,
+  Invalid
+};
+
 static bool
-GMPDecryptsAndDecodesH264(mozIGeckoMediaPluginService* aGMPS,
-                          const nsAString& aKeySystem,
-                          DecoderDoctorDiagnostics* aDiagnostics)
+CanDecryptAndDecode(mozIGeckoMediaPluginService* aGMPService,
+                    const nsString& aKeySystem,
+                    const nsString& aContentType,
+                    CodecType aCodecType,
+                    const KeySystemContainerSupport& aContainerSupport,
+                    const nsTArray<GMPCodecString>& aCodecs,
+                    DecoderDoctorDiagnostics* aDiagnostics)
 {
-  MOZ_ASSERT(HaveGMPFor(aGMPS,
-                        NS_ConvertUTF16toUTF8(aKeySystem),
-                        NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  return HaveGMPFor(aGMPS,
-                    NS_ConvertUTF16toUTF8(aKeySystem),
-                    NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
-                    NS_LITERAL_CSTRING("h264"));
-}
-
-// If this keysystem's CDM explicitly says it doesn't support decoding,
-// that means it's OK with passing the decrypted samples back to Gecko
-// for decoding.
-static bool
-GMPDecryptsAndGeckoDecodesH264(mozIGeckoMediaPluginService* aGMPService,
-                               const nsAString& aKeySystem,
-                               const nsAString& aContentType,
-                               DecoderDoctorDiagnostics* aDiagnostics)
-{
-  MOZ_ASSERT(HaveGMPFor(aGMPService,
-                        NS_ConvertUTF16toUTF8(aKeySystem),
-                        NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  MOZ_ASSERT(IsH264ContentType(aContentType));
-  return !HaveGMPFor(aGMPService,
-                     NS_ConvertUTF16toUTF8(aKeySystem),
-                     NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
-                     NS_LITERAL_CSTRING("h264")) &&
-         MP4Decoder::CanHandleMediaType(aContentType, aDiagnostics);
-}
-
-static bool
-GMPDecryptsAndGeckoDecodesAAC(mozIGeckoMediaPluginService* aGMPService,
-                              const nsAString& aKeySystem,
-                              const nsAString& aContentType,
-                              DecoderDoctorDiagnostics* aDiagnostics)
-{
+  MOZ_ASSERT(aCodecType != Invalid);
   MOZ_ASSERT(HaveGMPFor(aGMPService,
                         NS_ConvertUTF16toUTF8(aKeySystem),
                         NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  MOZ_ASSERT(IsAACContentType(aContentType));
+  for (const GMPCodecString& codec : aCodecs) {
+    MOZ_ASSERT(!codec.IsEmpty());
+
+    nsCString api = (aCodecType == Audio) ? NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER)
+                                          : NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
+    if (aContainerSupport.DecryptsAndDecodes(codec) &&
+        HaveGMPFor(aGMPService,
+                   NS_ConvertUTF16toUTF8(aKeySystem),
+                   api,
+                   codec)) {
+      // GMP can decrypt-and-decode this codec.
+      continue;
+    }
 
-  if (HaveGMPFor(aGMPService,
-    NS_ConvertUTF16toUTF8(aKeySystem),
-    NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
-    NS_LITERAL_CSTRING("aac"))) {
-    // We do have a GMP for AAC -> Gecko itself does *not* decode AAC.
+    if (aContainerSupport.Decrypts(codec) &&
+        NS_SUCCEEDED(MediaSource::IsTypeSupported(aContentType, aDiagnostics))) {
+      // 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 our policy is to prevent
+    //  the Adobe GMP's unencrypted AAC decoding path being used to
+    // decode content decrypted by the Widevine CDM.
+    if (codec == GMP_CODEC_AAC &&
+        aKeySystem.EqualsLiteral("com.widevine.alpha") &&
+        !WMFDecoderModule::HasAAC()) {
+      if (aDiagnostics) {
+        aDiagnostics->SetKeySystemIssue(
+          DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+      }
+    }
+#endif
     return false;
   }
-#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 our policy is to prevent
-  //  the Adobe GMP's unencrypted AAC decoding path being used to
-  // decode content decrypted by the Widevine CDM.
-  if (aKeySystem.EqualsLiteral("com.widevine.alpha") &&
-      !WMFDecoderModule::HasAAC()) {
-    if (aDiagnostics) {
-      aDiagnostics->SetKeySystemIssue(
-        DecoderDoctorDiagnostics::eWidevineWithNoWMF);
-    }
-    return false;
-  }
-#endif
-  return MP4Decoder::CanHandleMediaType(aContentType, aDiagnostics);
-}
-
-static bool
-GMPDecryptsAndGeckoDecodesVorbis(mozIGeckoMediaPluginService* aGMPService,
-                                const nsAString& aKeySystem,
-                                const nsAString& aContentType,
-                                DecoderDoctorDiagnostics* aDiagnostics)
-{
-  MOZ_ASSERT(HaveGMPFor(aGMPService,
-             NS_ConvertUTF16toUTF8(aKeySystem),
-             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  MOZ_ASSERT(IsVorbisContentType(aContentType));
-  return !HaveGMPFor(aGMPService,
-                     NS_ConvertUTF16toUTF8(aKeySystem),
-                     NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
-                     NS_LITERAL_CSTRING("vorbis")) &&
-         WebMDecoder::CanHandleMediaType(aContentType);
+  return true;
 }
 
 static bool
-GMPDecryptsAndGeckoDecodesVP8(mozIGeckoMediaPluginService* aGMPService,
-                             const nsAString& aKeySystem,
-                             const nsAString& aContentType,
-                             DecoderDoctorDiagnostics* aDiagnostics)
-{
-  MOZ_ASSERT(HaveGMPFor(aGMPService,
-             NS_ConvertUTF16toUTF8(aKeySystem),
-             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  MOZ_ASSERT(IsVP8ContentType(aContentType));
-  return !HaveGMPFor(aGMPService,
-                     NS_ConvertUTF16toUTF8(aKeySystem),
-                     NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
-                     NS_LITERAL_CSTRING("vp8")) &&
-         WebMDecoder::CanHandleMediaType(aContentType);
-}
-
-static bool
-GMPDecryptsAndGeckoDecodesVP9(mozIGeckoMediaPluginService* aGMPService,
-                             const nsAString& aKeySystem,
-                             const nsAString& aContentType,
-                             DecoderDoctorDiagnostics* aDiagnostics)
+ToSessionType(const nsAString& aSessionType, MediaKeySessionType& aOutType)
 {
-  MOZ_ASSERT(HaveGMPFor(aGMPService,
-             NS_ConvertUTF16toUTF8(aKeySystem),
-             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  MOZ_ASSERT(IsVP9ContentType(aContentType));
-  return !HaveGMPFor(aGMPService,
-                     NS_ConvertUTF16toUTF8(aKeySystem),
-                     NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
-                     NS_LITERAL_CSTRING("vp9")) &&
-         WebMDecoder::CanHandleMediaType(aContentType);
-}
-
-static bool
-IsSupportedAudio(mozIGeckoMediaPluginService* aGMPService,
-                 const nsAString& aKeySystem,
-                 const nsAString& aAudioType,
-                 DecoderDoctorDiagnostics* aDiagnostics)
-{
-  if (IsAACContentType(aAudioType)) {
-    return GMPDecryptsAndDecodesAAC(aGMPService, aKeySystem, aDiagnostics) ||
-           GMPDecryptsAndGeckoDecodesAAC(aGMPService, aKeySystem, aAudioType, aDiagnostics);
+  using MediaKeySessionTypeValues::strings;
+  const char* temporary =
+    strings[static_cast<uint32_t>(MediaKeySessionType::Temporary)].value;
+  if (aSessionType.EqualsASCII(temporary)) {
+    aOutType = MediaKeySessionType::Temporary;
+    return true;
   }
-  if (IsVorbisContentType(aAudioType) && aKeySystem.EqualsLiteral("org.w3.clearkey")) {
-    // GMP does not decode Vorbis, so don't bother checking
-    return GMPDecryptsAndGeckoDecodesVorbis(aGMPService, aKeySystem, aAudioType, aDiagnostics);
+  const char* persistentLicense =
+    strings[static_cast<uint32_t>(MediaKeySessionType::Persistent_license)].value;
+  if (aSessionType.EqualsASCII(persistentLicense)) {
+    aOutType = MediaKeySessionType::Persistent_license;
+    return true;
   }
   return false;
 }
 
+// 5.2.1 Is persistent session type?
 static bool
-IsSupportedVideo(mozIGeckoMediaPluginService* aGMPService,
-                 const nsAString& aKeySystem,
-                 const nsAString& aVideoType,
-                 DecoderDoctorDiagnostics* aDiagnostics)
+IsPersistentSessionType(MediaKeySessionType aSessionType)
 {
-  if (IsH264ContentType(aVideoType)) {
-    return GMPDecryptsAndDecodesH264(aGMPService, aKeySystem, aDiagnostics) ||
-           GMPDecryptsAndGeckoDecodesH264(aGMPService, aKeySystem, aVideoType, aDiagnostics);
+  return aSessionType == MediaKeySessionType::Persistent_license;
+}
+
+CodecType
+GetMajorType(const nsAString& aContentType)
+{
+  if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("audio/"), aContentType)) {
+    return Audio;
   }
-  if (IsVP8ContentType(aVideoType) && aKeySystem.EqualsLiteral("org.w3.clearkey")) {
-    return GMPDecryptsAndGeckoDecodesVP8(aGMPService, aKeySystem, aVideoType, aDiagnostics);
+  if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("video/"), aContentType)) {
+    return Video;
   }
-  if (IsVP9ContentType(aVideoType) && aKeySystem.EqualsLiteral("org.w3.clearkey")) {
-    return GMPDecryptsAndGeckoDecodesVP9(aGMPService, aKeySystem, aVideoType, aDiagnostics);
-  }
-  return false;
+  return Invalid;
 }
 
-static bool
-IsSupportedInitDataType(const nsString& aCandidate, const nsAString& aKeySystem)
+// 3.1.2.3 Get Supported Capabilities for Audio/Video Type
+static Sequence<MediaKeySystemMediaCapability>
+GetSupportedCapabilities(mozIGeckoMediaPluginService* aGMPService,
+                         const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
+                         const MediaKeySystemConfiguration& aPartialConfig,
+                         const KeySystemConfig& aKeySystem,
+                         DecoderDoctorDiagnostics* aDiagnostics)
 {
-  // All supported keySystems can handle "cenc" initDataType.
-  // ClearKey also supports "keyids" and "webm" initDataTypes.
-  return aCandidate.EqualsLiteral("cenc") ||
-    ((aKeySystem.EqualsLiteral("org.w3.clearkey")
-    || aKeySystem.EqualsLiteral("com.widevine.alpha")) &&
-    (aCandidate.EqualsLiteral("keyids") || aCandidate.EqualsLiteral("webm")));
+  // 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& contentType = capabilities.mContentType;
+    // Let robustness be requested media capability's robustness member.
+    const nsString& robustness = capabilities.mRobustness;
+    // If content type is the empty string, return null.
+    if (contentType.IsEmpty()) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') rejected; "
+              "audio or video capability has empty contentType.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      return Sequence<MediaKeySystemMediaCapability>();
+    }
+    // If content type is an invalid or unrecognized MIME type, continue
+    // to the next iteration.
+    nsAutoString container;
+    nsTArray<nsString> codecStrings;
+    if (!ParseMIMETypeString(contentType, container, codecStrings)) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "failed to parse contentType as MIME type.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      continue;
+    }
+    bool invalid = false;
+    nsTArray<GMPCodecString> codecs;
+    for (const nsString& codecString : codecStrings) {
+      GMPCodecString gmpCodec = ToGMPAPICodecString(codecString);
+      if (gmpCodec.IsEmpty()) {
+        invalid = true;
+        EME_LOG("MediaKeySystemConfiguration (label='%s') "
+                "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+                "'%s' is an invalid codec string.",
+                NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+                NS_ConvertUTF16toUTF8(contentType).get(),
+                NS_ConvertUTF16toUTF8(robustness).get(),
+                NS_ConvertUTF16toUTF8(codecString).get());
+        break;
+      }
+      codecs.AppendElement(gmpCodec);
+    }
+    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.)
+    NS_ConvertUTF16toUTF8 container_utf8(container);
+    const bool isMP4 = DecoderTraits::IsMP4TypeAndEnabled(container_utf8, aDiagnostics);
+    if (isMP4 && !aKeySystem.mMP4.IsSupported()) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "MP4 requested but unsupported.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      continue;
+    }
+    const bool isWebM = DecoderTraits::IsWebMTypeAndEnabled(container_utf8);
+    if (isWebM && !aKeySystem.mWebM.IsSupported()) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "WebM requested but unsupported.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      continue;
+    }
+    if (!isMP4 && !isWebM) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "Unsupported or unrecognized container requested.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).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.
+    // 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:
+    const auto majorType = GetMajorType(container);
+    if (codecs.IsEmpty()) {
+      // If container normatively implies a specific set of codecs and codec constraints:
+      // Let parameters be that set.
+      if (isMP4) {
+        if (majorType == Audio) {
+          codecs.AppendElement(GMP_CODEC_AAC);
+        } else if (majorType == Video) {
+          codecs.AppendElement(GMP_CODEC_H264);
+        }
+      } else if (isWebM) {
+        if (majorType == Audio) {
+          codecs.AppendElement(GMP_CODEC_VORBIS);
+        } else if (majorType == Video) {
+          codecs.AppendElement(GMP_CODEC_VP8);
+        }
+      }
+      // Otherwise: Continue to the next iteration.
+      // (Note: all containers we support have implied codecs, so don't continue here.)
+    }
+
+    // If content type is not strictly a audio/video type, continue to the next iteration.
+    if (majorType == Invalid) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "MIME type is not an audio or video MIME type.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).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') unsupported; "
+                "unsupported robustness string.",
+                NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+                NS_ConvertUTF16toUTF8(contentType).get(),
+                NS_ConvertUTF16toUTF8(robustness).get());
+        continue;
+      }
+      if (majorType == Video && !aKeySystem.mVideoRobustness.Contains(robustness)) {
+        EME_LOG("MediaKeySystemConfiguration (label='%s') "
+                "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+                "unsupported robustness string.",
+                NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+                NS_ConvertUTF16toUTF8(contentType).get(),
+                NS_ConvertUTF16toUTF8(robustness).get());
+        continue;
+      }
+      // Note: specified robustness requirements are satisfied.
+    }
+
+    // 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(aGMPService,
+                             aKeySystem.mKeySystem,
+                             contentType,
+                             majorType,
+                             containerSupport,
+                             codecs,
+                             aDiagnostics)) {
+        EME_LOG("MediaKeySystemConfiguration (label='%s') "
+                "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+                "codec unsupported by CDM requested.",
+                NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+                NS_ConvertUTF16toUTF8(contentType).get(),
+                NS_ConvertUTF16toUTF8(robustness).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 Move(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 KeySystemFeatureSupport aFeatureSupport,
+                 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 &&
+      aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
+    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 (aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
+        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 (aFeatureSupport == KeySystemFeatureSupport::Required) {
+        return false;
+      }
+      break;
+    }
+    default: {
+      return false;
+    }
+  }
+
+  // Set the requirement member of accumulated configuration to equal
+  // calculated requirement.
+  aOutRequirement = requirement;
+
+  return true;
+}
+
+// 3.1.2.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 {
+    using MediaKeySessionTypeValues::strings;
+    const char* temporary = strings[static_cast<uint32_t>(MediaKeySessionType::Temporary)].value;
+    // Note: fallible. Results in an empty array.
+    sessionTypes.AppendElement(NS_ConvertUTF8toUTF16(nsDependentCString(temporary)), mozilla::fallible);
+  }
+  return sessionTypes;
+}
+
+// 3.1.2.2 Get Supported Configuration and Consent
 static bool
 GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
-                   const nsAString& aKeySystem,
+                   const KeySystemConfig& aKeySystem,
                    const MediaKeySystemConfiguration& aCandidate,
                    MediaKeySystemConfiguration& aOutConfig,
                    DecoderDoctorDiagnostics* aDiagnostics)
 {
+  // 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 (aCandidate.mInitDataTypes.WasPassed()) {
-    nsTArray<nsString> initDataTypes;
-    for (const nsString& candidate : aCandidate.mInitDataTypes.Value()) {
-      if (IsSupportedInitDataType(candidate, aKeySystem)) {
-        initDataTypes.AppendElement(candidate);
+  // 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 (initDataTypes.IsEmpty()) {
+    // 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;
+  }
+
+  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 (!aKeySystem.mSessionTypes.Contains(sessionType)) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+              "session type '%s' unsupported by keySystem.",
+              NS_ConvertUTF16toUTF8(aCandidate.mLabel).get(),
+              NS_ConvertUTF16toUTF8(sessionTypeString).get());
       return false;
     }
-    config.mInitDataTypes.Construct();
-    config.mInitDataTypes.Value().Assign(initDataTypes);
+    // 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;
+    }
   }
-  if (aCandidate.mAudioCapabilities.WasPassed()) {
-    nsTArray<MediaKeySystemMediaCapability> caps;
-    for (const MediaKeySystemMediaCapability& cap : aCandidate.mAudioCapabilities.Value()) {
-      if (IsSupportedAudio(aGMPService, aKeySystem, cap.mContentType, aDiagnostics)) {
-        caps.AppendElement(cap);
-      }
-    }
+  // Set the sessionTypes member of accumulated configuration to session types.
+  config.mSessionTypes.Construct(Move(sessionTypes));
+
+  // If the videoCapabilities and audioCapabilities members in candidate
+  // configuration are both empty, return NotSupported.
+  // 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.
+
+  // 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(aGMPService,
+                               aCandidate.mVideoCapabilities,
+                               config,
+                               aKeySystem,
+                               aDiagnostics);
+    // 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;
     }
-    config.mAudioCapabilities.Construct();
-    config.mAudioCapabilities.Value().Assign(caps);
+    // Set the videoCapabilities member of accumulated configuration to video capabilities.
+    config.mVideoCapabilities = Move(caps);
+  } else {
+    // Otherwise:
+    // Set the videoCapabilities member of accumulated configuration to an empty sequence.
   }
-  if (aCandidate.mVideoCapabilities.WasPassed()) {
-    nsTArray<MediaKeySystemMediaCapability> caps;
-    for (const MediaKeySystemMediaCapability& cap : aCandidate.mVideoCapabilities.Value()) {
-      if (IsSupportedVideo(aGMPService, aKeySystem, cap.mContentType, aDiagnostics)) {
-        caps.AppendElement(cap);
-      }
-    }
+
+  // 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(aGMPService,
+                               aCandidate.mAudioCapabilities,
+                               config,
+                               aKeySystem,
+                               aDiagnostics);
+    // 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;
     }
-    config.mVideoCapabilities.Construct();
-    config.mVideoCapabilities.Value().Assign(caps);
+    // Set the audioCapabilities member of accumulated configuration to audio capabilities.
+    config.mAudioCapabilities = 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 == KeySystemFeatureSupport::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 == KeySystemFeatureSupport::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 (aKeySystem.EqualsLiteral("com.widevine.alpha") &&
-      (!aCandidate.mAudioCapabilities.WasPassed() ||
-       !aCandidate.mVideoCapabilities.WasPassed()) &&
+  if (aKeySystem.mKeySystem.EqualsLiteral("com.widevine.alpha") &&
+      (aCandidate.mAudioCapabilities.IsEmpty() ||
+       aCandidate.mVideoCapabilities.IsEmpty()) &&
      !WMFDecoderModule::HasAAC()) {
     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,
@@ -571,26 +1120,29 @@ MediaKeySystemAccess::GetSupportedConfig
                                          MediaKeySystemConfiguration& aOutConfig,
                                          DecoderDoctorDiagnostics* aDiagnostics)
 {
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (NS_WARN_IF(!mps)) {
     return false;
   }
-
+  const KeySystemConfig* implementation = nullptr;
   if (!HaveGMPFor(mps,
                   NS_ConvertUTF16toUTF8(aKeySystem),
-                  NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
+                  NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)) ||
+      !(implementation = GetKeySystemConfig(aKeySystem))) {
     return false;
   }
-
-  for (const MediaKeySystemConfiguration& config : aConfigs) {
-    if (mozilla::dom::GetSupportedConfig(
-          mps, aKeySystem, config, aOutConfig, aDiagnostics)) {
+  for (const MediaKeySystemConfiguration& candidate : aConfigs) {
+    if (mozilla::dom::GetSupportedConfig(mps,
+                                         *implementation,
+                                         candidate,
+                                         aOutConfig,
+                                         aDiagnostics)) {
       return true;
     }
   }
 
   return false;
 }
 
 
--- a/dom/media/eme/MediaKeySystemAccessManager.cpp
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -76,16 +76,31 @@ MediaKeySystemAccessManager::Request(Det
 void
 MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
                                      const nsAString& aKeySystem,
                                      const Sequence<MediaKeySystemConfiguration>& aConfigs,
                                      RequestType aType)
 {
   EME_LOG("MediaKeySystemAccessManager::Request %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
 
+  if (aKeySystem.IsEmpty()) {
+    aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+                          NS_LITERAL_CSTRING("Key system string is empty"));
+    // Don't notify DecoderDoctor, as there's nothing we or the user can
+    // do to fix this situation; the site is using the API wrong.
+    return;
+  }
+  if (aConfigs.IsEmpty()) {
+    aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+                          NS_LITERAL_CSTRING("Candidate MediaKeySystemConfigs is empty"));
+    // Don't notify DecoderDoctor, as there's nothing we or the user can
+    // do to fix this situation; the site is using the API wrong.
+    return;
+  }
+
   DecoderDoctorDiagnostics diagnostics;
 
   // Parse keysystem, split it out into keySystem prefix, and version suffix.
   nsAutoString keySystem;
   int32_t minCdmVersion = NO_CDM_VERSION;
   if (!ParseKeySystem(aKeySystem, keySystem, minCdmVersion)) {
     // Not to inform user, because nothing to do if the keySystem is not
     // supported.
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -76,18 +76,21 @@ IsWebMForced(DecoderDoctorDiagnostics* a
 {
   bool mp4supported =
     DecoderTraits::IsMP4TypeAndEnabled(NS_LITERAL_CSTRING("video/mp4"),
                                        aDiagnostics);
   bool hwsupported = gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding();
   return !mp4supported || !hwsupported || VP9Benchmark::IsVP9DecodeFast();
 }
 
-static nsresult
-IsTypeSupported(const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics)
+namespace dom {
+
+/* static */
+nsresult
+MediaSource::IsTypeSupported(const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (aType.IsEmpty()) {
     return NS_ERROR_DOM_TYPE_ERR;
   }
   nsContentTypeParser parser(aType);
   nsAutoString mimeType;
   nsresult rv = parser.GetType(mimeType);
   if (NS_FAILED(rv)) {
@@ -127,18 +130,16 @@ IsTypeSupported(const nsAString& aType, 
         return NS_OK;
       }
     }
   }
 
   return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 }
 
-namespace dom {
-
 /* static */ already_AddRefed<MediaSource>
 MediaSource::Constructor(const GlobalObject& aGlobal,
                          ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
   if (!window) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
@@ -216,17 +217,17 @@ MediaSource::SetDuration(double aDuratio
   mDecoder->SetMediaSourceDuration(aDuration);
 }
 
 already_AddRefed<SourceBuffer>
 MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecoderDoctorDiagnostics diagnostics;
-  nsresult rv = mozilla::IsTypeSupported(aType, &diagnostics);
+  nsresult rv = IsTypeSupported(aType, &diagnostics);
   diagnostics.StoreFormatDiagnostics(GetOwner()
                                      ? GetOwner()->GetExtantDoc()
                                      : nullptr,
                                      aType, NS_SUCCEEDED(rv), __func__);
   MSE_API("AddSourceBuffer(aType=%s)%s",
           NS_ConvertUTF16toUTF8(aType).get(),
           rv == NS_OK ? "" : " [not supported]");
   if (NS_FAILED(rv)) {
@@ -336,17 +337,17 @@ MediaSource::EndOfStream(const Optional<
   }
 }
 
 /* static */ bool
 MediaSource::IsTypeSupported(const GlobalObject& aOwner, const nsAString& aType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecoderDoctorDiagnostics diagnostics;
-  nsresult rv = mozilla::IsTypeSupported(aType, &diagnostics);
+  nsresult rv = IsTypeSupported(aType, &diagnostics);
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aOwner.GetAsSupports());
   diagnostics.StoreFormatDiagnostics(window ? window->GetExtantDoc() : nullptr,
                                      aType, NS_SUCCEEDED(rv), __func__);
 #define this nullptr
   MSE_API("IsTypeSupported(aType=%s)%s ",
           NS_ConvertUTF16toUTF8(aType).get(), rv == NS_OK ? "OK" : "[not supported]");
 #undef this // don't ever remove this line !
   return NS_SUCCEEDED(rv);
--- a/dom/media/mediasource/MediaSource.h
+++ b/dom/media/mediasource/MediaSource.h
@@ -60,16 +60,17 @@ public:
   void RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv);
 
   void EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv);
 
   void SetLiveSeekableRange(double aStart, double aEnd, ErrorResult& aRv);
   void ClearLiveSeekableRange(ErrorResult& aRv);
 
   static bool IsTypeSupported(const GlobalObject&, const nsAString& aType);
+  static nsresult IsTypeSupported(const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics);
 
   static bool Enabled(JSContext* cx, JSObject* aGlobal);
 
   IMPL_EVENT_HANDLER(sourceopen);
   IMPL_EVENT_HANDLER(sourceended);
   IMPL_EVENT_HANDLER(sourceclosed);
 
   /** End WebIDL Methods. */