Bug 848994 - p3. Check MediaKeySystem requests - r?cpearce draft
authorGerald Squelart <gsquelart@mozilla.com>
Fri, 22 Apr 2016 11:48:28 +1000
changeset 355184 63977c43feccf317a8d21715926b2b19d4325dc1
parent 355183 f2506b33d79174616120f8413b7d890871bf239a
child 355185 57607a048a776cc53b28546dbf56f1f1a4fe5ef4
push id16221
push usergsquelart@mozilla.com
push dateFri, 22 Apr 2016 01:56:41 +0000
reviewerscpearce
bugs848994
milestone48.0a1
Bug 848994 - p3. Check MediaKeySystem requests - r?cpearce Media Key System access requests are now recorded with their success/failure, as well as accompanying issues of importance. In this bug we focus on the Widevine-with-no-WMF case. MozReview-Commit-ID: ElBN6cXKwAW
dom/media/DecoderDoctorDiagnostics.cpp
dom/media/DecoderDoctorDiagnostics.h
dom/media/eme/MediaKeySystemAccess.cpp
dom/media/eme/MediaKeySystemAccess.h
dom/media/eme/MediaKeySystemAccessManager.cpp
--- a/dom/media/DecoderDoctorDiagnostics.cpp
+++ b/dom/media/DecoderDoctorDiagnostics.cpp
@@ -298,32 +298,49 @@ DecoderDoctorDocumentWatcher::Synthesize
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   bool canPlay = false;
 #if defined(MOZ_FFMPEG)
   bool FFMpegNeeded = false;
 #endif
   nsAutoString unplayableFormats;
+  nsAutoString unsupportedKeySystems;
 
   for (auto& diag : mDiagnosticsSequence) {
-    if (!diag.mDecoderDoctorDiagnostics.Format().IsEmpty()) {
+    switch (diag.mDecoderDoctorDiagnostics.Type()) {
+    case DecoderDoctorDiagnostics::eFormatSupportCheck:
       if (diag.mDecoderDoctorDiagnostics.CanPlay()) {
         canPlay = true;
       } else {
 #if defined(MOZ_FFMPEG)
         if (diag.mDecoderDoctorDiagnostics.DidFFmpegFailToLoad()) {
           FFMpegNeeded = true;
         }
 #endif
         if (!unplayableFormats.IsEmpty()) {
           unplayableFormats += NS_LITERAL_STRING(", ");
         }
         unplayableFormats += diag.mDecoderDoctorDiagnostics.Format();
       }
+      break;
+    case DecoderDoctorDiagnostics::eMediaKeySystemAccessRequest:
+      if (!diag.mDecoderDoctorDiagnostics.IsKeySystemSupported()) {
+        if (!unsupportedKeySystems.IsEmpty()) {
+          unsupportedKeySystems += NS_LITERAL_STRING(", ");
+        }
+        unsupportedKeySystems += diag.mDecoderDoctorDiagnostics.KeySystem();
+      }
+      break;
+    default:
+      MOZ_ASSERT(diag.mDecoderDoctorDiagnostics.Type()
+                   == DecoderDoctorDiagnostics::eFormatSupportCheck
+                 || diag.mDecoderDoctorDiagnostics.Type()
+                      == DecoderDoctorDiagnostics::eMediaKeySystemAccessRequest);
+      break;
     }
   }
 
   if (!canPlay) {
 #if defined(MOZ_FFMPEG)
     if (FFMpegNeeded) {
       DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because platform decoder was not found",
                this, mDocument, NS_ConvertUTF16toUTF8(unplayableFormats).get());
@@ -405,16 +422,20 @@ DecoderDoctorDocumentWatcher::Notify(nsI
 
 void
 DecoderDoctorDiagnostics::StoreFormatDiagnostics(nsIDocument* aDocument,
                                                  const nsAString& aFormat,
                                                  bool aCanPlay,
                                                  const char* aCallSite)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  // Make sure we only store once.
+  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
+  mDiagnosticsType = eFormatSupportCheck;
+
   if (NS_WARN_IF(!aDocument)) {
     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=nullptr, format='%s', can-play=%d, call site '%s')",
             this, NS_ConvertUTF16toUTF8(aFormat).get(), aCanPlay, aCallSite);
     return;
   }
   if (NS_WARN_IF(aFormat.IsEmpty())) {
     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=%p, format=<empty>, can-play=%d, call site '%s')",
             this, aDocument, aCanPlay, aCallSite);
@@ -431,31 +452,96 @@ DecoderDoctorDiagnostics::StoreFormatDia
   }
 
   mFormat = aFormat;
   mCanPlay = aCanPlay;
 
   // StoreDiagnostics should only be called once, after all data is available,
   // so it is safe to Move() from this object.
   watcher->AddDiagnostics(Move(*this), aCallSite);
+  // Even though it's moved-from, the type should stay set
+  // (Only used to ensure that we do store only once.)
+  MOZ_ASSERT(mDiagnosticsType == eFormatSupportCheck);
+}
+
+void
+DecoderDoctorDiagnostics::StoreMediaKeySystemAccess(nsIDocument* aDocument,
+                                                    const nsAString& aKeySystem,
+                                                    bool aIsSupported,
+                                                    const char* aCallSite)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // Make sure we only store once.
+  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
+  mDiagnosticsType = eMediaKeySystemAccessRequest;
+
+  if (NS_WARN_IF(!aDocument)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=nullptr, keysystem='%s', supported=%d, call site '%s')",
+            this, NS_ConvertUTF16toUTF8(aKeySystem).get(), aIsSupported, aCallSite);
+    return;
+  }
+  if (NS_WARN_IF(aKeySystem.IsEmpty())) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem=<empty>, supported=%d, call site '%s')",
+            this, aDocument, aIsSupported, aCallSite);
+    return;
+  }
+
+  RefPtr<DecoderDoctorDocumentWatcher> watcher =
+    DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
+
+  if (NS_WARN_IF(!watcher)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem='%s', supported=%d, call site '%s') - Could not create document watcher",
+            this, NS_ConvertUTF16toUTF8(aKeySystem).get(), aIsSupported, aCallSite);
+    return;
+  }
+
+  mKeySystem = aKeySystem;
+  mIsKeySystemSupported = aIsSupported;
+
+  // StoreDiagnostics should only be called once, after all data is available,
+  // so it is safe to Move() from this object.
+  watcher->AddDiagnostics(Move(*this), aCallSite);
+  // Even though it's moved-from, the type should stay set
+  // (Only used to ensure that we do store only once.)
+  MOZ_ASSERT(mDiagnosticsType == eMediaKeySystemAccessRequest);
 }
 
 nsCString
 DecoderDoctorDiagnostics::GetDescription() const
 {
+  MOZ_ASSERT(mDiagnosticsType == eFormatSupportCheck
+             || mDiagnosticsType == eMediaKeySystemAccessRequest);
   nsCString s;
-  if (!mFormat.IsEmpty()) {
+  switch (mDiagnosticsType) {
+  case eUnsaved:
+    s = "Unsaved diagnostics, cannot get accurate description";
+    break;
+  case eFormatSupportCheck:
     s = "format='";
     s += NS_ConvertUTF16toUTF8(mFormat).get();
     s += mCanPlay ? "', can play" : "', cannot play";
     if (mWMFFailedToLoad) {
       s += ", Windows platform decoder failed to load";
     }
     if (mFFmpegFailedToLoad) {
       s += ", Linux platform decoder failed to load";
     }
-  } else {
-    s = "?";
+    break;
+  case eMediaKeySystemAccessRequest:
+    s = "key system='";
+    s += NS_ConvertUTF16toUTF8(mKeySystem).get();
+    s += mIsKeySystemSupported ? "', supported" : "', not supported";
+    switch (mKeySystemIssue) {
+    case eUnset:
+      break;
+    case eWidevineWithNoWMF:
+      s += ", Widevine with no WMF";
+      break;
+    }
+    break;
+    default:
+      s = "?";
+      break;
   }
   return s;
 }
 
 } // namespace mozilla
--- a/dom/media/DecoderDoctorDiagnostics.h
+++ b/dom/media/DecoderDoctorDiagnostics.h
@@ -2,18 +2,19 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DecoderDoctorDiagnostics_h_
 #define DecoderDoctorDiagnostics_h_
 
+#include "nsString.h"
+
 class nsIDocument;
-class nsAString;
 
 namespace mozilla {
 
 // DecoderDoctorDiagnostics class, used to gather data from PDMs/DecoderTraits,
 // and then notify the user about issues preventing (or worsening) playback.
 //
 // The expected usage is:
 // 1. Instantiate a DecoderDoctorDiagnostics in a function (close to the point
@@ -35,34 +36,70 @@ public:
   // given format. All diagnostics for a document will be analyzed together
   // within a short timeframe.
   // Should only be called once.
   void StoreFormatDiagnostics(nsIDocument* aDocument,
                               const nsAString& aFormat,
                               bool aCanPlay,
                               const char* aCallSite);
 
-  // Description string, for logging purposes.
+  void StoreMediaKeySystemAccess(nsIDocument* aDocument,
+                                 const nsAString& aKeySystem,
+                                 bool aIsSupported,
+                                 const char* aCallSite);
+
+  enum DiagnosticsType {
+    eUnsaved,
+    eFormatSupportCheck,
+    eMediaKeySystemAccessRequest
+  };
+  DiagnosticsType Type() const { return mDiagnosticsType; }
+
+  // Description string, for logging purposes; only call on stored diags.
   nsCString GetDescription() const;
 
   // Methods to record diagnostic information:
 
   const nsAString& Format() const { return mFormat; }
   bool CanPlay() const { return mCanPlay; }
 
   void SetWMFFailedToLoad() { mWMFFailedToLoad = true; }
   bool DidWMFFailToLoad() const { return mWMFFailedToLoad; }
 
   void SetFFmpegFailedToLoad() { mFFmpegFailedToLoad = true; }
   bool DidFFmpegFailToLoad() const { return mFFmpegFailedToLoad; }
 
+  const nsAString& KeySystem() const { return mKeySystem; }
+  bool IsKeySystemSupported() const { return mIsKeySystemSupported; }
+  enum KeySystemIssue {
+    eUnset,
+    eWidevineWithNoWMF
+  };
+  void SetKeySystemIssue(KeySystemIssue aKeySystemIssue)
+  {
+    mKeySystemIssue = aKeySystemIssue;
+  }
+  KeySystemIssue GetKeySystemIssue() const
+  {
+    return mKeySystemIssue;
+  }
+
 private:
+  // Currently-known type of diagnostics. Set from one of the 'Store...' methods.
+  // This helps ensure diagnostics are only stored once,
+  // and makes it easy to know what information they contain.
+  DiagnosticsType mDiagnosticsType = eUnsaved;
+
   nsString mFormat;
   // True if there is at least one decoder that can play that format.
   bool mCanPlay = false;
 
   bool mWMFFailedToLoad = false;
   bool mFFmpegFailedToLoad = false;
+
+  nsString mKeySystem;
+  bool mIsKeySystemSupported = false;
+  KeySystemIssue mKeySystemIssue = eUnset;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -28,16 +28,17 @@
 #include "mozilla/EMEUtils.h"
 #include "GMPUtils.h"
 #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"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
@@ -316,129 +317,144 @@ MediaKeySystemAccess::GetKeySystemStatus
   }
 #endif
 
   return MediaKeySystemStatus::Cdm_not_supported;
 }
 
 static bool
 GMPDecryptsAndDecodesAAC(mozIGeckoMediaPluginService* aGMPS,
-                         const nsAString& aKeySystem)
+                         const nsAString& aKeySystem,
+                         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_AUDIO_DECODER),
                     NS_LITERAL_CSTRING("aac"));
 }
 
 static bool
 GMPDecryptsAndDecodesH264(mozIGeckoMediaPluginService* aGMPS,
-                          const nsAString& aKeySystem)
+                          const nsAString& aKeySystem,
+                          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)
+                               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,
-                                        /* DecoderDoctorDiagnostics* */ nullptr);
+         MP4Decoder::CanHandleMediaType(aContentType, aDiagnostics);
 }
 
 static bool
 GMPDecryptsAndGeckoDecodesAAC(mozIGeckoMediaPluginService* aGMPService,
                               const nsAString& aKeySystem,
-                              const nsAString& aContentType)
+                              const nsAString& aContentType,
+                              DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(HaveGMPFor(aGMPService,
                         NS_ConvertUTF16toUTF8(aKeySystem),
                         NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
   MOZ_ASSERT(IsAACContentType(aContentType));
 
-  return !HaveGMPFor(aGMPService,
-                     NS_ConvertUTF16toUTF8(aKeySystem),
-                     NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
-                     NS_LITERAL_CSTRING("aac")) &&
+  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.
+    return false;
+  }
 #if defined(MOZ_WIDEVINE_EME) && 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.
-        (!aKeySystem.EqualsLiteral("com.widevine.alpha") || WMFDecoderModule::HasAAC()) &&
+  // 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
-    MP4Decoder::CanHandleMediaType(aContentType,
-                                   /* DecoderDoctorDiagnostics* */ nullptr);
+  return MP4Decoder::CanHandleMediaType(aContentType, aDiagnostics);
 }
 
 static bool
 IsSupportedAudio(mozIGeckoMediaPluginService* aGMPService,
                  const nsAString& aKeySystem,
-                 const nsAString& aAudioType)
+                 const nsAString& aAudioType,
+                 DecoderDoctorDiagnostics* aDiagnostics)
 {
   return IsAACContentType(aAudioType) &&
-         (GMPDecryptsAndDecodesAAC(aGMPService, aKeySystem) ||
-          GMPDecryptsAndGeckoDecodesAAC(aGMPService, aKeySystem, aAudioType));
+         (GMPDecryptsAndDecodesAAC(aGMPService, aKeySystem, aDiagnostics) ||
+          GMPDecryptsAndGeckoDecodesAAC(aGMPService, aKeySystem, aAudioType, aDiagnostics));
 }
 
 static bool
 IsSupportedVideo(mozIGeckoMediaPluginService* aGMPService,
                  const nsAString& aKeySystem,
-                 const nsAString& aVideoType)
+                 const nsAString& aVideoType,
+                 DecoderDoctorDiagnostics* aDiagnostics)
 {
   return IsH264ContentType(aVideoType) &&
-         (GMPDecryptsAndDecodesH264(aGMPService, aKeySystem) ||
-          GMPDecryptsAndGeckoDecodesH264(aGMPService, aKeySystem, aVideoType));
+         (GMPDecryptsAndDecodesH264(aGMPService, aKeySystem, aDiagnostics) ||
+          GMPDecryptsAndGeckoDecodesH264(aGMPService, aKeySystem, aVideoType, aDiagnostics));
 }
 
 static bool
 IsSupported(mozIGeckoMediaPluginService* aGMPService,
             const nsAString& aKeySystem,
-            const MediaKeySystemConfiguration& aConfig)
+            const MediaKeySystemConfiguration& aConfig,
+            DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (aConfig.mInitDataType.IsEmpty() &&
       aConfig.mAudioType.IsEmpty() &&
       aConfig.mVideoType.IsEmpty()) {
     // Not an old-style request.
     return false;
   }
 
   // Backwards compatibility with legacy MediaKeySystemConfiguration method.
   if (!aConfig.mInitDataType.IsEmpty() &&
       !aConfig.mInitDataType.EqualsLiteral("cenc")) {
     return false;
   }
   if (!aConfig.mAudioType.IsEmpty() &&
-      !IsSupportedAudio(aGMPService, aKeySystem, aConfig.mAudioType)) {
+      !IsSupportedAudio(aGMPService, aKeySystem, aConfig.mAudioType, aDiagnostics)) {
     return false;
   }
   if (!aConfig.mVideoType.IsEmpty() &&
-      !IsSupportedVideo(aGMPService, aKeySystem, aConfig.mVideoType)) {
+      !IsSupportedVideo(aGMPService, aKeySystem, aConfig.mVideoType, aDiagnostics)) {
     return false;
   }
 
   return true;
 }
 
 static bool
 IsSupportedInitDataType(const nsString& aCandidate, const nsAString& aKeySystem)
@@ -453,17 +469,18 @@ IsSupportedInitDataType(const nsString& 
     ) &&
     (aCandidate.EqualsLiteral("keyids") || aCandidate.EqualsLiteral("webm)")));
 }
 
 static bool
 GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
                    const nsAString& aKeySystem,
                    const MediaKeySystemConfiguration& aCandidate,
-                   MediaKeySystemConfiguration& aOutConfig)
+                   MediaKeySystemConfiguration& aOutConfig,
+                   DecoderDoctorDiagnostics* aDiagnostics)
 {
   MediaKeySystemConfiguration config;
   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);
@@ -473,30 +490,30 @@ GetSupportedConfig(mozIGeckoMediaPluginS
       return false;
     }
     config.mInitDataTypes.Construct();
     config.mInitDataTypes.Value().Assign(initDataTypes);
   }
   if (aCandidate.mAudioCapabilities.WasPassed()) {
     nsTArray<MediaKeySystemMediaCapability> caps;
     for (const MediaKeySystemMediaCapability& cap : aCandidate.mAudioCapabilities.Value()) {
-      if (IsSupportedAudio(aGMPService, aKeySystem, cap.mContentType)) {
+      if (IsSupportedAudio(aGMPService, aKeySystem, cap.mContentType, aDiagnostics)) {
         caps.AppendElement(cap);
       }
     }
     if (caps.IsEmpty()) {
       return false;
     }
     config.mAudioCapabilities.Construct();
     config.mAudioCapabilities.Value().Assign(caps);
   }
   if (aCandidate.mVideoCapabilities.WasPassed()) {
     nsTArray<MediaKeySystemMediaCapability> caps;
     for (const MediaKeySystemMediaCapability& cap : aCandidate.mVideoCapabilities.Value()) {
-      if (IsSupportedVideo(aGMPService, aKeySystem, cap.mContentType)) {
+      if (IsSupportedVideo(aGMPService, aKeySystem, cap.mContentType, aDiagnostics)) {
         caps.AppendElement(cap);
       }
     }
     if (caps.IsEmpty()) {
       return false;
     }
     config.mVideoCapabilities.Construct();
     config.mVideoCapabilities.Value().Assign(caps);
@@ -504,72 +521,79 @@ GetSupportedConfig(mozIGeckoMediaPluginS
 
 #if defined(MOZ_WIDEVINE_EME) && 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()) &&
      !WMFDecoderModule::HasAAC()) {
+    if (aDiagnostics) {
+      aDiagnostics->SetKeySystemIssue(
+        DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+    }
     return false;
   }
 #endif
 
   aOutConfig = config;
 
   return true;
 }
 
 // Backwards compatibility with legacy requestMediaKeySystemAccess with fields
 // from old MediaKeySystemOptions dictionary.
 /* static */
 bool
 MediaKeySystemAccess::IsSupported(const nsAString& aKeySystem,
-                                  const Sequence<MediaKeySystemConfiguration>& aConfigs)
+                                  const Sequence<MediaKeySystemConfiguration>& aConfigs,
+                                  DecoderDoctorDiagnostics* aDiagnostics)
 {
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (NS_WARN_IF(!mps)) {
     return false;
   }
 
   if (!HaveGMPFor(mps,
                   NS_ConvertUTF16toUTF8(aKeySystem),
                   NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
     return false;
   }
 
   for (const MediaKeySystemConfiguration& config : aConfigs) {
-    if (mozilla::dom::IsSupported(mps, aKeySystem, config)) {
+    if (mozilla::dom::IsSupported(mps, aKeySystem, config, aDiagnostics)) {
       return true;
     }
   }
   return false;
 }
 
 /* static */
 bool
 MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem,
                                          const Sequence<MediaKeySystemConfiguration>& aConfigs,
-                                         MediaKeySystemConfiguration& aOutConfig)
+                                         MediaKeySystemConfiguration& aOutConfig,
+                                         DecoderDoctorDiagnostics* aDiagnostics)
 {
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (NS_WARN_IF(!mps)) {
     return false;
   }
 
   if (!HaveGMPFor(mps,
                   NS_ConvertUTF16toUTF8(aKeySystem),
                   NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
     return false;
   }
 
   for (const MediaKeySystemConfiguration& config : aConfigs) {
-    if (mozilla::dom::GetSupportedConfig(mps, aKeySystem, config, aOutConfig)) {
+    if (mozilla::dom::GetSupportedConfig(
+          mps, aKeySystem, config, aOutConfig, aDiagnostics)) {
       return true;
     }
   }
 
   return false;
 }
 
 
--- a/dom/media/eme/MediaKeySystemAccess.h
+++ b/dom/media/eme/MediaKeySystemAccess.h
@@ -14,16 +14,19 @@
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/MediaKeySystemAccessBinding.h"
 #include "mozilla/dom/MediaKeysRequestStatusBinding.h"
 
 #include "js/TypeDecls.h"
 
 namespace mozilla {
+
+class DecoderDoctorDiagnostics;
+
 namespace dom {
 
 class MediaKeySystemAccess final : public nsISupports,
                                    public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeySystemAccess)
@@ -51,29 +54,31 @@ public:
 
 
   static MediaKeySystemStatus GetKeySystemStatus(const nsAString& aKeySystem,
                                                  int32_t aMinCdmVersion,
                                                  nsACString& aOutExceptionMessage,
                                                  nsACString& aOutCdmVersion);
 
   static bool IsSupported(const nsAString& aKeySystem,
-                          const Sequence<MediaKeySystemConfiguration>& aConfigs);
+                          const Sequence<MediaKeySystemConfiguration>& aConfigs,
+                          DecoderDoctorDiagnostics* aDiagnostics);
 
   static void NotifyObservers(nsPIDOMWindowInner* aWindow,
                               const nsAString& aKeySystem,
                               MediaKeySystemStatus aStatus);
 
   static bool IsGMPPresentOnDisk(const nsAString& aKeySystem,
                                  const nsACString& aVersion,
                                  nsACString& aOutMessage);
 
   static bool GetSupportedConfig(const nsAString& aKeySystem,
                                  const Sequence<MediaKeySystemConfiguration>& aConfigs,
-                                 MediaKeySystemConfiguration& aOutConfig);
+                                 MediaKeySystemConfiguration& aOutConfig,
+                                 DecoderDoctorDiagnostics* aDiagnostics);
 
 private:
   nsCOMPtr<nsPIDOMWindowInner> mParent;
   const nsString mKeySystem;
   const nsString mCDMVersion;
   const MediaKeySystemConfiguration mConfig;
 };
 
--- a/dom/media/eme/MediaKeySystemAccessManager.cpp
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -1,13 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaKeySystemAccessManager.h"
+#include "DecoderDoctorDiagnostics.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/EMEUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "mozilla/DetailedPromise.h"
 #ifdef XP_WIN
@@ -75,35 +76,41 @@ 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());
 
+  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.
     aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
                           NS_LITERAL_CSTRING("Key system string is invalid,"
                                              " or key system is unsupported"));
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, false, __func__);
     return;
   }
 
   if (!Preferences::GetBool("media.eme.enabled", false)) {
     // EME disabled by user, send notification to chrome so UI can inform user.
     MediaKeySystemAccess::NotifyObservers(mWindow,
                                           aKeySystem,
                                           MediaKeySystemStatus::Api_disabled);
     aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
                           NS_LITERAL_CSTRING("EME has been preffed off"));
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, false, __func__);
     return;
   }
 
   nsAutoCString message;
   nsAutoCString cdmVersion;
   MediaKeySystemStatus status =
     MediaKeySystemAccess::GetKeySystemStatus(keySystem, minCdmVersion, message, cdmVersion);
 
@@ -136,46 +143,54 @@ MediaKeySystemAccessManager::Request(Det
       MediaKeySystemAccess::NotifyObservers(mWindow, keySystem, status);
     } else {
       // We waited or can't wait for an update and we still can't service
       // the request. Give up. Chrome will still be showing a "I can't play,
       // updating" notification.
       aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
                             NS_LITERAL_CSTRING("Gave up while waiting for a CDM update"));
     }
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, false, __func__);
     return;
   }
   if (status != MediaKeySystemStatus::Available) {
     if (status != MediaKeySystemStatus::Error) {
       // Failed due to user disabling something, send a notification to
       // chrome, so we can show some UI to explain how the user can rectify
       // the situation.
       MediaKeySystemAccess::NotifyObservers(mWindow, keySystem, status);
       aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, message);
       return;
     }
     aPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
                           NS_LITERAL_CSTRING("GetKeySystemAccess failed"));
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, false, __func__);
     return;
   }
 
   MediaKeySystemConfiguration config;
   // TODO: Remove IsSupported() check here once we remove backwards
   // compatibility with initial implementation...
-  if (MediaKeySystemAccess::GetSupportedConfig(keySystem, aConfigs, config) ||
-      MediaKeySystemAccess::IsSupported(keySystem, aConfigs)) {
+  if (MediaKeySystemAccess::GetSupportedConfig(keySystem, aConfigs, config, &diagnostics) ||
+      MediaKeySystemAccess::IsSupported(keySystem, aConfigs, &diagnostics)) {
     RefPtr<MediaKeySystemAccess> access(
       new MediaKeySystemAccess(mWindow, keySystem, NS_ConvertUTF8toUTF16(cdmVersion), config));
     aPromise->MaybeResolve(access);
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, true, __func__);
     return;
   }
   // Not to inform user, because nothing to do if the corresponding keySystem
   // configuration is not supported.
   aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
                         NS_LITERAL_CSTRING("Key system configuration is not supported"));
+  diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                        aKeySystem, false, __func__);
 }
 
 MediaKeySystemAccessManager::PendingRequest::PendingRequest(DetailedPromise* aPromise,
                                                             const nsAString& aKeySystem,
                                                             const Sequence<MediaKeySystemConfiguration>& aConfigs,
                                                             nsITimer* aTimer)
   : mPromise(aPromise)
   , mKeySystem(aKeySystem)