tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit a3f2b955ffe575ff63834121063b60c6e454aaa2
parent 9ba705aa632e333700c3a0663e5ea7c07895826a
Author: Stephen Thompson <sthompson@mozilla.com>
Date:   Tue, 14 Oct 2025 21:00:36 +0000

Bug 1988855 Part 2 - telemetry for opening links from external apps r=tabbrowser-reviewers,Gijs

When a user clicks a link in another app and that link opens in Firefox, we want to record this fact. We also want to record whether the resulting new tab was placed at the end of the tab strip or next to the active tab, which is governed by the new about:preferences setting "Open links from apps next to your active tab."

This telemetry event is intended to tell us whether people are using the "Open links from apps next to your active tab" feature or not.

Differential Revision: https://phabricator.services.mozilla.com/D266917

Diffstat:
Mbrowser/components/tabbrowser/content/tabbrowser.js | 12+++++++++++-
Mbrowser/components/tabbrowser/metrics.yaml | 23+++++++++++++++++++++++
Mbrowser/components/tabbrowser/test/browser/tabs/browser_tabs_openURI_after_current.js | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mbrowser/modules/BrowserUsageTelemetry.sys.mjs | 29+++++++++++++++++++++--------
4 files changed, 132 insertions(+), 23 deletions(-)

diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js @@ -2864,6 +2864,12 @@ /** * @param {string} uriString * @param {object} options + * @param {object} [options.eventDetail] + * Additional information to include in the `CustomEvent.detail` + * of the resulting TabOpen event. + * @param {boolean} [options.fromExternal] + * Whether this tab was opened from a URL supplied to Firefox from an + * external application. * @param {MozTabbrowserTabGroup} [options.tabGroup] * A related tab group where this tab should be added, when applicable. * When present, the tab is expected to reside in this tab group. When @@ -3080,7 +3086,11 @@ if (insertTab) { // Fire a TabOpen event - this._fireTabOpen(t, eventDetail); + const tabOpenDetail = { + ...eventDetail, + fromExternal, + }; + this._fireTabOpen(t, tabOpenDetail); this._kickOffBrowserLoad(b, { uri, diff --git a/browser/components/tabbrowser/metrics.yaml b/browser/components/tabbrowser/metrics.yaml @@ -810,3 +810,26 @@ link.handling: type: boolean no_lint: - COMMON_PREFIX + open_from_external_app: + type: event + disabled: true + description: > + This is recorded when a link is clicked outside of Firefox and this external + link is then opened inside of Firefox. The "Open links from apps next to + your active tab" option governs the behavior of where the link will be opened. + bugs: + - https://bugzil.la/1988855 + data_reviews: + - https://bugzil.la/1988855 + data_sensitivity: + - interaction + notification_emails: + - sthompson@mozilla.com + expires: never + extra_keys: + next_to_active_tab: + description: > + Whether the link was opened next to the active tab. + type: boolean + no_lint: + - COMMON_PREFIX diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_tabs_openURI_after_current.js b/browser/components/tabbrowser/test/browser/tabs/browser_tabs_openURI_after_current.js @@ -13,20 +13,17 @@ const { FirefoxViewTestUtilsInit(this, window); -add_setup(async () => { - await SpecialPowers.pushPrefEnv({ - set: [ - [ - "browser.link.open_newwindow.override.external", - Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT, - ], - ], - }); -}); - -registerCleanupFunction(async () => { - await SpecialPowers.popPrefEnv(); -}); +let resetTelemetry = async () => { + await Services.fog.testFlushAllChildren(); + Services.fog.testResetFOG(); + Services.fog.applyServerKnobsConfig( + JSON.stringify({ + metrics_enabled: { + "link.handling.open_from_external_app": true, + }, + }) + ); +}; /** * Simulates opening a link external to Firefox, returning the newly created tab. @@ -46,6 +43,14 @@ async function openExternalLink() { } add_task(async function test_browser_tabs_openURI_after_current() { + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "browser.link.open_newwindow.override.external", + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT, + ], + ], + }); info("Set up the initial conditions"); const initialTab = gBrowser.selectedTab; const pinnedTab1 = BrowserTestUtils.addTab(gBrowser, "about:blank", { @@ -149,4 +154,62 @@ add_task(async function test_browser_tabs_openURI_after_current() { BrowserTestUtils.removeTab(pinnedTab1); BrowserTestUtils.removeTab(pinnedTab2); BrowserTestUtils.removeTab(lastTab); + + await SpecialPowers.popPrefEnv(); }); + +/** + * @param {number} prefValue + * Set `browser.link.open_newwindow.override.external` to this value. + * @param {string} nextToActiveTabValue + * Check `Glean.linkHandling.openFromExternalApp` event `next_to_active_tab` + * value against this value. + */ +async function test_browser_tabs_openURI_external_telemetry( + prefValue, + nextToActiveTabValue +) { + await resetTelemetry(); + await SpecialPowers.pushPrefEnv({ + set: [["browser.link.open_newwindow.override.external", prefValue]], + }); + const tab = await openExternalLink(); + await TestUtils.waitForCondition( + () => Glean.linkHandling.openFromExternalApp.testGetValue()?.length == 1, + "wait for an event to be recorded" + ); + let openExternalLinkEvents = + Glean.linkHandling.openFromExternalApp.testGetValue(); + Assert.ok( + openExternalLinkEvents, + "there should have been an external link open event recorded" + ); + Assert.equal( + openExternalLinkEvents.length, + 1, + "one external link open event should have been recorded" + ); + Assert.equal( + openExternalLinkEvents[0].extra.next_to_active_tab, + nextToActiveTabValue, + "event should have recorded correct next_to_active_tab value" + ); + await SpecialPowers.popPrefEnv(); + BrowserTestUtils.removeTab(tab); + await resetTelemetry(); +} + +add_task( + function test_browser_tabs_openURI_external_telemetry_end_of_tab_strip() { + return test_browser_tabs_openURI_external_telemetry(-1, "false"); + } +); + +add_task( + function test_browser_tabs_openURI_external_telemetry_next_to_active_tab() { + return test_browser_tabs_openURI_external_telemetry( + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT, + "true" + ); + } +); diff --git a/browser/modules/BrowserUsageTelemetry.sys.mjs b/browser/modules/BrowserUsageTelemetry.sys.mjs @@ -56,6 +56,12 @@ XPCOMUtils.defineLazyPreferenceGetter( BrowserUsageTelemetry.recordPinnedTabsCount(pinnedTabCount); } ); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "openExternalLinkOverridePref", + "browser.link.open_newwindow.override.external", + -1 +); // The upper bound for the count of the visited unique domain names. const MAX_UNIQUE_VISITED_DOMAINS = 100; @@ -1226,19 +1232,18 @@ export let BrowserUsageTelemetry = { }, /** - * @param {number} prefValue - * pref `browser.link.open_newwindow.override.external` + * @returns {boolean} */ - _isOpenNextToActiveTabSettingEnabled(prefValue) { - return prefValue == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT; + _isOpenNextToActiveTabSettingEnabled() { + return ( + lazy.openExternalLinkOverridePref == + Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT + ); }, _recordOpenNextToActiveTabSettingValue() { - const value = Services.prefs.getIntPref( - "browser.link.open_newwindow.override.external" - ); Glean.linkHandling.openNextToActiveTabSettingsEnabled.set( - this._isOpenNextToActiveTabSettingEnabled(value) + this._isOpenNextToActiveTabSettingEnabled() ); }, @@ -1298,6 +1303,8 @@ export let BrowserUsageTelemetry = { /** * Updates the tab counts. + * @param {CustomEvent} [event] + * `TabOpen` event */ _onTabOpen(event) { // Update the "tab opened" count and its maximum. @@ -1311,6 +1318,12 @@ export let BrowserUsageTelemetry = { Glean.tabgroup.tabInteractions.new.add(); } + if (event?.detail?.fromExternal) { + Glean.linkHandling.openFromExternalApp.record({ + next_to_active_tab: this._isOpenNextToActiveTabSettingEnabled(), + }); + } + const userContextId = event?.target?.getAttribute("usercontextid"); if (userContextId) { Glean.containers.containerTabOpened.record({