static void
DispatchNotification(nsISupports* aSubject,
                     const NotificationAndReportStringId& aNotification,
                     bool aIsSolved,
                     const nsAString& aFormats)
{
  if (!aSubject) {
    return;
  }
  dom::DecoderDoctorNotification data;
  data.mType = aNotification.mNotificationType;
  data.mIsSolved = aIsSolved;
  data.mDecoderDoctorReportId.Assign(
    NS_ConvertUTF8toUTF16(aNotification.mReportStringId));
  if (!aFormats.IsEmpty()) {
    data.mFormats.Construct(aFormats);
  }
  nsAutoString json;
  data.ToJSON(json);
  if (json.IsEmpty()) {
    DD_WARN("DecoderDoctorDiagnostics/DispatchEvent() - Could not create json for dispatch");
    // No point in dispatching this notification without data, the front-end
    // wouldn't know what to display.
    return;
  }
  DD_DEBUG("DecoderDoctorDiagnostics/DispatchEvent() %s", NS_ConvertUTF16toUTF8(json).get());
  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
  if (obs) {
    obs->NotifyObservers(aSubject, "decoder-doctor-notification", json.get());
  }
}
void
DecoderDoctorDiagnostics::StoreMediaKeySystemAccess(nsIDocument* aDocument,
                                                    const nsAString& aKeySystem,
                                                    bool aIsSupported,
                                                    const char* aCallSite)
{
  MOZ_ASSERT(NS_IsMainThread());
  // Make sure we only store once.
  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
  mDiagnosticsType = eMediaKeySystemAccessRequest;

  if (NS_WARN_IF(!aDocument)) {
    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=nullptr, keysystem='%s', supported=%d, call site '%s')",
            this, NS_ConvertUTF16toUTF8(aKeySystem).get(), aIsSupported, aCallSite);
    return;
  }
  if (NS_WARN_IF(aKeySystem.IsEmpty())) {
    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem=<empty>, supported=%d, call site '%s')",
            this, aDocument, aIsSupported, aCallSite);
    return;
  }

  RefPtr<DecoderDoctorDocumentWatcher> watcher =
    DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);

  if (NS_WARN_IF(!watcher)) {
    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem='%s', supported=%d, call site '%s') - Could not create document watcher",
            this, aDocument, NS_ConvertUTF16toUTF8(aKeySystem).get(), aIsSupported, aCallSite);
    return;
  }

  mKeySystem = aKeySystem;
  mIsKeySystemSupported = aIsSupported;

  // StoreMediaKeySystemAccess should only be called once, after all data is
  // available, so it is safe to std::move() from this object.
  watcher->AddDiagnostics(std::move(*this), aCallSite);
  // Even though it's moved-from, the type should stay set
  // (Only used to ensure that we do store only once.)
  MOZ_ASSERT(mDiagnosticsType == eMediaKeySystemAccessRequest);
}
void
DecoderDoctorDiagnostics::StoreFormatDiagnostics(nsIDocument* aDocument,
                                                 const nsAString& aFormat,
                                                 bool aCanPlay,
                                                 const char* aCallSite)
{
  MOZ_ASSERT(NS_IsMainThread());
  // Make sure we only store once.
  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
  mDiagnosticsType = eFormatSupportCheck;

  if (NS_WARN_IF(!aDocument)) {
    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=nullptr, format='%s', can-play=%d, call site '%s')",
            this, NS_ConvertUTF16toUTF8(aFormat).get(), aCanPlay, aCallSite);
    return;
  }
  if (NS_WARN_IF(aFormat.IsEmpty())) {
    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=%p, format=<empty>, can-play=%d, call site '%s')",
            this, aDocument, aCanPlay, aCallSite);
    return;
  }

  RefPtr<DecoderDoctorDocumentWatcher> watcher =
    DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);

  if (NS_WARN_IF(!watcher)) {
    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=%p, format='%s', can-play=%d, call site '%s') - Could not create document watcher",
            this, aDocument, NS_ConvertUTF16toUTF8(aFormat).get(), aCanPlay, aCallSite);
    return;
  }

  mFormat = aFormat;
  mCanPlay = aCanPlay;

  // StoreDiagnostics should only be called once, after all data is available,
  // so it is safe to std::move() from this object.
  watcher->AddDiagnostics(std::move(*this), aCallSite);
  // Even though it's moved-from, the type should stay set
  // (Only used to ensure that we do store only once.)
  MOZ_ASSERT(mDiagnosticsType == eFormatSupportCheck);
}
void
DecoderDoctorDiagnostics::StoreDecodeWarning(nsIDocument* aDocument,
                                             const MediaResult& aWarning,
                                             const nsString& aMediaSrc,
                                             const char* aCallSite)
{
  MOZ_ASSERT(NS_IsMainThread());
  // Make sure we only store once.
  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
  mDiagnosticsType = eDecodeWarning;

  if (NS_WARN_IF(!aDocument)) {
    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeWarning("
            "nsIDocument* aDocument=nullptr, aWarning=%s,"
            " aMediaSrc=<provided>, call site '%s')",
            this, aWarning.Description().get(), aCallSite);
    return;
  }

  RefPtr<DecoderDoctorDocumentWatcher> watcher =
    DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);

  if (NS_WARN_IF(!watcher)) {
    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDecodeWarning("
            "nsIDocument* aDocument=%p, aWarning='%s', aMediaSrc=<provided>,"
            " call site '%s') - Could not create document watcher",
            this, aDocument, aWarning.Description().get(), aCallSite);
    return;
  }

  mDecodeIssue = aWarning;
  mDecodeIssueMediaSrc = aMediaSrc;

  // StoreDecodeWarning should only be called once, after all data is
  // available, so it is safe to std::move() from this object.
  watcher->AddDiagnostics(std::move(*this), aCallSite);
  // Even though it's moved-from, the type should stay set
  // (Only used to ensure that we do store only once.)
  MOZ_ASSERT(mDiagnosticsType == eDecodeWarning);
}
void
DecoderDoctorDiagnostics::StoreEvent(nsIDocument* aDocument,
                                     const DecoderDoctorEvent& aEvent,
                                     const char* aCallSite)
{
  MOZ_ASSERT(NS_IsMainThread());
  // Make sure we only store once.
  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
  mDiagnosticsType = eEvent;
  mEvent = aEvent;

  if (NS_WARN_IF(!aDocument)) {
    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreEvent(nsIDocument* aDocument=nullptr, aEvent=%s, call site '%s')",
            this, GetDescription().get(), aCallSite);
    return;
  }

  // Don't keep events for later processing, just handle them now.
#ifdef MOZ_PULSEAUDIO
  switch (aEvent.mDomain) {
    case DecoderDoctorEvent::eAudioSinkStartup:
      if (aEvent.mResult == NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR) {
        DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::AddDiagnostics() - unable to initialize PulseAudio",
                this, aDocument);
        ReportAnalysis(aDocument, sCannotInitializePulseAudio,
                       false, NS_LITERAL_STRING("*"));
      } else if (aEvent.mResult == NS_OK) {
        DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::AddDiagnostics() - now able to initialize PulseAudio",
                this, aDocument);
        ReportAnalysis(aDocument, sCannotInitializePulseAudio,
                       true, NS_LITERAL_STRING("*"));
      }
      break;
  }
#endif // MOZ_PULSEAUDIO
}