/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; /** * Tests the warning that is displayed when switching to background * tabs when sharing the browser window or screen */ // The number of tabs to have in the background for testing. const NEW_BACKGROUND_TABS_TO_OPEN = 5; const WARNING_PANEL_ID = "sharing-tabs-warning-panel"; const ALLOW_BUTTON_ID = "sharing-warning-proceed-to-tab"; const DISABLE_WARNING_FOR_SESSION_CHECKBOX_ID = "sharing-warning-disable-for-session"; const WINDOW_SHARING_HEADER_ID = "sharing-warning-window-panel-header"; const SCREEN_SHARING_HEADER_ID = "sharing-warning-screen-panel-header"; // The number of milliseconds we're willing to wait for the // warning panel before we decide that it's not coming. const WARNING_PANEL_TIMEOUT_MS = 1000; const CTRL_TAB_RUO_PREF = "browser.ctrlTab.sortByRecentlyUsed"; /** * Common helper function that pretendToShareWindow and pretendToShareScreen * call into. Ensures that the first tab is selected, and then (optionally) * does the first "freebie" tab switch to the second tab. * * @param {boolean} doFirstTabSwitch - True if this function should take * care of doing the "freebie" tab switch for you. * @return {Promise} * @resolves {undefined} - Once the simulation is set up. */ async function pretendToShareDisplay(doFirstTabSwitch) { Assert.equal( gBrowser.selectedTab, gBrowser.tabs[0], "Should start on the first tab." ); webrtcUI.sharingDisplay = true; if (doFirstTabSwitch) { await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]); } } /** * Simulates the sharing of a particular browser window. The * simulation doesn't actually share the window over WebRTC, but * does enough to convince webrtcUI that the window is in the shared * window list. * * It is assumed that the first tab is the selected tab when calling * this function. * * This helper function can also automatically perform the first * "freebie" tab switch that never warns. This is its default behaviour. * * @param {DOM Window} aWindow - The window that we're simulating sharing. * @param {boolean} doFirstTabSwitch - True if this function should take * care of doing the "freebie" tab switch for you. Defaults to true. * @return {Promise} * @resolves {undefined} - Once the simulation is set up. */ async function pretendToShareWindow(aWindow, doFirstTabSwitch = true) { // Poke into webrtcUI so that it thinks that the current browser // window is being shared. webrtcUI.sharedBrowserWindows.add(aWindow); await pretendToShareDisplay(doFirstTabSwitch); } /** * Simulates the sharing of the screen. The simulation doesn't actually share * the screen over WebRTC, but does enough to convince webrtcUI that the screen * is being shared. * * It is assumed that the first tab is the selected tab when calling * this function. * * This helper function can also automatically perform the first * "freebie" tab switch that never warns. This is its default behaviour. * * @param {boolean} doFirstTabSwitch - True if this function should take * care of doing the "freebie" tab switch for you. Defaults to true. * @return {Promise} * @resolves {undefined} - Once the simulation is set up. */ async function pretendToShareScreen(doFirstTabSwitch = true) { // Poke into webrtcUI so that it thinks that the current screen is being // shared. webrtcUI.sharingScreen = true; await pretendToShareDisplay(doFirstTabSwitch); } /** * Resets webrtcUI's notion of what is being shared. This also clears * out any simulated shared windows, and resets any state that only * persists for a sharing session. * * This helper function will also: * 1. Switch back to the first tab if it's not already selected. * 2. Check if the tab switch warning panel is open, and if so, close it. * * @return {Promise} * @resolves {undefined} - Once the state is reset. */ async function resetDisplaySharingState() { let firstTabBC = gBrowser.browsers[0].browsingContext; webrtcUI.streamAddedOrRemoved(firstTabBC, { remove: true }); if (gBrowser.selectedTab !== gBrowser.tabs[0]) { await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]); } let panel = document.getElementById(WARNING_PANEL_ID); if (panel && (panel.state == "open" || panel.state == "showing")) { info("Closing the warning panel."); let panelHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden"); panel.hidePopup(); await panelHidden; } } /** * Checks to make sure that a tab switch warning doesn't show * within WARNING_PANEL_TIMEOUT_MS milliseconds. * * @return {Promise} * @resolves {undefined} - Once the check is complete. */ async function ensureNoWarning() { let timerExpired = false; let sawWarning = false; let resolver; let timeoutOrPopupShowingPromise = new Promise(resolve => { resolver = resolve; }); let onPopupShowing = event => { if (event.target.id == WARNING_PANEL_ID) { sawWarning = true; resolver(); } }; // The panel might not have been lazily-inserted yet, so we // attach the popupshowing handler to the window instead. window.addEventListener("popupshowing", onPopupShowing); // eslint-disable-next-line mozilla/no-arbitrary-setTimeout let timer = setTimeout(() => { timerExpired = true; resolver(); }, WARNING_PANEL_TIMEOUT_MS); await timeoutOrPopupShowingPromise; clearTimeout(timer); window.removeEventListener("popupshowing", onPopupShowing); Assert.ok(timerExpired, "Timer should have expired."); Assert.ok(!sawWarning, "Should not have shown the tab switch warning."); } /** * Checks to make sure that a tab switch warning appears for * a particular tab. * * @param {} tab - The tab that the warning should be anchored to. * @return {Promise} * @resolves {undefined} - Once the check is complete. */ async function ensureWarning(tab) { let popupShowingEvent = await BrowserTestUtils.waitForEvent( window, "popupshowing", false, event => { return event.target.id == WARNING_PANEL_ID; } ); let panel = popupShowingEvent.target; Assert.equal( panel.anchorNode, tab, "Expected the warning to be anchored to the right tab." ); } add_setup(async function () { await SpecialPowers.pushPrefEnv({ set: [["privacy.webrtc.sharedTabWarning", true]], }); // Loads up NEW_BACKGROUND_TABS_TO_OPEN background tabs at about:blank, // and waits until they're fully open. let uris = new Array(NEW_BACKGROUND_TABS_TO_OPEN).fill("about:blank"); let loadPromises = Promise.all( uris.map(uri => BrowserTestUtils.waitForNewTab(gBrowser, uri, false, true)) ); gBrowser.loadTabs(uris, { inBackground: true, triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), }); await loadPromises; // Switches to the first tab and closes all of the rest. registerCleanupFunction(async () => { await resetDisplaySharingState(); gBrowser.removeAllTabsBut(gBrowser.tabs[0]); }); }); /** * Tests that when sharing the window that the first tab switch does _not_ show * the warning. This is because we presume that the first tab switch since * starting display sharing is for a tab that is intentionally being shared. */ add_task(async function testFirstTabSwitchAllowed() { pretendToShareWindow(window, false); let targetTab = gBrowser.tabs[1]; let noWarningPromise = ensureNoWarning(); await BrowserTestUtils.switchTab(gBrowser, targetTab); await noWarningPromise; await resetDisplaySharingState(); }); /** * Tests that the second tab switch after sharing is not allowed * without a warning. Also tests that the warning can "allow" * the tab switch to proceed, and that no warning is subsequently * shown for the "allowed" tab. Finally, ensures that if the sharing * session ends and a new session begins, that warnings are shown * again for the allowed tabs. */ add_task(async function testWarningOnSecondTabSwitch() { pretendToShareWindow(window); let originalTab = gBrowser.selectedTab; // pretendToShareWindow will have switched us to the second // tab automatically as the first "freebie" tab switch let targetTab = gBrowser.tabs[2]; // Ensure that we show the warning on the second tab switch let warningPromise = ensureWarning(targetTab); await BrowserTestUtils.switchTab(gBrowser, targetTab); await warningPromise; // Not only should we have warned, but we should have prevented // the tab switch from occurring. Assert.equal( gBrowser.selectedTab, originalTab, "Should still be on the original tab." ); // Now test the "Allow" button in the warning to make sure the tab // switch goes through. let tabSwitchPromise = BrowserTestUtils.waitForEvent( gBrowser, "TabSwitchDone" ); let allowButton = document.getElementById(ALLOW_BUTTON_ID); allowButton.click(); await tabSwitchPromise; Assert.equal( gBrowser.selectedTab, targetTab, "Should have switched tabs to the target." ); // We shouldn't see a warning when switching back to that first // "freebie" tab. let noWarningPromise = ensureNoWarning(); await BrowserTestUtils.switchTab(gBrowser, originalTab); await noWarningPromise; Assert.equal( gBrowser.selectedTab, originalTab, "Should have switched tabs back to the original tab." ); // We shouldn't see a warning when switching back to the tab that // we had just allowed. noWarningPromise = ensureNoWarning(); await BrowserTestUtils.switchTab(gBrowser, targetTab); await noWarningPromise; Assert.equal( gBrowser.selectedTab, targetTab, "Should have switched tabs back to the target tab." ); // Reset the sharing state, and make sure that warnings can // be displayed again. await resetDisplaySharingState(); pretendToShareWindow(window); // pretendToShareWindow will have switched us to the second // tab automatically as the first "freebie" tab switch // // Make sure we get the warning again when switching to the // target tab. warningPromise = ensureWarning(targetTab); await BrowserTestUtils.switchTab(gBrowser, targetTab); await warningPromise; await resetDisplaySharingState(); }); /** * Tests that warnings can be skipped for a session via the * checkbox in the warning panel. Also checks that once the * session ends and a new one begins that warnings are displayed * again. */ add_task(async function testDisableWarningForSession() { pretendToShareWindow(window); // pretendToShareWindow will have switched us to the second // tab automatically as the first "freebie" tab switch let targetTab = gBrowser.tabs[2]; // Ensure that we show the warning on the second tab switch let warningPromise = ensureWarning(targetTab); await BrowserTestUtils.switchTab(gBrowser, targetTab); await warningPromise; // Check the checkbox to suppress warnings for the rest of this session. let checkbox = document.getElementById( DISABLE_WARNING_FOR_SESSION_CHECKBOX_ID ); checkbox.checked = true; // Now test the "Allow" button in the warning to make sure the tab // switch goes through. let tabSwitchPromise = BrowserTestUtils.waitForEvent( gBrowser, "TabSwitchDone" ); let allowButton = document.getElementById(ALLOW_BUTTON_ID); allowButton.click(); await tabSwitchPromise; Assert.equal( gBrowser.selectedTab, targetTab, "Should have switched tabs to the target tab." ); // Switching to the 4th and 5th tabs should now not show warnings. let noWarningPromise = ensureNoWarning(); await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[3]); await noWarningPromise; noWarningPromise = ensureNoWarning(); await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[4]); await noWarningPromise; // Reset the sharing state, and make sure that warnings can // be displayed again. await resetDisplaySharingState(); pretendToShareWindow(window); // pretendToShareWindow will have switched us to the second // tab automatically as the first "freebie" tab switch // // Make sure we get the warning again when switching to the // target tab. warningPromise = ensureWarning(targetTab); await BrowserTestUtils.switchTab(gBrowser, targetTab); await warningPromise; await resetDisplaySharingState(); }); /** * Tests that we don't show a warning when sharing a different * window than the one we're switching tabs in. */ add_task(async function testOtherWindow() { let otherWin = await BrowserTestUtils.openNewBrowserWindow(); await SimpleTest.promiseFocus(window); pretendToShareWindow(otherWin); // Switching to the 4th and 5th tabs should now not show warnings. let noWarningPromise = ensureNoWarning(); await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[3]); await noWarningPromise; noWarningPromise = ensureNoWarning(); await BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[4]); await noWarningPromise; await BrowserTestUtils.closeWindow(otherWin); await resetDisplaySharingState(); }); /** * Tests that we show a different label when sharing the screen * vs when sharing a window. */ add_task(async function testWindowVsScreenLabel() { pretendToShareWindow(window); // pretendToShareWindow will have switched us to the second // tab automatically as the first "freebie" tab switch. // Let's now switch to the third tab. let targetTab = gBrowser.tabs[2]; // Ensure that we show the warning on this second tab switch let warningPromise = ensureWarning(targetTab); await BrowserTestUtils.switchTab(gBrowser, targetTab); await warningPromise; let windowHeader = document.getElementById(WINDOW_SHARING_HEADER_ID); let screenHeader = document.getElementById(SCREEN_SHARING_HEADER_ID); Assert.ok( !BrowserTestUtils.is_hidden(windowHeader), "Should be showing window sharing header" ); Assert.ok( BrowserTestUtils.is_hidden(screenHeader), "Should not be showing screen sharing header" ); // Reset the sharing state, and then pretend to share the screen. await resetDisplaySharingState(); pretendToShareScreen(); // Ensure that we show the warning on this second tab switch warningPromise = ensureWarning(targetTab); await BrowserTestUtils.switchTab(gBrowser, targetTab); await warningPromise; Assert.ok( BrowserTestUtils.is_hidden(windowHeader), "Should not be showing window sharing header" ); Assert.ok( !BrowserTestUtils.is_hidden(screenHeader), "Should be showing screen sharing header" ); await resetDisplaySharingState(); }); /** * Tests that tab switching via the keyboard can also trigger the * tab switch warnings. */ add_task(async function testKeyboardTabSwitching() { let pressCtrlTab = async (expectPanel = false) => { let promise; if (expectPanel) { promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popupshown"); } else { promise = BrowserTestUtils.waitForEvent(document, "keyup"); } EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: false, }); await promise; }; let releaseCtrl = async () => { let promise; if (ctrlTab.isOpen) { promise = BrowserTestUtils.waitForEvent(ctrlTab.panel, "popuphidden"); } else { promise = BrowserTestUtils.waitForEvent(document, "keyup"); } EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" }); return promise; }; // Ensure that the (on by default) ctrl-tab switch panel is enabled. await SpecialPowers.pushPrefEnv({ set: [[CTRL_TAB_RUO_PREF, true]], }); pretendToShareWindow(window); let originalTab = gBrowser.selectedTab; await pressCtrlTab(true); // The Ctrl-Tab MRU list should be: // 0: Second tab (currently selected) // 1: First tab // 2: Last tab // // Having pressed Ctrl-Tab once, 1 (First tab) is selected in the // panel. We want a tab that will warn, so let's hit Ctrl-Tab again // to choose 2 (Last tab). let targetTab = ctrlTab.tabList[2]; await pressCtrlTab(); let warningPromise = ensureWarning(targetTab); await releaseCtrl(); await warningPromise; // Hide the warning without allowing the tab switch. let panel = document.getElementById(WARNING_PANEL_ID); panel.hidePopup(); Assert.equal( gBrowser.selectedTab, originalTab, "Should not have changed from the original tab." ); // Now switch to the in-order tab switching keyboard shortcut mode. await SpecialPowers.popPrefEnv(); await SpecialPowers.pushPrefEnv({ set: [[CTRL_TAB_RUO_PREF, false]], }); // Hitting Ctrl-Tab should choose the _next_ tab over from // the originalTab, which should be the third tab. targetTab = gBrowser.tabs[2]; warningPromise = ensureWarning(targetTab); await pressCtrlTab(); await warningPromise; await resetDisplaySharingState(); });