browser_notification_unexpected_script.js (16565B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ 3 */ 4 5 const TEST_URL = 6 getRootDirectory(gTestPath).replace( 7 "chrome://mochitests/content", 8 "https://example.com" 9 ) + "empty_file.html"; 10 11 /* 12 * Register cleanup function to reset prefs after other tasks have run. 13 */ 14 15 add_setup(async function () { 16 // Per browser_enable_DRM_prompt.js , SpecialPowers.pushPrefEnv has 17 // problems with buttons on the notification bar toggling the prefs. 18 // So manually reset the prefs the UI we're testing toggles. 19 20 // This preference is needed because tests run with 21 // xpinstall.signatures.required=false to install a helper extension 22 // This triggers the JSHacks exemption, which we need to disable 23 // for our test to work. 24 Services.prefs.setBoolPref( 25 "security.parent_unrestricted_js_loads.skip_jshacks", 26 true 27 ); 28 // This preference is needed to prevent the Opt builds from crashing. 29 // Currently we allow a single crash from an unexpected script load, 30 // to try and understand the problem better via crash reports. 31 // (It has not worked.) 32 let originalCrashes = Services.prefs.getIntPref( 33 "security.crash_tracking.js_load_1.maxCrashes", 34 0 35 ); 36 Services.prefs.setIntPref("security.crash_tracking.js_load_1.maxCrashes", 0); 37 38 registerCleanupFunction(function () { 39 Services.prefs.setBoolPref( 40 "security.block_parent_unrestricted_js_loads.temporary", 41 false 42 ); 43 Services.prefs.setBoolPref( 44 "security.allow_parent_unrestricted_js_loads", 45 false 46 ); 47 Services.prefs.setBoolPref( 48 "security.parent_unrestricted_js_loads.skip_jshacks", 49 false 50 ); 51 Services.prefs.setIntPref( 52 "security.crash_tracking.js_load_1.maxCrashes", 53 originalCrashes 54 ); 55 }); 56 }); 57 58 async function runWorkflow(actions_to_take, expected_results) { 59 // ============================================================ 60 // ============================================================ 61 // Reset things for each invoation 62 Services.obs.notifyObservers( 63 null, 64 "UnexpectedJavaScriptLoad-ResetNotification" 65 ); 66 Services.fog.testResetFOG(); 67 68 await SpecialPowers.pushPrefEnv({ 69 set: [ 70 [ 71 "datareporting.healthreport.uploadEnabled", 72 actions_to_take.enable_telemetry, 73 ], 74 ], 75 }); 76 77 // ============================================================ 78 // ============================================================ 79 // Set up expected results 80 expected_results = expected_results || {}; 81 82 // All of these functions take an integer indicating the number of telemetry events 83 // They return true if that number is expected. 84 expected_results.inforBarDismissed = function (num_events) { 85 return num_events == 0; 86 }; 87 88 expected_results.dialogDismissed = function (num_events) { 89 return num_events == 0; 90 }; 91 92 expected_results.moreInfoOpened = function (num_events) { 93 return num_events == 0; 94 }; 95 96 if (actions_to_take.click_block === true) { 97 expected_results.first_paragraph_text = 98 "unexpected-script-load-detail-1-block"; 99 expected_results.report_checkbox_checked_by_default = true; 100 expected_results.block_pref_set = true; 101 expected_results.allow_pref_set = false; 102 expected_results.scriptBlockedOpened = function (num_events) { 103 return num_events > 0; 104 }; 105 expected_results.scriptAllowedOpened = function (num_events) { 106 return num_events == 0; 107 }; 108 expected_results.scriptBlocked = function (num_events) { 109 return num_events > 0; 110 }; 111 expected_results.scriptAllowed = function (num_events) { 112 return num_events == 0; 113 }; 114 } else { 115 expected_results.first_paragraph_text = 116 "unexpected-script-load-detail-1-allow"; 117 expected_results.report_checkbox_checked_by_default = false; 118 expected_results.block_pref_set = false; 119 expected_results.allow_pref_set = true; 120 expected_results.scriptBlockedOpened = function (num_events) { 121 return num_events == 0; 122 }; 123 expected_results.scriptAllowedOpened = function (num_events) { 124 return num_events > 0; 125 }; 126 expected_results.scriptBlocked = function (num_events) { 127 return num_events == 0; 128 }; 129 expected_results.scriptAllowed = function (num_events) { 130 return num_events > 0; 131 }; 132 } 133 134 if (actions_to_take.report) { 135 expected_results.should_have_report = true; 136 } else { 137 expected_results.should_have_report = false; 138 } 139 140 if (actions_to_take.report_email) { 141 expected_results.should_have_report_email = true; 142 } else { 143 expected_results.should_have_report_email = false; 144 } 145 146 // ============================================================ 147 // ============================================================ 148 let window = BrowserWindowTracker.getTopWindow(); 149 let notificationShownPromise = BrowserTestUtils.waitForGlobalNotificationBar( 150 window, 151 "unexpected-script-notification" 152 ); 153 154 // ============================================================ 155 // ============================================================ 156 // Main body of test - enclosed in a function so we can put it 157 // first (in the file) before the telemetry assertions 158 let runTest = async () => { 159 // ============================================================ 160 // Trigger the notification 161 let sandbox = Cu.Sandbox(null); 162 try { 163 // This will trigger the unexpected script load notification. 164 // On Nightly, where we enforce the restriction, it will also 165 // cause an error, necessitating the try/catch. 166 Cu.evalInSandbox( 167 "let x = 1", 168 sandbox, 169 "1.8", 170 "https://example.net/script.js", 171 1 172 ); 173 } catch (e) {} 174 175 let notification = await notificationShownPromise; 176 177 // ============================================================ 178 // Verify the notification bar showed. 179 ok(notification, "Notification should be visible"); 180 is( 181 notification.getAttribute("value"), 182 "unexpected-script-notification", 183 "Should be showing the right notification" 184 ); 185 186 // ============================================================ 187 // Verify the buttons are there. 188 let buttons = notification.buttonContainer.querySelectorAll( 189 ".notification-button" 190 ); 191 is(buttons.length, 2, "Should have two buttons."); 192 let learnMoreLinks = notification.querySelectorAll(".notification-link"); 193 is(learnMoreLinks.length, 1, "Should have one learn more link."); 194 195 // ============================================================ 196 // Open the dialog by clicking the Block Button 197 let dialogShownPromise = new Promise(resolve => { 198 BrowserTestUtils.waitForEvent( 199 window.document.getElementById("window-modal-dialog"), 200 "dialogopen", 201 false, 202 () => { 203 return true; 204 } 205 ).then(event => { 206 resolve(event.originalTarget); 207 }); 208 }); 209 210 // On the notification bar, the order of the buttons is constant: always [Allow] [Block] 211 // it only changes on the dialog, but we choose buttons there based on id 212 let buttonIndex = actions_to_take.click_block ? 1 : 0; 213 buttons[buttonIndex].click(); 214 215 await dialogShownPromise; 216 217 // ============================================================ 218 // Confirm we opened the dialog 219 is( 220 window?.gDialogBox?.dialog?._openedURL, 221 "chrome://browser/content/security/unexpectedScriptLoad.xhtml", 222 "Should have an open dialog" 223 ); 224 225 // ============================================================ 226 // Verify the dialog says the right thing 227 let firstParagraph = 228 window.gDialogBox.dialog._frame.contentDocument.getElementById( 229 "unexpected-script-load-detail-1" 230 ); 231 isnot(firstParagraph, null, "Should have one detail paragraph."); 232 is( 233 firstParagraph.getAttribute("data-l10n-id"), 234 expected_results.first_paragraph_text, 235 "Should have the right detail text." 236 ); 237 238 // ============================================================ 239 // Verify the telemetry message 240 let telemetryMessage = 241 window.gDialogBox.dialog._frame.contentDocument.getElementById( 242 "telemetry-disabled-message" 243 ); 244 isnot(telemetryMessage, null, "Should have one telemetry message."); 245 is( 246 telemetryMessage.hasAttribute("hidden"), 247 !!actions_to_take.enable_telemetry, 248 `Telemetry Message should ${actions_to_take.enable_telemetry ? "" : "not"} be visible` 249 ); 250 251 // ============================================================ 252 // Verify the report checkbox is checked 253 let reportCheckbox = 254 window.gDialogBox.dialog._frame.contentDocument.getElementById( 255 "reportCheckbox" 256 ); 257 isnot(reportCheckbox, null, "Should have one report checkbox."); 258 is( 259 reportCheckbox.checked, 260 expected_results.report_checkbox_checked_by_default && 261 actions_to_take.enable_telemetry, 262 `Report checkbox should ${expected_results.report_checkbox_checked_by_default && actions_to_take.enable_telemetry ? "" : "not"} be checked by default` 263 ); 264 265 // ============================================================ 266 // Fill in the checkboxes and email field as appropriate 267 if (actions_to_take.report) { 268 reportCheckbox.checked = true; 269 } 270 271 let emailCheckbox = 272 window.gDialogBox.dialog._frame.contentDocument.getElementById( 273 "emailCheckbox" 274 ); 275 isnot(emailCheckbox, null, "Should have one email checkbox."); 276 is( 277 emailCheckbox.checked, 278 false, 279 "Email checkbox should not be checked by default" 280 ); 281 282 if (actions_to_take.report_email) { 283 emailCheckbox.checked = true; 284 let emailField = 285 window.gDialogBox.dialog._frame.contentDocument.getElementById( 286 "emailInput" 287 ); 288 isnot(emailField, null, "Should have one email field."); 289 emailField.value = "test@example.com"; 290 } 291 292 // ============================================================ 293 // Find the Buttons 294 let blockButton = 295 window.gDialogBox.dialog._frame.contentDocument.getElementById( 296 "block-button" 297 ); 298 isnot(blockButton, null, "Should have one block button."); 299 300 let allowButton = 301 window.gDialogBox.dialog._frame.contentDocument.getElementById( 302 "allow-button" 303 ); 304 isnot(allowButton, null, "Should have one allow button."); 305 306 // ============================================================ 307 // Prepare to close the dialog 308 let dialogClosePromise = new Promise(resolve => { 309 BrowserTestUtils.waitForEvent( 310 window.document.getElementById("window-modal-dialog"), 311 "dialogclose", 312 false, 313 () => { 314 return true; 315 } 316 ).then(event => { 317 resolve(event.originalTarget); 318 }); 319 }); 320 321 // ============================================================ 322 // Take an action and close the dialog 323 if (actions_to_take.click_block) { 324 blockButton.click(); 325 } else { 326 allowButton.click(); 327 } 328 329 await dialogClosePromise; 330 331 // ============================================================ 332 // Confirm the action did the right thing 333 let isBlocked = Services.prefs.getBoolPref( 334 "security.block_parent_unrestricted_js_loads.temporary", 335 false 336 ); 337 is( 338 isBlocked, 339 expected_results.block_pref_set, 340 `Should ${expected_results.block_pref_set ? "" : "not"} have set the pref` 341 ); 342 343 let isAllowed = Services.prefs.getBoolPref( 344 "security.allow_parent_unrestricted_js_loads", 345 false 346 ); 347 is( 348 isAllowed, 349 expected_results.allow_pref_set, 350 `Should ${expected_results.allow_pref_set ? "" : "not"} have set the allow pref` 351 ); 352 }; 353 354 // ============================================================ 355 // ============================================================ 356 // Now test the telemetry. 357 // (in actually this function call runs the test itself, as the above 358 // is all just a function definition, but it's awkward that the order 359 // has to be this way, so we're jumping through a hoop so you can 360 // read the file in the same order it executes in.) 361 await GleanPings.unexpectedScriptLoad.testSubmission(async () => { 362 isnot( 363 // We always see this one 364 Glean.unexpectedScriptLoad.infobarShown.testGetValue()?.length ?? 0, 365 0, 366 "Should have recorded an infobarShown telemetry event" 367 ); 368 ok( 369 expected_results.inforBarDismissed( 370 Glean.unexpectedScriptLoad.infobarDismissed.testGetValue()?.length ?? 0 371 ), 372 "InfoBarDismissed telemetry event" 373 ); 374 ok( 375 expected_results.dialogDismissed( 376 Glean.unexpectedScriptLoad.dialogDismissed.testGetValue()?.length ?? 0 377 ), 378 "DialogDismissed telemetry event" 379 ); 380 let scriptBlockedOpenedSeen = 381 Glean.unexpectedScriptLoad.scriptBlockedOpened.testGetValue()?.length ?? 382 0; 383 ok( 384 expected_results.scriptBlockedOpened(scriptBlockedOpenedSeen), 385 `ScriptBlockedOpened telemetry event: saw ${scriptBlockedOpenedSeen}` 386 ); 387 let scriptAllowedOpenedSeen = 388 Glean.unexpectedScriptLoad.scriptAllowedOpened.testGetValue()?.length ?? 389 0; 390 ok( 391 expected_results.scriptAllowedOpened(scriptAllowedOpenedSeen), 392 `scriptAllowedOpened telemetry event: saw ${scriptAllowedOpenedSeen}` 393 ); 394 ok( 395 expected_results.moreInfoOpened( 396 Glean.unexpectedScriptLoad.moreInfoOpened.testGetValue()?.length ?? 0 397 ), 398 "moreInfoOpened telemetry event" 399 ); 400 let scriptBlockedSeen = 401 Glean.unexpectedScriptLoad.scriptBlocked.testGetValue()?.length ?? 0; 402 ok( 403 expected_results.scriptBlocked(scriptBlockedSeen), 404 `scriptBlocked telemetry event: saw ${scriptBlockedSeen}` 405 ); 406 let scriptAllowedSeen = 407 Glean.unexpectedScriptLoad.scriptAllowed.testGetValue()?.length ?? 0; 408 ok( 409 expected_results.scriptAllowed(scriptAllowedSeen), 410 `scriptAllowed telemetry event: saw ${scriptAllowedSeen}` 411 ); 412 let script_reported_event = 413 Glean.unexpectedScriptLoad.scriptReported.testGetValue(); 414 if (script_reported_event && expected_results.should_have_report) { 415 script_reported_event = script_reported_event[0]; 416 is( 417 script_reported_event.extra.script_url, 418 "https://example.net/script.js", 419 "Should have recorded the script URL" 420 ); 421 if (actions_to_take.report_email) { 422 is( 423 script_reported_event.extra.user_email, 424 "test@example.com", 425 "Should have recorded the email address" 426 ); 427 } else { 428 is( 429 script_reported_event.extra.user_email, 430 undefined, 431 "Should not have recorded the email address" 432 ); 433 } 434 ok(true, "Should have recorded a scriptReported telemetry event"); 435 } else if (script_reported_event && !expected_results.should_have_report) { 436 ok(false, "Should not have recorded a scriptReported telemetry event"); 437 } else if (!script_reported_event && expected_results.should_have_report) { 438 ok(false, "Should have recorded a scriptReported telemetry event"); 439 } else if (!script_reported_event && !expected_results.should_have_report) { 440 ok(true, "Should not have recorded a scriptReported telemetry event"); 441 } else { 442 ok(false, "How did we get here?"); 443 } 444 }, runTest); 445 446 // Cleanup 447 Services.prefs.clearUserPref( 448 "security.block_parent_unrestricted_js_loads.temporary" 449 ); 450 Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads"); 451 452 await SpecialPowers.popPrefEnv(); 453 } 454 455 add_task(async function test_block_workflow() { 456 await runWorkflow({ 457 click_block: true, 458 report: true, 459 report_email: true, 460 enable_telemetry: true, 461 }); 462 }); 463 464 add_task(async function test_allow_workflow() { 465 await runWorkflow({ 466 click_block: false, 467 report: true, 468 report_email: true, 469 }); 470 }); 471 472 add_task(async function test_allow_with_no_report_workflow() { 473 await runWorkflow({ 474 click_block: false, 475 report: false, 476 report_email: false, 477 }); 478 }); 479 480 add_task(async function test_block_with_no_report_email_workflow() { 481 await runWorkflow({ 482 click_block: true, 483 report: true, 484 report_email: false, 485 enable_telemetry: true, 486 }); 487 }); 488 489 add_task(async function test_block_workflow_no_telemetry() { 490 await runWorkflow({ 491 click_block: true, 492 report: true, 493 report_email: true, 494 enable_telemetry: false, 495 }); 496 });