new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabs/browser_abandonment_telemetry.js
@@ -0,0 +1,305 @@
+"use strict";
+
+const {TabStateFlusher} = Cu.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,<html>Page 2</html>";
+
+/**
+ * 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 (<xul: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 (<xul: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",
+
+ * prepare(browser) {
+ browser.loadURI(PAGE_2);
+ yield BrowserTestUtils.browserLoaded(browser);
+ },
+
+ * doAction(browser) {
+ let pageShow =
+ BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ document.getElementById("Browser:Back").doCommand();
+ yield pageShow;
+ },
+ },
+
+ // Test going forward to a previous page
+ {
+ name: "Going forward to the next page",
+
+ category: "forward",
+
+ * prepare(browser) {
+ browser.loadURI(PAGE_2);
+ yield BrowserTestUtils.browserLoaded(browser);
+ let pageShow =
+ BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ browser.goBack();
+ yield pageShow;
+ },
+
+ * doAction(browser) {
+ let pageShow =
+ BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ document.getElementById("Browser:Forward").doCommand();
+ yield pageShow;
+ },
+ },
+
+ // Test going backwards more than one page back via gotoIndex
+ {
+ name: "Going backward to a previous page via gotoIndex",
+
+ category: "historyNavigation",
+
+ * prepare(browser) {
+ browser.loadURI(PAGE_2);
+ yield BrowserTestUtils.browserLoaded(browser);
+ browser.loadURI("http://example.com");
+ yield BrowserTestUtils.browserLoaded(browser);
+ yield TabStateFlusher.flush(browser);
+ },
+
+ * doAction(browser) {
+ let pageShow =
+ BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ synthesizeHistoryNavigationToIndex(0);
+ yield pageShow;
+ },
+ },
+
+ // Test going forwards more than one page back via gotoIndex
+ {
+ name: "Going forward to a previous page via gotoIndex",
+
+ category: "historyNavigation",
+
+ * prepare(browser) {
+ browser.loadURI(PAGE_2);
+ yield BrowserTestUtils.browserLoaded(browser);
+ browser.loadURI("http://example.com");
+ yield BrowserTestUtils.browserLoaded(browser);
+ let pageShow =
+ BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ browser.gotoIndex(0);
+ yield pageShow;
+ yield TabStateFlusher.flush(browser);
+ },
+
+ * doAction(browser) {
+ let pageShow =
+ BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ synthesizeHistoryNavigationToIndex(2);
+ yield pageShow;
+ },
+ },
+
+ // Test reloading the tab
+ {
+ name: "Reloading the browser",
+
+ category: "reload",
+
+ * prepare(browser) {},
+
+ * doAction(browser) {
+ document.getElementById("Browser:Reload").doCommand();
+ yield 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) {},
+
+ * doAction(browser) {
+ openUILinkIn(PAGE_2, "current");
+ yield 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(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) {
+ yield BrowserTestUtils.withNewTab({
+ gBrowser,
+ url: "http://example.com",
+ }, function*(browser) {
+ let tab = gBrowser.getTabForBrowser(browser);
+ info(`Test: "${probeTest.name}"`);
+
+ yield* 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();
+ yield* 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 = yield 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);
+
+ yield BrowserTestUtils.removeTab(newTab);
+ let snapshot = histogram.snapshot();
+ assertOnlyOneTypeSet(snapshot, "tabClosed");
+});