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