2016-04-19 10:36:19 +03:00
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/. */
# include "DecoderDoctorDiagnostics.h"
2016-04-19 10:36:19 +03:00
# include "mozilla/dom/DecoderDoctorNotificationBinding.h"
2016-04-19 10:36:19 +03:00
# include "mozilla/Logging.h"
2016-04-22 06:42:11 +03:00
# include "mozilla/Preferences.h"
2016-04-27 14:03:44 +03:00
# include "nsContentUtils.h"
2016-04-19 10:36:19 +03:00
# include "nsGkAtoms.h"
# include "nsIDocument.h"
2016-04-19 10:36:19 +03:00
# include "nsIObserverService.h"
2016-04-27 14:03:44 +03:00
# include "nsIScriptError.h"
2016-04-19 10:36:19 +03:00
# include "nsITimer.h"
# include "nsIWeakReference.h"
2016-04-22 06:42:11 +03:00
# include "nsPluginHost.h"
2016-05-26 12:24:35 +03:00
# include "VideoUtils.h"
2016-04-19 10:36:19 +03:00
2016-07-22 06:12:15 +03:00
# if defined(XP_WIN)
# include "mozilla/WindowsVersion.h"
# endif
2016-04-19 10:36:19 +03:00
static mozilla : : LazyLogModule sDecoderDoctorLog ( " DecoderDoctor " ) ;
# define DD_LOG(level, arg, ...) MOZ_LOG(sDecoderDoctorLog, level, (arg, ##__VA_ARGS__))
# define DD_DEBUG(arg, ...) DD_LOG(mozilla::LogLevel::Debug, arg, ##__VA_ARGS__)
2016-04-19 10:36:19 +03:00
# define DD_INFO(arg, ...) DD_LOG(mozilla::LogLevel::Info, arg, ##__VA_ARGS__)
2016-04-19 10:36:19 +03:00
# define DD_WARN(arg, ...) DD_LOG(mozilla::LogLevel::Warning, arg, ##__VA_ARGS__)
2016-05-16 14:35:14 +03:00
namespace mozilla {
2016-04-19 10:36:19 +03:00
2016-05-24 18:03:21 +03:00
struct NotificationAndReportStringId
{
2016-07-22 06:12:15 +03:00
// Notification type, handled by browser-media.js.
2016-05-24 18:03:21 +03:00
dom : : DecoderDoctorNotificationType mNotificationType ;
2016-07-22 06:12:15 +03:00
// Console message id. Key in dom/locales/.../chrome/dom/dom.properties.
2016-05-24 18:03:21 +03:00
const char * mReportStringId ;
} ;
2016-04-19 10:36:19 +03:00
// Class that collects a sequence of diagnostics from the same document over a
// small period of time, in order to provide a synthesized analysis.
//
// Referenced by the document through a nsINode property, mTimer, and
// inter-task captures.
// When notified that the document is dead, or when the timer expires but
// nothing new happened, StopWatching() will remove the document property and
// timer (if present), so no more work will happen and the watcher will be
// destroyed once all references are gone.
class DecoderDoctorDocumentWatcher : public nsITimerCallback
{
public :
2016-04-22 06:42:11 +03:00
static already_AddRefed < DecoderDoctorDocumentWatcher >
2016-04-19 10:36:19 +03:00
RetrieveOrCreate ( nsIDocument * aDocument ) ;
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
2016-04-22 06:42:11 +03:00
void AddDiagnostics ( DecoderDoctorDiagnostics & & aDiagnostics ,
const char * aCallSite ) ;
2016-04-19 10:36:19 +03:00
private :
explicit DecoderDoctorDocumentWatcher ( nsIDocument * aDocument ) ;
virtual ~ DecoderDoctorDocumentWatcher ( ) ;
// This will prevent further work from happening, watcher will deregister
// itself from document (if requested) and cancel any timer, and soon die.
void StopWatching ( bool aRemoveProperty ) ;
// Remove property from document; will call DestroyPropertyCallback.
void RemovePropertyFromDocument ( ) ;
// Callback for property destructor, will be automatically called when the
// document (in aObject) is being destroyed.
static void DestroyPropertyCallback ( void * aObject ,
nsIAtom * aPropertyName ,
void * aPropertyValue ,
void * aData ) ;
static const uint32_t sAnalysisPeriod_ms = 1000 ;
void EnsureTimerIsStarted ( ) ;
2016-05-24 18:03:21 +03:00
void ReportAnalysis ( const NotificationAndReportStringId & aNotification ,
2016-05-26 16:26:10 +03:00
bool aIsSolved ,
2016-04-19 10:36:19 +03:00
const nsAString & aFormats ) ;
void SynthesizeAnalysis ( ) ;
// Raw pointer to an nsIDocument.
// Must be non-null during construction.
// Nulled when we want to stop watching, because either:
// 1. The document has been destroyed (notified through
// DestroyPropertyCallback).
// 2. We have not received new diagnostic information within a short time
// period, so we just stop watching.
// Once nulled, no more actual work will happen, and the watcher will be
// destroyed soon.
nsIDocument * mDocument ;
struct Diagnostics
{
Diagnostics ( DecoderDoctorDiagnostics & & aDiagnostics ,
const char * aCallSite )
: mDecoderDoctorDiagnostics ( Move ( aDiagnostics ) )
, mCallSite ( aCallSite )
{ }
Diagnostics ( const Diagnostics & ) = delete ;
Diagnostics ( Diagnostics & & aOther )
: mDecoderDoctorDiagnostics ( Move ( aOther . mDecoderDoctorDiagnostics ) )
, mCallSite ( Move ( aOther . mCallSite ) )
{ }
const DecoderDoctorDiagnostics mDecoderDoctorDiagnostics ;
const nsCString mCallSite ;
} ;
typedef nsTArray < Diagnostics > DiagnosticsSequence ;
DiagnosticsSequence mDiagnosticsSequence ;
nsCOMPtr < nsITimer > mTimer ; // Keep timer alive until we run.
DiagnosticsSequence : : size_type mDiagnosticsHandled = 0 ;
} ;
NS_IMPL_ISUPPORTS ( DecoderDoctorDocumentWatcher , nsITimerCallback )
// static
2016-04-22 06:42:11 +03:00
already_AddRefed < DecoderDoctorDocumentWatcher >
2016-04-19 10:36:19 +03:00
DecoderDoctorDocumentWatcher : : RetrieveOrCreate ( nsIDocument * aDocument )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
MOZ_ASSERT ( aDocument ) ;
RefPtr < DecoderDoctorDocumentWatcher > watcher =
static_cast < DecoderDoctorDocumentWatcher * > (
aDocument - > GetProperty ( nsGkAtoms : : decoderDoctor ) ) ;
if ( ! watcher ) {
watcher = new DecoderDoctorDocumentWatcher ( aDocument ) ;
if ( NS_WARN_IF ( NS_FAILED (
aDocument - > SetProperty ( nsGkAtoms : : decoderDoctor ,
watcher . get ( ) ,
DestroyPropertyCallback ,
/*transfer*/ false ) ) ) ) {
DD_WARN ( " DecoderDoctorDocumentWatcher::RetrieveOrCreate(doc=%p) - Could not set property in document, will destroy new watcher[%p] " ,
aDocument , watcher . get ( ) ) ;
return nullptr ;
}
// Document owns watcher through this property.
// Released in DestroyPropertyCallback().
NS_ADDREF ( watcher . get ( ) ) ;
}
2016-04-22 06:42:11 +03:00
return watcher . forget ( ) ;
2016-04-19 10:36:19 +03:00
}
DecoderDoctorDocumentWatcher : : DecoderDoctorDocumentWatcher ( nsIDocument * aDocument )
: mDocument ( aDocument )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
MOZ_ASSERT ( mDocument ) ;
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p]::DecoderDoctorDocumentWatcher(doc=%p) " ,
this , mDocument ) ;
}
DecoderDoctorDocumentWatcher : : ~ DecoderDoctorDocumentWatcher ( )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p, doc=%p <- expect 0]::~DecoderDoctorDocumentWatcher() " ,
this , mDocument ) ;
// mDocument should have been reset through StopWatching()!
MOZ_ASSERT ( ! mDocument ) ;
}
void
DecoderDoctorDocumentWatcher : : RemovePropertyFromDocument ( )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
DecoderDoctorDocumentWatcher * watcher =
static_cast < DecoderDoctorDocumentWatcher * > (
mDocument - > GetProperty ( nsGkAtoms : : decoderDoctor ) ) ;
if ( ! watcher ) {
return ;
}
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::RemovePropertyFromDocument() \n " ,
watcher , watcher - > mDocument ) ;
// This will remove the property and call our DestroyPropertyCallback.
mDocument - > DeleteProperty ( nsGkAtoms : : decoderDoctor ) ;
}
// Callback for property destructors. |aObject| is the object
// the property is being removed for, |aPropertyName| is the property
// being removed, |aPropertyValue| is the value of the property, and |aData|
// is the opaque destructor data that was passed to SetProperty().
// static
void
DecoderDoctorDocumentWatcher : : DestroyPropertyCallback ( void * aObject ,
nsIAtom * aPropertyName ,
void * aPropertyValue ,
void * )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
# ifdef DEBUG
nsIDocument * document = static_cast < nsIDocument * > ( aObject ) ;
# endif
MOZ_ASSERT ( aPropertyName = = nsGkAtoms : : decoderDoctor ) ;
DecoderDoctorDocumentWatcher * watcher =
static_cast < DecoderDoctorDocumentWatcher * > ( aPropertyValue ) ;
MOZ_ASSERT ( watcher ) ;
MOZ_ASSERT ( watcher - > mDocument = = document ) ;
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::DestroyPropertyCallback() \n " ,
watcher , watcher - > mDocument ) ;
// 'false': StopWatching should not try and remove the property.
watcher - > StopWatching ( false ) ;
NS_RELEASE ( watcher ) ;
}
void
DecoderDoctorDocumentWatcher : : StopWatching ( bool aRemoveProperty )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
// StopWatching() shouldn't be called twice.
MOZ_ASSERT ( mDocument ) ;
if ( aRemoveProperty ) {
RemovePropertyFromDocument ( ) ;
}
// Forget document now, this will prevent more work from being started.
mDocument = nullptr ;
if ( mTimer ) {
mTimer - > Cancel ( ) ;
mTimer = nullptr ;
}
}
void
DecoderDoctorDocumentWatcher : : EnsureTimerIsStarted ( )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
if ( ! mTimer ) {
mTimer = do_CreateInstance ( NS_TIMER_CONTRACTID ) ;
if ( NS_WARN_IF ( ! mTimer ) ) {
return ;
}
if ( NS_WARN_IF ( NS_FAILED (
mTimer - > InitWithCallback (
this , sAnalysisPeriod_ms , nsITimer : : TYPE_ONE_SHOT ) ) ) ) {
mTimer = nullptr ;
}
}
}
2016-05-24 18:03:21 +03:00
static const NotificationAndReportStringId sMediaWidevineNoWMFNoSilverlight =
{ dom : : DecoderDoctorNotificationType : : Platform_decoder_not_found ,
" MediaWidevineNoWMFNoSilverlight " } ;
static const NotificationAndReportStringId sMediaWMFNeeded =
{ dom : : DecoderDoctorNotificationType : : Platform_decoder_not_found ,
" MediaWMFNeeded " } ;
2016-07-22 06:12:15 +03:00
static const NotificationAndReportStringId sMediaUnsupportedBeforeWindowsVista =
{ dom : : DecoderDoctorNotificationType : : Platform_decoder_not_found ,
" MediaUnsupportedBeforeWindowsVista " } ;
2016-05-24 18:03:21 +03:00
static const NotificationAndReportStringId sMediaPlatformDecoderNotFound =
{ dom : : DecoderDoctorNotificationType : : Platform_decoder_not_found ,
" MediaPlatformDecoderNotFound " } ;
static const NotificationAndReportStringId sMediaCannotPlayNoDecoders =
{ dom : : DecoderDoctorNotificationType : : Cannot_play ,
" MediaCannotPlayNoDecoders " } ;
static const NotificationAndReportStringId sMediaNoDecoders =
{ dom : : DecoderDoctorNotificationType : : Can_play_but_some_missing_decoders ,
" MediaNoDecoders " } ;
2016-05-24 17:41:53 +03:00
static const NotificationAndReportStringId *
sAllNotificationsAndReportStringIds [ ] =
{
& sMediaWidevineNoWMFNoSilverlight ,
& sMediaWMFNeeded ,
2016-07-22 06:12:15 +03:00
& sMediaUnsupportedBeforeWindowsVista ,
2016-05-24 17:41:53 +03:00
& sMediaPlatformDecoderNotFound ,
& sMediaCannotPlayNoDecoders ,
& sMediaNoDecoders
} ;
2016-04-19 10:36:19 +03:00
static void
DispatchNotification ( nsISupports * aSubject ,
2016-05-24 18:03:21 +03:00
const NotificationAndReportStringId & aNotification ,
2016-05-26 16:26:10 +03:00
bool aIsSolved ,
2016-04-19 10:36:19 +03:00
const nsAString & aFormats )
{
if ( ! aSubject ) {
return ;
}
dom : : DecoderDoctorNotification data ;
2016-05-24 18:03:21 +03:00
data . mType = aNotification . mNotificationType ;
2016-05-26 16:26:10 +03:00
data . mIsSolved = aIsSolved ;
data . mDecoderDoctorReportId . Assign (
2016-05-24 18:03:21 +03:00
NS_ConvertUTF8toUTF16 ( aNotification . mReportStringId ) ) ;
2016-04-19 10:36:19 +03:00
if ( ! aFormats . IsEmpty ( ) ) {
data . mFormats . Construct ( aFormats ) ;
}
nsAutoString json ;
data . ToJSON ( json ) ;
if ( json . IsEmpty ( ) ) {
DD_WARN ( " DecoderDoctorDiagnostics/DispatchEvent() - Could not create json for dispatch " ) ;
// No point in dispatching this notification without data, the front-end
// wouldn't know what to display.
return ;
}
DD_DEBUG ( " DecoderDoctorDiagnostics/DispatchEvent() %s " , NS_ConvertUTF16toUTF8 ( json ) . get ( ) ) ;
nsCOMPtr < nsIObserverService > obs = services : : GetObserverService ( ) ;
if ( obs ) {
obs - > NotifyObservers ( aSubject , " decoder-doctor-notification " , json . get ( ) ) ;
}
}
2016-04-19 10:36:19 +03:00
void
2016-04-19 10:36:19 +03:00
DecoderDoctorDocumentWatcher : : ReportAnalysis (
2016-05-24 18:03:21 +03:00
const NotificationAndReportStringId & aNotification ,
2016-05-26 16:26:10 +03:00
bool aIsSolved ,
2016-04-22 06:42:11 +03:00
const nsAString & aParams )
2016-04-19 10:36:19 +03:00
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
if ( ! mDocument ) {
return ;
}
2016-05-26 16:26:10 +03:00
// Report non-solved issues to console.
if ( ! aIsSolved ) {
// 'params' will only be forwarded for non-empty strings.
const char16_t * params [ 1 ] = { aParams . Data ( ) } ;
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::ReportAnalysis() ReportToConsole - aMsg='%s' params[0]='%s' " ,
2016-05-24 18:03:21 +03:00
this , mDocument , aNotification . mReportStringId ,
2016-05-26 16:26:10 +03:00
aParams . IsEmpty ( ) ? " <no params> " : NS_ConvertUTF16toUTF8 ( params [ 0 ] ) . get ( ) ) ;
nsContentUtils : : ReportToConsole ( nsIScriptError : : warningFlag ,
NS_LITERAL_CSTRING ( " Media " ) ,
mDocument ,
nsContentUtils : : eDOM_PROPERTIES ,
2016-05-24 18:03:21 +03:00
aNotification . mReportStringId ,
2016-05-26 16:26:10 +03:00
aParams . IsEmpty ( ) ? nullptr : params ,
aParams . IsEmpty ( ) ? 0 : 1 ) ;
}
2016-04-19 10:36:19 +03:00
2016-04-22 06:42:11 +03:00
// "media.decoder-doctor.notifications-allowed" controls which notifications
// may be dispatched to the front-end. It either contains:
// - '*' -> Allow everything.
// - Comma-separater list of ids -> Allow if aReportStringId (from
// dom.properties) is one of them.
// - Nothing (missing or empty) -> Disable everything.
nsAdoptingCString filter =
Preferences : : GetCString ( " media.decoder-doctor.notifications-allowed " ) ;
filter . StripWhitespace ( ) ;
2016-05-26 10:01:52 +03:00
if ( filter . EqualsLiteral ( " * " )
2016-05-24 18:03:21 +03:00
| | StringListContains ( filter , aNotification . mReportStringId ) ) {
2016-04-19 10:36:20 +03:00
DispatchNotification (
2016-05-24 18:03:21 +03:00
mDocument - > GetInnerWindow ( ) , aNotification , aIsSolved , aParams ) ;
2016-04-19 10:36:20 +03:00
}
2016-04-19 10:36:19 +03:00
}
2016-04-22 06:42:11 +03:00
enum SilverlightPresence {
eNoSilverlight ,
eSilverlightDisabled ,
eSilverlightEnabled
} ;
static SilverlightPresence
CheckSilverlight ( )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
RefPtr < nsPluginHost > pluginHost = nsPluginHost : : GetInst ( ) ;
if ( ! pluginHost ) {
return eNoSilverlight ;
}
nsTArray < nsCOMPtr < nsIInternalPluginTag > > plugins ;
pluginHost - > GetPlugins ( plugins , /*aIncludeDisabled*/ true ) ;
for ( const auto & plugin : plugins ) {
for ( const auto & mime : plugin - > MimeTypes ( ) ) {
if ( mime . LowerCaseEqualsLiteral ( " application/x-silverlight " )
| | mime . LowerCaseEqualsLiteral ( " application/x-silverlight-2 " ) ) {
return plugin - > IsEnabled ( ) ? eSilverlightEnabled : eSilverlightDisabled ;
}
}
}
return eNoSilverlight ;
}
2016-05-24 20:24:14 +03:00
static nsString
CleanItemForFormatsList ( const nsAString & aItem )
2016-04-22 06:42:11 +03:00
{
2016-05-24 20:24:14 +03:00
nsString item ( aItem ) ;
// Remove commas from item, as commas are used to separate items. It's fine
// to have a one-way mapping, it's only used for comparisons and in
// console display (where formats shouldn't contain commas in the first place)
item . ReplaceChar ( ' , ' , ' ' ) ;
item . CompressWhitespace ( ) ;
return item ;
}
static void
AppendToFormatsList ( nsAString & aList , const nsAString & aItem )
{
if ( ! aList . IsEmpty ( ) ) {
aList + = NS_LITERAL_STRING ( " , " ) ;
2016-04-22 06:42:11 +03:00
}
2016-05-24 20:24:14 +03:00
aList + = CleanItemForFormatsList ( aItem ) ;
}
static bool
FormatsListContains ( const nsAString & aList , const nsAString & aItem )
{
return StringListContains ( aList , CleanItemForFormatsList ( aItem ) ) ;
2016-04-22 06:42:11 +03:00
}
2016-04-19 10:36:19 +03:00
void
DecoderDoctorDocumentWatcher : : SynthesizeAnalysis ( )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
2016-05-12 09:28:11 +03:00
nsAutoString playableFormats ;
nsAutoString unplayableFormats ;
// Subsets of unplayableFormats that require a specific platform decoder:
2016-04-22 06:42:11 +03:00
# if defined(XP_WIN)
2016-05-12 09:28:11 +03:00
nsAutoString formatsRequiringWMF ;
2016-04-22 06:42:11 +03:00
# endif
2016-04-19 10:36:20 +03:00
# if defined(MOZ_FFMPEG)
2016-05-12 09:28:11 +03:00
nsAutoString formatsRequiringFFMpeg ;
2016-04-19 10:36:20 +03:00
# endif
2016-04-22 06:42:11 +03:00
nsAutoString supportedKeySystems ;
2016-04-22 06:42:11 +03:00
nsAutoString unsupportedKeySystems ;
2016-04-22 06:42:11 +03:00
DecoderDoctorDiagnostics : : KeySystemIssue lastKeySystemIssue =
DecoderDoctorDiagnostics : : eUnset ;
2016-04-22 06:42:11 +03:00
2016-05-12 08:52:24 +03:00
for ( const auto & diag : mDiagnosticsSequence ) {
2016-04-22 06:42:11 +03:00
switch ( diag . mDecoderDoctorDiagnostics . Type ( ) ) {
2016-05-16 14:35:14 +03:00
case DecoderDoctorDiagnostics : : eFormatSupportCheck :
if ( diag . mDecoderDoctorDiagnostics . CanPlay ( ) ) {
2016-05-24 20:24:14 +03:00
AppendToFormatsList ( playableFormats ,
diag . mDecoderDoctorDiagnostics . Format ( ) ) ;
2016-05-16 14:35:14 +03:00
} else {
2016-05-24 20:24:14 +03:00
AppendToFormatsList ( unplayableFormats ,
diag . mDecoderDoctorDiagnostics . Format ( ) ) ;
2016-04-22 06:42:11 +03:00
# if defined(XP_WIN)
2016-05-16 14:35:14 +03:00
if ( diag . mDecoderDoctorDiagnostics . DidWMFFailToLoad ( ) ) {
2016-05-24 20:24:14 +03:00
AppendToFormatsList ( formatsRequiringWMF ,
diag . mDecoderDoctorDiagnostics . Format ( ) ) ;
2016-05-16 14:35:14 +03:00
}
2016-04-22 06:42:11 +03:00
# endif
2016-04-19 10:36:20 +03:00
# if defined(MOZ_FFMPEG)
2016-05-16 14:35:14 +03:00
if ( diag . mDecoderDoctorDiagnostics . DidFFmpegFailToLoad ( ) ) {
2016-05-24 20:24:14 +03:00
AppendToFormatsList ( formatsRequiringFFMpeg ,
diag . mDecoderDoctorDiagnostics . Format ( ) ) ;
2016-05-16 14:35:14 +03:00
}
2016-04-19 10:36:20 +03:00
# endif
2016-04-22 06:42:11 +03:00
}
2016-05-16 14:35:14 +03:00
break ;
case DecoderDoctorDiagnostics : : eMediaKeySystemAccessRequest :
if ( diag . mDecoderDoctorDiagnostics . IsKeySystemSupported ( ) ) {
2016-05-24 20:24:14 +03:00
AppendToFormatsList ( supportedKeySystems ,
diag . mDecoderDoctorDiagnostics . KeySystem ( ) ) ;
2016-05-16 14:35:14 +03:00
} else {
2016-05-24 20:24:14 +03:00
AppendToFormatsList ( unsupportedKeySystems ,
diag . mDecoderDoctorDiagnostics . KeySystem ( ) ) ;
2016-05-16 14:35:14 +03:00
DecoderDoctorDiagnostics : : KeySystemIssue issue =
diag . mDecoderDoctorDiagnostics . GetKeySystemIssue ( ) ;
if ( issue ! = DecoderDoctorDiagnostics : : eUnset ) {
lastKeySystemIssue = issue ;
}
}
break ;
default :
MOZ_ASSERT ( diag . mDecoderDoctorDiagnostics . Type ( )
= = DecoderDoctorDiagnostics : : eFormatSupportCheck
| | diag . mDecoderDoctorDiagnostics . Type ( )
= = DecoderDoctorDiagnostics : : eMediaKeySystemAccessRequest ) ;
break ;
2016-04-19 10:36:19 +03:00
}
}
2016-04-22 06:42:11 +03:00
2016-05-24 17:41:53 +03:00
// Check if issues have been solved, by finding if some now-playable
// key systems or formats were previously recorded as having issues.
if ( ! supportedKeySystems . IsEmpty ( ) | | ! playableFormats . IsEmpty ( ) ) {
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - supported key systems '%s', playable formats '%s'; See if they show issues have been solved... " ,
this , mDocument ,
NS_ConvertUTF16toUTF8 ( supportedKeySystems ) . Data ( ) ,
NS_ConvertUTF16toUTF8 ( playableFormats ) . get ( ) ) ;
const nsAString * workingFormatsArray [ ] =
{ & supportedKeySystems , & playableFormats } ;
// For each type of notification, retrieve the pref that contains formats/
// key systems with issues.
for ( const NotificationAndReportStringId * id :
sAllNotificationsAndReportStringIds ) {
nsAutoCString formatsPref ( " media.decoder-doctor. " ) ;
formatsPref + = id - > mReportStringId ;
formatsPref + = " .formats " ;
nsAdoptingString formatsWithIssues =
Preferences : : GetString ( formatsPref . Data ( ) ) ;
if ( formatsWithIssues . IsEmpty ( ) ) {
continue ;
}
// See if that list of formats-with-issues contains any formats that are
// now playable/supported.
bool solved = false ;
for ( const nsAString * workingFormats : workingFormatsArray ) {
for ( const auto & workingFormat : MakeStringListRange ( * workingFormats ) ) {
if ( FormatsListContains ( formatsWithIssues , workingFormat ) ) {
// This now-working format used not to work -> Report solved issue.
DD_INFO ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - %s solved ('%s' now works, it was in pref(%s)='%s') " ,
this , mDocument , id - > mReportStringId ,
NS_ConvertUTF16toUTF8 ( workingFormat ) . get ( ) ,
formatsPref . Data ( ) ,
NS_ConvertUTF16toUTF8 ( formatsWithIssues ) . get ( ) ) ;
ReportAnalysis ( * id , true , workingFormat ) ;
// This particular Notification&ReportId has been solved, no need
// to keep looking at other keysys/formats that might solve it too.
solved = true ;
break ;
}
}
if ( solved ) {
break ;
}
}
if ( ! solved ) {
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - %s not solved (pref(%s)='%s') " ,
this , mDocument , id - > mReportStringId , formatsPref . Data ( ) ,
NS_ConvertUTF16toUTF8 ( formatsWithIssues ) . get ( ) ) ;
}
}
}
2016-05-12 05:49:36 +03:00
// Look at Key System issues first, as they take precedence over format checks.
2016-04-22 06:42:11 +03:00
if ( ! unsupportedKeySystems . IsEmpty ( ) & & supportedKeySystems . IsEmpty ( ) ) {
// No supported key systems!
switch ( lastKeySystemIssue ) {
2016-05-16 14:35:14 +03:00
case DecoderDoctorDiagnostics : : eWidevineWithNoWMF :
if ( CheckSilverlight ( ) ! = eSilverlightEnabled ) {
2016-05-24 17:41:53 +03:00
DD_INFO ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unsupported key systems: %s, widevine without WMF nor Silverlight " ,
this , mDocument , NS_ConvertUTF16toUTF8 ( unsupportedKeySystems ) . get ( ) ) ;
2016-05-26 16:26:10 +03:00
ReportAnalysis (
2016-05-24 18:03:21 +03:00
sMediaWidevineNoWMFNoSilverlight , false , unsupportedKeySystems ) ;
2016-05-16 14:35:14 +03:00
return ;
}
break ;
default :
break ;
2016-04-22 06:42:11 +03:00
}
}
2016-05-12 05:49:36 +03:00
// Next, check playability of requested formats.
if ( ! unplayableFormats . IsEmpty ( ) ) {
// Some requested formats cannot be played.
if ( playableFormats . IsEmpty ( ) ) {
2016-05-12 09:28:11 +03:00
// No requested formats can be played. See if we can help the user, by
// going through expected decoders from most to least desirable.
2016-04-22 06:42:11 +03:00
# if defined(XP_WIN)
2016-05-12 09:28:11 +03:00
if ( ! formatsRequiringWMF . IsEmpty ( ) ) {
2016-07-22 06:12:15 +03:00
if ( IsVistaOrLater ( ) ) {
DD_INFO ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because WMF was not found " ,
this , mDocument , NS_ConvertUTF16toUTF8 ( formatsRequiringWMF ) . get ( ) ) ;
ReportAnalysis ( sMediaWMFNeeded , false , formatsRequiringWMF ) ;
} else {
DD_INFO ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media before Windows Vista " ,
this , mDocument , NS_ConvertUTF16toUTF8 ( formatsRequiringWMF ) . get ( ) ) ;
ReportAnalysis ( sMediaUnsupportedBeforeWindowsVista , false , formatsRequiringWMF ) ;
}
2016-05-12 05:49:36 +03:00
return ;
}
2016-04-22 06:42:11 +03:00
# endif
2016-04-19 10:36:20 +03:00
# if defined(MOZ_FFMPEG)
2016-05-12 09:28:11 +03:00
if ( ! formatsRequiringFFMpeg . IsEmpty ( ) ) {
2016-05-24 17:41:53 +03:00
DD_INFO ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because platform decoder was not found " ,
this , mDocument , NS_ConvertUTF16toUTF8 ( formatsRequiringFFMpeg ) . get ( ) ) ;
2016-05-24 18:03:21 +03:00
ReportAnalysis ( sMediaPlatformDecoderNotFound ,
false , formatsRequiringFFMpeg ) ;
2016-05-12 05:49:36 +03:00
return ;
}
# endif
2016-05-24 17:41:53 +03:00
DD_INFO ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Cannot play media, unplayable formats: %s " ,
2016-05-12 05:49:36 +03:00
this , mDocument , NS_ConvertUTF16toUTF8 ( unplayableFormats ) . get ( ) ) ;
2016-05-24 18:03:21 +03:00
ReportAnalysis ( sMediaCannotPlayNoDecoders , false , unplayableFormats ) ;
2016-04-22 06:42:11 +03:00
return ;
2016-04-19 10:36:20 +03:00
}
2016-05-12 05:49:36 +03:00
2016-04-22 06:42:11 +03:00
DD_INFO ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Can play media, but no decoders for some requested formats: %s " ,
this , mDocument , NS_ConvertUTF16toUTF8 ( unplayableFormats ) . get ( ) ) ;
2016-04-22 06:42:11 +03:00
if ( Preferences : : GetBool ( " media.decoder-doctor.verbose " , false ) ) {
2016-05-24 18:03:21 +03:00
ReportAnalysis ( sMediaNoDecoders , false , unplayableFormats ) ;
2016-04-19 10:36:19 +03:00
}
2016-04-22 06:42:11 +03:00
return ;
2016-04-19 10:36:19 +03:00
}
2016-04-22 06:42:11 +03:00
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Can play media, decoders available for all requested formats " ,
this , mDocument ) ;
2016-04-19 10:36:19 +03:00
}
void
2016-04-22 06:42:11 +03:00
DecoderDoctorDocumentWatcher : : AddDiagnostics ( DecoderDoctorDiagnostics & & aDiagnostics ,
const char * aCallSite )
2016-04-19 10:36:19 +03:00
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
if ( ! mDocument ) {
return ;
}
2016-04-22 06:42:11 +03:00
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::AddDiagnostics(DecoderDoctorDiagnostics{%s}, call site '%s') " ,
this , mDocument , aDiagnostics . GetDescription ( ) . Data ( ) , aCallSite ) ;
2016-04-19 10:36:19 +03:00
mDiagnosticsSequence . AppendElement (
2016-04-22 06:42:11 +03:00
Diagnostics ( Move ( aDiagnostics ) , aCallSite ) ) ;
2016-04-19 10:36:19 +03:00
EnsureTimerIsStarted ( ) ;
}
NS_IMETHODIMP
DecoderDoctorDocumentWatcher : : Notify ( nsITimer * timer )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
MOZ_ASSERT ( timer = = mTimer ) ;
// Forget timer. (Assuming timer keeps itself and us alive during this call.)
mTimer = nullptr ;
if ( ! mDocument ) {
return NS_OK ;
}
if ( mDiagnosticsSequence . Length ( ) > mDiagnosticsHandled ) {
// We have new diagnostic data.
mDiagnosticsHandled = mDiagnosticsSequence . Length ( ) ;
SynthesizeAnalysis ( ) ;
// Restart timer, to redo analysis or stop watching this document,
// depending on whether anything new happens.
EnsureTimerIsStarted ( ) ;
} else {
DD_DEBUG ( " DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - No new diagnostics to analyze -> Stop watching " ,
this , mDocument ) ;
// Stop watching this document, we don't expect more diagnostics for now.
// If more diagnostics come in, we'll treat them as another burst, separately.
// 'true' to remove the property from the document.
StopWatching ( true ) ;
}
return NS_OK ;
}
2016-04-19 10:36:19 +03:00
void
2016-04-22 06:42:11 +03:00
DecoderDoctorDiagnostics : : StoreFormatDiagnostics ( nsIDocument * aDocument ,
const nsAString & aFormat ,
bool aCanPlay ,
const char * aCallSite )
2016-04-19 10:36:19 +03:00
{
2016-04-19 10:36:19 +03:00
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
2016-04-22 06:42:11 +03:00
// Make sure we only store once.
MOZ_ASSERT ( mDiagnosticsType = = eUnsaved ) ;
mDiagnosticsType = eFormatSupportCheck ;
2016-04-19 10:36:19 +03:00
if ( NS_WARN_IF ( ! aDocument ) ) {
2016-04-22 06:42:11 +03:00
DD_WARN ( " DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=nullptr, format='%s', can-play=%d, call site '%s') " ,
this , NS_ConvertUTF16toUTF8 ( aFormat ) . get ( ) , aCanPlay , aCallSite ) ;
return ;
}
if ( NS_WARN_IF ( aFormat . IsEmpty ( ) ) ) {
DD_WARN ( " DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=%p, format=<empty>, can-play=%d, call site '%s') " ,
this , aDocument , aCanPlay , aCallSite ) ;
2016-04-19 10:36:19 +03:00
return ;
}
2016-04-19 10:36:19 +03:00
RefPtr < DecoderDoctorDocumentWatcher > watcher =
DecoderDoctorDocumentWatcher : : RetrieveOrCreate ( aDocument ) ;
if ( NS_WARN_IF ( ! watcher ) ) {
2016-04-22 06:42:11 +03:00
DD_WARN ( " DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=%p, format='%s', can-play=%d, call site '%s') - Could not create document watcher " ,
this , aDocument , NS_ConvertUTF16toUTF8 ( aFormat ) . get ( ) , aCanPlay , aCallSite ) ;
2016-04-19 10:36:19 +03:00
return ;
}
2016-04-22 06:42:11 +03:00
mFormat = aFormat ;
mCanPlay = aCanPlay ;
2016-04-19 10:36:19 +03:00
// StoreDiagnostics should only be called once, after all data is available,
// so it is safe to Move() from this object.
2016-04-22 06:42:11 +03:00
watcher - > AddDiagnostics ( Move ( * this ) , aCallSite ) ;
2016-04-22 06:42:11 +03:00
// Even though it's moved-from, the type should stay set
// (Only used to ensure that we do store only once.)
MOZ_ASSERT ( mDiagnosticsType = = eFormatSupportCheck ) ;
}
void
DecoderDoctorDiagnostics : : StoreMediaKeySystemAccess ( nsIDocument * aDocument ,
const nsAString & aKeySystem ,
bool aIsSupported ,
const char * aCallSite )
{
MOZ_ASSERT ( NS_IsMainThread ( ) ) ;
// Make sure we only store once.
MOZ_ASSERT ( mDiagnosticsType = = eUnsaved ) ;
mDiagnosticsType = eMediaKeySystemAccessRequest ;
if ( NS_WARN_IF ( ! aDocument ) ) {
DD_WARN ( " DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=nullptr, keysystem='%s', supported=%d, call site '%s') " ,
this , NS_ConvertUTF16toUTF8 ( aKeySystem ) . get ( ) , aIsSupported , aCallSite ) ;
return ;
}
if ( NS_WARN_IF ( aKeySystem . IsEmpty ( ) ) ) {
DD_WARN ( " DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem=<empty>, supported=%d, call site '%s') " ,
this , aDocument , aIsSupported , aCallSite ) ;
return ;
}
RefPtr < DecoderDoctorDocumentWatcher > watcher =
DecoderDoctorDocumentWatcher : : RetrieveOrCreate ( aDocument ) ;
if ( NS_WARN_IF ( ! watcher ) ) {
DD_WARN ( " DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem='%s', supported=%d, call site '%s') - Could not create document watcher " ,
this , NS_ConvertUTF16toUTF8 ( aKeySystem ) . get ( ) , aIsSupported , aCallSite ) ;
return ;
}
mKeySystem = aKeySystem ;
mIsKeySystemSupported = aIsSupported ;
// StoreDiagnostics should only be called once, after all data is available,
// so it is safe to Move() from this object.
watcher - > AddDiagnostics ( Move ( * this ) , aCallSite ) ;
// Even though it's moved-from, the type should stay set
// (Only used to ensure that we do store only once.)
MOZ_ASSERT ( mDiagnosticsType = = eMediaKeySystemAccessRequest ) ;
2016-04-22 06:42:11 +03:00
}
nsCString
DecoderDoctorDiagnostics : : GetDescription ( ) const
{
2016-04-22 06:42:11 +03:00
MOZ_ASSERT ( mDiagnosticsType = = eFormatSupportCheck
| | mDiagnosticsType = = eMediaKeySystemAccessRequest ) ;
2016-04-22 06:42:11 +03:00
nsCString s ;
2016-04-22 06:42:11 +03:00
switch ( mDiagnosticsType ) {
2016-05-16 14:35:14 +03:00
case eUnsaved :
s = " Unsaved diagnostics, cannot get accurate description " ;
2016-04-22 06:42:11 +03:00
break ;
2016-05-16 14:35:14 +03:00
case eFormatSupportCheck :
s = " format=' " ;
s + = NS_ConvertUTF16toUTF8 ( mFormat ) . get ( ) ;
s + = mCanPlay ? " ', can play " : " ', cannot play " ;
if ( mWMFFailedToLoad ) {
s + = " , Windows platform decoder failed to load " ;
}
if ( mFFmpegFailedToLoad ) {
s + = " , Linux platform decoder failed to load " ;
}
if ( mGMPPDMFailedToStartup ) {
s + = " , GMP PDM failed to startup " ;
} else if ( ! mGMP . IsEmpty ( ) ) {
s + = " , Using GMP ' " ;
s + = mGMP ;
s + = " ' " ;
}
break ;
case eMediaKeySystemAccessRequest :
s = " key system=' " ;
s + = NS_ConvertUTF16toUTF8 ( mKeySystem ) . get ( ) ;
s + = mIsKeySystemSupported ? " ', supported " : " ', not supported " ;
switch ( mKeySystemIssue ) {
case eUnset :
break ;
case eWidevineWithNoWMF :
s + = " , Widevine with no WMF " ;
break ;
}
2016-04-22 06:42:11 +03:00
break ;
default :
s = " ? " ;
break ;
2016-04-22 06:42:11 +03:00
}
return s ;
2016-04-19 10:36:19 +03:00
}
} // namespace mozilla