Bug 848994 - p3. Check MediaKeySystem requests - r=cpearce
authorGerald Squelart <gsquelart@mozilla.com>
Fri, 22 Apr 2016 13:42:11 +1000
changeset 332308 826d477a117b93d745f8dddb2ca3e1a3f2bc77eb
parent 332307 cf1b1f91f522f42c5987a95b4960a7c13431373d
child 332309 1c4133b49948431d4c98d0e9bde33eb6f38f3916
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs848994
milestone48.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 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)