browser_doctor_notification.js (9233B)
1 /** 2 * This test is used to test whether the decoder doctor would report the error 3 * on the notification banner (checking that by observing message) or on the web 4 * console (checking that by listening to the test event). 5 * Error should be reported after calling `DecoderDoctorDiagnostics::StoreXXX` 6 * methods. 7 * - StoreFormatDiagnostics() [for checking if type is supported] 8 * - StoreDecodeError() [when decode error occurs] 9 * - StoreEvent() [for reporting audio sink error] 10 */ 11 12 // Only types being listed here would be allowed to display on a 13 // notification banner. Otherwise, the error would only be showed on the 14 // web console. 15 var gAllowedNotificationTypes = 16 "MediaWMFNeeded,MediaFFMpegNotFound,MediaUnsupportedLibavcodec,MediaDecodeError,MediaCannotInitializePulseAudio,"; 17 18 // Used to check if the mime type in the notification is equal to what we set 19 // before. This mime type doesn't reflect the real world siutation, i.e. not 20 // every error listed in this test would happen on this type. An example, ffmpeg 21 // not found would only happen on H264/AAC media. 22 const gMimeType = "video/mp4"; 23 24 add_task(async function setupTestingPref() { 25 await SpecialPowers.pushPrefEnv({ 26 set: [ 27 ["media.decoder-doctor.testing", true], 28 ["media.decoder-doctor.verbose", true], 29 ["media.decoder-doctor.notifications-allowed", gAllowedNotificationTypes], 30 ], 31 }); 32 // transfer types to lower cases in order to match with `DecoderDoctorReportType` 33 gAllowedNotificationTypes = gAllowedNotificationTypes.toLowerCase(); 34 }); 35 36 add_task(async function testWMFIsNeeded() { 37 const tab = await createTab("about:blank"); 38 await setFormatDiagnosticsReportForMimeType(tab, { 39 type: "platform-decoder-not-found", 40 decoderDoctorReportId: "mediawmfneeded", 41 formats: gMimeType, 42 }); 43 BrowserTestUtils.removeTab(tab); 44 }); 45 46 add_task(async function testFFMpegNotFound() { 47 const tab = await createTab("about:blank"); 48 await setFormatDiagnosticsReportForMimeType(tab, { 49 type: "platform-decoder-not-found", 50 decoderDoctorReportId: "mediaplatformdecodernotfound", 51 formats: gMimeType, 52 }); 53 BrowserTestUtils.removeTab(tab); 54 }); 55 56 add_task(async function testLibAVCodecUnsupported() { 57 const tab = await createTab("about:blank"); 58 await setFormatDiagnosticsReportForMimeType(tab, { 59 type: "unsupported-libavcodec", 60 decoderDoctorReportId: "mediaunsupportedlibavcodec", 61 formats: gMimeType, 62 }); 63 BrowserTestUtils.removeTab(tab); 64 }); 65 66 add_task(async function testCanNotPlayNoDecoder() { 67 const tab = await createTab("about:blank"); 68 await setFormatDiagnosticsReportForMimeType(tab, { 69 type: "cannot-play", 70 decoderDoctorReportId: "mediacannotplaynodecoders", 71 formats: gMimeType, 72 }); 73 BrowserTestUtils.removeTab(tab); 74 }); 75 76 add_task(async function testNoDecoder() { 77 const tab = await createTab("about:blank"); 78 await setFormatDiagnosticsReportForMimeType(tab, { 79 type: "can-play-but-some-missing-decoders", 80 decoderDoctorReportId: "medianodecoders", 81 formats: gMimeType, 82 }); 83 BrowserTestUtils.removeTab(tab); 84 }); 85 86 const gErrorList = [ 87 "NS_ERROR_DOM_MEDIA_ABORT_ERR", 88 "NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR", 89 "NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR", 90 "NS_ERROR_DOM_MEDIA_DECODE_ERR", 91 "NS_ERROR_DOM_MEDIA_FATAL_ERR", 92 "NS_ERROR_DOM_MEDIA_METADATA_ERR", 93 "NS_ERROR_DOM_MEDIA_OVERFLOW_ERR", 94 "NS_ERROR_DOM_MEDIA_MEDIASINK_ERR", 95 "NS_ERROR_DOM_MEDIA_DEMUXER_ERR", 96 "NS_ERROR_DOM_MEDIA_CDM_ERR", 97 "NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR", 98 ]; 99 100 add_task(async function testDecodeError() { 101 const type = "decode-error"; 102 const decoderDoctorReportId = "mediadecodeerror"; 103 for (let error of gErrorList) { 104 const tab = await createTab("about:blank"); 105 info(`first to try if the error is not allowed to be reported`); 106 // No error is allowed to be reported in the notification banner. 107 await SpecialPowers.pushPrefEnv({ 108 set: [["media.decoder-doctor.decode-errors-allowed", ""]], 109 }); 110 await setDecodeError(tab, { 111 type, 112 decoderDoctorReportId, 113 error, 114 shouldReportNotification: false, 115 }); 116 117 // If the notification type is `MediaDecodeError` and the error type is 118 // listed in the pref, then the error would be reported to the 119 // notification banner. 120 info(`Then to try if the error is allowed to be reported`); 121 await SpecialPowers.pushPrefEnv({ 122 set: [["media.decoder-doctor.decode-errors-allowed", error]], 123 }); 124 await setDecodeError(tab, { 125 type, 126 decoderDoctorReportId, 127 error, 128 shouldReportNotification: true, 129 }); 130 BrowserTestUtils.removeTab(tab); 131 } 132 }); 133 134 add_task(async function testAudioSinkFailedStartup() { 135 const tab = await createTab("about:blank"); 136 await setAudioSinkFailedStartup(tab, { 137 type: "cannot-initialize-pulseaudio", 138 decoderDoctorReportId: "mediacannotinitializepulseaudio", 139 // This error comes with `*`, see `DecoderDoctorDiagnostics::StoreEvent` 140 formats: "*", 141 }); 142 BrowserTestUtils.removeTab(tab); 143 }); 144 145 /** 146 * Following are helper functions 147 */ 148 async function createTab(url) { 149 let tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, url); 150 // Create observer in the content process in order to check the decoder 151 // doctor's notification that would be sent when an error occurs. 152 await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { 153 content._notificationName = "decoder-doctor-notification"; 154 content._obs = { 155 observe(subject, topic, data) { 156 let { type, decoderDoctorReportId, formats } = JSON.parse(data); 157 decoderDoctorReportId = decoderDoctorReportId.toLowerCase(); 158 info(`received '${type}:${decoderDoctorReportId}:${formats}'`); 159 if (!this._resolve) { 160 ok(false, "receive unexpected notification?"); 161 } 162 if ( 163 type == this._type && 164 decoderDoctorReportId == this._decoderDoctorReportId && 165 formats == this._formats 166 ) { 167 ok(true, `received correct notification`); 168 Services.obs.removeObserver(content._obs, content._notificationName); 169 this._resolve(); 170 this._resolve = null; 171 } 172 }, 173 // Return a promise that will be resolved once receiving a notification 174 // which has equal data with the input parameters. 175 waitFor({ type, decoderDoctorReportId, formats }) { 176 if (this._resolve) { 177 ok(false, "already has a pending promise!"); 178 return Promise.reject(); 179 } 180 Services.obs.addObserver(content._obs, content._notificationName); 181 return new Promise(resolve => { 182 info(`waiting for '${type}:${decoderDoctorReportId}:${formats}'`); 183 this._resolve = resolve; 184 this._type = type; 185 this._decoderDoctorReportId = decoderDoctorReportId; 186 this._formats = formats; 187 }); 188 }, 189 }; 190 content._waitForReport = (params, shouldReportNotification) => { 191 const reportToConsolePromise = new Promise(r => { 192 content.document.addEventListener( 193 "mozreportmediaerror", 194 _ => { 195 r(); 196 }, 197 { once: true } 198 ); 199 }); 200 const reportToNotificationBannerPromise = shouldReportNotification 201 ? content._obs.waitFor(params) 202 : Promise.resolve(); 203 info( 204 `waitForConsole=true, waitForNotificationBanner=${shouldReportNotification}` 205 ); 206 return Promise.all([ 207 reportToConsolePromise, 208 reportToNotificationBannerPromise, 209 ]); 210 }; 211 }); 212 return tab; 213 } 214 215 async function setFormatDiagnosticsReportForMimeType(tab, params) { 216 const shouldReportNotification = gAllowedNotificationTypes.includes( 217 params.decoderDoctorReportId 218 ); 219 await SpecialPowers.spawn( 220 tab.linkedBrowser, 221 [params, shouldReportNotification], 222 async (params, shouldReportNotification) => { 223 const video = content.document.createElement("video"); 224 SpecialPowers.wrap(video).setFormatDiagnosticsReportForMimeType( 225 params.formats, 226 params.decoderDoctorReportId 227 ); 228 await content._waitForReport(params, shouldReportNotification); 229 } 230 ); 231 ok(true, `finished check for ${params.decoderDoctorReportId}`); 232 } 233 234 async function setDecodeError(tab, params) { 235 info(`start check for ${params.error}`); 236 await SpecialPowers.spawn(tab.linkedBrowser, [params], async params => { 237 const video = content.document.createElement("video"); 238 SpecialPowers.wrap(video).setDecodeError(params.error); 239 await content._waitForReport(params, params.shouldReportNotification); 240 }); 241 ok(true, `finished check for ${params.error}`); 242 } 243 244 async function setAudioSinkFailedStartup(tab, params) { 245 const shouldReportNotification = gAllowedNotificationTypes.includes( 246 params.decoderDoctorReportId 247 ); 248 await SpecialPowers.spawn( 249 tab.linkedBrowser, 250 [params, shouldReportNotification], 251 async (params, shouldReportNotification) => { 252 const video = content.document.createElement("video"); 253 const waitPromise = content._waitForReport( 254 params, 255 shouldReportNotification 256 ); 257 SpecialPowers.wrap(video).setAudioSinkFailedStartup(); 258 await waitPromise; 259 } 260 ); 261 }