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