Coverage Report

Created: 2018-09-25 14:53

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