Bug 1278198 - Implement "Get Supported Configuration" algorithm in MediaKeySystemAccess. r=gerald draft
authorChris Pearce <cpearce@mozilla.com>
Fri, 01 Jul 2016 13:36:57 +1200
changeset 390993 a465eee5c29acb8ece448b1cc371fcf893563fff
parent 390992 5f9eddd8e8860b47386150dff2894b820f3c7a37
child 390994 528d9c344aa2a2c1c4b0eb321ff239397d4c0d29
push id23779
push usercpearce@mozilla.com
push dateThu, 21 Jul 2016 22:43:31 +0000
reviewersgerald
bugs1278198
milestone50.0a1
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
@@ -80,18 +80,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)) {
@@ -131,18 +134,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;
@@ -220,17 +221,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)) {
@@ -340,17 +341,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. */