browser_decoderDoctor.js (11130B)
1 "use strict"; 2 3 // 'data' contains the notification data object: 4 // - data.type must be provided. 5 // - data.isSolved and data.decoderDoctorReportId will be added if not provided 6 // (false and "testReportId" resp.) 7 // - Other fields (e.g.: data.formats) may be provided as needed. 8 // 'notificationMessage': Expected message in the notification bar. 9 // Falsy if nothing is expected after the notification is sent, in which case 10 // we won't have further checks, so the following parameters are not needed. 11 // 'label': Expected button label. Falsy if no button is expected, in which case 12 // we won't have further checks, so the following parameters are not needed. 13 // 'accessKey': Expected access key for the button. 14 // 'tabChecker': function(openedTab) called with the opened tab that resulted 15 // from clicking the button. 16 async function test_decoder_doctor_notification( 17 data, 18 notificationMessage, 19 label, 20 accessKey, 21 isLink, 22 tabChecker 23 ) { 24 const TEST_URL = "https://example.org"; 25 // A helper closure to test notifications in same or different origins. 26 // 'test_cross_origin' is used to determine if the observers used in the test 27 // are notified in the same frame (when false) or in a cross origin iframe 28 // (when true). 29 async function create_tab_and_test(test_cross_origin) { 30 await BrowserTestUtils.withNewTab( 31 { gBrowser, url: TEST_URL }, 32 async function (browser) { 33 let awaitNotificationBar; 34 if (notificationMessage) { 35 awaitNotificationBar = BrowserTestUtils.waitForNotificationBar( 36 gBrowser, 37 browser, 38 "decoder-doctor-notification" 39 ); 40 } 41 42 await SpecialPowers.spawn( 43 browser, 44 [data, test_cross_origin], 45 /* eslint-disable-next-line no-shadow */ 46 async function (data, test_cross_origin) { 47 if (!test_cross_origin) { 48 // Notify in the same origin. 49 Services.obs.notifyObservers( 50 content.window, 51 "decoder-doctor-notification", 52 JSON.stringify(data) 53 ); 54 return; 55 // Done notifying in the same origin. 56 } 57 58 // Notify in a different origin. 59 const CROSS_ORIGIN_URL = "https://example.com"; 60 let frame = content.document.createElement("iframe"); 61 frame.src = CROSS_ORIGIN_URL; 62 await new Promise(resolve => { 63 frame.addEventListener("load", () => { 64 resolve(); 65 }); 66 content.document.body.appendChild(frame); 67 }); 68 69 await content.SpecialPowers.spawn( 70 frame, 71 [data], 72 async function ( 73 /* eslint-disable-next-line no-shadow */ 74 data 75 ) { 76 Services.obs.notifyObservers( 77 content.window, 78 "decoder-doctor-notification", 79 JSON.stringify(data) 80 ); 81 } 82 ); 83 // Done notifying in a different origin. 84 } 85 ); 86 87 if (!notificationMessage) { 88 ok( 89 true, 90 "Tested notifying observers with a nonsensical message, no effects expected" 91 ); 92 return; 93 } 94 95 let notification; 96 try { 97 notification = await awaitNotificationBar; 98 } catch (ex) { 99 ok(false, ex); 100 return; 101 } 102 ok(notification, "Got decoder-doctor-notification notification"); 103 if (label?.l10nId) { 104 // Without the following statement, the 105 // test_cannot_initialize_pulseaudio 106 // will permanently fail on Linux. 107 if (label.l10nId === "moz-support-link-text") { 108 MozXULElement.insertFTLIfNeeded( 109 "toolkit/global/mozSupportLink.ftl" 110 ); 111 } 112 label = await document.l10n.formatValue(label.l10nId); 113 } 114 if (isLink) { 115 let link = notification.supportLinkEls[0]; 116 if (link) { 117 // Seems to be a Windows specific quirk, but without this 118 // mutation observer the notification.messageText.textContent 119 // will not be updated. This will cause consistent failures 120 // on Windows. 121 await BrowserTestUtils.waitForMutationCondition( 122 link, 123 { childList: true }, 124 () => link.textContent.trim() 125 ); 126 } 127 } 128 is( 129 notification.messageText.textContent.trim(), 130 notificationMessage, 131 "notification message should match expectation" 132 ); 133 134 let button = notification.buttonContainer.querySelector("button"); 135 let link = notification.supportLinkEls[0]; 136 if (!label) { 137 ok(!button, "There should not be a button"); 138 ok(!link, "There should not be a link"); 139 return; 140 } 141 142 if (isLink) { 143 ok(!button, "There should not be a button"); 144 is(link.textContent, label, `notification link should be '${label}'`); 145 ok( 146 !link.hasAttribute("accesskey"), 147 "notification link should not have accesskey" 148 ); 149 } else { 150 ok(!link, "There should not be a link"); 151 is( 152 button.getAttribute("label"), 153 label, 154 `notification button should be '${label}'` 155 ); 156 is( 157 button.getAttribute("accesskey"), 158 accessKey, 159 "notification button should have accesskey" 160 ); 161 } 162 163 if (!tabChecker) { 164 ok(false, "Test implementation error: Missing tabChecker"); 165 return; 166 } 167 let awaitNewTab = BrowserTestUtils.waitForNewTab(gBrowser); 168 if (button) { 169 button.click(); 170 } else { 171 link.click(); 172 } 173 let openedTab = await awaitNewTab; 174 tabChecker(openedTab); 175 BrowserTestUtils.removeTab(openedTab); 176 } 177 ); 178 } 179 180 if (typeof data.type === "undefined") { 181 ok(false, "Test implementation error: data.type must be provided"); 182 return; 183 } 184 data.isSolved = data.isSolved || false; 185 if (typeof data.decoderDoctorReportId === "undefined") { 186 data.decoderDoctorReportId = "testReportId"; 187 } 188 189 // Test same origin. 190 await create_tab_and_test(false); 191 // Test cross origin. 192 await create_tab_and_test(true); 193 } 194 195 function tab_checker_for_sumo(expectedPath) { 196 return function (openedTab) { 197 let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL"); 198 let url = baseURL + expectedPath; 199 is( 200 openedTab.linkedBrowser.currentURI.spec, 201 url, 202 `Expected '${url}' in new tab` 203 ); 204 }; 205 } 206 207 function tab_checker_for_webcompat(expectedParams) { 208 return function (openedTab) { 209 let urlString = openedTab.linkedBrowser.currentURI.spec; 210 let endpoint = Services.prefs.getStringPref( 211 "media.decoder-doctor.new-issue-endpoint", 212 "" 213 ); 214 ok( 215 urlString.startsWith(endpoint), 216 `Expected URL starting with '${endpoint}', got '${urlString}'` 217 ); 218 let params = new URL(urlString).searchParams; 219 for (let k in expectedParams) { 220 if (!params.has(k)) { 221 ok(false, `Expected ${k} in webcompat URL`); 222 } else { 223 is( 224 params.get(k), 225 expectedParams[k], 226 `Expected ${k}='${expectedParams[k]}' in webcompat URL` 227 ); 228 } 229 } 230 }; 231 } 232 233 add_task(async function test_platform_decoder_not_found() { 234 let message = ""; 235 let decoderDoctorReportId = ""; 236 let isLinux = AppConstants.platform == "linux"; 237 if (isLinux) { 238 message = gNavigatorBundle.getString("decoder.noCodecsLinux.message"); 239 decoderDoctorReportId = "MediaPlatformDecoderNotFound"; 240 } else if (AppConstants.platform == "win") { 241 message = gNavigatorBundle.getString("decoder.noHWAcceleration.message"); 242 decoderDoctorReportId = "MediaWMFNeeded"; 243 } 244 245 await test_decoder_doctor_notification( 246 { 247 type: "platform-decoder-not-found", 248 decoderDoctorReportId, 249 formats: "testFormat", 250 }, 251 message, 252 isLinux ? "" : { l10nId: "moz-support-link-text" }, 253 isLinux ? "" : gNavigatorBundle.getString("decoder.noCodecs.accesskey"), 254 true, 255 tab_checker_for_sumo("fix-video-audio-problems-firefox-windows") 256 ); 257 }); 258 259 add_task(async function test_cannot_initialize_pulseaudio() { 260 let message = ""; 261 // This is only sent on Linux. 262 if (AppConstants.platform == "linux") { 263 message = gNavigatorBundle.getString("decoder.noPulseAudio.message"); 264 } 265 266 await test_decoder_doctor_notification( 267 { type: "cannot-initialize-pulseaudio", formats: "testFormat" }, 268 message, 269 { l10nId: "moz-support-link-text" }, 270 gNavigatorBundle.getString("decoder.noCodecs.accesskey"), 271 true, 272 tab_checker_for_sumo("fix-common-audio-and-video-issues") 273 ); 274 }); 275 276 add_task(async function test_unsupported_libavcodec() { 277 let message = ""; 278 // This is only sent on Linux. 279 if (AppConstants.platform == "linux") { 280 message = gNavigatorBundle.getString( 281 "decoder.unsupportedLibavcodec.message" 282 ); 283 } 284 285 await test_decoder_doctor_notification( 286 { type: "unsupported-libavcodec", formats: "testFormat" }, 287 message 288 ); 289 }); 290 291 add_task(async function test_decode_error() { 292 await SpecialPowers.pushPrefEnv({ 293 set: [ 294 [ 295 "media.decoder-doctor.new-issue-endpoint", 296 "http://example.com/webcompat", 297 ], 298 ["browser.fixup.fallback-to-https", false], 299 ], 300 }); 301 let message = gNavigatorBundle.getString("decoder.decodeError.message"); 302 await test_decoder_doctor_notification( 303 { 304 type: "decode-error", 305 decodeIssue: "DecodeIssue", 306 docURL: "DocURL", 307 resourceURL: "ResURL", 308 }, 309 message, 310 gNavigatorBundle.getString("decoder.decodeError.button"), 311 gNavigatorBundle.getString("decoder.decodeError.accesskey"), 312 false, 313 tab_checker_for_webcompat({ 314 url: "DocURL", 315 label: "type-media", 316 problem_type: "video_bug", 317 details: JSON.stringify({ 318 "Technical Information:": "DecodeIssue", 319 "Resource:": "ResURL", 320 }), 321 }) 322 ); 323 }); 324 325 add_task(async function test_decode_warning() { 326 await SpecialPowers.pushPrefEnv({ 327 set: [ 328 [ 329 "media.decoder-doctor.new-issue-endpoint", 330 "http://example.com/webcompat", 331 ], 332 ], 333 }); 334 let message = gNavigatorBundle.getString("decoder.decodeWarning.message"); 335 await test_decoder_doctor_notification( 336 { 337 type: "decode-warning", 338 decodeIssue: "DecodeIssue", 339 docURL: "DocURL", 340 resourceURL: "ResURL", 341 }, 342 message, 343 gNavigatorBundle.getString("decoder.decodeError.button"), 344 gNavigatorBundle.getString("decoder.decodeError.accesskey"), 345 false, 346 tab_checker_for_webcompat({ 347 url: "DocURL", 348 label: "type-media", 349 problem_type: "video_bug", 350 details: JSON.stringify({ 351 "Technical Information:": "DecodeIssue", 352 "Resource:": "ResURL", 353 }), 354 }) 355 ); 356 });