Bug 1961161: Additional metrics for closing tabs within groups r=dwalker,fxview-reviewers,tabbrowser-reviewers,nsharpley

Differential Revision: https://phabricator.services.mozilla.com/D248438
This commit is contained in:
Jeremy Swinarton
2025-05-22 22:00:34 +00:00
committed by jswinarton@mozilla.com
parent 44151f9298
commit 1f70d01002
12 changed files with 482 additions and 152 deletions

View File

@@ -396,7 +396,9 @@ var BrowserCommands = {
// In a multi-select context, close all selected tabs // In a multi-select context, close all selected tabs
if (gBrowser.multiSelectedTabsCount) { if (gBrowser.multiSelectedTabsCount) {
gBrowser.removeMultiSelectedTabs(); gBrowser.removeMultiSelectedTabs(
gBrowser.TabMetrics.userTriggeredContext()
);
return; return;
} }
@@ -414,7 +416,10 @@ var BrowserCommands = {
} }
// If the current tab is the last one, this will close the window. // If the current tab is the last one, this will close the window.
gBrowser.removeCurrentTab({ animate: true }); gBrowser.removeCurrentTab({
animate: true,
...gBrowser.TabMetrics.userTriggeredContext(),
});
}, },
tryToCloseWindow(event) { tryToCloseWindow(event) {

View File

@@ -88,16 +88,28 @@ document.addEventListener(
TabContextMenu.closeContextTabs(); TabContextMenu.closeContextTabs();
break; break;
case "context_closeDuplicateTabs": case "context_closeDuplicateTabs":
gBrowser.removeDuplicateTabs(TabContextMenu.contextTab); gBrowser.removeDuplicateTabs(
TabContextMenu.contextTab,
lazy.TabMetrics.userTriggeredContext()
);
break; break;
case "context_closeTabsToTheStart": case "context_closeTabsToTheStart":
gBrowser.removeTabsToTheStartFrom(TabContextMenu.contextTab); gBrowser.removeTabsToTheStartFrom(
TabContextMenu.contextTab,
lazy.TabMetrics.userTriggeredContext()
);
break; break;
case "context_closeTabsToTheEnd": case "context_closeTabsToTheEnd":
gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab); gBrowser.removeTabsToTheEndFrom(
TabContextMenu.contextTab,
lazy.TabMetrics.userTriggeredContext()
);
break; break;
case "context_closeOtherTabs": case "context_closeOtherTabs":
gBrowser.removeAllTabsBut(TabContextMenu.contextTab); gBrowser.removeAllTabsBut(
TabContextMenu.contextTab,
lazy.TabMetrics.userTriggeredContext()
);
break; break;
case "context_unloadTab": case "context_unloadTab":
TabContextMenu.explicitUnloadTabs(); TabContextMenu.explicitUnloadTabs();

View File

@@ -26,6 +26,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
NonPrivateTabs: "resource:///modules/OpenTabs.sys.mjs", NonPrivateTabs: "resource:///modules/OpenTabs.sys.mjs",
getTabsTargetForWindow: "resource:///modules/OpenTabs.sys.mjs", getTabsTargetForWindow: "resource:///modules/OpenTabs.sys.mjs",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
TabMetrics: "moz-src:///browser/components/tabbrowser/TabMetrics.sys.mjs",
}); });
ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => { ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
@@ -503,7 +504,10 @@ class OpenTabsInViewCard extends ViewPageContent {
closeTab(event) { closeTab(event) {
const tab = event.originalTarget.tabElement; const tab = event.originalTarget.tabElement;
tab?.ownerGlobal.gBrowser.removeTab(tab); tab?.ownerGlobal.gBrowser.removeTab(
tab,
lazy.TabMetrics.userTriggeredContext()
);
Glean.firefoxviewNext.closeOpenTabTabs.record(); Glean.firefoxviewNext.closeOpenTabTabs.record();
} }

View File

@@ -55,6 +55,7 @@ const METRIC_REOPEN_TYPE = Object.freeze({
* @returns {TabMetricsContext} * @returns {TabMetricsContext}
*/ */
function userTriggeredContext(telemetrySource) { function userTriggeredContext(telemetrySource) {
telemetrySource = telemetrySource || METRIC_SOURCE.UNKNOWN;
return { return {
isUserTriggered: true, isUserTriggered: true,
telemetrySource, telemetrySource,

View File

@@ -324,9 +324,12 @@ export class TabsPanel extends TabsListBase {
break; break;
} }
if (event.target.classList.contains("all-tabs-close-button")) { if (event.target.classList.contains("all-tabs-close-button")) {
this.gBrowser.removeTab(event.target.tab, { this.gBrowser.removeTab(
telemetrySource: lazy.TabMetrics.METRIC_SOURCE.TAB_OVERFLOW_MENU, event.target.tab,
}); lazy.TabMetrics.userTriggeredContext(
lazy.TabMetrics.METRIC_SOURCE.TAB_OVERFLOW_MENU
)
);
break; break;
} }
if ("tabGroupId" in event.target.dataset) { if ("tabGroupId" in event.target.dataset) {

View File

@@ -555,14 +555,18 @@
if (event.target.classList.contains("tab-close-button")) { if (event.target.classList.contains("tab-close-button")) {
if (this.multiselected) { if (this.multiselected) {
gBrowser.removeMultiSelectedTabs({ gBrowser.removeMultiSelectedTabs(
telemetrySource: lazy.TabMetrics.METRIC_SOURCE.TAB_STRIP, lazy.TabMetrics.userTriggeredContext(
}); lazy.TabMetrics.METRIC_SOURCE.TAB_STRIP
)
);
} else { } else {
gBrowser.removeTab(this, { gBrowser.removeTab(this, {
animate: true, animate: true,
triggeringEvent: event, triggeringEvent: event,
telemetrySource: lazy.TabMetrics.METRIC_SOURCE.TAB_STRIP, ...lazy.TabMetrics.userTriggeredContext(
lazy.TabMetrics.METRIC_SOURCE.TAB_STRIP
),
}); });
} }
// This enables double-click protection for the tab container // This enables double-click protection for the tab container

View File

@@ -4139,15 +4139,16 @@
return duplicateTabs; return duplicateTabs;
} }
removeDuplicateTabs(aTab) { removeDuplicateTabs(aTab, options) {
this._removeDuplicateTabs( this._removeDuplicateTabs(
aTab, aTab,
this.getDuplicateTabsToClose(aTab), this.getDuplicateTabsToClose(aTab),
this.closingTabsEnum.DUPLICATES this.closingTabsEnum.DUPLICATES,
options
); );
} }
_removeDuplicateTabs(aConfirmationAnchor, tabs, aCloseTabs) { _removeDuplicateTabs(aConfirmationAnchor, tabs, aCloseTabs, options) {
if (!tabs.length) { if (!tabs.length) {
return; return;
} }
@@ -4156,7 +4157,7 @@
return; return;
} }
this.removeTabs(tabs); this.removeTabs(tabs, options);
ConfirmationHint.show( ConfirmationHint.show(
aConfirmationAnchor, aConfirmationAnchor,
"confirmation-hint-duplicate-tabs-closed", "confirmation-hint-duplicate-tabs-closed",
@@ -4179,7 +4180,7 @@
* In a multi-select context, the tabs (except pinned tabs) that are located to the * In a multi-select context, the tabs (except pinned tabs) that are located to the
* left of the leftmost selected tab will be removed. * left of the leftmost selected tab will be removed.
*/ */
removeTabsToTheStartFrom(aTab) { removeTabsToTheStartFrom(aTab, options) {
let tabs = this._getTabsToTheStartFrom(aTab); let tabs = this._getTabsToTheStartFrom(aTab);
if ( if (
!this.warnAboutClosingTabs(tabs.length, this.closingTabsEnum.TO_START) !this.warnAboutClosingTabs(tabs.length, this.closingTabsEnum.TO_START)
@@ -4187,14 +4188,14 @@
return; return;
} }
this.removeTabs(tabs); this.removeTabs(tabs, options);
} }
/** /**
* In a multi-select context, the tabs (except pinned tabs) that are located to the * In a multi-select context, the tabs (except pinned tabs) that are located to the
* right of the rightmost selected tab will be removed. * right of the rightmost selected tab will be removed.
*/ */
removeTabsToTheEndFrom(aTab) { removeTabsToTheEndFrom(aTab, options) {
let tabs = this._getTabsToTheEndFrom(aTab); let tabs = this._getTabsToTheEndFrom(aTab);
if ( if (
!this.warnAboutClosingTabs(tabs.length, this.closingTabsEnum.TO_END) !this.warnAboutClosingTabs(tabs.length, this.closingTabsEnum.TO_END)
@@ -4202,7 +4203,7 @@
return; return;
} }
this.removeTabs(tabs); this.removeTabs(tabs, options);
} }
/** /**
@@ -4258,7 +4259,7 @@
this.removeTabs(tabsToRemove, aParams); this.removeTabs(tabsToRemove, aParams);
} }
removeMultiSelectedTabs({ telemetrySource } = {}) { removeMultiSelectedTabs({ isUserTriggered, telemetrySource } = {}) {
let selectedTabs = this.selectedTabs; let selectedTabs = this.selectedTabs;
if ( if (
!this.warnAboutClosingTabs( !this.warnAboutClosingTabs(
@@ -4269,7 +4270,7 @@
return; return;
} }
this.removeTabs(selectedTabs, { telemetrySource }); this.removeTabs(selectedTabs, { isUserTriggered, telemetrySource });
} }
/** /**
@@ -4313,6 +4314,7 @@
skipPermitUnload, skipPermitUnload,
skipRemoves, skipRemoves,
skipSessionStore, skipSessionStore,
isUserTriggered,
telemetrySource, telemetrySource,
} }
) { ) {
@@ -4396,6 +4398,7 @@
prewarmed: true, prewarmed: true,
skipPermitUnload, skipPermitUnload,
skipSessionStore, skipSessionStore,
isUserTriggered,
telemetrySource, telemetrySource,
}); });
} }
@@ -4509,6 +4512,7 @@
* @param {boolean} [options.skipGroupCheck] * @param {boolean} [options.skipGroupCheck]
* Skip separate processing of whole tab groups from the set of tabs. * Skip separate processing of whole tab groups from the set of tabs.
* Used by removeTabGroup. * Used by removeTabGroup.
* TODO add docs
*/ */
removeTabs( removeTabs(
tabs, tabs,
@@ -4518,6 +4522,7 @@
skipPermitUnload = false, skipPermitUnload = false,
skipSessionStore = false, skipSessionStore = false,
skipGroupCheck = false, skipGroupCheck = false,
isUserTriggered = false,
telemetrySource, telemetrySource,
} = {} } = {}
) { ) {
@@ -4553,6 +4558,8 @@
animate, animate,
skipSessionStore, skipSessionStore,
skipPermitUnload, skipPermitUnload,
isUserTriggered,
telemetrySource,
}); });
}); });
tabs = leftoverTabs; tabs = leftoverTabs;
@@ -4565,6 +4572,7 @@
skipPermitUnload, skipPermitUnload,
skipRemoves: false, skipRemoves: false,
skipSessionStore, skipSessionStore,
isUserTriggered,
telemetrySource, telemetrySource,
}); });
@@ -4589,6 +4597,8 @@
prewarmed: true, prewarmed: true,
skipPermitUnload, skipPermitUnload,
skipSessionStore, skipSessionStore,
isUserTriggered,
telemetrySource,
}; };
// Now run again sequentially the beforeunload listeners that will result in a prompt. // Now run again sequentially the beforeunload listeners that will result in a prompt.
@@ -4626,6 +4636,7 @@
closeWindowWithLastTab, closeWindowWithLastTab,
prewarmed, prewarmed,
skipSessionStore, skipSessionStore,
isUserTriggered,
telemetrySource, telemetrySource,
} = {} } = {}
) { ) {
@@ -4664,6 +4675,7 @@
closeWindowWithLastTab, closeWindowWithLastTab,
prewarmed, prewarmed,
skipSessionStore, skipSessionStore,
isUserTriggered,
telemetrySource, telemetrySource,
}) })
) { ) {
@@ -4752,6 +4764,7 @@
skipPermitUnload, skipPermitUnload,
prewarmed, prewarmed,
skipSessionStore = false, skipSessionStore = false,
isUserTriggered,
telemetrySource, telemetrySource,
} = {} } = {}
) { ) {
@@ -4904,7 +4917,12 @@
// inspect the tab that's about to close. // inspect the tab that's about to close.
let evt = new CustomEvent("TabClose", { let evt = new CustomEvent("TabClose", {
bubbles: true, bubbles: true,
detail: { adoptedBy: adoptedByTab, skipSessionStore, telemetrySource }, detail: {
adoptedBy: adoptedByTab,
skipSessionStore,
isUserTriggered,
telemetrySource,
},
}); });
aTab.dispatchEvent(evt); aTab.dispatchEvent(evt);
@@ -9274,13 +9292,17 @@ var TabContextMenu = {
closeContextTabs() { closeContextTabs() {
if (this.contextTab.multiselected) { if (this.contextTab.multiselected) {
gBrowser.removeMultiSelectedTabs({ gBrowser.removeMultiSelectedTabs(
telemetrySource: gBrowser.TabMetrics.METRIC_SOURCE.TAB_STRIP, gBrowser.TabMetrics.userTriggeredContext(
}); gBrowser.TabMetrics.METRIC_SOURCE.TAB_STRIP
)
);
} else { } else {
gBrowser.removeTab(this.contextTab, { gBrowser.removeTab(this.contextTab, {
animate: true, animate: true,
telemetrySource: gBrowser.TabMetrics.METRIC_SOURCE.TAB_STRIP, ...gBrowser.TabMetrics.userTriggeredContext(
gBrowser.TabMetrics.METRIC_SOURCE.TAB_STRIP
),
}); });
} }
}, },

View File

@@ -396,9 +396,11 @@ tabgroup:
bugs: bugs:
- https://bugzil.la/1938405 - https://bugzil.la/1938405
- https://bugzil.la/1960360 - https://bugzil.la/1960360
- https://bugzil.la/1961161
data_reviews: data_reviews:
- https://bugzil.la/1938405 - https://bugzil.la/1938405
- https://bugzil.la/1960360 - https://bugzil.la/1960360
- https://bugzil.la/1961161
data_sensitivity: data_sensitivity:
- interaction - interaction
labels: labels:
@@ -408,6 +410,7 @@ tabgroup:
- new - new
- close_tabstrip - close_tabstrip
- close_tabmenu - close_tabmenu
- close_tab_other
- reorder - reorder
- remove_same_window - remove_same_window
- remove_other_window - remove_other_window

View File

@@ -514,6 +514,8 @@ tags = "vertical-tabs"
["browser_tab_groups_keyboard_focus.js"] ["browser_tab_groups_keyboard_focus.js"]
tags = "vertical-tabs" tags = "vertical-tabs"
["browser_tab_groups_tab_interactions_telemetry.js"]
["browser_tab_groups_telemetry.js"] ["browser_tab_groups_telemetry.js"]
["browser_tab_label_during_reload.js"] ["browser_tab_label_during_reload.js"]

View File

@@ -0,0 +1,393 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const {
openFirefoxViewTab,
closeFirefoxViewTab,
init: FirefoxViewTestUtilsInit,
} = ChromeUtils.importESModule(
"resource://testing-common/FirefoxViewTestUtils.sys.mjs"
);
FirefoxViewTestUtilsInit(this);
const { TabStateFlusher } = ChromeUtils.importESModule(
"resource:///modules/sessionstore/TabStateFlusher.sys.mjs"
);
const { UrlbarTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/UrlbarTestUtils.sys.mjs"
);
let resetTelemetry = async () => {
await Services.fog.testFlushAllChildren();
Services.fog.testResetFOG();
};
let assertMetricEmpty = async metricName => {
Assert.equal(
Glean.tabgroup.tabInteractions[metricName].testGetValue(),
null,
`tab_interactions.${metricName} starts empty`
);
};
let assertMetricFoundFor = async (metricName, count = 1) => {
await BrowserTestUtils.waitForCondition(() => {
return Glean.tabgroup.tabInteractions[metricName].testGetValue() == count;
}, `Wait for tab_interactions.${metricName} to be recorded`);
Assert.equal(
Glean.tabgroup.tabInteractions[metricName].testGetValue(),
count,
`tab_interactions.${metricName} was recorded`
);
};
let activateTabContextMenuItem = async (
selectedTab,
menuItemSelector,
submenuItemSelector
) => {
let submenuItem;
let submenuItemHiddenPromise;
const tabContextMenu = window.document.getElementById("tabContextMenu");
Assert.equal(
tabContextMenu.state,
"closed",
"context menu is initially closed"
);
const contextMenuShown = BrowserTestUtils.waitForEvent(
tabContextMenu,
"popupshown",
false,
ev => ev.target == tabContextMenu
);
EventUtils.synthesizeMouseAtCenter(
selectedTab,
{ type: "contextmenu", button: 2 },
window
);
await contextMenuShown;
if (submenuItemSelector) {
submenuItem = tabContextMenu.querySelector(submenuItemSelector);
const submenuPopupPromise = BrowserTestUtils.waitForEvent(
submenuItem.menupopup,
"popupshown"
);
submenuItem.openMenu(true);
await submenuPopupPromise;
submenuItemHiddenPromise = BrowserTestUtils.waitForEvent(
submenuItem.menupopup,
"popuphidden"
);
}
const contextMenuHidden = BrowserTestUtils.waitForEvent(
tabContextMenu,
"popuphidden",
false,
ev => ev.target == tabContextMenu
);
tabContextMenu.activateItem(tabContextMenu.querySelector(menuItemSelector));
await contextMenuHidden;
if (submenuItemSelector) {
await submenuItemHiddenPromise;
}
Assert.equal(tabContextMenu.state, "closed", "context menu is closed");
};
add_setup(async () => {
await SpecialPowers.pushPrefEnv({
set: [["browser.tabs.groups.enabled", true]],
});
window.gTabsPanel.init();
registerCleanupFunction(async () => {
await SpecialPowers.popPrefEnv();
});
});
add_task(async function test_tabInteractionsBasic() {
let initialTab = window.gBrowser.tabs[0];
await resetTelemetry();
let tab = BrowserTestUtils.addTab(window.gBrowser, "https://example.com");
let group = window.gBrowser.addTabGroup([tab]);
info(
"Test that selecting a tab in a group records tab_interactions.activate"
);
await assertMetricEmpty("activate");
const tabSelectEvent = BrowserTestUtils.waitForEvent(window, "TabSelect");
window.gBrowser.selectTabAtIndex(1);
await tabSelectEvent;
await assertMetricFoundFor("activate");
info(
"Test that moving an existing tab into a tab group records tab_interactions.add"
);
let tab1 = BrowserTestUtils.addTab(window.gBrowser, "https://example.com");
await assertMetricEmpty("add");
window.gBrowser.moveTabToGroup(tab1, group, { isUserTriggered: true });
await assertMetricFoundFor("add");
info(
"Test that adding a new tab to a tab group records tab_interactions.new"
);
await assertMetricEmpty("new");
BrowserTestUtils.addTab(window.gBrowser, "https://example.com", {
tabGroup: group,
});
await assertMetricFoundFor("new");
info("Test that moving a tab within a group calls tab_interactions.reorder");
await assertMetricEmpty("reorder");
window.gBrowser.moveTabTo(group.tabs[0], {
tabIndex: 3,
isUserTriggered: true,
});
await assertMetricFoundFor("reorder");
info(
"Test that duplicating a tab within a group calls tab_interactions.duplicate"
);
await assertMetricEmpty("duplicate");
window.gBrowser.duplicateTab(group.tabs[0], true, { tabIndex: 2 });
await assertMetricFoundFor("duplicate");
window.gBrowser.removeAllTabsBut(initialTab);
await resetTelemetry();
});
add_task(async function test_tabInteractionsClose() {
let initialTab = window.gBrowser.tabs[0];
await resetTelemetry();
FirefoxViewTestUtilsInit(this, window);
let tabs = Array.from({ length: 5 }, () => {
return BrowserTestUtils.addTab(window.gBrowser, "https://example.com", {
skipAnimation: true,
});
});
let group = window.gBrowser.addTabGroup(tabs);
info(
"Test that closing a tab using the tab's close button calls tab_interactions.close_tabstrip"
);
await assertMetricEmpty("close_tabstrip");
group.tabs.at(-1).querySelector(".tab-close-button").click();
await assertMetricFoundFor("close_tabstrip");
info(
"Test that closing a tab via the tab context menu calls tab_interactions.close_tabstrip"
);
await activateTabContextMenuItem(group.tabs[0], "#context_closeTab");
await assertMetricFoundFor("close_tabstrip", 2);
info(
"Test that closing a tab via the tab close keyboard shortcut calls tab_interactions.close_tab_other"
);
window.gBrowser.selectedTab = group.tabs.at(-1);
await assertMetricEmpty("close_tab_other");
EventUtils.synthesizeKey("w", { accelKey: true }, window);
await assertMetricFoundFor("close_tab_other");
info(
"Test that closing a tab via top menu calls tab_interactions.close_tab_other"
);
window.document.getElementById("cmd_close").doCommand();
await assertMetricFoundFor("close_tab_other", 2);
info(
"Test that closing a tab via firefox view calls tab_interactions.close_tab_other"
);
await openFirefoxViewTab(window).then(async viewTab => {
const openTabs = viewTab.linkedBrowser.contentDocument
.querySelector("named-deck > view-recentbrowsing view-opentabs")
.shadowRoot.querySelector("view-opentabs-card").tabList.rowEls;
const tabElement = Array.from(openTabs).find(t => t.__tabElement.group);
tabElement.shadowRoot.querySelector("moz-button.dismiss-button").click();
await assertMetricFoundFor("close_tab_other", 3);
});
await closeFirefoxViewTab(window);
window.gBrowser.removeAllTabsBut(initialTab);
await resetTelemetry();
});
add_task(async function test_tabInteractionsCloseViaAnotherTabContext() {
let initialTab = window.gBrowser.tabs[0];
await resetTelemetry();
window.gBrowser.addTabGroup([
BrowserTestUtils.addTab(window.gBrowser, "https://example.com", {
skipAnimation: true,
}),
]);
await assertMetricEmpty("close_tab_other");
info(
"Test that closing a tab via the tab context menu 'close other tabs' command calls tab_interactions.close_tab_other"
);
await activateTabContextMenuItem(
initialTab,
"#context_closeOtherTabs",
"#context_closeTabOptions"
);
await assertMetricFoundFor("close_tab_other");
info(
"Test that closing a tab via the tab context menu 'close tabs to left' command calls tab_interactions.close_tab_other"
);
window.gBrowser.addTabGroup([
BrowserTestUtils.addTab(window.gBrowser, "https://example.com", {
skipAnimation: true,
}),
]);
window.gBrowser.moveTabToEnd(initialTab);
await activateTabContextMenuItem(
initialTab,
"#context_closeTabsToTheStart",
"#context_closeTabOptions"
);
await assertMetricFoundFor("close_tab_other", 2);
info(
"Test that closing a tab via the tab context menu 'close tabs to right' command calls tab_interactions.close_tab_other"
);
window.gBrowser.addTabGroup([
BrowserTestUtils.addTab(window.gBrowser, "https://example.com", {
skipAnimation: true,
}),
]);
await activateTabContextMenuItem(
initialTab,
"#context_closeTabsToTheEnd",
"#context_closeTabOptions"
);
await assertMetricFoundFor("close_tab_other", 3);
info(
"Test that closing a tab via the tab context menu 'close duplicate tabs' command calls tab_interactions.close_tab_other"
);
let duplicateTabs = [
BrowserTestUtils.addTab(window.gBrowser, "https://example.com", {
skipAnimation: true,
}),
BrowserTestUtils.addTab(window.gBrowser, "https://example.com", {
skipAnimation: true,
}),
];
await Promise.all(
duplicateTabs.map(t => BrowserTestUtils.browserLoaded(t.linkedBrowser))
);
window.gBrowser.addTabGroup([duplicateTabs[1]]);
await activateTabContextMenuItem(
duplicateTabs[0],
"#context_closeDuplicateTabs",
"#context_closeTabOptions"
);
await assertMetricFoundFor("close_tab_other", 4);
window.gBrowser.removeAllTabsBut(initialTab);
await resetTelemetry();
});
add_task(async function test_tabInteractionsCloseTabOverflowMenu() {
let initialTab = window.gBrowser.tabs[0];
await resetTelemetry();
FirefoxViewTestUtilsInit(this, window);
let tab = BrowserTestUtils.addTab(window.gBrowser, "https://example.com", {
skipAnimation: true,
});
window.gBrowser.addTabGroup([tab]);
info(
"Test that closing a tab from the tab overflow menu calls tab_interactions.close_tabmenu"
);
let viewShown = BrowserTestUtils.waitForEvent(
window.document.getElementById("allTabsMenu-allTabsView"),
"ViewShown"
);
window.document.getElementById("alltabs-button").click();
await viewShown;
await assertMetricEmpty("close_tabmenu");
window.document
.querySelector(".all-tabs-item.grouped .all-tabs-close-button")
.click();
await assertMetricFoundFor("close_tabmenu");
let panel = window.document
.getElementById("allTabsMenu-allTabsView")
.closest("panel");
if (!panel) {
return;
}
let hidden = BrowserTestUtils.waitForPopupEvent(panel, "hidden");
panel.hidePopup();
await hidden;
window.gBrowser.removeAllTabsBut(initialTab);
await resetTelemetry();
});
add_task(async function test_tabInteractionsRemoveFromGroup() {
let initialTab = window.gBrowser.tabs[0];
await resetTelemetry();
let tabs = Array.from({ length: 3 }, () => {
return BrowserTestUtils.addTab(window.gBrowser, "https://example.com", {
skipAnimation: true,
});
});
let group = window.gBrowser.addTabGroup(tabs);
info(
"Test that moving a tab out of a tab group calls tab_interactions.remove_same_window"
);
await assertMetricEmpty("remove_same_window");
window.gBrowser.moveTabTo(group.tabs[0], {
tabIndex: 0,
isUserTriggered: true,
});
await assertMetricFoundFor("remove_same_window");
info(
"Test that moving a tab out of a tab group and into a different (existing) window calls tab_interactions.remove_other_window"
);
await assertMetricEmpty("remove_other_window");
let newWin = await BrowserTestUtils.openNewBrowserWindow();
newWin.gBrowser.adoptTab(group.tabs[0]);
await assertMetricFoundFor("remove_other_window");
await BrowserTestUtils.closeWindow(newWin);
info(
"Test that moving a tab out of a tab group and into a different (new) window calls tab_interactions.remove_new_window"
);
await assertMetricEmpty("remove_new_window");
let newWindowPromise = BrowserTestUtils.waitForNewWindow();
await EventUtils.synthesizePlainDragAndDrop({
srcElement: group.tabs[0],
srcWindow: window,
destElement: null,
// don't move horizontally because that could cause a tab move
// animation, and there's code to prevent a tab detaching if
// the dragged tab is released while the animation is running.
stepX: 0,
stepY: 100,
});
newWin = await newWindowPromise;
await assertMetricFoundFor("remove_new_window");
await BrowserTestUtils.closeWindow(newWin);
window.gBrowser.removeAllTabsBut(initialTab);
await resetTelemetry();
});

View File

@@ -715,128 +715,6 @@ add_task(async function test_tabContextMenu_addTabsToGroup() {
await resetTelemetry(); await resetTelemetry();
}); });
add_task(async function test_tabInteractions() {
let assertMetricEmpty = async metricName => {
Assert.equal(
Glean.tabgroup.tabInteractions[metricName].testGetValue(),
null,
`tab_interactions.${metricName} starts empty`
);
};
let assertOneMetricFoundFor = async metricName => {
await BrowserTestUtils.waitForCondition(() => {
return Glean.tabgroup.tabInteractions[metricName].testGetValue() !== null;
}, `Wait for tab_interactions.${metricName} to be recorded`);
Assert.equal(
Glean.tabgroup.tabInteractions[metricName].testGetValue(),
1,
`tab_interactions.${metricName} was recorded`
);
};
let initialTab = win.gBrowser.tabs[0];
await resetTelemetry();
let group = await makeTabGroup();
info(
"Test that selecting a tab in a group records tab_interactions.activate"
);
await assertMetricEmpty("activate");
const tabSelectEvent = BrowserTestUtils.waitForEvent(win, "TabSelect");
win.gBrowser.selectTabAtIndex(1);
await tabSelectEvent;
await assertOneMetricFoundFor("activate");
info(
"Test that moving an existing tab into a tab group records tab_interactions.add"
);
let tab1 = BrowserTestUtils.addTab(win.gBrowser, "https://example.com");
await assertMetricEmpty("add");
win.gBrowser.moveTabToGroup(tab1, group, { isUserTriggered: true });
await assertOneMetricFoundFor("add");
info(
"Test that adding a new tab to a tab group records tab_interactions.new"
);
await assertMetricEmpty("new");
BrowserTestUtils.addTab(win.gBrowser, "https://example.com", {
tabGroup: group,
});
await assertOneMetricFoundFor("new");
info("Test that moving a tab within a group calls tab_interactions.reorder");
await assertMetricEmpty("reorder");
win.gBrowser.moveTabTo(group.tabs[0], { tabIndex: 3, isUserTriggered: true });
await assertOneMetricFoundFor("reorder");
info(
"Test that duplicating a tab within a group calls tab_interactions.duplicate"
);
await assertMetricEmpty("duplicate");
win.gBrowser.duplicateTab(group.tabs[0], true, { tabIndex: 2 });
await assertOneMetricFoundFor("duplicate");
info(
"Test that closing a tab using the tab's close button calls tab_interactions.close_tabstrip"
);
await assertMetricEmpty("close_tabstrip");
group.tabs.at(-1).querySelector(".tab-close-button").click();
await assertOneMetricFoundFor("close_tabstrip");
info(
"Test that closing a tab from the tab overflow menu calls tab_interactions.close_tabmenu"
);
await openTabsMenu();
await assertMetricEmpty("close_tabmenu");
win.document
.querySelector(".all-tabs-item.grouped .all-tabs-close-button")
.click();
await assertOneMetricFoundFor("close_tabmenu");
await closeTabsMenu();
info(
"Test that moving a tab out of a tab group calls tab_interactions.remove_same_window"
);
await assertMetricEmpty("remove_same_window");
win.gBrowser.moveTabTo(group.tabs[0], { tabIndex: 0, isUserTriggered: true });
await assertOneMetricFoundFor("remove_same_window");
info(
"Test that moving a tab out of a tab group and into a different (existing) window calls tab_interactions.remove_other_window"
);
await assertMetricEmpty("remove_other_window");
let tab2 = BrowserTestUtils.addTab(win.gBrowser, "https://example.com");
win.gBrowser.moveTabToGroup(tab2, group, { isUserTriggered: true });
let newWin = await BrowserTestUtils.openNewBrowserWindow();
newWin.gBrowser.adoptTab(tab2);
await assertOneMetricFoundFor("remove_other_window");
await BrowserTestUtils.closeWindow(newWin);
info(
"Test that moving a tab out of a tab group and into a different (new) window calls tab_interactions.remove_new_window"
);
await assertMetricEmpty("remove_new_window");
let newWindowPromise = BrowserTestUtils.waitForNewWindow();
await EventUtils.synthesizePlainDragAndDrop({
srcElement: group.tabs[0],
srcWindow: win,
destElement: null,
// don't move horizontally because that could cause a tab move
// animation, and there's code to prevent a tab detaching if
// the dragged tab is released while the animation is running.
stepX: 0,
stepY: 100,
});
newWin = await newWindowPromise;
await assertOneMetricFoundFor("remove_new_window");
await BrowserTestUtils.closeWindow(newWin);
win.gBrowser.removeAllTabsBut(initialTab);
await resetTelemetry();
});
add_task(async function test_groupInteractions() { add_task(async function test_groupInteractions() {
await resetTelemetry(); await resetTelemetry();
let group = await makeTabGroup(); let group = await makeTabGroup();

View File

@@ -1269,13 +1269,16 @@ export let BrowserUsageTelemetry = {
_onTabClosed(event) { _onTabClosed(event) {
const group = event.target?.group; const group = event.target?.group;
const isUserTriggered = event.detail?.isUserTriggered;
const source = event.detail?.telemetrySource; const source = event.detail?.telemetrySource;
if (group) { if (group && isUserTriggered) {
if (source == lazy.TabMetrics.METRIC_SOURCE.TAB_STRIP) { if (source == lazy.TabMetrics.METRIC_SOURCE.TAB_STRIP) {
Glean.tabgroup.tabInteractions.close_tabstrip.add(); Glean.tabgroup.tabInteractions.close_tabstrip.add();
} else if (source == lazy.TabMetrics.METRIC_SOURCE.TAB_OVERFLOW_MENU) { } else if (source == lazy.TabMetrics.METRIC_SOURCE.TAB_OVERFLOW_MENU) {
Glean.tabgroup.tabInteractions.close_tabmenu.add(); Glean.tabgroup.tabInteractions.close_tabmenu.add();
} else {
Glean.tabgroup.tabInteractions.close_tab_other.add();
} }
} }
}, },