tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DecoderDoctorDiagnostics.cpp (50359B)


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