fune/browser/base/content/browser-allTabsMenu.js
Chris H-C 0bf8a2c00f Bug 1889111 - Instrument Firefox Desktop UI Interactions with events r=TravisLong,kcochrane,Gijs
Assumption: Browser Usage Telemetry (ideally) records only and all interesting
interactions with Firefox Desktop's UI, and preserving syntax and semantics
when instrumenting using events is valuable.

Value this provides over existing keyed scalars:
* Order of operations (did three tabs open and then three tabs close, or did
  a single tab open-close three times?)
* Flow control (several atomic interactions combine to a user task. flow_id
  grouping allows us to see that easily in analysis. e.g. Open a tab, open
  prefs, privacy prefs, change a setting.)
* Glean

This is aiming for prototype quality and a prototype lifetime, to see if it's
worth investing more than just a week or two into.

Differential Revision: https://phabricator.services.mozilla.com/D207908
2024-05-03 15:19:15 +00:00

194 lines
5.7 KiB
JavaScript

/* 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/. */
// This file is loaded into the browser window scope.
/* eslint-env mozilla/browser-window */
ChromeUtils.defineESModuleGetters(this, {
BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
TabsPanel: "resource:///modules/TabsList.sys.mjs",
});
var gTabsPanel = {
kElements: {
allTabsButton: "alltabs-button",
allTabsView: "allTabsMenu-allTabsView",
allTabsViewTabs: "allTabsMenu-allTabsView-tabs",
dropIndicator: "allTabsMenu-dropIndicator",
containerTabsView: "allTabsMenu-containerTabsView",
hiddenTabsButton: "allTabsMenu-hiddenTabsButton",
hiddenTabsView: "allTabsMenu-hiddenTabsView",
},
_initialized: false,
_initializedElements: false,
initElements() {
if (this._initializedElements) {
return;
}
let template = document.getElementById("allTabsMenu-container");
template.replaceWith(template.content);
for (let [name, id] of Object.entries(this.kElements)) {
this[name] = document.getElementById(id);
}
this._initializedElements = true;
},
init() {
if (this._initialized) {
return;
}
this.initElements();
this.hiddenAudioTabsPopup = new TabsPanel({
view: this.allTabsView,
insertBefore: document.getElementById("allTabsMenu-tabsSeparator"),
filterFn: tab => tab.hidden && tab.soundPlaying,
});
let showPinnedTabs = Services.prefs.getBoolPref(
"browser.tabs.tabmanager.enabled"
);
this.allTabsPanel = new TabsPanel({
view: this.allTabsView,
containerNode: this.allTabsViewTabs,
filterFn: tab =>
!tab.hidden && (!tab.pinned || (showPinnedTabs && tab.pinned)),
dropIndicator: this.dropIndicator,
});
this.allTabsView.addEventListener("ViewShowing", () => {
PanelUI._ensureShortcutsShown(this.allTabsView);
let containersEnabled =
Services.prefs.getBoolPref("privacy.userContext.enabled") &&
!PrivateBrowsingUtils.isWindowPrivate(window);
document.getElementById("allTabsMenu-containerTabsButton").hidden =
!containersEnabled;
let hasHiddenTabs = gBrowser.visibleTabs.length < gBrowser.tabs.length;
document.getElementById("allTabsMenu-hiddenTabsButton").hidden =
!hasHiddenTabs;
document.getElementById("allTabsMenu-hiddenTabsSeparator").hidden =
!hasHiddenTabs;
});
this.allTabsView.addEventListener("ViewShown", () =>
this.allTabsView
.querySelector(".all-tabs-item[selected]")
?.scrollIntoView({ block: "center" })
);
let containerTabsMenuSeparator =
this.containerTabsView.querySelector("toolbarseparator");
this.containerTabsView.addEventListener("ViewShowing", e => {
let elements = [];
let frag = document.createDocumentFragment();
ContextualIdentityService.getPublicIdentities().forEach(identity => {
let menuitem = document.createXULElement("toolbarbutton");
menuitem.setAttribute("class", "subviewbutton subviewbutton-iconic");
if (identity.name) {
menuitem.setAttribute("label", identity.name);
} else {
document.l10n.setAttributes(menuitem, identity.l10nId);
}
// The styles depend on this.
menuitem.setAttribute("usercontextid", identity.userContextId);
// The command handler depends on this.
menuitem.setAttribute("data-usercontextid", identity.userContextId);
menuitem.classList.add("identity-icon-" + identity.icon);
menuitem.classList.add("identity-color-" + identity.color);
menuitem.setAttribute("command", "Browser:NewUserContextTab");
frag.appendChild(menuitem);
elements.push(menuitem);
});
e.target.addEventListener(
"ViewHiding",
() => {
for (let element of elements) {
element.remove();
}
},
{ once: true }
);
containerTabsMenuSeparator.parentNode.insertBefore(
frag,
containerTabsMenuSeparator
);
});
this.hiddenTabsPopup = new TabsPanel({
view: this.hiddenTabsView,
filterFn: tab => tab.hidden,
});
this._initialized = true;
},
get canOpen() {
this.initElements();
return isElementVisible(this.allTabsButton);
},
showAllTabsPanel(event, entrypoint = "unknown") {
// Note that event may be null.
// Only space and enter should open the popup, ignore other keypresses:
if (event?.type == "keypress" && event.key != "Enter" && event.key != " ") {
return;
}
this.init();
if (this.canOpen) {
Services.telemetry.keyedScalarAdd(
"browser.ui.interaction.all_tabs_panel_entrypoint",
entrypoint,
1
);
BrowserUsageTelemetry.recordInteractionEvent(
entrypoint,
"all-tabs-panel-entrypoint"
);
PanelUI.showSubView(
this.kElements.allTabsView,
this.allTabsButton,
event
);
}
},
hideAllTabsPanel() {
if (this.allTabsView) {
PanelMultiView.hidePopup(this.allTabsView.closest("panel"));
}
},
showHiddenTabsPanel(event, entrypoint = "unknown") {
this.init();
if (!this.canOpen) {
return;
}
this.allTabsView.addEventListener(
"ViewShown",
() => {
PanelUI.showSubView(
this.kElements.hiddenTabsView,
this.hiddenTabsButton
);
},
{ once: true }
);
this.showAllTabsPanel(event, entrypoint);
},
searchTabs() {
gURLBar.search(UrlbarTokenizer.RESTRICT.OPENPAGE, {
searchModeEntry: "tabmenu",
});
},
};