diff options
Diffstat (limited to 'dom/media/doctor/test/browser/browser_doctor_notification.js')
-rw-r--r-- | dom/media/doctor/test/browser/browser_doctor_notification.js | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/dom/media/doctor/test/browser/browser_doctor_notification.js b/dom/media/doctor/test/browser/browser_doctor_notification.js new file mode 100644 index 0000000000..5789622e23 --- /dev/null +++ b/dom/media/doctor/test/browser/browser_doctor_notification.js @@ -0,0 +1,265 @@ +/** + * This test is used to test whether the decoder doctor would report the error + * on the notification banner (checking that by observing message) or on the web + * console (checking that by listening to the test event). + * Error should be reported after calling `DecoderDoctorDiagnostics::StoreXXX` + * methods. + * - StoreFormatDiagnostics() [for checking if type is supported] + * - StoreDecodeError() [when decode error occurs] + * - StoreEvent() [for reporting audio sink error] + */ + +// Only types being listed here would be allowed to display on a +// notification banner. Otherwise, the error would only be showed on the +// web console. +var gAllowedNotificationTypes = + "MediaWMFNeeded,MediaFFMpegNotFound,MediaUnsupportedLibavcodec,MediaDecodeError,MediaCannotInitializePulseAudio,"; + +// Used to check if the mime type in the notification is equal to what we set +// before. This mime type doesn't reflect the real world siutation, i.e. not +// every error listed in this test would happen on this type. An example, ffmpeg +// not found would only happen on H264/AAC media. +const gMimeType = "video/mp4"; + +add_task(async function setupTestingPref() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.decoder-doctor.testing", true], + ["media.decoder-doctor.verbose", true], + ["media.decoder-doctor.notifications-allowed", gAllowedNotificationTypes], + ], + }); + // transfer types to lower cases in order to match with `DecoderDoctorReportType` + gAllowedNotificationTypes = gAllowedNotificationTypes.toLowerCase(); +}); + +add_task(async function testWMFIsNeeded() { + const tab = await createTab("about:blank"); + await setFormatDiagnosticsReportForMimeType(tab, { + type: "platform-decoder-not-found", + decoderDoctorReportId: "mediawmfneeded", + formats: gMimeType, + }); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testFFMpegNotFound() { + const tab = await createTab("about:blank"); + await setFormatDiagnosticsReportForMimeType(tab, { + type: "platform-decoder-not-found", + decoderDoctorReportId: "mediaplatformdecodernotfound", + formats: gMimeType, + }); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testLibAVCodecUnsupported() { + const tab = await createTab("about:blank"); + await setFormatDiagnosticsReportForMimeType(tab, { + type: "unsupported-libavcodec", + decoderDoctorReportId: "mediaunsupportedlibavcodec", + formats: gMimeType, + }); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testCanNotPlayNoDecoder() { + const tab = await createTab("about:blank"); + await setFormatDiagnosticsReportForMimeType(tab, { + type: "cannot-play", + decoderDoctorReportId: "mediacannotplaynodecoders", + formats: gMimeType, + }); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testNoDecoder() { + const tab = await createTab("about:blank"); + await setFormatDiagnosticsReportForMimeType(tab, { + type: "can-play-but-some-missing-decoders", + decoderDoctorReportId: "medianodecoders", + formats: gMimeType, + }); + BrowserTestUtils.removeTab(tab); +}); + +const gErrorList = [ + "NS_ERROR_DOM_MEDIA_ABORT_ERR", + "NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR", + "NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR", + "NS_ERROR_DOM_MEDIA_DECODE_ERR", + "NS_ERROR_DOM_MEDIA_FATAL_ERR", + "NS_ERROR_DOM_MEDIA_METADATA_ERR", + "NS_ERROR_DOM_MEDIA_OVERFLOW_ERR", + "NS_ERROR_DOM_MEDIA_MEDIASINK_ERR", + "NS_ERROR_DOM_MEDIA_DEMUXER_ERR", + "NS_ERROR_DOM_MEDIA_CDM_ERR", + "NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR", +]; + +add_task(async function testDecodeError() { + const type = "decode-error"; + const decoderDoctorReportId = "mediadecodeerror"; + for (let error of gErrorList) { + const tab = await createTab("about:blank"); + info(`first to try if the error is not allowed to be reported`); + // No error is allowed to be reported in the notification banner. + await SpecialPowers.pushPrefEnv({ + set: [["media.decoder-doctor.decode-errors-allowed", ""]], + }); + await setDecodeError(tab, { + type, + decoderDoctorReportId, + error, + shouldReportNotification: false, + }); + + // If the notification type is `MediaDecodeError` and the error type is + // listed in the pref, then the error would be reported to the + // notification banner. + info(`Then to try if the error is allowed to be reported`); + await SpecialPowers.pushPrefEnv({ + set: [["media.decoder-doctor.decode-errors-allowed", error]], + }); + await setDecodeError(tab, { + type, + decoderDoctorReportId, + error, + shouldReportNotification: true, + }); + BrowserTestUtils.removeTab(tab); + } +}); + +add_task(async function testAudioSinkFailedStartup() { + const tab = await createTab("about:blank"); + await setAudioSinkFailedStartup(tab, { + type: "cannot-initialize-pulseaudio", + decoderDoctorReportId: "mediacannotinitializepulseaudio", + // This error comes with `*`, see `DecoderDoctorDiagnostics::StoreEvent` + formats: "*", + }); + BrowserTestUtils.removeTab(tab); +}); + +/** + * Following are helper functions + */ +async function createTab(url) { + let tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, url); + // Create observer in the content process in order to check the decoder + // doctor's notification that would be sent when an error occurs. + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + content._notificationName = "decoder-doctor-notification"; + content._obs = { + observe(subject, topic, data) { + let { type, decoderDoctorReportId, formats } = JSON.parse(data); + decoderDoctorReportId = decoderDoctorReportId.toLowerCase(); + info(`received '${type}:${decoderDoctorReportId}:${formats}'`); + if (!this._resolve) { + ok(false, "receive unexpected notification?"); + } + if ( + type == this._type && + decoderDoctorReportId == this._decoderDoctorReportId && + formats == this._formats + ) { + ok(true, `received correct notification`); + Services.obs.removeObserver(content._obs, content._notificationName); + this._resolve(); + this._resolve = null; + } + }, + // Return a promise that will be resolved once receiving a notification + // which has equal data with the input parameters. + waitFor({ type, decoderDoctorReportId, formats }) { + if (this._resolve) { + ok(false, "already has a pending promise!"); + return Promise.reject(); + } + Services.obs.addObserver(content._obs, content._notificationName); + return new Promise(resolve => { + info(`waiting for '${type}:${decoderDoctorReportId}:${formats}'`); + this._resolve = resolve; + this._type = type; + this._decoderDoctorReportId = decoderDoctorReportId; + this._formats = formats; + }); + }, + }; + content._waitForReport = (params, shouldReportNotification) => { + const reportToConsolePromise = new Promise(r => { + content.document.addEventListener( + "mozreportmediaerror", + _ => { + r(); + }, + { once: true } + ); + }); + const reportToNotificationBannerPromise = shouldReportNotification + ? content._obs.waitFor(params) + : Promise.resolve(); + info( + `waitForConsole=true, waitForNotificationBanner=${shouldReportNotification}` + ); + return Promise.all([ + reportToConsolePromise, + reportToNotificationBannerPromise, + ]); + }; + }); + return tab; +} + +async function setFormatDiagnosticsReportForMimeType(tab, params) { + const shouldReportNotification = gAllowedNotificationTypes.includes( + params.decoderDoctorReportId + ); + await SpecialPowers.spawn( + tab.linkedBrowser, + [params, shouldReportNotification], + async (params, shouldReportNotification) => { + const video = content.document.createElement("video"); + SpecialPowers.wrap(video).setFormatDiagnosticsReportForMimeType( + params.formats, + params.decoderDoctorReportId + ); + await content._waitForReport(params, shouldReportNotification); + } + ); + ok(true, `finished check for ${params.decoderDoctorReportId}`); +} + +async function setDecodeError(tab, params) { + info(`start check for ${params.error}`); + await SpecialPowers.spawn( + tab.linkedBrowser, + [params], + async (params, shouldReportNotification) => { + const video = content.document.createElement("video"); + SpecialPowers.wrap(video).setDecodeError(params.error); + await content._waitForReport(params, params.shouldReportNotification); + } + ); + ok(true, `finished check for ${params.error}`); +} + +async function setAudioSinkFailedStartup(tab, params) { + const shouldReportNotification = gAllowedNotificationTypes.includes( + params.decoderDoctorReportId + ); + await SpecialPowers.spawn( + tab.linkedBrowser, + [params, shouldReportNotification], + async (params, shouldReportNotification) => { + const video = content.document.createElement("video"); + const waitPromise = content._waitForReport( + params, + shouldReportNotification + ); + SpecialPowers.wrap(video).setAudioSinkFailedStartup(); + await waitPromise; + } + ); +} |