"use strict"; ChromeUtils.defineESModuleGetters(this, { ExtensionControlledPopup: "resource:///modules/ExtensionControlledPopup.sys.mjs", SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs", }); const triggeringPrincipal_base64 = E10SUtils.SERIALIZED_SYSTEMPRINCIPAL; async function doorhangerTest(testFn) { let extension = ExtensionTestUtils.loadExtension({ manifest: { permissions: ["tabs", "tabHide"], icons: { 48: "addon-icon.png", }, }, background() { browser.test.onMessage.addListener(async (msg, data) => { let tabs = await browser.tabs.query(data); await browser.tabs[msg](tabs.map(t => t.id)); browser.test.sendMessage("done"); }); }, useAddonManager: "temporary", }); await extension.startup(); // Open some tabs so we can hide them. let firstTab = gBrowser.selectedTab; let tabs = [ await BrowserTestUtils.openNewForegroundTab( gBrowser, "http://example.com/?one", true, true ), await BrowserTestUtils.openNewForegroundTab( gBrowser, "http://example.com/?two", true, true ), ]; gBrowser.selectedTab = firstTab; await testFn(extension); BrowserTestUtils.removeTab(tabs[0]); BrowserTestUtils.removeTab(tabs[1]); await extension.unload(); } add_task(function test_doorhanger_keep() { return doorhangerTest(async function (extension) { is(gBrowser.visibleTabs.length, 3, "There are 3 visible tabs"); // Hide the first tab, expect the doorhanger. let panel = ExtensionControlledPopup._getAndMaybeCreatePanel(document); let popupShown = promisePopupShown(panel); extension.sendMessage("hide", { url: "*://*/?one" }); await extension.awaitMessage("done"); await popupShown; is(gBrowser.visibleTabs.length, 2, "There are 2 visible tabs now"); is( panel.anchorNode.closest("toolbarbutton").id, "alltabs-button", "The doorhanger is anchored to the all tabs button" ); // Click the Keep Tabs Hidden button. let popupnotification = document.getElementById( "extension-tab-hide-notification" ); let popupHidden = promisePopupHidden(panel); popupnotification.button.click(); await popupHidden; // Hide another tab and ensure the popup didn't open. extension.sendMessage("hide", { url: "*://*/?two" }); await extension.awaitMessage("done"); is(panel.state, "closed", "The popup is still closed"); is(gBrowser.visibleTabs.length, 1, "There's one visible tab now"); extension.sendMessage("show", {}); await extension.awaitMessage("done"); }); }); add_task(function test_doorhanger_disable() { return doorhangerTest(async function (extension) { is(gBrowser.visibleTabs.length, 3, "There are 3 visible tabs"); // Hide the first tab, expect the doorhanger. let panel = ExtensionControlledPopup._getAndMaybeCreatePanel(document); let popupShown = promisePopupShown(panel); extension.sendMessage("hide", { url: "*://*/?one" }); await extension.awaitMessage("done"); await popupShown; is(gBrowser.visibleTabs.length, 2, "There are 2 visible tabs now"); is( panel.anchorNode.closest("toolbarbutton").id, "alltabs-button", "The doorhanger is anchored to the all tabs button" ); // verify the contents of the description. let popupnotification = document.getElementById( "extension-tab-hide-notification" ); let description = popupnotification.querySelector( "#extension-tab-hide-notification-description" ); let addon = await AddonManager.getAddonByID(extension.id); ok( description.textContent.includes(addon.name), "The extension name is in the description" ); let images = Array.from(description.querySelectorAll("image")); is(images.length, 2, "There are two images"); ok( images.some(img => img.src.includes("addon-icon.png")), "There's an icon for the extension" ); ok( images.some(img => getComputedStyle(img).backgroundImage.includes("arrow-down.svg") ), "There's an icon for the all tabs menu" ); // Click the Disable Extension button. let popupHidden = promisePopupHidden(panel); popupnotification.secondaryButton.click(); await popupHidden; await new Promise(executeSoon); is(gBrowser.visibleTabs.length, 3, "There are 3 visible tabs again"); is(addon.userDisabled, true, "The extension is now disabled"); }); }); add_task(async function test_tabs_showhide() { async function background() { browser.test.onMessage.addListener(async (msg, data) => { switch (msg) { case "hideall": { let tabs = await browser.tabs.query({ hidden: false }); browser.test.assertEq(tabs.length, 5, "got 5 tabs"); let ids = tabs.map(tab => tab.id); browser.test.log(`working with ids ${JSON.stringify(ids)}`); let hidden = await browser.tabs.hide(ids); browser.test.assertEq(hidden.length, 3, "hid 3 tabs"); tabs = await browser.tabs.query({ hidden: true }); ids = tabs.map(tab => tab.id); browser.test.assertEq( JSON.stringify(hidden.sort()), JSON.stringify(ids.sort()), "hidden tabIds match" ); browser.test.sendMessage("hidden", { hidden }); break; } case "showall": { let tabs = await browser.tabs.query({ hidden: true }); for (let tab of tabs) { browser.test.assertTrue(tab.hidden, "tab is hidden"); } let ids = tabs.map(tab => tab.id); browser.tabs.show(ids); browser.test.sendMessage("shown"); break; } } }); } let extdata = { manifest: { permissions: ["tabs", "tabHide"] }, background, useAddonManager: "temporary", // So the doorhanger can find the addon. }; let extension = ExtensionTestUtils.loadExtension(extdata); await extension.startup(); let sessData = { windows: [ { tabs: [ { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, { entries: [ { url: "https://example.com/", triggeringPrincipal_base64 }, ], }, { entries: [ { url: "https://mochi.test:8888/", triggeringPrincipal_base64 }, ], }, ], }, { tabs: [ { entries: [{ url: "about:blank", triggeringPrincipal_base64 }] }, { entries: [ { url: "http://test1.example.com/", triggeringPrincipal_base64 }, ], }, ], }, ], }; // Set up a test session with 2 windows and 5 tabs. let oldState = SessionStore.getBrowserState(); let restored = TestUtils.topicObserved("sessionstore-browser-state-restored"); SessionStore.setBrowserState(JSON.stringify(sessData)); await restored; if (!Services.prefs.getBoolPref("browser.tabs.tabmanager.enabled")) { for (let win of BrowserWindowIterator()) { let allTabsButton = win.document.getElementById("alltabs-button"); is( getComputedStyle(allTabsButton).display, "none", "The all tabs button is hidden" ); } } // Attempt to hide all the tabs, however the active tab in each window cannot // be hidden, so the result will be 3 hidden tabs. extension.sendMessage("hideall"); await extension.awaitMessage("hidden"); // We have 2 windows in this session. Otherwin is the non-current window. // In each window, the first tab will be the selected tab and should not be // hidden. The rest of the tabs should be hidden at this point. Hidden // status was already validated inside the extension, this double checks // from chrome code. let otherwin; for (let win of BrowserWindowIterator()) { if (win != window) { otherwin = win; } let tabs = Array.from(win.gBrowser.tabs); ok(!tabs[0].hidden, "first tab not hidden"); for (let i = 1; i < tabs.length; i++) { ok(tabs[i].hidden, "tab hidden value is correct"); let id = SessionStore.getCustomTabValue(tabs[i], "hiddenBy"); is(id, extension.id, "tab hiddenBy value is correct"); await TabStateFlusher.flush(tabs[i].linkedBrowser); } let allTabsButton = win.document.getElementById("alltabs-button"); isnot( getComputedStyle(allTabsButton).display, "none", "The all tabs button is visible" ); } // Close the other window then restore it to test that the tabs are // restored with proper hidden state, and the correct extension id. await BrowserTestUtils.closeWindow(otherwin); otherwin = SessionStore.undoCloseWindow(0); await BrowserTestUtils.waitForEvent(otherwin, "load"); let tabs = Array.from(otherwin.gBrowser.tabs); ok(!tabs[0].hidden, "first tab not hidden"); for (let i = 1; i < tabs.length; i++) { ok(tabs[i].hidden, "tab hidden value is correct"); let id = SessionStore.getCustomTabValue(tabs[i], "hiddenBy"); is(id, extension.id, "tab hiddenBy value is correct"); } // Test closing the last visible tab, the next tab which is hidden should become // the selectedTab and will be visible. ok(!otherwin.gBrowser.selectedTab.hidden, "selected tab is not hidden"); BrowserTestUtils.removeTab(otherwin.gBrowser.selectedTab); ok(!otherwin.gBrowser.selectedTab.hidden, "tab was unhidden"); // Showall will unhide any remaining hidden tabs. extension.sendMessage("showall"); await extension.awaitMessage("shown"); // Check from chrome code that all tabs are visible again. for (let win of BrowserWindowIterator()) { let tabs = Array.from(win.gBrowser.tabs); for (let i = 0; i < tabs.length; i++) { ok(!tabs[i].hidden, "tab hidden value is correct"); } } // Close second window. await BrowserTestUtils.closeWindow(otherwin); await extension.unload(); // Restore pre-test state. restored = TestUtils.topicObserved("sessionstore-browser-state-restored"); SessionStore.setBrowserState(oldState); await restored; }); // Test our shutdown handling. Currently this means any hidden tabs will be // shown when a tabHide extension is shutdown. We additionally test the // tabs.onUpdated listener gets called with hidden state changes. add_task(async function test_tabs_shutdown() { let tabs = [ await BrowserTestUtils.openNewForegroundTab( gBrowser, "http://example.com/", true, true ), await BrowserTestUtils.openNewForegroundTab( gBrowser, "http://mochi.test:8888/", true, true ), ]; async function background() { let tabs = await browser.tabs.query({ url: "http://example.com/" }); let testTab = tabs[0]; browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { if ("hidden" in changeInfo) { browser.test.assertEq(tabId, testTab.id, "correct tab was hidden"); browser.test.assertTrue(changeInfo.hidden, "tab is hidden"); browser.test.assertEq(tab.url, testTab.url, "tab has correct URL"); browser.test.sendMessage("changeInfo"); } }); let hidden = await browser.tabs.hide(testTab.id); browser.test.assertEq(hidden[0], testTab.id, "tab was hidden"); tabs = await browser.tabs.query({ hidden: true }); browser.test.assertEq(tabs[0].id, testTab.id, "tab was hidden"); browser.test.sendMessage("ready"); } let extdata = { manifest: { permissions: ["tabs", "tabHide"] }, useAddonManager: "temporary", // For testing onShutdown. background, }; let extension = ExtensionTestUtils.loadExtension(extdata); await extension.startup(); // test onUpdated await Promise.all([ extension.awaitMessage("ready"), extension.awaitMessage("changeInfo"), ]); Assert.ok(tabs[0].hidden, "Tab is hidden by extension"); await extension.unload(); Assert.ok(!tabs[0].hidden, "Tab is not hidden after unloading extension"); BrowserTestUtils.removeTab(tabs[0]); BrowserTestUtils.removeTab(tabs[1]); });