summaryrefslogtreecommitdiffstats
path: root/dom/media/doctor/test/browser/browser_doctor_notification.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/doctor/test/browser/browser_doctor_notification.js')
-rw-r--r--dom/media/doctor/test/browser/browser_doctor_notification.js265
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;
+ }
+ );
+}