browser_tab_switch_warning.js (16990B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * Tests the warning that is displayed when switching to background 8 * tabs when sharing the browser window or screen 9 */ 10 11 // The number of tabs to have in the background for testing. 12 const NEW_BACKGROUND_TABS_TO_OPEN = 5; 13 const WARNING_PANEL_ID = "sharing-tabs-warning-panel"; 14 const ALLOW_BUTTON_ID = "sharing-warning-proceed-to-tab"; 15 const DISABLE_WARNING_FOR_SESSION_CHECKBOX_ID = 16 "sharing-warning-disable-for-session"; 17 const WINDOW_SHARING_HEADER_ID = "sharing-warning-window-panel-header"; 18 const SCREEN_SHARING_HEADER_ID = "sharing-warning-screen-panel-header"; 19 // The number of milliseconds we're willing to wait for the 20 // warning panel before we decide that it's not coming. 21 const WARNING_PANEL_TIMEOUT_MS = 1000; 22 const CTRL_TAB_RUO_PREF = "browser.ctrlTab.sortByRecentlyUsed"; 23 24 /** 25 * Common helper function that pretendToShareWindow and pretendToShareScreen 26 * call into. Ensures that the first tab is selected, and then (optionally) 27 * does the first "freebie" tab switch to the second tab. 28 * 29 * @param {boolean} doFirstTabSwitch - True if this function should take 30 * care of doing the "freebie" tab switch for you. 31 * @returns {Promise<void>} 32 * Resolves once the simulation is set up. 33 */ 34 async function pretendToShareDisplay(doFirstTabSwitch) { 35 Assert.equal( 36 gBrowser.selectedTab, 37 gBrowser.tabs[0], 38 "Should start on the first tab." 39 ); 40 41 webrtcUI.sharingDisplay = true; 42 if (doFirstTabSwitch) { 43 await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]); 44 } 45 } 46 47 /** 48 * Simulates the sharing of a particular browser window. The 49 * simulation doesn't actually share the window over WebRTC, but 50 * does enough to convince webrtcUI that the window is in the shared 51 * window list. 52 * 53 * It is assumed that the first tab is the selected tab when calling 54 * this function. 55 * 56 * This helper function can also automatically perform the first 57 * "freebie" tab switch that never warns. This is its default behaviour. 58 * 59 * @param {DOM Window} aWindow - The window that we're simulating sharing. 60 * @param {boolean} doFirstTabSwitch - True if this function should take 61 * care of doing the "freebie" tab switch for you. Defaults to true. 62 * @returns {Promise<void>} 63 * Resolves once the simulation is set up. 64 */ 65 async function pretendToShareWindow(aWindow, doFirstTabSwitch = true) { 66 // Poke into webrtcUI so that it thinks that the current browser 67 // window is being shared. 68 webrtcUI.sharedBrowserWindows.add(aWindow); 69 await pretendToShareDisplay(doFirstTabSwitch); 70 } 71 72 /** 73 * Simulates the sharing of the screen. The simulation doesn't actually share 74 * the screen over WebRTC, but does enough to convince webrtcUI that the screen 75 * is being shared. 76 * 77 * It is assumed that the first tab is the selected tab when calling 78 * this function. 79 * 80 * This helper function can also automatically perform the first 81 * "freebie" tab switch that never warns. This is its default behaviour. 82 * 83 * @param {boolean} doFirstTabSwitch - True if this function should take 84 * care of doing the "freebie" tab switch for you. Defaults to true. 85 * @returns {Promise<void>} 86 * Resolves once the simulation is set up. 87 */ 88 async function pretendToShareScreen(doFirstTabSwitch = true) { 89 // Poke into webrtcUI so that it thinks that the current screen is being 90 // shared. 91 webrtcUI.sharingScreen = true; 92 await pretendToShareDisplay(doFirstTabSwitch); 93 } 94 95 /** 96 * Resets webrtcUI's notion of what is being shared. This also clears 97 * out any simulated shared windows, and resets any state that only 98 * persists for a sharing session. 99 * 100 * This helper function will also: 101 * 1. Switch back to the first tab if it's not already selected. 102 * 2. Check if the tab switch warning panel is open, and if so, close it. 103 * 104 * @returns {Promise<void>} 105 * Resolves once the state is reset. 106 */ 107 async function resetDisplaySharingState() { 108 let firstTabBC = gBrowser.browsers[0].browsingContext; 109 webrtcUI.streamAddedOrRemoved(firstTabBC, { remove: true }); 110 111 if (gBrowser.selectedTab !== gBrowser.tabs[0]) { 112 await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]); 113 } 114 115 let panel = document.getElementById(WARNING_PANEL_ID); 116 if (panel && (panel.state == "open" || panel.state == "showing")) { 117 info("Closing the warning panel."); 118 let panelHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden"); 119 panel.hidePopup(); 120 await panelHidden; 121 } 122 } 123 124 /** 125 * Checks to make sure that a tab switch warning doesn't show 126 * within WARNING_PANEL_TIMEOUT_MS milliseconds. 127 * 128 * @returns {Promise<void>} 129 * Resolves once the check is complete. 130 */ 131 async function ensureNoWarning() { 132 let timerExpired = false; 133 let sawWarning = false; 134 135 let resolver; 136 let timeoutOrPopupShowingPromise = new Promise(resolve => { 137 resolver = resolve; 138 }); 139 140 let onPopupShowing = event => { 141 if (event.target.id == WARNING_PANEL_ID) { 142 sawWarning = true; 143 resolver(); 144 } 145 }; 146 // The panel might not have been lazily-inserted yet, so we 147 // attach the popupshowing handler to the window instead. 148 window.addEventListener("popupshowing", onPopupShowing); 149 150 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 151 let timer = setTimeout(() => { 152 timerExpired = true; 153 resolver(); 154 }, WARNING_PANEL_TIMEOUT_MS); 155 156 await timeoutOrPopupShowingPromise; 157 158 clearTimeout(timer); 159 window.removeEventListener("popupshowing", onPopupShowing); 160 161 Assert.ok(timerExpired, "Timer should have expired."); 162 Assert.ok(!sawWarning, "Should not have shown the tab switch warning."); 163 } 164 165 /** 166 * Checks to make sure that a tab switch warning appears for 167 * a particular tab. 168 * 169 * @param {<xul:tab>} tab - The tab that the warning should be anchored to. 170 * @returns {Promise<void>} 171 * Resolves once the check is complete. 172 */ 173 async function ensureWarning(tab) { 174 let popupShowingEvent = await BrowserTestUtils.waitForEvent( 175 window, 176 "popupshowing", 177 false, 178 event => { 179 return event.target.id == WARNING_PANEL_ID; 180 } 181 ); 182 let panel = popupShowingEvent.target; 183 184 Assert.equal( 185 panel.anchorNode, 186 tab, 187 "Expected the warning to be anchored to the right tab." 188 ); 189 } 190 191 add_setup(async function () { 192 await SpecialPowers.pushPrefEnv({ 193 set: [ 194 ["test.wait300msAfterTabSwitch", true], 195 ["privacy.webrtc.sharedTabWarning", true], 196 ], 197 }); 198 199 // Loads up NEW_BACKGROUND_TABS_TO_OPEN background tabs at about:blank, 200 // and waits until they're fully open. 201 let uris = new Array(NEW_BACKGROUND_TABS_TO_OPEN).fill("about:blank"); 202 203 let loadPromises = Promise.all( 204 uris.map(uri => BrowserTestUtils.waitForNewTab(gBrowser, uri, false, true)) 205 ); 206 207 gBrowser.loadTabs(uris, { 208 inBackground: true, 209 triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), 210 }); 211 212 await loadPromises; 213 214 // Switches to the first tab and closes all of the rest. 215 registerCleanupFunction(async () => { 216 await resetDisplaySharingState(); 217 gBrowser.removeAllTabsBut(gBrowser.tabs[0]); 218 }); 219 }); 220 221 /** 222 * Tests that when sharing the window that the first tab switch does _not_ show 223 * the warning. This is because we presume that the first tab switch since 224 * starting display sharing is for a tab that is intentionally being shared. 225 */ 226 add_task(async function testFirstTabSwitchAllowed() { 227 pretendToShareWindow(window, false); 228 229 let targetTab = gBrowser.tabs[1]; 230 231 let noWarningPromise = ensureNoWarning(); 232 await BrowserTestUtils.switchTab(gBrowser, targetTab); 233 await noWarningPromise; 234 235 await resetDisplaySharingState(); 236 }); 237 238 /** 239 * Tests that the second tab switch after sharing is not allowed 240 * without a warning. Also tests that the warning can "allow" 241 * the tab switch to proceed, and that no warning is subsequently 242 * shown for the "allowed" tab. Finally, ensures that if the sharing 243 * session ends and a new session begins, that warnings are shown 244 * again for the allowed tabs. 245 */ 246 add_task(async function testWarningOnSecondTabSwitch() { 247 pretendToShareWindow(window); 248 let originalTab = gBrowser.selectedTab; 249 250 // pretendToShareWindow will have switched us to the second 251 // tab automatically as the first "freebie" tab switch 252 253 let targetTab = gBrowser.tabs[2]; 254 255 // Ensure that we show the warning on the second tab switch 256 let warningPromise = ensureWarning(targetTab); 257 await BrowserTestUtils.switchTab(gBrowser, targetTab); 258 await warningPromise; 259 260 // Not only should we have warned, but we should have prevented 261 // the tab switch from occurring. 262 Assert.equal( 263 gBrowser.selectedTab, 264 originalTab, 265 "Should still be on the original tab." 266 ); 267 268 // Now test the "Allow" button in the warning to make sure the tab 269 // switch goes through. 270 let tabSwitchPromise = BrowserTestUtils.waitForEvent( 271 gBrowser, 272 "TabSwitchDone" 273 ); 274 let allowButton = document.getElementById(ALLOW_BUTTON_ID); 275 allowButton.click(); 276 await tabSwitchPromise; 277 278 Assert.equal( 279 gBrowser.selectedTab, 280 targetTab, 281 "Should have switched tabs to the target." 282 ); 283 284 // We shouldn't see a warning when switching back to that first 285 // "freebie" tab. 286 let noWarningPromise = ensureNoWarning(); 287 await BrowserTestUtils.switchTab(gBrowser, originalTab); 288 await noWarningPromise; 289 290 Assert.equal( 291 gBrowser.selectedTab, 292 originalTab, 293 "Should have switched tabs back to the original tab." 294 ); 295 296 // We shouldn't see a warning when switching back to the tab that 297 // we had just allowed. 298 noWarningPromise = ensureNoWarning(); 299 await BrowserTestUtils.switchTab(gBrowser, targetTab); 300 await noWarningPromise; 301 302 Assert.equal( 303 gBrowser.selectedTab, 304 targetTab, 305 "Should have switched tabs back to the target tab." 306 ); 307 308 // Reset the sharing state, and make sure that warnings can 309 // be displayed again. 310 await resetDisplaySharingState(); 311 pretendToShareWindow(window); 312 313 // pretendToShareWindow will have switched us to the second 314 // tab automatically as the first "freebie" tab switch 315 // 316 // Make sure we get the warning again when switching to the 317 // target tab. 318 warningPromise = ensureWarning(targetTab); 319 await BrowserTestUtils.switchTab(gBrowser, targetTab); 320 await warningPromise; 321 322 await resetDisplaySharingState(); 323 }); 324 325 /** 326 * Tests that warnings can be skipped for a session via the 327 * checkbox in the warning panel. Also checks that once the 328 * session ends and a new one begins that warnings are displayed 329 * again. 330 */ 331 add_task(async function testDisableWarningForSession() { 332 pretendToShareWindow(window); 333 334 // pretendToShareWindow will have switched us to the second 335 // tab automatically as the first "freebie" tab switch 336 let targetTab = gBrowser.tabs[2]; 337 338 // Ensure that we show the warning on the second tab switch 339 let warningPromise = ensureWarning(targetTab); 340 await BrowserTestUtils.switchTab(gBrowser, targetTab); 341 await warningPromise; 342 343 // Check the checkbox to suppress warnings for the rest of this session. 344 let checkbox = document.getElementById( 345 DISABLE_WARNING_FOR_SESSION_CHECKBOX_ID 346 ); 347 checkbox.checked = true; 348 349 // Now test the "Allow" button in the warning to make sure the tab 350 // switch goes through. 351 let tabSwitchPromise = BrowserTestUtils.waitForEvent( 352 gBrowser, 353 "TabSwitchDone" 354 ); 355 let allowButton = document.getElementById(ALLOW_BUTTON_ID); 356 allowButton.click(); 357 await tabSwitchPromise; 358 359 Assert.equal( 360 gBrowser.selectedTab, 361 targetTab, 362 "Should have switched tabs to the target tab." 363 ); 364 365 // Switching to the 4th and 5th tabs should now not show warnings. 366 let noWarningPromise = ensureNoWarning(); 367 await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[3]); 368 await noWarningPromise; 369 370 noWarningPromise = ensureNoWarning(); 371 await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[4]); 372 await noWarningPromise; 373 374 // Reset the sharing state, and make sure that warnings can 375 // be displayed again. 376 await resetDisplaySharingState(); 377 pretendToShareWindow(window); 378 379 // pretendToShareWindow will have switched us to the second 380 // tab automatically as the first "freebie" tab switch 381 // 382 // Make sure we get the warning again when switching to the 383 // target tab. 384 warningPromise = ensureWarning(targetTab); 385 await BrowserTestUtils.switchTab(gBrowser, targetTab); 386 await warningPromise; 387 388 await resetDisplaySharingState(); 389 }); 390 391 /** 392 * Tests that we don't show a warning when sharing a different 393 * window than the one we're switching tabs in. 394 */ 395 add_task(async function testOtherWindow() { 396 let otherWin = await BrowserTestUtils.openNewBrowserWindow(); 397 await SimpleTest.promiseFocus(window); 398 pretendToShareWindow(otherWin); 399 400 // Switching to the 4th and 5th tabs should now not show warnings. 401 let noWarningPromise = ensureNoWarning(); 402 await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[3]); 403 await noWarningPromise; 404 405 noWarningPromise = ensureNoWarning(); 406 await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[4]); 407 await noWarningPromise; 408 409 await BrowserTestUtils.closeWindow(otherWin); 410 411 await resetDisplaySharingState(); 412 }); 413 414 /** 415 * Tests that we show a different label when sharing the screen 416 * vs when sharing a window. 417 */ 418 add_task(async function testWindowVsScreenLabel() { 419 pretendToShareWindow(window); 420 421 // pretendToShareWindow will have switched us to the second 422 // tab automatically as the first "freebie" tab switch. 423 // Let's now switch to the third tab. 424 let targetTab = gBrowser.tabs[2]; 425 426 // Ensure that we show the warning on this second tab switch 427 let warningPromise = ensureWarning(targetTab); 428 await BrowserTestUtils.switchTab(gBrowser, targetTab); 429 await warningPromise; 430 431 let windowHeader = document.getElementById(WINDOW_SHARING_HEADER_ID); 432 let screenHeader = document.getElementById(SCREEN_SHARING_HEADER_ID); 433 Assert.ok( 434 !BrowserTestUtils.isHidden(windowHeader), 435 "Should be showing window sharing header" 436 ); 437 Assert.ok( 438 BrowserTestUtils.isHidden(screenHeader), 439 "Should not be showing screen sharing header" 440 ); 441 442 // Reset the sharing state, and then pretend to share the screen. 443 await resetDisplaySharingState(); 444 pretendToShareScreen(); 445 446 // Ensure that we show the warning on this second tab switch 447 warningPromise = ensureWarning(targetTab); 448 await BrowserTestUtils.switchTab(gBrowser, targetTab); 449 await warningPromise; 450 451 Assert.ok( 452 BrowserTestUtils.isHidden(windowHeader), 453 "Should not be showing window sharing header" 454 ); 455 Assert.ok( 456 !BrowserTestUtils.isHidden(screenHeader), 457 "Should be showing screen sharing header" 458 ); 459 await resetDisplaySharingState(); 460 }); 461 462 /** 463 * Tests that tab switching via the keyboard can also trigger the 464 * tab switch warnings. 465 */ 466 add_task(async function testKeyboardTabSwitching() { 467 let pressCtrlTab = async (expectPanel = false) => { 468 let promise; 469 if (expectPanel) { 470 promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popupshown"); 471 } else { 472 promise = BrowserTestUtils.waitForEvent(document, "keyup"); 473 } 474 EventUtils.synthesizeKey("VK_TAB", { 475 ctrlKey: true, 476 shiftKey: false, 477 }); 478 await promise; 479 }; 480 481 let releaseCtrl = async () => { 482 let promise; 483 if (ctrlTab.isOpen) { 484 promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popuphidden"); 485 } else { 486 promise = BrowserTestUtils.waitForEvent(document, "keyup"); 487 } 488 EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" }); 489 return promise; 490 }; 491 492 // Ensure that the (on by default) ctrl-tab switch panel is enabled. 493 await SpecialPowers.pushPrefEnv({ 494 set: [[CTRL_TAB_RUO_PREF, true]], 495 }); 496 497 pretendToShareWindow(window); 498 let originalTab = gBrowser.selectedTab; 499 await pressCtrlTab(true); 500 501 // The Ctrl-Tab MRU list should be: 502 // 0: Second tab (currently selected) 503 // 1: First tab 504 // 2: Last tab 505 // 506 // Having pressed Ctrl-Tab once, 1 (First tab) is selected in the 507 // panel. We want a tab that will warn, so let's hit Ctrl-Tab again 508 // to choose 2 (Last tab). 509 let targetTab = ctrlTab.tabList[2]; 510 await pressCtrlTab(); 511 512 let warningPromise = ensureWarning(targetTab); 513 await releaseCtrl(); 514 await warningPromise; 515 516 // Hide the warning without allowing the tab switch. 517 let panel = document.getElementById(WARNING_PANEL_ID); 518 panel.hidePopup(); 519 520 Assert.equal( 521 gBrowser.selectedTab, 522 originalTab, 523 "Should not have changed from the original tab." 524 ); 525 526 // Now switch to the in-order tab switching keyboard shortcut mode. 527 await SpecialPowers.popPrefEnv(); 528 await SpecialPowers.pushPrefEnv({ 529 set: [[CTRL_TAB_RUO_PREF, false]], 530 }); 531 532 // Hitting Ctrl-Tab should choose the _next_ tab over from 533 // the originalTab, which should be the third tab. 534 targetTab = gBrowser.tabs[2]; 535 536 warningPromise = ensureWarning(targetTab); 537 await pressCtrlTab(); 538 await warningPromise; 539 540 await resetDisplaySharingState(); 541 });