tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 });