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
if (gBrowser.multiSelectedTabsCount) {
gBrowser.removeMultiSelectedTabs();
gBrowser.removeMultiSelectedTabs(
gBrowser.TabMetrics.userTriggeredContext()
);
return;
}
@@ -414,7 +416,10 @@ var BrowserCommands = {
}
// 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) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -555,14 +555,18 @@
if (event.target.classList.contains("tab-close-button")) {
if (this.multiselected) {
gBrowser.removeMultiSelectedTabs({
telemetrySource: lazy.TabMetrics.METRIC_SOURCE.TAB_STRIP,
});
gBrowser.removeMultiSelectedTabs(
lazy.TabMetrics.userTriggeredContext(
lazy.TabMetrics.METRIC_SOURCE.TAB_STRIP
)
);
} else {
gBrowser.removeTab(this, {
animate: true,
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

View File

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

View File

@@ -514,6 +514,8 @@ tags = "vertical-tabs"
["browser_tab_groups_keyboard_focus.js"]
tags = "vertical-tabs"
["browser_tab_groups_tab_interactions_telemetry.js"]
["browser_tab_groups_telemetry.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();
});
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() {
await resetTelemetry();
let group = await makeTabGroup();

View File

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