browser_test_local_network_access.js (22905B)
1 "use strict"; 2 3 const PROMPT_ALLOW_BUTTON = -1; 4 const PROMPT_NOT_NOW_BUTTON = 0; 5 6 const { HttpServer } = ChromeUtils.importESModule( 7 "resource://testing-common/httpd.sys.mjs" 8 ); 9 10 const baseURL = getRootDirectory(gTestPath).replace( 11 "chrome://mochitests/content", 12 "https://example.com" 13 ); 14 15 async function restorePermissions() { 16 info("Restoring permissions"); 17 Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk"); 18 Services.perms.removeAll(); 19 } 20 21 add_setup(async function () { 22 await SpecialPowers.pushPrefEnv({ 23 set: [ 24 ["permissions.manager.defaultsUrl", ""], 25 ["network.websocket.delay-failed-reconnects", false], 26 ["network.websocket.max-connections", 1000], 27 ["network.lna.block_trackers", true], 28 ["network.lna.blocking", true], 29 ["network.http.rcwn.enabled", false], 30 ["network.lna.websocket.enabled", true], 31 ["network.lna.local-network-to-localhost.skip-checks", false], 32 ], 33 }); 34 Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk"); 35 36 const server = new HttpServer(); 37 server.start(21555); 38 registerServerHandlers(server); 39 40 registerCleanupFunction(async () => { 41 await restorePermissions(); 42 await new Promise(resolve => { 43 server.stop(resolve); 44 }); 45 }); 46 }); 47 48 requestLongerTimeout(10); 49 50 function clickDoorhangerButton(buttonIndex, browser, notificationID) { 51 let popup = PopupNotifications.getNotification(notificationID, browser); 52 let notification = popup?.owner?.panel?.childNodes?.[0]; 53 ok(notification, "Notification popup is available"); 54 55 if (buttonIndex === PROMPT_ALLOW_BUTTON) { 56 ok(true, "Triggering main action (allow)"); 57 notification.button.doCommand(); 58 } else { 59 ok(true, "Triggering secondary action (deny)"); 60 notification.secondaryButton.doCommand(); 61 } 62 } 63 64 function observeAndCheck(testType, rand, expectedStatus, message) { 65 return new Promise(resolve => { 66 const url = `http://localhost:21555/?type=${testType}&rand=${rand}`; 67 const observer = { 68 observe(subject, topic) { 69 if (topic !== "http-on-stop-request") { 70 return; 71 } 72 73 let channel = subject.QueryInterface(Ci.nsIHttpChannel); 74 if (!channel || channel.URI.spec !== url) { 75 return; 76 } 77 78 is(channel.status, expectedStatus, message); 79 Services.obs.removeObserver(observer, "http-on-stop-request"); 80 resolve(); 81 }, 82 }; 83 Services.obs.addObserver(observer, "http-on-stop-request"); 84 }); 85 } 86 87 const testCases = [ 88 { 89 type: "fetch", 90 allowStatus: Cr.NS_OK, 91 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 92 }, 93 { 94 type: "xhr", 95 allowStatus: Cr.NS_OK, 96 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 97 }, 98 { 99 type: "img", 100 allowStatus: Cr.NS_OK, 101 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 102 }, 103 { 104 type: "video", 105 allowStatus: Cr.NS_OK, 106 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 107 }, 108 { 109 type: "audio", 110 allowStatus: Cr.NS_OK, 111 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 112 }, 113 { 114 type: "iframe", 115 allowStatus: Cr.NS_OK, 116 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 117 }, 118 { 119 type: "script", 120 allowStatus: Cr.NS_OK, 121 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 122 }, 123 { 124 type: "font", 125 allowStatus: Cr.NS_OK, 126 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 127 }, 128 { 129 type: "websocket", 130 allowStatus: Cr.NS_ERROR_WEBSOCKET_CONNECTION_REFUSED, 131 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 132 }, 133 ]; 134 135 function registerServerHandlers(server) { 136 server.registerPathHandler("/", (request, response) => { 137 const params = new URLSearchParams(request.queryString); 138 const type = params.get("type"); 139 140 response.setHeader("Access-Control-Allow-Origin", "*", false); 141 142 switch (type) { 143 case "img": 144 response.setHeader("Content-Type", "image/gif", false); 145 response.setStatusLine(request.httpVersion, 200, "OK"); 146 response.write( 147 atob("R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==") 148 ); 149 break; 150 case "audio": 151 response.setHeader("Content-Type", "audio/wav", false); 152 response.setStatusLine(request.httpVersion, 200, "OK"); 153 response.write( 154 atob("UklGRhYAAABXQVZFZm10IBAAAAABAAEAIlYAAESsAAACABAAZGF0YQAAAAA=") 155 ); 156 break; 157 case "video": 158 response.setHeader("Content-Type", "video/mp4", false); 159 response.setStatusLine(request.httpVersion, 200, "OK"); 160 response.write( 161 atob( 162 "GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA=" 163 ) 164 ); 165 break; 166 default: 167 response.setHeader("Content-Type", "text/plain", false); 168 response.setStatusLine(request.httpVersion, 200, "OK"); 169 response.write("hello"); 170 } 171 }); 172 } 173 174 async function runSingleTestCase( 175 test, 176 rand, 177 expectedStatus, 178 description, 179 userAction = null, 180 notificationID = null 181 ) { 182 info(description); 183 184 const promise = observeAndCheck(test.type, rand, expectedStatus, description); 185 const tab = await BrowserTestUtils.openNewForegroundTab( 186 gBrowser, 187 `${baseURL}page_with_non_trackers.html?test=${test.type}&rand=${rand}` 188 ); 189 190 if (userAction && notificationID) { 191 const buttonNum = 192 userAction === "allow" ? PROMPT_ALLOW_BUTTON : PROMPT_NOT_NOW_BUTTON; 193 194 await BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); 195 clickDoorhangerButton(buttonNum, gBrowser.selectedBrowser, notificationID); 196 } 197 198 await promise; 199 gBrowser.removeTab(tab); 200 } 201 202 async function runPromptedLnaTest(test, overrideLabel, notificationID) { 203 const promptActions = ["allow", "deny"]; 204 for (const userAction of promptActions) { 205 const rand = Math.random(); 206 const expectedStatus = 207 userAction === "allow" ? test.allowStatus : test.denyStatus; 208 209 await runSingleTestCase( 210 test, 211 rand, 212 expectedStatus, 213 `LNA test (${overrideLabel}) for ${test.type} with user action: ${userAction}`, 214 userAction, 215 notificationID 216 ); 217 218 // Wait some time for cache entry to be updated 219 // XXX(valentin) though this should not be necessary. 220 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 221 await new Promise(resolve => setTimeout(resolve, 300)); 222 223 // Now run the test again with cached main document 224 await runSingleTestCase( 225 test, 226 rand, 227 expectedStatus, 228 `LNA test (${overrideLabel}) for ${test.type} with user action: ${userAction}`, 229 userAction, 230 notificationID 231 ); 232 } 233 } 234 235 add_task(async function test_lna_prompt_behavior() { 236 // Non-LNA test: no prompt expected 237 for (const test of testCases) { 238 const rand = Math.random(); 239 await runSingleTestCase( 240 test, 241 rand, 242 test.allowStatus, 243 `Non-LNA test for ${test.type}` 244 ); 245 } 246 247 // Public -> Local test (localhost permission) 248 Services.prefs.setCharPref( 249 "network.lna.address_space.public.override", 250 "127.0.0.1:4443" 251 ); 252 for (const test of testCases) { 253 await runPromptedLnaTest(test, "public", "localhost"); 254 } 255 256 // Public -> Private (local-network permission) 257 Services.prefs.setCharPref( 258 "network.lna.address_space.private.override", 259 "127.0.0.1:21555" 260 ); 261 for (const test of testCases) { 262 await runPromptedLnaTest(test, "private", "local-network"); 263 } 264 265 Services.prefs.clearUserPref("network.lna.address_space.public.override"); 266 Services.prefs.clearUserPref("network.lna.address_space.private.override"); 267 }); 268 269 add_task(async function test_lna_cancellation_during_prompt() { 270 info("Testing LNA cancellation during permission prompt"); 271 272 // Disable RCWN but enable caching for this test 273 await SpecialPowers.pushPrefEnv({ 274 set: [ 275 ["network.http.rcwn.enabled", false], 276 ["browser.cache.disk.enable", true], 277 ["browser.cache.memory.enable", true], 278 ["network.lna.address_space.public.override", "127.0.0.1:4443"], 279 ], 280 }); 281 282 const testType = "fetch"; 283 const rand1 = Math.random(); 284 285 // Test 1: Cancel request during LNA prompt and verify proper cleanup 286 info( 287 "Step 1: Making request that will trigger LNA prompt, then cancelling it" 288 ); 289 290 // Open tab and wait for LNA prompt 291 const tab1 = await BrowserTestUtils.openNewForegroundTab( 292 gBrowser, 293 `${baseURL}page_with_non_trackers.html?test=${testType}&rand=${rand1}` 294 ); 295 296 // Wait for the LNA permission prompt to appear 297 await BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); 298 info("LNA permission prompt appeared"); 299 gBrowser.removeTab(tab1); 300 // Navigate to a new URL (which should cancel the pending request) 301 const tab2 = await BrowserTestUtils.openNewForegroundTab( 302 gBrowser, 303 `${baseURL}page_with_non_trackers.html?test=${testType}&rand=${rand1}` 304 ); 305 info("Navigated to new URL, request should be cancelled"); 306 307 // Wait for the navigation to complete 308 await BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); 309 clickDoorhangerButton( 310 PROMPT_ALLOW_BUTTON, 311 gBrowser.selectedBrowser, 312 "localhost" 313 ); 314 315 // Close the first tab now that we're done with it 316 gBrowser.removeTab(tab2); 317 318 // The main test objective is complete - we verified that cancellation 319 // during LNA prompt works without hanging channels. The navigation 320 // completed successfully, which means our fix is working correctly. 321 info( 322 "Test completed successfully - cancellation during LNA prompt handled correctly" 323 ); 324 325 await SpecialPowers.popPrefEnv(); 326 }); 327 328 add_task(async function test_lna_top_level_navigation_bypass() { 329 info("Testing that top-level navigation to localhost bypasses LNA checks"); 330 331 // Set up LNA to trigger for localhost connections and enable top-level navigation bypass 332 await SpecialPowers.pushPrefEnv({ 333 set: [ 334 ["network.lna.address_space.public.override", "127.0.0.1:4443"], 335 ["network.lna.allow_top_level_navigation", true], 336 ], 337 }); 338 339 requestLongerTimeout(1); 340 341 // Observer to verify that the navigation request succeeds without LNA error 342 const navigationObserver = { 343 observe(subject, topic) { 344 if (topic !== "http-on-stop-request") { 345 return; 346 } 347 348 let channel = subject.QueryInterface(Ci.nsIHttpChannel); 349 if (!channel || !channel.URI.spec.includes("localhost:21555")) { 350 return; 351 } 352 353 // For top-level navigation, we expect success (not LNA denied) 354 // The channel status should be NS_OK, not NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED 355 is( 356 channel.status, 357 Cr.NS_OK, 358 "Top-level navigation to localhost should not be blocked by LNA" 359 ); 360 361 Services.obs.removeObserver(navigationObserver, "http-on-stop-request"); 362 }, 363 }; 364 365 Services.obs.addObserver(navigationObserver, "http-on-stop-request"); 366 367 try { 368 // Load the test page which will automatically navigate to localhost 369 info("Loading test page that will trigger navigation to localhost"); 370 371 // Open the initial page - it will automatically navigate to localhost 372 const tab = await BrowserTestUtils.openNewForegroundTab( 373 gBrowser, 374 `${baseURL}page_with_non_trackers.html?isTopLevelNavigation=true` 375 ); 376 377 // Wait for the navigation to complete 378 info("Waiting for navigation to localhost to complete"); 379 await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url => 380 url.includes("localhost:21555") 381 ); 382 383 // Verify that no LNA permission prompt appeared 384 // If our fix works correctly, there should be no popup notification 385 let popup = PopupNotifications.getNotification( 386 "localhost", 387 tab.linkedBrowser 388 ); 389 ok( 390 !popup, 391 "No LNA permission prompt should appear for top-level navigation" 392 ); 393 394 // Verify the page loaded successfully 395 let location = await SpecialPowers.spawn(tab.linkedBrowser, [], () => { 396 return content.location.href; 397 }); 398 399 ok( 400 location.includes("localhost:21555"), 401 "Top-level navigation to localhost should succeed" 402 ); 403 404 gBrowser.removeTab(tab); 405 406 info("Top-level navigation test completed successfully"); 407 } catch (error) { 408 ok(false, `Top-level navigation test failed: ${error.message}`); 409 } 410 411 await SpecialPowers.popPrefEnv(); 412 }); 413 414 add_task(async function test_lna_top_level_navigation_disabled() { 415 info("Testing that top-level navigation LNA bypass can be disabled via pref"); 416 417 // Set up LNA to trigger for localhost connections but disable top-level navigation bypass 418 await SpecialPowers.pushPrefEnv({ 419 set: [ 420 ["network.lna.address_space.public.override", "127.0.0.1:4443"], 421 ["network.lna.allow_top_level_navigation", false], 422 ], 423 }); 424 425 requestLongerTimeout(1); 426 427 try { 428 // Load the test page which will attempt to navigate to localhost 429 info("Loading test page that will try to navigate to localhost"); 430 const tab = await BrowserTestUtils.openNewForegroundTab( 431 gBrowser, 432 `${baseURL}page_with_non_trackers.html?isTopLevelNavigation=true` 433 ); 434 435 // Wait for LNA permission prompt to appear (since bypass is disabled) 436 info("Waiting for LNA permission prompt to appear"); 437 await BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); 438 439 // Verify that LNA permission prompt did appear 440 let popup = PopupNotifications.getNotification( 441 "localhost", 442 tab.linkedBrowser 443 ); 444 ok(popup, "LNA permission prompt should appear when bypass is disabled"); 445 446 // Allow the permission to complete the navigation 447 clickDoorhangerButton( 448 PROMPT_ALLOW_BUTTON, 449 gBrowser.selectedBrowser, 450 "localhost" 451 ); 452 453 // Wait for navigation to complete after permission granted 454 await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url => 455 url.includes("localhost:21555") 456 ); 457 458 gBrowser.removeTab(tab); 459 460 info("Top-level navigation disabled test completed successfully"); 461 } catch (error) { 462 ok(false, `Top-level navigation disabled test failed: ${error.message}`); 463 } 464 465 await SpecialPowers.popPrefEnv(); 466 }); 467 468 add_task(async function test_lna_websocket_preference() { 469 info("Testing network.lna.websocket.enabled preference"); 470 471 // Set up LNA to trigger for localhost connections 472 await SpecialPowers.pushPrefEnv({ 473 set: [ 474 ["network.lna.address_space.public.override", "127.0.0.1:4443"], 475 ["network.lna.blocking", true], 476 ["network.lna.websocket.enabled", false], // Disable WebSocket LNA checks 477 ], 478 }); 479 480 try { 481 // Test WebSocket with LNA disabled - should bypass LNA and get connection refused 482 const websocketTest = { 483 type: "websocket", 484 allowStatus: Cr.NS_ERROR_WEBSOCKET_CONNECTION_REFUSED, 485 denyStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 486 }; 487 488 const rand = Math.random(); 489 const promise = observeAndCheck( 490 websocketTest.type, 491 rand, 492 websocketTest.allowStatus, // Should get connection refused, not LNA denied 493 "WebSocket test with LNA disabled should bypass LNA checks" 494 ); 495 496 const tab = await BrowserTestUtils.openNewForegroundTab( 497 gBrowser, 498 `${baseURL}page_with_non_trackers.html?test=${websocketTest.type}&rand=${rand}` 499 ); 500 501 await promise; 502 gBrowser.removeTab(tab); 503 504 info( 505 "WebSocket LNA disabled test completed - connection was allowed to proceed" 506 ); 507 508 // Now test with WebSocket LNA enabled - should trigger LNA denial 509 await SpecialPowers.pushPrefEnv({ 510 set: [ 511 ["network.lna.websocket.enabled", true], // Enable WebSocket LNA checks 512 ["network.localhost.prompt.testing", true], 513 ["network.localhost.prompt.testing.allow", false], 514 ], 515 }); 516 517 const rand2 = Math.random(); 518 const promise2 = observeAndCheck( 519 websocketTest.type, 520 rand2, 521 websocketTest.denyStatus, // Should get LNA denied 522 "WebSocket test with LNA enabled should trigger LNA checks" 523 ); 524 525 const tab2 = await BrowserTestUtils.openNewForegroundTab( 526 gBrowser, 527 `${baseURL}page_with_non_trackers.html?test=${websocketTest.type}&rand=${rand2}` 528 ); 529 530 await promise2; 531 gBrowser.removeTab(tab2); 532 533 info("WebSocket LNA enabled test completed - LNA checks were applied"); 534 } catch (error) { 535 ok(false, `WebSocket LNA preference test failed: ${error.message}`); 536 } 537 538 await SpecialPowers.popPrefEnv(); 539 }); 540 541 add_task(async function test_lna_prompt_timeout() { 542 info("Testing LNA permission prompt timeout"); 543 544 // Set up a short timeout for testing (1 second instead of 5 minutes) 545 await SpecialPowers.pushPrefEnv({ 546 set: [ 547 ["network.lna.address_space.public.override", "127.0.0.1:4443"], 548 ["network.lna.prompt.timeout", 1000], // 1 second timeout for testing 549 ], 550 }); 551 552 try { 553 const testType = "fetch"; 554 const rand = Math.random(); 555 556 info("Triggering LNA prompt that will timeout"); 557 558 // Set up observer to verify request fails with LNA denied status 559 const promise = observeAndCheck( 560 testType, 561 rand, 562 Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 563 "LNA request should fail after prompt timeout" 564 ); 565 566 // Open tab that will trigger LNA prompt 567 const tab = await BrowserTestUtils.openNewForegroundTab( 568 gBrowser, 569 `${baseURL}page_with_non_trackers.html?test=${testType}&rand=${rand}` 570 ); 571 572 // Wait for LNA permission prompt to appear 573 await BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); 574 info("LNA permission prompt appeared"); 575 576 // Verify prompt is visible 577 let popup = PopupNotifications.getNotification( 578 "localhost", 579 tab.linkedBrowser 580 ); 581 ok(popup, "LNA permission prompt should be visible"); 582 583 // Do NOT click any button - let it timeout 584 info("Waiting for prompt to timeout (1 second)..."); 585 586 // Wait for timeout + a small buffer to ensure timeout has fired 587 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 588 await new Promise(resolve => setTimeout(resolve, 1500)); 589 590 // Verify prompt has been dismissed 591 popup = PopupNotifications.getNotification("localhost", tab.linkedBrowser); 592 ok(!popup, "LNA permission prompt should be dismissed after timeout"); 593 594 // Wait for the network request to complete with denial status 595 await promise; 596 597 gBrowser.removeTab(tab); 598 599 info("LNA prompt timeout test completed successfully"); 600 } catch (error) { 601 ok(false, `LNA prompt timeout test failed: ${error.message}`); 602 } 603 604 await SpecialPowers.popPrefEnv(); 605 }); 606 607 // Test that telemetry is recorded when LNA prompt is shown 608 // and not incremented for subsequent requests with cached permission 609 add_task(async function test_lna_prompt_telemetry() { 610 await restorePermissions(); 611 612 // Reset telemetry 613 Services.fog.testResetFOG(); 614 await SpecialPowers.pushPrefEnv({ 615 set: [["network.lna.address_space.public.override", "127.0.0.1:4443"]], 616 }); 617 618 const rand1 = Math.random(); 619 const tab = await BrowserTestUtils.openNewForegroundTab( 620 gBrowser, 621 `${baseURL}page_with_non_trackers.html?test=fetch&rand=${rand1}` 622 ); 623 624 // Wait for the prompt to appear 625 await BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); 626 627 // Verify telemetry was recorded 628 let metricValue = 629 await Glean.networking.localNetworkAccessPromptsShown.localhost.testGetValue(); 630 is(metricValue, 1, "Should record telemetry when localhost prompt is shown"); 631 632 // Grant permission 633 clickDoorhangerButton( 634 PROMPT_ALLOW_BUTTON, 635 gBrowser.selectedBrowser, 636 "localhost" 637 ); 638 639 // Wait for permission to be saved 640 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 641 await new Promise(resolve => setTimeout(resolve, 300)); 642 643 // Make a second request in the same tab with cached permission 644 const rand2 = Math.random(); 645 const promise = observeAndCheck( 646 "fetch", 647 rand2, 648 Cr.NS_OK, 649 "Second request should succeed without prompt" 650 ); 651 await SpecialPowers.spawn(tab.linkedBrowser, [rand2], async rand => { 652 await content.fetch(`http://localhost:21555/?type=fetch&rand=${rand}`); 653 }); 654 await promise; 655 656 // Verify telemetry was not incremented 657 metricValue = 658 await Glean.networking.localNetworkAccessPromptsShown.localhost.testGetValue(); 659 is( 660 metricValue, 661 1, 662 "Telemetry should not increment for requests with cached permission" 663 ); 664 665 gBrowser.removeTab(tab); 666 await SpecialPowers.popPrefEnv(); 667 }); 668 669 // Test that telemetry is recorded when user denies LNA prompt 670 // and not incremented for subsequent requests with temporary deny permission 671 add_task(async function test_lna_prompt_telemetry_deny() { 672 await restorePermissions(); 673 674 // Reset telemetry 675 Services.fog.testResetFOG(); 676 await SpecialPowers.pushPrefEnv({ 677 set: [["network.lna.address_space.public.override", "127.0.0.1:4443"]], 678 }); 679 680 const rand1 = Math.random(); 681 const promise1 = observeAndCheck( 682 "fetch", 683 rand1, 684 Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 685 "First request should be denied" 686 ); 687 const tab = await BrowserTestUtils.openNewForegroundTab( 688 gBrowser, 689 `${baseURL}page_with_non_trackers.html?test=fetch&rand=${rand1}` 690 ); 691 692 // Wait for the prompt to appear 693 await BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown"); 694 695 // Verify telemetry was recorded 696 let metricValue = 697 await Glean.networking.localNetworkAccessPromptsShown.localhost.testGetValue(); 698 is(metricValue, 1, "Should record telemetry when localhost prompt is shown"); 699 700 // Deny permission 701 clickDoorhangerButton( 702 PROMPT_NOT_NOW_BUTTON, 703 gBrowser.selectedBrowser, 704 "localhost" 705 ); 706 707 await promise1; 708 709 // Wait for permission to be saved 710 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 711 await new Promise(resolve => setTimeout(resolve, 300)); 712 713 // Make a second request - should be auto-denied without showing prompt 714 // because a temporary deny permission was saved 715 const rand2 = Math.random(); 716 const promise2 = observeAndCheck( 717 "fetch", 718 rand2, 719 Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, 720 "Second request should be auto-denied with temporary permission" 721 ); 722 await SpecialPowers.spawn(tab.linkedBrowser, [rand2], async rand => { 723 await content 724 .fetch(`http://localhost:21555/?type=fetch&rand=${rand}`) 725 .catch(() => {}); 726 }); 727 728 await promise2; 729 730 // Verify telemetry was not incremented (no prompt shown with temporary deny) 731 metricValue = 732 await Glean.networking.localNetworkAccessPromptsShown.localhost.testGetValue(); 733 is( 734 metricValue, 735 1, 736 "Telemetry should not increment for requests with temporary deny permission" 737 ); 738 739 gBrowser.removeTab(tab); 740 await SpecialPowers.popPrefEnv(); 741 });