LCOV - code coverage report
Current view: top level - dom/media - DecoderDoctorDiagnostics.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 435 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 35 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim:set ts=2 sw=2 sts=2 et cindent: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "DecoderDoctorDiagnostics.h"
       8             : 
       9             : #include "mozilla/dom/DecoderDoctorNotificationBinding.h"
      10             : #include "mozilla/Logging.h"
      11             : #include "mozilla/Preferences.h"
      12             : #include "nsContentUtils.h"
      13             : #include "nsGkAtoms.h"
      14             : #include "nsIDocument.h"
      15             : #include "nsIObserverService.h"
      16             : #include "nsIScriptError.h"
      17             : #include "nsITimer.h"
      18             : #include "nsIWeakReference.h"
      19             : #include "nsPluginHost.h"
      20             : #include "nsPrintfCString.h"
      21             : #include "VideoUtils.h"
      22             : 
      23             : #if defined(MOZ_FFMPEG)
      24             : #include "FFmpegRuntimeLinker.h"
      25             : #endif
      26             : 
      27             : static mozilla::LazyLogModule sDecoderDoctorLog("DecoderDoctor");
      28             : #define DD_LOG(level, arg, ...) MOZ_LOG(sDecoderDoctorLog, level, (arg, ##__VA_ARGS__))
      29             : #define DD_DEBUG(arg, ...) DD_LOG(mozilla::LogLevel::Debug, arg, ##__VA_ARGS__)
      30             : #define DD_INFO(arg, ...) DD_LOG(mozilla::LogLevel::Info, arg, ##__VA_ARGS__)
      31             : #define DD_WARN(arg, ...) DD_LOG(mozilla::LogLevel::Warning, arg, ##__VA_ARGS__)
      32             : 
      33             : namespace mozilla {
      34             : 
      35             : // Class that collects a sequence of diagnostics from the same document over a
      36             : // small period of time, in order to provide a synthesized analysis.
      37             : //
      38             : // Referenced by the document through a nsINode property, mTimer, and
      39             : // inter-task captures.
      40             : // When notified that the document is dead, or when the timer expires but
      41             : // nothing new happened, StopWatching() will remove the document property and
      42             : // timer (if present), so no more work will happen and the watcher will be
      43             : // destroyed once all references are gone.
      44             : class DecoderDoctorDocumentWatcher : public nsITimerCallback, public nsINamed
      45             : {
      46             : public:
      47             :   static already_AddRefed<DecoderDoctorDocumentWatcher>
      48             :   RetrieveOrCreate(nsIDocument* aDocument);
      49             : 
      50             :   NS_DECL_ISUPPORTS
      51             :   NS_DECL_NSITIMERCALLBACK
      52             :   NS_DECL_NSINAMED
      53             : 
      54             :   void AddDiagnostics(DecoderDoctorDiagnostics&& aDiagnostics,
      55             :                       const char* aCallSite);
      56             : 
      57             : private:
      58             :   explicit DecoderDoctorDocumentWatcher(nsIDocument* aDocument);
      59             :   virtual ~DecoderDoctorDocumentWatcher();
      60             : 
      61             :   // This will prevent further work from happening, watcher will deregister
      62             :   // itself from document (if requested) and cancel any timer, and soon die.
      63             :   void StopWatching(bool aRemoveProperty);
      64             : 
      65             :   // Remove property from document; will call DestroyPropertyCallback.
      66             :   void RemovePropertyFromDocument();
      67             :   // Callback for property destructor, will be automatically called when the
      68             :   // document (in aObject) is being destroyed.
      69             :   static void DestroyPropertyCallback(void* aObject,
      70             :                                       nsIAtom* aPropertyName,
      71             :                                       void* aPropertyValue,
      72             :                                       void* aData);
      73             : 
      74             :   static const uint32_t sAnalysisPeriod_ms = 1000;
      75             :   void EnsureTimerIsStarted();
      76             : 
      77             :   void SynthesizeAnalysis();
      78             : 
      79             :   // Raw pointer to an nsIDocument.
      80             :   // Must be non-null during construction.
      81             :   // Nulled when we want to stop watching, because either:
      82             :   // 1. The document has been destroyed (notified through
      83             :   //    DestroyPropertyCallback).
      84             :   // 2. We have not received new diagnostic information within a short time
      85             :   //    period, so we just stop watching.
      86             :   // Once nulled, no more actual work will happen, and the watcher will be
      87             :   // destroyed soon.
      88             :   nsIDocument* mDocument;
      89             : 
      90           0 :   struct Diagnostics
      91             :   {
      92           0 :     Diagnostics(DecoderDoctorDiagnostics&& aDiagnostics,
      93             :                 const char* aCallSite)
      94           0 :       : mDecoderDoctorDiagnostics(Move(aDiagnostics))
      95           0 :       , mCallSite(aCallSite)
      96           0 :     {}
      97             :     Diagnostics(const Diagnostics&) = delete;
      98           0 :     Diagnostics(Diagnostics&& aOther)
      99           0 :       : mDecoderDoctorDiagnostics(Move(aOther.mDecoderDoctorDiagnostics))
     100           0 :       , mCallSite(Move(aOther.mCallSite))
     101           0 :     {}
     102             : 
     103             :     const DecoderDoctorDiagnostics mDecoderDoctorDiagnostics;
     104             :     const nsCString mCallSite;
     105             :   };
     106             :   typedef nsTArray<Diagnostics> DiagnosticsSequence;
     107             :   DiagnosticsSequence mDiagnosticsSequence;
     108             : 
     109             :   nsCOMPtr<nsITimer> mTimer; // Keep timer alive until we run.
     110             :   DiagnosticsSequence::size_type mDiagnosticsHandled = 0;
     111             : };
     112             : 
     113             : 
     114           0 : NS_IMPL_ISUPPORTS(DecoderDoctorDocumentWatcher, nsITimerCallback, nsINamed)
     115             : 
     116             : // static
     117             : already_AddRefed<DecoderDoctorDocumentWatcher>
     118           0 : DecoderDoctorDocumentWatcher::RetrieveOrCreate(nsIDocument* aDocument)
     119             : {
     120           0 :   MOZ_ASSERT(NS_IsMainThread());
     121           0 :   MOZ_ASSERT(aDocument);
     122             :   RefPtr<DecoderDoctorDocumentWatcher> watcher =
     123             :     static_cast<DecoderDoctorDocumentWatcher*>(
     124           0 :       aDocument->GetProperty(nsGkAtoms::decoderDoctor));
     125           0 :   if (!watcher) {
     126           0 :     watcher = new DecoderDoctorDocumentWatcher(aDocument);
     127           0 :     if (NS_WARN_IF(NS_FAILED(
     128             :           aDocument->SetProperty(nsGkAtoms::decoderDoctor,
     129             :                                  watcher.get(),
     130             :                                  DestroyPropertyCallback,
     131             :                                  /*transfer*/ false)))) {
     132           0 :       DD_WARN("DecoderDoctorDocumentWatcher::RetrieveOrCreate(doc=%p) - Could not set property in document, will destroy new watcher[%p]",
     133             :               aDocument, watcher.get());
     134           0 :       return nullptr;
     135             :     }
     136             :     // Document owns watcher through this property.
     137             :     // Released in DestroyPropertyCallback().
     138           0 :     NS_ADDREF(watcher.get());
     139             :   }
     140           0 :   return watcher.forget();
     141             : }
     142             : 
     143           0 : DecoderDoctorDocumentWatcher::DecoderDoctorDocumentWatcher(nsIDocument* aDocument)
     144           0 :   : mDocument(aDocument)
     145             : {
     146           0 :   MOZ_ASSERT(NS_IsMainThread());
     147           0 :   MOZ_ASSERT(mDocument);
     148           0 :   DD_DEBUG("DecoderDoctorDocumentWatcher[%p]::DecoderDoctorDocumentWatcher(doc=%p)",
     149             :            this, mDocument);
     150           0 : }
     151             : 
     152           0 : DecoderDoctorDocumentWatcher::~DecoderDoctorDocumentWatcher()
     153             : {
     154           0 :   MOZ_ASSERT(NS_IsMainThread());
     155           0 :   DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p <- expect 0]::~DecoderDoctorDocumentWatcher()",
     156             :            this, mDocument);
     157             :   // mDocument should have been reset through StopWatching()!
     158           0 :   MOZ_ASSERT(!mDocument);
     159           0 : }
     160             : 
     161             : void
     162           0 : DecoderDoctorDocumentWatcher::RemovePropertyFromDocument()
     163             : {
     164           0 :   MOZ_ASSERT(NS_IsMainThread());
     165             :   DecoderDoctorDocumentWatcher* watcher =
     166             :     static_cast<DecoderDoctorDocumentWatcher*>(
     167           0 :       mDocument->GetProperty(nsGkAtoms::decoderDoctor));
     168           0 :   if (!watcher) {
     169           0 :     return;
     170             :   }
     171           0 :   DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::RemovePropertyFromDocument()\n",
     172             :            watcher, watcher->mDocument);
     173             :   // This will remove the property and call our DestroyPropertyCallback.
     174           0 :   mDocument->DeleteProperty(nsGkAtoms::decoderDoctor);
     175             : }
     176             : 
     177             : // Callback for property destructors. |aObject| is the object
     178             : // the property is being removed for, |aPropertyName| is the property
     179             : // being removed, |aPropertyValue| is the value of the property, and |aData|
     180             : // is the opaque destructor data that was passed to SetProperty().
     181             : // static
     182             : void
     183           0 : DecoderDoctorDocumentWatcher::DestroyPropertyCallback(void* aObject,
     184             :                                                       nsIAtom* aPropertyName,
     185             :                                                       void* aPropertyValue,
     186             :                                                       void*)
     187             : {
     188           0 :   MOZ_ASSERT(NS_IsMainThread());
     189           0 :   MOZ_ASSERT(aPropertyName == nsGkAtoms::decoderDoctor);
     190             :   DecoderDoctorDocumentWatcher* watcher =
     191           0 :     static_cast<DecoderDoctorDocumentWatcher*>(aPropertyValue);
     192           0 :   MOZ_ASSERT(watcher);
     193             : #ifdef DEBUG
     194           0 :   nsIDocument* document = static_cast<nsIDocument*>(aObject);
     195           0 :   MOZ_ASSERT(watcher->mDocument == document);
     196             : #endif
     197           0 :   DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::DestroyPropertyCallback()\n",
     198             :            watcher, watcher->mDocument);
     199             :   // 'false': StopWatching should not try and remove the property.
     200           0 :   watcher->StopWatching(false);
     201           0 :   NS_RELEASE(watcher);
     202           0 : }
     203             : 
     204             : void
     205           0 : DecoderDoctorDocumentWatcher::StopWatching(bool aRemoveProperty)
     206             : {
     207           0 :   MOZ_ASSERT(NS_IsMainThread());
     208             :   // StopWatching() shouldn't be called twice.
     209           0 :   MOZ_ASSERT(mDocument);
     210             : 
     211           0 :   if (aRemoveProperty) {
     212           0 :     RemovePropertyFromDocument();
     213             :   }
     214             : 
     215             :   // Forget document now, this will prevent more work from being started.
     216           0 :   mDocument = nullptr;
     217             : 
     218           0 :   if (mTimer) {
     219           0 :     mTimer->Cancel();
     220           0 :     mTimer = nullptr;
     221             :   }
     222           0 : }
     223             : 
     224             : void
     225           0 : DecoderDoctorDocumentWatcher::EnsureTimerIsStarted()
     226             : {
     227           0 :   MOZ_ASSERT(NS_IsMainThread());
     228             : 
     229           0 :   if (!mTimer) {
     230           0 :     mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
     231           0 :     if (NS_WARN_IF(!mTimer)) {
     232           0 :       return;
     233             :     }
     234           0 :     if (NS_WARN_IF(NS_FAILED(
     235             :           mTimer->InitWithCallback(
     236             :             this, sAnalysisPeriod_ms, nsITimer::TYPE_ONE_SHOT)))) {
     237           0 :       mTimer = nullptr;
     238             :     }
     239             :   }
     240             : }
     241             : 
     242             : enum class ReportParam : uint8_t
     243             : {
     244             :   // Marks the end of the parameter list.
     245             :   // Keep this zero! (For implicit zero-inits when used in definitions below.)
     246             :   None = 0,
     247             : 
     248             :   Formats,
     249             :   DecodeIssue,
     250             :   DocURL,
     251             :   ResourceURL
     252             : };
     253             : 
     254             : struct NotificationAndReportStringId
     255             : {
     256             :   // Notification type, handled by browser-media.js.
     257             :   dom::DecoderDoctorNotificationType mNotificationType;
     258             :   // Console message id. Key in dom/locales/.../chrome/dom/dom.properties.
     259             :   const char* mReportStringId;
     260             :   static const int maxReportParams = 4;
     261             :   ReportParam mReportParams[maxReportParams];
     262             : };
     263             : 
     264             : // Note: ReportStringIds are limited to alphanumeric only.
     265             : static const NotificationAndReportStringId sMediaWidevineNoWMF=
     266             :   { dom::DecoderDoctorNotificationType::Platform_decoder_not_found,
     267             :     "MediaWidevineNoWMF", { ReportParam::None } };
     268             : static const NotificationAndReportStringId sMediaWMFNeeded =
     269             :   { dom::DecoderDoctorNotificationType::Platform_decoder_not_found,
     270             :     "MediaWMFNeeded", { ReportParam::Formats } };
     271             : static const NotificationAndReportStringId sMediaPlatformDecoderNotFound =
     272             :   { dom::DecoderDoctorNotificationType::Platform_decoder_not_found,
     273             :     "MediaPlatformDecoderNotFound", { ReportParam::Formats } };
     274             : static const NotificationAndReportStringId sMediaCannotPlayNoDecoders =
     275             :   { dom::DecoderDoctorNotificationType::Cannot_play,
     276             :     "MediaCannotPlayNoDecoders", { ReportParam::Formats } };
     277             : static const NotificationAndReportStringId sMediaNoDecoders =
     278             :   { dom::DecoderDoctorNotificationType::Can_play_but_some_missing_decoders,
     279             :     "MediaNoDecoders", { ReportParam::Formats } };
     280             : static const NotificationAndReportStringId sCannotInitializePulseAudio =
     281             :   { dom::DecoderDoctorNotificationType::Cannot_initialize_pulseaudio,
     282             :     "MediaCannotInitializePulseAudio", { ReportParam::None } };
     283             : static const NotificationAndReportStringId sUnsupportedLibavcodec =
     284             :   { dom::DecoderDoctorNotificationType::Unsupported_libavcodec,
     285             :     "MediaUnsupportedLibavcodec", { ReportParam::None } };
     286             : static const NotificationAndReportStringId sMediaDecodeError =
     287             :   { dom::DecoderDoctorNotificationType::Decode_error,
     288             :     "MediaDecodeError",
     289             :     { ReportParam::ResourceURL, ReportParam::DecodeIssue } };
     290             : static const NotificationAndReportStringId sMediaDecodeWarning =
     291             :   { dom::DecoderDoctorNotificationType::Decode_warning,
     292             :     "MediaDecodeWarning",
     293             :     { ReportParam::ResourceURL, ReportParam::DecodeIssue } };
     294             : 
     295             : static const NotificationAndReportStringId *const
     296             : sAllNotificationsAndReportStringIds[] =
     297             : {
     298             :   &sMediaWidevineNoWMF,
     299             :   &sMediaWMFNeeded,
     300             :   &sMediaPlatformDecoderNotFound,
     301             :   &sMediaCannotPlayNoDecoders,
     302             :   &sMediaNoDecoders,
     303             :   &sCannotInitializePulseAudio,
     304             :   &sUnsupportedLibavcodec,
     305             :   &sMediaDecodeError,
     306             :   &sMediaDecodeWarning
     307             : };
     308             : 
     309             : // Create a webcompat-friendly description of a MediaResult.
     310             : static nsString
     311           0 : MediaResultDescription(const MediaResult& aResult, bool aIsError)
     312             : {
     313           0 :   nsCString name;
     314           0 :   GetErrorName(aResult.Code(), name);
     315           0 :   return NS_ConvertUTF8toUTF16(
     316           0 :            nsPrintfCString(
     317             :              "%s Code: %s (0x%08" PRIx32 ")%s%s",
     318             :              aIsError ? "Error" : "Warning", name.get(),
     319           0 :              static_cast<uint32_t>(aResult.Code()),
     320           0 :              aResult.Message().IsEmpty() ? "" : "\nDetails: ",
     321           0 :              aResult.Message().get()));
     322             : }
     323             : 
     324             : static void
     325           0 : DispatchNotification(nsISupports* aSubject,
     326             :                      const NotificationAndReportStringId& aNotification,
     327             :                      bool aIsSolved,
     328             :                      const nsAString& aFormats,
     329             :                      const nsAString& aDecodeIssue,
     330             :                      const nsACString& aDocURL,
     331             :                      const nsAString& aResourceURL)
     332             : {
     333           0 :   if (!aSubject) {
     334           0 :     return;
     335             :   }
     336           0 :   dom::DecoderDoctorNotification data;
     337           0 :   data.mType = aNotification.mNotificationType;
     338           0 :   data.mIsSolved = aIsSolved;
     339             :   data.mDecoderDoctorReportId.Assign(
     340           0 :     NS_ConvertUTF8toUTF16(aNotification.mReportStringId));
     341           0 :   if (!aFormats.IsEmpty()) {
     342           0 :     data.mFormats.Construct(aFormats);
     343             :   }
     344           0 :   if (!aDecodeIssue.IsEmpty()) {
     345           0 :     data.mDecodeIssue.Construct(aDecodeIssue);
     346             :   }
     347           0 :   if (!aDocURL.IsEmpty()) {
     348           0 :     data.mDocURL.Construct(NS_ConvertUTF8toUTF16(aDocURL));
     349             :   }
     350           0 :   if (!aResourceURL.IsEmpty()) {
     351           0 :     data.mResourceURL.Construct(aResourceURL);
     352             :   }
     353           0 :   nsAutoString json;
     354           0 :   data.ToJSON(json);
     355           0 :   if (json.IsEmpty()) {
     356           0 :     DD_WARN("DecoderDoctorDiagnostics/DispatchEvent() - Could not create json for dispatch");
     357             :     // No point in dispatching this notification without data, the front-end
     358             :     // wouldn't know what to display.
     359           0 :     return;
     360             :   }
     361           0 :   DD_DEBUG("DecoderDoctorDiagnostics/DispatchEvent() %s", NS_ConvertUTF16toUTF8(json).get());
     362           0 :   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     363           0 :   if (obs) {
     364           0 :     obs->NotifyObservers(aSubject, "decoder-doctor-notification", json.get());
     365             :   }
     366             : }
     367             : 
     368             : static void
     369           0 : ReportToConsole(nsIDocument* aDocument,
     370             :                 const char* aConsoleStringId,
     371             :                 nsTArray<const char16_t*>& aParams)
     372             : {
     373           0 :   MOZ_ASSERT(NS_IsMainThread());
     374           0 :   MOZ_ASSERT(aDocument);
     375             : 
     376           0 :   DD_DEBUG("DecoderDoctorDiagnostics.cpp:ReportToConsole(doc=%p) ReportToConsole"
     377             :            " - aMsg='%s' params={%s%s%s%s}",
     378             :            aDocument, aConsoleStringId,
     379             :            aParams.IsEmpty()
     380             :            ? "<no params>"
     381             :            : NS_ConvertUTF16toUTF8(aParams[0]).get(),
     382             :            (aParams.Length() < 1 || !aParams[1]) ? "" : ", ",
     383             :            (aParams.Length() < 1 || !aParams[1])
     384             :            ? ""
     385             :            : NS_ConvertUTF16toUTF8(aParams[1]).get(),
     386             :            aParams.Length() < 2 ? "" : ", ...");
     387           0 :   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
     388           0 :                                   NS_LITERAL_CSTRING("Media"),
     389             :                                   aDocument,
     390             :                                   nsContentUtils::eDOM_PROPERTIES,
     391             :                                   aConsoleStringId,
     392           0 :                                   aParams.IsEmpty()
     393             :                                   ? nullptr
     394           0 :                                   : aParams.Elements(),
     395           0 :                                   aParams.Length());
     396           0 : }
     397             : 
     398             : static bool
     399           0 : AllowNotification(const NotificationAndReportStringId& aNotification)
     400             : {
     401             :   // "media.decoder-doctor.notifications-allowed" controls which notifications
     402             :   // may be dispatched to the front-end. It either contains:
     403             :   // - '*' -> Allow everything.
     404             :   // - Comma-separater list of ids -> Allow if aReportStringId (from
     405             :   //                                  dom.properties) is one of them.
     406             :   // - Nothing (missing or empty) -> Disable everything.
     407             :   nsAdoptingCString filter =
     408           0 :     Preferences::GetCString("media.decoder-doctor.notifications-allowed");
     409           0 :   return filter.EqualsLiteral("*") ||
     410           0 :          StringListContains(filter, aNotification.mReportStringId);
     411             : }
     412             : 
     413             : static bool
     414           0 : AllowDecodeIssue(const MediaResult& aDecodeIssue, bool aDecodeIssueIsError)
     415             : {
     416           0 :   if (aDecodeIssue == NS_OK) {
     417             :     // 'NS_OK' means we are not actually reporting a decode issue, so we
     418             :     // allow the report.
     419           0 :     return true;
     420             :   }
     421             : 
     422             :   // "media.decoder-doctor.decode-{errors,warnings}-allowed" controls which
     423             :   // decode issues may be dispatched to the front-end. It either contains:
     424             :   // - '*' -> Allow everything.
     425             :   // - Comma-separater list of ids -> Allow if the issue name is one of them.
     426             :   // - Nothing (missing or empty) -> Disable everything.
     427             :   nsAdoptingCString filter =
     428             :     Preferences::GetCString(aDecodeIssueIsError
     429             :                             ? "media.decoder-doctor.decode-errors-allowed"
     430           0 :                             : "media.decoder-doctor.decode-warnings-allowed");
     431           0 :   if (filter.EqualsLiteral("*")) {
     432           0 :     return true;
     433             :   }
     434             : 
     435           0 :   nsCString decodeIssueName;
     436           0 :   GetErrorName(aDecodeIssue.Code(), static_cast<nsACString&>(decodeIssueName));
     437           0 :   return StringListContains(filter, decodeIssueName);
     438             : }
     439             : 
     440             : static void
     441           0 : ReportAnalysis(nsIDocument* aDocument,
     442             :                const NotificationAndReportStringId& aNotification,
     443             :                bool aIsSolved,
     444             :                const nsAString& aFormats = NS_LITERAL_STRING(""),
     445             :                const MediaResult& aDecodeIssue = NS_OK,
     446             :                bool aDecodeIssueIsError = true,
     447             :                const nsACString& aDocURL = NS_LITERAL_CSTRING(""),
     448             :                const nsAString& aResourceURL = NS_LITERAL_STRING(""))
     449             : {
     450           0 :   MOZ_ASSERT(NS_IsMainThread());
     451             : 
     452           0 :   if (!aDocument) {
     453           0 :     return;
     454             :   }
     455             : 
     456           0 :   nsString decodeIssueDescription;
     457           0 :   if (aDecodeIssue != NS_OK) {
     458           0 :     decodeIssueDescription.Assign(MediaResultDescription(aDecodeIssue,
     459           0 :                                                          aDecodeIssueIsError));
     460             :   }
     461             : 
     462             :   // Report non-solved issues to console.
     463           0 :   if (!aIsSolved) {
     464             :     // Build parameter array needed by console message.
     465             :     AutoTArray<const char16_t*,
     466           0 :                NotificationAndReportStringId::maxReportParams> params;
     467           0 :     for (int i = 0; i < NotificationAndReportStringId::maxReportParams; ++i) {
     468           0 :       if (aNotification.mReportParams[i] == ReportParam::None) {
     469           0 :         break;
     470             :       }
     471           0 :       switch (aNotification.mReportParams[i]) {
     472             :       case ReportParam::Formats:
     473           0 :         params.AppendElement(aFormats.Data());
     474           0 :         break;
     475             :       case ReportParam::DecodeIssue:
     476           0 :         params.AppendElement(decodeIssueDescription.Data());
     477           0 :         break;
     478             :       case ReportParam::DocURL:
     479           0 :         params.AppendElement(NS_ConvertUTF8toUTF16(aDocURL).Data());
     480           0 :         break;
     481             :       case ReportParam::ResourceURL:
     482           0 :         params.AppendElement(aResourceURL.Data());
     483           0 :         break;
     484             :       default:
     485           0 :         MOZ_ASSERT_UNREACHABLE("Bad notification parameter choice");
     486             :         break;
     487             :       }
     488             :     }
     489           0 :     ReportToConsole(aDocument, aNotification.mReportStringId, params);
     490             :   }
     491             : 
     492           0 :   if (AllowNotification(aNotification) &&
     493           0 :       AllowDecodeIssue(aDecodeIssue, aDecodeIssueIsError)) {
     494           0 :     DispatchNotification(
     495           0 :       aDocument->GetInnerWindow(), aNotification, aIsSolved,
     496             :       aFormats,
     497             :       decodeIssueDescription,
     498             :       aDocURL,
     499           0 :       aResourceURL);
     500             :   }
     501             : }
     502             : 
     503             : static nsString
     504           0 : CleanItemForFormatsList(const nsAString& aItem)
     505             : {
     506           0 :   nsString item(aItem);
     507             :   // Remove commas from item, as commas are used to separate items. It's fine
     508             :   // to have a one-way mapping, it's only used for comparisons and in
     509             :   // console display (where formats shouldn't contain commas in the first place)
     510           0 :   item.ReplaceChar(',', ' ');
     511           0 :   item.CompressWhitespace();
     512           0 :   return item;
     513             : }
     514             : 
     515             : static void
     516           0 : AppendToFormatsList(nsAString& aList, const nsAString& aItem)
     517             : {
     518           0 :   if (!aList.IsEmpty()) {
     519           0 :     aList += NS_LITERAL_STRING(", ");
     520             :   }
     521           0 :   aList += CleanItemForFormatsList(aItem);
     522           0 : }
     523             : 
     524             : static bool
     525           0 : FormatsListContains(const nsAString& aList, const nsAString& aItem)
     526             : {
     527           0 :   return StringListContains(aList, CleanItemForFormatsList(aItem));
     528             : }
     529             : 
     530             : void
     531           0 : DecoderDoctorDocumentWatcher::SynthesizeAnalysis()
     532             : {
     533           0 :   MOZ_ASSERT(NS_IsMainThread());
     534             : 
     535           0 :   nsAutoString playableFormats;
     536           0 :   nsAutoString unplayableFormats;
     537             :   // Subsets of unplayableFormats that require a specific platform decoder:
     538             : #if defined(XP_WIN)
     539             :   nsAutoString formatsRequiringWMF;
     540             : #endif
     541             : #if defined(MOZ_FFMPEG)
     542           0 :   nsAutoString formatsRequiringFFMpeg;
     543             : #endif
     544           0 :   nsAutoString supportedKeySystems;
     545           0 :   nsAutoString unsupportedKeySystems;
     546             :   DecoderDoctorDiagnostics::KeySystemIssue lastKeySystemIssue =
     547           0 :     DecoderDoctorDiagnostics::eUnset;
     548             :   // Only deal with one decode error per document (the first one found).
     549           0 :   const MediaResult* firstDecodeError = nullptr;
     550           0 :   const nsString* firstDecodeErrorMediaSrc = nullptr;
     551             :   // Only deal with one decode warning per document (the first one found).
     552           0 :   const MediaResult* firstDecodeWarning = nullptr;
     553           0 :   const nsString* firstDecodeWarningMediaSrc = nullptr;
     554             : 
     555           0 :   for (const auto& diag : mDiagnosticsSequence) {
     556           0 :     switch (diag.mDecoderDoctorDiagnostics.Type()) {
     557             :       case DecoderDoctorDiagnostics::eFormatSupportCheck:
     558           0 :         if (diag.mDecoderDoctorDiagnostics.CanPlay()) {
     559           0 :           AppendToFormatsList(playableFormats,
     560           0 :                               diag.mDecoderDoctorDiagnostics.Format());
     561             :         } else {
     562           0 :           AppendToFormatsList(unplayableFormats,
     563           0 :                               diag.mDecoderDoctorDiagnostics.Format());
     564             : #if defined(XP_WIN)
     565             :           if (diag.mDecoderDoctorDiagnostics.DidWMFFailToLoad()) {
     566             :             AppendToFormatsList(formatsRequiringWMF,
     567             :                                 diag.mDecoderDoctorDiagnostics.Format());
     568             :           }
     569             : #endif
     570             : #if defined(MOZ_FFMPEG)
     571           0 :           if (diag.mDecoderDoctorDiagnostics.DidFFmpegFailToLoad()) {
     572           0 :             AppendToFormatsList(formatsRequiringFFMpeg,
     573           0 :                                 diag.mDecoderDoctorDiagnostics.Format());
     574             :           }
     575             : #endif
     576             :         }
     577           0 :         break;
     578             :       case DecoderDoctorDiagnostics::eMediaKeySystemAccessRequest:
     579           0 :         if (diag.mDecoderDoctorDiagnostics.IsKeySystemSupported()) {
     580           0 :           AppendToFormatsList(supportedKeySystems,
     581           0 :                               diag.mDecoderDoctorDiagnostics.KeySystem());
     582             :         } else {
     583           0 :           AppendToFormatsList(unsupportedKeySystems,
     584           0 :                               diag.mDecoderDoctorDiagnostics.KeySystem());
     585             :           DecoderDoctorDiagnostics::KeySystemIssue issue =
     586           0 :             diag.mDecoderDoctorDiagnostics.GetKeySystemIssue();
     587           0 :           if (issue != DecoderDoctorDiagnostics::eUnset) {
     588           0 :             lastKeySystemIssue = issue;
     589             :           }
     590             :         }
     591           0 :         break;
     592             :       case DecoderDoctorDiagnostics::eEvent:
     593           0 :         MOZ_ASSERT_UNREACHABLE("Events shouldn't be stored for processing.");
     594             :         break;
     595             :       case DecoderDoctorDiagnostics::eDecodeError:
     596           0 :         if (!firstDecodeError) {
     597           0 :           firstDecodeError = &diag.mDecoderDoctorDiagnostics.DecodeIssue();
     598             :           firstDecodeErrorMediaSrc =
     599           0 :             &diag.mDecoderDoctorDiagnostics.DecodeIssueMediaSrc();
     600             :         }
     601           0 :         break;
     602             :       case DecoderDoctorDiagnostics::eDecodeWarning:
     603           0 :         if (!firstDecodeWarning) {
     604           0 :           firstDecodeWarning = &diag.mDecoderDoctorDiagnostics.DecodeIssue();
     605             :           firstDecodeWarningMediaSrc =
     606           0 :             &diag.mDecoderDoctorDiagnostics.DecodeIssueMediaSrc();
     607             :         }
     608           0 :         break;
     609             :       default:
     610           0 :         MOZ_ASSERT_UNREACHABLE("Unhandled DecoderDoctorDiagnostics type");
     611             :         break;
     612             :     }
     613             :   }
     614             : 
     615             :   // Check if issues have been solved, by finding if some now-playable
     616             :   // key systems or formats were previously recorded as having issues.
     617           0 :   if (!supportedKeySystems.IsEmpty() || !playableFormats.IsEmpty()) {
     618           0 :     DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - supported key systems '%s', playable formats '%s'; See if they show issues have been solved...",
     619             :              this, mDocument,
     620             :              NS_ConvertUTF16toUTF8(supportedKeySystems).Data(),
     621             :              NS_ConvertUTF16toUTF8(playableFormats).get());
     622             :     const nsAString* workingFormatsArray[] =
     623           0 :       { &supportedKeySystems, &playableFormats };
     624             :     // For each type of notification, retrieve the pref that contains formats/
     625             :     // key systems with issues.
     626           0 :     for (const NotificationAndReportStringId* id :
     627           0 :            sAllNotificationsAndReportStringIds) {
     628           0 :       nsAutoCString formatsPref("media.decoder-doctor.");
     629           0 :       formatsPref += id->mReportStringId;
     630           0 :       formatsPref += ".formats";
     631             :       nsAdoptingString formatsWithIssues =
     632           0 :         Preferences::GetString(formatsPref.Data());
     633           0 :       if (formatsWithIssues.IsEmpty()) {
     634           0 :         continue;
     635             :       }
     636             :       // See if that list of formats-with-issues contains any formats that are
     637             :       // now playable/supported.
     638           0 :       bool solved = false;
     639           0 :       for (const nsAString* workingFormats : workingFormatsArray) {
     640           0 :         for (const auto& workingFormat : MakeStringListRange(*workingFormats)) {
     641           0 :           if (FormatsListContains(formatsWithIssues, workingFormat)) {
     642             :             // This now-working format used not to work -> Report solved issue.
     643           0 :             DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - %s solved ('%s' now works, it was in pref(%s)='%s')",
     644             :                     this, mDocument, id->mReportStringId,
     645             :                     NS_ConvertUTF16toUTF8(workingFormat).get(),
     646             :                     formatsPref.Data(),
     647             :                     NS_ConvertUTF16toUTF8(formatsWithIssues).get());
     648           0 :             ReportAnalysis(mDocument, *id, true, workingFormat);
     649             :             // This particular Notification&ReportId has been solved, no need
     650             :             // to keep looking at other keysys/formats that might solve it too.
     651           0 :             solved = true;
     652           0 :             break;
     653             :           }
     654             :         }
     655           0 :         if (solved) {
     656           0 :           break;
     657             :         }
     658             :       }
     659           0 :       if (!solved) {
     660           0 :         DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - %s not solved (pref(%s)='%s')",
     661             :                  this, mDocument, id->mReportStringId, formatsPref.Data(),
     662             :                  NS_ConvertUTF16toUTF8(formatsWithIssues).get());
     663             :       }
     664             :     }
     665             :   }
     666             : 
     667             :   // Look at Key System issues first, as they take precedence over format checks.
     668           0 :   if (!unsupportedKeySystems.IsEmpty() && supportedKeySystems.IsEmpty()) {
     669             :     // No supported key systems!
     670           0 :     switch (lastKeySystemIssue) {
     671             :       case DecoderDoctorDiagnostics::eWidevineWithNoWMF:
     672           0 :         DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unsupported key systems: %s, Widevine without WMF",
     673             :                 this, mDocument, NS_ConvertUTF16toUTF8(unsupportedKeySystems).get());
     674           0 :         ReportAnalysis(mDocument, sMediaWidevineNoWMF, false,
     675           0 :                        unsupportedKeySystems);
     676           0 :         return;
     677             :       default:
     678           0 :         break;
     679             :     }
     680             :   }
     681             : 
     682             :   // Next, check playability of requested formats.
     683           0 :   if (!unplayableFormats.IsEmpty()) {
     684             :     // Some requested formats cannot be played.
     685           0 :     if (playableFormats.IsEmpty()) {
     686             :       // No requested formats can be played. See if we can help the user, by
     687             :       // going through expected decoders from most to least desirable.
     688             : #if defined(XP_WIN)
     689             :       if (!formatsRequiringWMF.IsEmpty()) {
     690             :         DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because WMF was not found",
     691             :                 this, mDocument, NS_ConvertUTF16toUTF8(formatsRequiringWMF).get());
     692             :         ReportAnalysis(mDocument, sMediaWMFNeeded, false, formatsRequiringWMF);
     693             :         return;
     694             :       }
     695             : #endif
     696             : #if defined(MOZ_FFMPEG)
     697           0 :       if (!formatsRequiringFFMpeg.IsEmpty()) {
     698           0 :         switch (FFmpegRuntimeLinker::LinkStatusCode()) {
     699             :           case FFmpegRuntimeLinker::LinkStatus_INVALID_FFMPEG_CANDIDATE:
     700             :           case FFmpegRuntimeLinker::LinkStatus_UNUSABLE_LIBAV57:
     701             :           case FFmpegRuntimeLinker::LinkStatus_INVALID_LIBAV_CANDIDATE:
     702             :           case FFmpegRuntimeLinker::LinkStatus_OBSOLETE_FFMPEG:
     703             :           case FFmpegRuntimeLinker::LinkStatus_OBSOLETE_LIBAV:
     704             :           case FFmpegRuntimeLinker::LinkStatus_INVALID_CANDIDATE:
     705           0 :             DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because of unsupported %s (Reason: %s)",
     706             :                     this, mDocument,
     707             :                     NS_ConvertUTF16toUTF8(formatsRequiringFFMpeg).get(),
     708             :                     FFmpegRuntimeLinker::LinkStatusLibraryName(),
     709             :                     FFmpegRuntimeLinker::LinkStatusString());
     710           0 :             ReportAnalysis(mDocument, sUnsupportedLibavcodec,
     711           0 :                            false, formatsRequiringFFMpeg);
     712           0 :             return;
     713             :           case FFmpegRuntimeLinker::LinkStatus_INIT:
     714           0 :             MOZ_FALLTHROUGH_ASSERT("Unexpected LinkStatus_INIT");
     715             :           case FFmpegRuntimeLinker::LinkStatus_SUCCEEDED:
     716           0 :             MOZ_FALLTHROUGH_ASSERT("Unexpected LinkStatus_SUCCEEDED");
     717             :           case FFmpegRuntimeLinker::LinkStatus_NOT_FOUND:
     718           0 :             DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because platform decoder was not found (Reason: %s)",
     719             :                     this, mDocument,
     720             :                     NS_ConvertUTF16toUTF8(formatsRequiringFFMpeg).get(),
     721             :                     FFmpegRuntimeLinker::LinkStatusString());
     722           0 :             ReportAnalysis(mDocument, sMediaPlatformDecoderNotFound,
     723           0 :                            false, formatsRequiringFFMpeg);
     724           0 :             return;
     725             :         }
     726             :       }
     727             : #endif
     728           0 :       DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Cannot play media, unplayable formats: %s",
     729             :               this, mDocument, NS_ConvertUTF16toUTF8(unplayableFormats).get());
     730           0 :       ReportAnalysis(mDocument, sMediaCannotPlayNoDecoders,
     731           0 :                      false, unplayableFormats);
     732           0 :       return;
     733             :     }
     734             : 
     735           0 :     DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Can play media, but no decoders for some requested formats: %s",
     736             :             this, mDocument, NS_ConvertUTF16toUTF8(unplayableFormats).get());
     737           0 :     if (Preferences::GetBool("media.decoder-doctor.verbose", false)) {
     738           0 :       ReportAnalysis(mDocument, sMediaNoDecoders, false, unplayableFormats);
     739             :     }
     740           0 :     return;
     741             :   }
     742             : 
     743           0 :   if (firstDecodeError) {
     744           0 :     DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Decode error: %s",
     745             :             this, mDocument, firstDecodeError->Description().get());
     746           0 :     ReportAnalysis(mDocument, sMediaDecodeError, false,
     747           0 :                    NS_LITERAL_STRING(""),
     748             :                    *firstDecodeError,
     749             :                    true, // aDecodeIssueIsError=true
     750           0 :                    mDocument->GetDocumentURI()->GetSpecOrDefault(),
     751           0 :                    *firstDecodeErrorMediaSrc);
     752           0 :     return;
     753             :   }
     754             : 
     755           0 :   if (firstDecodeWarning) {
     756           0 :     DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Decode warning: %s",
     757             :             this, mDocument, firstDecodeWarning->Description().get());
     758           0 :     ReportAnalysis(mDocument, sMediaDecodeWarning, false,
     759           0 :                    NS_LITERAL_STRING(""),
     760             :                    *firstDecodeWarning,
     761             :                    false, // aDecodeIssueIsError=false
     762           0 :                    mDocument->GetDocumentURI()->GetSpecOrDefault(),
     763           0 :                    *firstDecodeWarningMediaSrc);
     764           0 :     return;
     765             :   }
     766             : 
     767           0 :   DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Can play media, decoders available for all requested formats",
     768             :            this, mDocument);
     769             : }
     770             : 
     771             : void
     772           0 : DecoderDoctorDocumentWatcher::AddDiagnostics(DecoderDoctorDiagnostics&& aDiagnostics,
     773             :                                              const char* aCallSite)
     774             : {
     775           0 :   MOZ_ASSERT(NS_IsMainThread());
     776           0 :   MOZ_ASSERT(aDiagnostics.Type() != DecoderDoctorDiagnostics::eEvent);
     777             : 
     778           0 :   if (!mDocument) {
     779           0 :     return;
     780             :   }
     781             : 
     782           0 :   DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::AddDiagnostics(DecoderDoctorDiagnostics{%s}, call site '%s')",
     783             :            this, mDocument, aDiagnostics.GetDescription().Data(), aCallSite);
     784           0 :   mDiagnosticsSequence.AppendElement(Diagnostics(Move(aDiagnostics), aCallSite));
     785           0 :   EnsureTimerIsStarted();
     786             : }
     787             : 
     788             : NS_IMETHODIMP
     789           0 : DecoderDoctorDocumentWatcher::Notify(nsITimer* timer)
     790             : {
     791           0 :   MOZ_ASSERT(NS_IsMainThread());
     792           0 :   MOZ_ASSERT(timer == mTimer);
     793             : 
     794             :   // Forget timer. (Assuming timer keeps itself and us alive during this call.)
     795           0 :   mTimer = nullptr;
     796             : 
     797           0 :   if (!mDocument) {
     798           0 :     return NS_OK;
     799             :   }
     800             : 
     801           0 :   if (mDiagnosticsSequence.Length() > mDiagnosticsHandled) {
     802             :     // We have new diagnostic data.
     803           0 :     mDiagnosticsHandled = mDiagnosticsSequence.Length();
     804             : 
     805           0 :     SynthesizeAnalysis();
     806             : 
     807             :     // Restart timer, to redo analysis or stop watching this document,
     808             :     // depending on whether anything new happens.
     809           0 :     EnsureTimerIsStarted();
     810             :   } else {
     811           0 :     DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - No new diagnostics to analyze -> Stop watching",
     812             :              this, mDocument);
     813             :     // Stop watching this document, we don't expect more diagnostics for now.
     814             :     // If more diagnostics come in, we'll treat them as another burst, separately.
     815             :     // 'true' to remove the property from the document.
     816           0 :     StopWatching(true);
     817             :   }
     818             : 
     819           0 :   return NS_OK;
     820             : }
     821             : 
     822             : NS_IMETHODIMP
     823           0 : DecoderDoctorDocumentWatcher::GetName(nsACString& aName)
     824             : {
     825           0 :   aName.AssignASCII("DecoderDoctorDocumentWatcher_timer");
     826           0 :   return NS_OK;
     827             : }
     828             : 
     829             : NS_IMETHODIMP
     830           0 : DecoderDoctorDocumentWatcher::SetName(const char* aName)
     831             : {
     832           0 :   return NS_ERROR_NOT_IMPLEMENTED;
     833             : }
     834             : 
     835             : void
     836           0 : DecoderDoctorDiagnostics::StoreFormatDiagnostics(nsIDocument* aDocument,
     837             :                                                  const nsAString& aFormat,
     838             :                                                  bool aCanPlay,
     839             :                                                  const char* aCallSite)
     840             : {
     841           0 :   MOZ_ASSERT(NS_IsMainThread());
     842             :   // Make sure we only store once.
     843           0 :   MOZ_ASSERT(mDiagnosticsType == eUnsaved);
     844           0 :   mDiagnosticsType = eFormatSupportCheck;
     845             : 
     846           0 :   if (NS_WARN_IF(!aDocument)) {
     847           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=nullptr, format='%s', can-play=%d, call site '%s')",
     848             :             this, NS_ConvertUTF16toUTF8(aFormat).get(), aCanPlay, aCallSite);
     849           0 :     return;
     850             :   }
     851           0 :   if (NS_WARN_IF(aFormat.IsEmpty())) {
     852           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=%p, format=<empty>, can-play=%d, call site '%s')",
     853             :             this, aDocument, aCanPlay, aCallSite);
     854           0 :     return;
     855             :   }
     856             : 
     857             :   RefPtr<DecoderDoctorDocumentWatcher> watcher =
     858           0 :     DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
     859             : 
     860           0 :   if (NS_WARN_IF(!watcher)) {
     861           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=%p, format='%s', can-play=%d, call site '%s') - Could not create document watcher",
     862             :             this, aDocument, NS_ConvertUTF16toUTF8(aFormat).get(), aCanPlay, aCallSite);
     863           0 :     return;
     864             :   }
     865             : 
     866           0 :   mFormat = aFormat;
     867           0 :   mCanPlay = aCanPlay;
     868             : 
     869             :   // StoreDiagnostics should only be called once, after all data is available,
     870             :   // so it is safe to Move() from this object.
     871           0 :   watcher->AddDiagnostics(Move(*this), aCallSite);
     872             :   // Even though it's moved-from, the type should stay set
     873             :   // (Only used to ensure that we do store only once.)
     874           0 :   MOZ_ASSERT(mDiagnosticsType == eFormatSupportCheck);
     875             : }
     876             : 
     877             : void
     878           0 : DecoderDoctorDiagnostics::StoreMediaKeySystemAccess(nsIDocument* aDocument,
     879             :                                                     const nsAString& aKeySystem,
     880             :                                                     bool aIsSupported,
     881             :                                                     const char* aCallSite)
     882             : {
     883           0 :   MOZ_ASSERT(NS_IsMainThread());
     884             :   // Make sure we only store once.
     885           0 :   MOZ_ASSERT(mDiagnosticsType == eUnsaved);
     886           0 :   mDiagnosticsType = eMediaKeySystemAccessRequest;
     887             : 
     888           0 :   if (NS_WARN_IF(!aDocument)) {
     889           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=nullptr, keysystem='%s', supported=%d, call site '%s')",
     890             :             this, NS_ConvertUTF16toUTF8(aKeySystem).get(), aIsSupported, aCallSite);
     891           0 :     return;
     892             :   }
     893           0 :   if (NS_WARN_IF(aKeySystem.IsEmpty())) {
     894           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem=<empty>, supported=%d, call site '%s')",
     895             :             this, aDocument, aIsSupported, aCallSite);
     896           0 :     return;
     897             :   }
     898             : 
     899             :   RefPtr<DecoderDoctorDocumentWatcher> watcher =
     900           0 :     DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
     901             : 
     902           0 :   if (NS_WARN_IF(!watcher)) {
     903           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem='%s', supported=%d, call site '%s') - Could not create document watcher",
     904             :             this, aDocument, NS_ConvertUTF16toUTF8(aKeySystem).get(), aIsSupported, aCallSite);
     905           0 :     return;
     906             :   }
     907             : 
     908           0 :   mKeySystem = aKeySystem;
     909           0 :   mIsKeySystemSupported = aIsSupported;
     910             : 
     911             :   // StoreMediaKeySystemAccess should only be called once, after all data is
     912             :   // available, so it is safe to Move() from this object.
     913           0 :   watcher->AddDiagnostics(Move(*this), aCallSite);
     914             :   // Even though it's moved-from, the type should stay set
     915             :   // (Only used to ensure that we do store only once.)
     916           0 :   MOZ_ASSERT(mDiagnosticsType == eMediaKeySystemAccessRequest);
     917             : }
     918             : 
     919             : void
     920           0 : DecoderDoctorDiagnostics::StoreEvent(nsIDocument* aDocument,
     921             :                                      const DecoderDoctorEvent& aEvent,
     922             :                                      const char* aCallSite)
     923             : {
     924           0 :   MOZ_ASSERT(NS_IsMainThread());
     925             :   // Make sure we only store once.
     926           0 :   MOZ_ASSERT(mDiagnosticsType == eUnsaved);
     927           0 :   mDiagnosticsType = eEvent;
     928           0 :   mEvent = aEvent;
     929             : 
     930           0 :   if (NS_WARN_IF(!aDocument)) {
     931           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreEvent(nsIDocument* aDocument=nullptr, aEvent=%s, call site '%s')",
     932             :             this, GetDescription().get(), aCallSite);
     933           0 :     return;
     934             :   }
     935             : 
     936             :   // Don't keep events for later processing, just handle them now.
     937             : #ifdef MOZ_PULSEAUDIO
     938           0 :   switch (aEvent.mDomain) {
     939             :     case DecoderDoctorEvent::eAudioSinkStartup:
     940           0 :       if (aEvent.mResult == NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR) {
     941           0 :         DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::AddDiagnostics() - unable to initialize PulseAudio",
     942             :                 this, aDocument);
     943           0 :         ReportAnalysis(aDocument, sCannotInitializePulseAudio,
     944           0 :                        false, NS_LITERAL_STRING("*"));
     945           0 :       } else if (aEvent.mResult == NS_OK) {
     946           0 :         DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::AddDiagnostics() - now able to initialize PulseAudio",
     947             :                 this, aDocument);
     948           0 :         ReportAnalysis(aDocument, sCannotInitializePulseAudio,
     949           0 :                        true, NS_LITERAL_STRING("*"));
     950             :       }
     951           0 :       break;
     952             :   }
     953             : #endif // MOZ_PULSEAUDIO
     954             : }
     955             : 
     956             : void
     957           0 : DecoderDoctorDiagnostics::StoreDecodeError(nsIDocument* aDocument,
     958             :                                            const MediaResult& aError,
     959             :                                            const nsString& aMediaSrc,
     960             :                                            const char* aCallSite)
     961             : {
     962           0 :   MOZ_ASSERT(NS_IsMainThread());
     963             :   // Make sure we only store once.
     964           0 :   MOZ_ASSERT(mDiagnosticsType == eUnsaved);
     965           0 :   mDiagnosticsType = eDecodeError;
     966             : 
     967           0 :   if (NS_WARN_IF(!aDocument)) {
     968           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeError("
     969             :             "nsIDocument* aDocument=nullptr, aError=%s,"
     970             :             " aMediaSrc=<provided>, call site '%s')",
     971             :             this, aError.Description().get(), aCallSite);
     972           0 :     return;
     973             :   }
     974             : 
     975             :   RefPtr<DecoderDoctorDocumentWatcher> watcher =
     976           0 :     DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
     977             : 
     978           0 :   if (NS_WARN_IF(!watcher)) {
     979           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeError("
     980             :             "nsIDocument* aDocument=%p, aError='%s', aMediaSrc=<provided>,"
     981             :             " call site '%s') - Could not create document watcher",
     982             :             this, aDocument, aError.Description().get(), aCallSite);
     983           0 :     return;
     984             :   }
     985             : 
     986           0 :   mDecodeIssue = aError;
     987           0 :   mDecodeIssueMediaSrc = aMediaSrc;
     988             : 
     989             :   // StoreDecodeError should only be called once, after all data is
     990             :   // available, so it is safe to Move() from this object.
     991           0 :   watcher->AddDiagnostics(Move(*this), aCallSite);
     992             :   // Even though it's moved-from, the type should stay set
     993             :   // (Only used to ensure that we do store only once.)
     994           0 :   MOZ_ASSERT(mDiagnosticsType == eDecodeError);
     995             : }
     996             : 
     997             : void
     998           0 : DecoderDoctorDiagnostics::StoreDecodeWarning(nsIDocument* aDocument,
     999             :                                              const MediaResult& aWarning,
    1000             :                                              const nsString& aMediaSrc,
    1001             :                                              const char* aCallSite)
    1002             : {
    1003           0 :   MOZ_ASSERT(NS_IsMainThread());
    1004             :   // Make sure we only store once.
    1005           0 :   MOZ_ASSERT(mDiagnosticsType == eUnsaved);
    1006           0 :   mDiagnosticsType = eDecodeWarning;
    1007             : 
    1008           0 :   if (NS_WARN_IF(!aDocument)) {
    1009           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeWarning("
    1010             :             "nsIDocument* aDocument=nullptr, aWarning=%s,"
    1011             :             " aMediaSrc=<provided>, call site '%s')",
    1012             :             this, aWarning.Description().get(), aCallSite);
    1013           0 :     return;
    1014             :   }
    1015             : 
    1016             :   RefPtr<DecoderDoctorDocumentWatcher> watcher =
    1017           0 :     DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
    1018             : 
    1019           0 :   if (NS_WARN_IF(!watcher)) {
    1020           0 :     DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeWarning("
    1021             :             "nsIDocument* aDocument=%p, aWarning='%s', aMediaSrc=<provided>,"
    1022             :             " call site '%s') - Could not create document watcher",
    1023             :             this, aDocument, aWarning.Description().get(), aCallSite);
    1024           0 :     return;
    1025             :   }
    1026             : 
    1027           0 :   mDecodeIssue = aWarning;
    1028           0 :   mDecodeIssueMediaSrc = aMediaSrc;
    1029             : 
    1030             :   // StoreDecodeWarning should only be called once, after all data is
    1031             :   // available, so it is safe to Move() from this object.
    1032           0 :   watcher->AddDiagnostics(Move(*this), aCallSite);
    1033             :   // Even though it's moved-from, the type should stay set
    1034             :   // (Only used to ensure that we do store only once.)
    1035           0 :   MOZ_ASSERT(mDiagnosticsType == eDecodeWarning);
    1036             : }
    1037             : 
    1038             : static const char*
    1039           0 : EventDomainString(DecoderDoctorEvent::Domain aDomain)
    1040             : {
    1041           0 :   switch (aDomain) {
    1042             :     case DecoderDoctorEvent::eAudioSinkStartup:
    1043           0 :       return "audio-sink-startup";
    1044             :   }
    1045           0 :   return "?";
    1046             : }
    1047             : 
    1048             : nsCString
    1049           0 : DecoderDoctorDiagnostics::GetDescription() const
    1050             : {
    1051           0 :   nsCString s;
    1052           0 :   switch (mDiagnosticsType) {
    1053             :     case eUnsaved:
    1054           0 :       s = "Unsaved diagnostics, cannot get accurate description";
    1055           0 :       break;
    1056             :     case eFormatSupportCheck:
    1057           0 :       s = "format='";
    1058           0 :       s += NS_ConvertUTF16toUTF8(mFormat).get();
    1059           0 :       s += mCanPlay ? "', can play" : "', cannot play";
    1060           0 :       if (mVideoNotSupported) {
    1061           0 :         s+= ", but video format not supported";
    1062             :       }
    1063           0 :       if (mAudioNotSupported) {
    1064           0 :         s+= ", but audio format not supported";
    1065             :       }
    1066           0 :       if (mWMFFailedToLoad) {
    1067           0 :         s += ", Windows platform decoder failed to load";
    1068             :       }
    1069           0 :       if (mFFmpegFailedToLoad) {
    1070           0 :         s += ", Linux platform decoder failed to load";
    1071             :       }
    1072           0 :       if (mGMPPDMFailedToStartup) {
    1073           0 :         s += ", GMP PDM failed to startup";
    1074           0 :       } else if (!mGMP.IsEmpty()) {
    1075           0 :         s += ", Using GMP '";
    1076           0 :         s += mGMP;
    1077           0 :         s += "'";
    1078             :       }
    1079           0 :       break;
    1080             :     case eMediaKeySystemAccessRequest:
    1081           0 :       s = "key system='";
    1082           0 :       s += NS_ConvertUTF16toUTF8(mKeySystem).get();
    1083           0 :       s += mIsKeySystemSupported ? "', supported" : "', not supported";
    1084           0 :       switch (mKeySystemIssue) {
    1085             :         case eUnset:
    1086           0 :           break;
    1087             :         case eWidevineWithNoWMF:
    1088           0 :           s += ", Widevine with no WMF";
    1089           0 :           break;
    1090             :       }
    1091           0 :       break;
    1092             :     case eEvent:
    1093           0 :       s = nsPrintfCString("event domain %s result=%" PRIu32,
    1094           0 :                           EventDomainString(mEvent.mDomain), static_cast<uint32_t>(mEvent.mResult));
    1095           0 :       break;
    1096             :     case eDecodeError:
    1097           0 :       s = "decode error: ";
    1098           0 :       s += mDecodeIssue.Description();
    1099           0 :       s += ", src='";
    1100           0 :       s += mDecodeIssueMediaSrc.IsEmpty() ? "<none>" : "<provided>";
    1101           0 :       s += "'";
    1102           0 :       break;
    1103             :     case eDecodeWarning:
    1104           0 :       s = "decode warning: ";
    1105           0 :       s += mDecodeIssue.Description();
    1106           0 :       s += ", src='";
    1107           0 :       s += mDecodeIssueMediaSrc.IsEmpty() ? "<none>" : "<provided>";
    1108           0 :       s += "'";
    1109           0 :       break;
    1110             :     default:
    1111           0 :       MOZ_ASSERT_UNREACHABLE("Unexpected DiagnosticsType");
    1112             :       s = "?";
    1113             :       break;
    1114             :   }
    1115           0 :   return s;
    1116             : }
    1117             : 
    1118             : } // namespace mozilla

Generated by: LCOV version 1.13