"use strict"; const {TabStateFlusher} = ChromeUtils.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {}); // Keep this in sync with the order in Histograms.json for // BUSY_TAB_ABANDONED const CATEGORIES = [ "stop", "back", "forward", "historyNavigation", "reload", "tabClosed", "newURI", ]; const PAGE_2 = "data:text/html,Page 2"; /** * This little framework helps to extract the unique things * involved in testing each category of the BUSY_TAB_ABANDONED * probe from all of the common things. The little framework * in this test will take each item in PROBE_TEST, let it * do some test preparation, then create a "busy" tab to be * manipulated by the test. The "category" of the test will then * be used to determine if the appropriate index in the * histogram was bumped. * * Here's a more verbose breakdown of what each PROBE_TEST * should supply: * * name (String): * Human readable description of the test. This is dumped * out via info(). * * category (String): * The string representation of the index that will get * incremented in the BUSY_TAB_ABANDONED probe. This should * be a value inside CATEGORIES. * * prepare (function*) * A function that can yield Promises. This will be run once * we have a brand new browser to deal with, and should be used by * the PROBE_TEST to do any history preparation before the browser * is made to appear "busy" and is tested. * * @param browser () * The newly created browser that the test will use. * * doAction (function*) * A function that can yield Promises. This will be run once * the browser appears busy, and should cause the user action * that will change the BUSY_TAB_ABANDONED probe. * * @param browser () * The busy browser to perform the action on. */ const PROBE_TESTS = [ // Test stopping the tab { name: "Stopping the browser", category: "stop", prepare(browser) {}, doAction(browser) { document.getElementById("Browser:Stop").doCommand(); }, }, // Test going back to a previous page { name: "Going back to a previous page", category: "back", async prepare(browser) { BrowserTestUtils.loadURI(browser, PAGE_2); await BrowserTestUtils.browserLoaded(browser); }, async doAction(browser) { let pageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow"); document.getElementById("Browser:Back").doCommand(); await pageShow; }, }, // Test going forward to a previous page { name: "Going forward to the next page", category: "forward", async prepare(browser) { BrowserTestUtils.loadURI(browser, PAGE_2); await BrowserTestUtils.browserLoaded(browser); let pageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow"); browser.goBack(); await pageShow; }, async doAction(browser) { let pageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow"); document.getElementById("Browser:Forward").doCommand(); await pageShow; }, }, // Test going backwards more than one page back via gotoIndex { name: "Going backward to a previous page via gotoIndex", category: "historyNavigation", async prepare(browser) { BrowserTestUtils.loadURI(browser, PAGE_2); await BrowserTestUtils.browserLoaded(browser); BrowserTestUtils.loadURI(browser, "http://example.com"); await BrowserTestUtils.browserLoaded(browser); await TabStateFlusher.flush(browser); }, async doAction(browser) { let pageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow"); synthesizeHistoryNavigationToIndex(0); await pageShow; }, }, // Test going forwards more than one page back via gotoIndex { name: "Going forward to a previous page via gotoIndex", category: "historyNavigation", async prepare(browser) { BrowserTestUtils.loadURI(browser, PAGE_2); await BrowserTestUtils.browserLoaded(browser); BrowserTestUtils.loadURI(browser, "http://example.com"); await BrowserTestUtils.browserLoaded(browser); let pageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow"); browser.gotoIndex(0); await pageShow; await TabStateFlusher.flush(browser); }, async doAction(browser) { let pageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow"); synthesizeHistoryNavigationToIndex(2); await pageShow; }, }, // Test reloading the tab { name: "Reloading the browser", category: "reload", prepare(browser) {}, async doAction(browser) { document.getElementById("Browser:Reload").doCommand(); await BrowserTestUtils.browserLoaded(browser); }, }, // Testing closing the tab is done in its own test later on // in this file. // Test browsing to a new URL { name: "Browsing to a new URL", category: "newURI", prepare(browser) {}, async doAction(browser) { openTrustedLinkIn(PAGE_2, "current"); await BrowserTestUtils.browserLoaded(browser); }, }, ]; /** * Takes a Telemetry histogram snapshot and makes sure * that the index for that value (as defined by CATEGORIES) * has a count of 1, and that it's the only value that * has been incremented. * * @param snapshot (Object) * The Telemetry histogram snapshot to examine. * @param category (String) * The category in CATEGORIES whose index we expect to have * been set to 1. */ function assertOnlyOneTypeSet(snapshot, category) { let categoryIndex = CATEGORIES.indexOf(category); Assert.equal(snapshot.counts[categoryIndex], 1, `Should have seen the ${category} count increment.`); // Use Array.prototype.reduce to sum up all of the // snapshot.count entries Assert.equal(snapshot.counts.reduce((a, b) => a + b), 1, "Should only be 1 collected value."); } /** * A helper function for simulating clicking on the history * navigation popup menu that you get if you click and hold * on the back or forward buttons when you have some browsing * history. * * @param index (int) * The index for the menuitem we want to simulate * clicking on. */ function synthesizeHistoryNavigationToIndex(index) { let popup = document.getElementById("backForwardMenu"); // I don't want to deal with popup listening - that's // notoriously flake-y in automated tests. I'll just // directly call the function that populates the menu. FillHistoryMenu(popup); Assert.ok(popup.childElementCount > 0, "Should have some items in the back/forward menu"); let menuitem = popup.querySelector(`menuitem[index="${index}"]`); Assert.ok(menuitem, `Should find a menuitem with index ${index}`); // Now pretend we clicked on the right item. let cmdEvent = new CustomEvent("command", { bubbles: true, cancelable: true, }); menuitem.dispatchEvent(cmdEvent); } /** * Goes through each of the categories for the BUSY_TAB_ABANDONED * probe, and tests that they're properly changed. */ add_task(async function test_probes() { let oldCanRecord = Services.telemetry.canRecordExtended; Services.telemetry.canRecordExtended = true; registerCleanupFunction(() => { Services.telemetry.canRecordExtended = oldCanRecord; }); let histogram = Services.telemetry .getHistogramById("BUSY_TAB_ABANDONED"); // If you want to add new tests for probes that don't involve // the tab or window hosting the tab closing, see the documentation // above PROBE_TESTS for how to hook into this little framework. for (let probeTest of PROBE_TESTS) { await BrowserTestUtils.withNewTab({ gBrowser, url: "http://example.com", }, async function(browser) { let tab = gBrowser.getTabForBrowser(browser); info(`Test: "${probeTest.name}"`); await probeTest.prepare(browser); // Instead of trying to fiddle with network state or // anything, we'll just set this attribute to fool our // telemetry probes into thinking the browser is in the // middle of loading some resources. tab.setAttribute("busy", true); histogram.clear(); await probeTest.doAction(browser); let snapshot = histogram.snapshot(); assertOnlyOneTypeSet(snapshot, probeTest.category); }); } // The above tests used BrowserTestUtils.withNewTab, which is // fine for almost all categories for this probe, except for // "tabClosed", since withNewTab closes the tab automatically // before resolving, which doesn't work well for a tabClosed // test in the above framework. So the tabClosed tests are // done separately below. histogram.clear(); // Now test that we can close a busy tab and get the tabClosed // measurement bumped. let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser); // As above, instead of trying to fiddle with network state // or anything, we'll just set this attribute to fool our // telemetry probes into thinking the browser is in the // middle of loading some resources. newTab.setAttribute("busy", true); BrowserTestUtils.removeTab(newTab); let snapshot = histogram.snapshot(); assertOnlyOneTypeSet(snapshot, "tabClosed"); });