tor-browser

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

commit 9a9da20b36d291ba5eb3cf67302d03b89b09cb88
parent 8ebd5f6b649c03877be37fe90d0f58ea81f1307d
Author: Stephen Thompson <sthompson@mozilla.com>
Date:   Wed, 22 Oct 2025 14:02:08 +0000

Bug 1979133 - integrate Nimbus for experimenting with opening external links after the active tab r=jswinarton,tabbrowser-reviewers,Gijs

Direct pref access to `browser.link.open_newwindow.override.external` is replaced by a Nimbus variable `openBehavior` in a new Nimbus feature, `externalLinkHandling`. The Nimbus variable falls back to the pref value.

I also enabled the Glean metric tab_movement and event open_from_external_app. I had them start disabled because I believed that Glean's Server Knobs could turn on the telemetry just for people enrolled in the experiment. It doesn't seem like that's true, so it's easier right now to just have these pieces of telemetry enabled for everyone. These telemetry need to be in place to measure the results of the Nimbus experiment, so I included these changes in this patch.

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

Diffstat:
Mbrowser/components/tabbrowser/metrics.yaml | 2--
Mbrowser/components/tabbrowser/test/browser/tabs/browser_move_externally_opened_tabs_telemetry.js | 10----------
Mbrowser/components/tabbrowser/test/browser/tabs/browser_tabs_openURI_after_current.js | 62+++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mbrowser/modules/BrowserDOMWindow.sys.mjs | 23+++++++++++++----------
Mbrowser/modules/BrowserUsageTelemetry.sys.mjs | 12+++++-------
Mtoolkit/components/nimbus/FeatureManifest.yaml | 24++++++++++++++++++++++++
6 files changed, 97 insertions(+), 36 deletions(-)

diff --git a/browser/components/tabbrowser/metrics.yaml b/browser/components/tabbrowser/metrics.yaml @@ -134,7 +134,6 @@ browser.ui.interaction: tab_movement: type: labeled_counter - disabled: true description: > Records information about user tab movements within the tab strip. bugs: @@ -827,7 +826,6 @@ link.handling: - 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 diff --git a/browser/components/tabbrowser/test/browser/tabs/browser_move_externally_opened_tabs_telemetry.js b/browser/components/tabbrowser/test/browser/tabs/browser_move_externally_opened_tabs_telemetry.js @@ -6,16 +6,6 @@ let resetTelemetry = async () => { await Services.fog.testFlushAllChildren(); Services.fog.testResetFOG(); - // `browser.ui.interaction.tab_movement` is disabled by default and enabled - // by server knobs, so this test needs to enable it manually in order - // to test it. - Services.fog.applyServerKnobsConfig( - JSON.stringify({ - metrics_enabled: { - "browser.ui.interaction.tab_movement": true, - }, - }) - ); }; /** 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 @@ -11,18 +11,15 @@ const { "resource://testing-common/FirefoxViewTestUtils.sys.mjs" ); +const { NimbusTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/NimbusTestUtils.sys.mjs" +); + FirefoxViewTestUtilsInit(this, window); let resetTelemetry = async () => { await Services.fog.testFlushAllChildren(); Services.fog.testResetFOG(); - Services.fog.applyServerKnobsConfig( - JSON.stringify({ - metrics_enabled: { - "link.handling.open_from_external_app": true, - }, - }) - ); }; /** @@ -158,6 +155,57 @@ add_task(async function test_browser_tabs_openURI_after_current() { await SpecialPowers.popPrefEnv(); }); +add_task(async function test_browser_tabs_nimbus_external_link_handling() { + const tab1 = BrowserTestUtils.addTab(gBrowser, "about:blank", { + skipAnimation: true, + }); + const tab2 = BrowserTestUtils.addTab(gBrowser, "about:blank", { + skipAnimation: true, + }); + const tab3 = BrowserTestUtils.addTab(gBrowser, "about:blank", { + skipAnimation: true, + }); + gBrowser.selectedTab = tab1; + + let doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({ + featureId: "externalLinkHandling", + value: { + openBehavior: Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT, + }, + }); + + const tabWithExperimentOn = await openExternalLink(); + Assert.equal( + tabWithExperimentOn.elementIndex, + tab1.elementIndex + 1, + "tab should have been opened next to the active tab" + ); + + await doExperimentCleanup(); + + doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({ + featureId: "externalLinkHandling", + value: { + openBehavior: -1, + }, + }); + + const tabWithExperimentOff = await openExternalLink(); + Assert.equal( + tabWithExperimentOff.elementIndex, + tab3.elementIndex + 1, + "tab should have been opened at the end of the tab strip" + ); + + await doExperimentCleanup(); + + BrowserTestUtils.removeTab(tab1); + BrowserTestUtils.removeTab(tabWithExperimentOn); + BrowserTestUtils.removeTab(tab2); + BrowserTestUtils.removeTab(tab3); + BrowserTestUtils.removeTab(tabWithExperimentOff); +}); + /** * @param {number} prefValue * Set `browser.link.open_newwindow.override.external` to this value. diff --git a/browser/modules/BrowserDOMWindow.sys.mjs b/browser/modules/BrowserDOMWindow.sys.mjs @@ -11,8 +11,9 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; let lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs", + NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", TaskbarTabsUtils: "resource:///modules/taskbartabs/TaskbarTabsUtils.sys.mjs", + URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs", }); XPCOMUtils.defineLazyPreferenceGetter( @@ -265,16 +266,18 @@ export class BrowserDOMWindow { return null; } + if (isExternal) { + lazy.NimbusFeatures.externalLinkHandling.recordExposureEvent({ + once: true, + }); + } + if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { - if ( - isExternal && - Services.prefs.prefHasUserValue( - "browser.link.open_newwindow.override.external" - ) - ) { - aWhere = Services.prefs.getIntPref( - "browser.link.open_newwindow.override.external" - ); + /** @type {number} proxy for `browser.link.open_newwindow.override.external` */ + const externalLinkOpeningBehavior = + lazy.NimbusFeatures.externalLinkHandling.getVariable("openBehavior"); + if (isExternal && externalLinkOpeningBehavior != -1) { + aWhere = externalLinkOpeningBehavior; } else { aWhere = Services.prefs.getIntPref("browser.link.open_newwindow"); } diff --git a/browser/modules/BrowserUsageTelemetry.sys.mjs b/browser/modules/BrowserUsageTelemetry.sys.mjs @@ -13,6 +13,7 @@ ChromeUtils.defineESModuleGetters(lazy, { CustomizableUI: "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs", DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs", + NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", PageActions: "resource:///modules/PageActions.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", SearchSERPTelemetry: @@ -56,12 +57,6 @@ 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; @@ -1241,8 +1236,11 @@ export let BrowserUsageTelemetry = { * @returns {boolean} */ _isOpenNextToActiveTabSettingEnabled() { + /** @type {number} proxy for `browser.link.open_newwindow.override.external` */ + const externalLinkOpeningBehavior = + lazy.NimbusFeatures.externalLinkHandling.getVariable("openBehavior"); return ( - lazy.openExternalLinkOverridePref == + externalLinkOpeningBehavior == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT ); }, diff --git a/toolkit/components/nimbus/FeatureManifest.yaml b/toolkit/components/nimbus/FeatureManifest.yaml @@ -5166,6 +5166,30 @@ smartTabGroups: pref: browser.tabs.groups.smart.searchTopicEnabled description: For single tab groupings, use the search query as the topic rather than use ML +externalLinkHandling: + description: Controls how externally-opened URLs will be handled by Firefox. + owner: sthompson@mozilla.com + hasExposure: true + exposureDescription: >- + Exposure is recorded when a user clicks on a link outside of Firefox and + that link is opened by Firefox. + variables: + openBehavior: + description: >- + Control how external links from another application should be opened in Firefox. + See the nsIBrowserDOMWindow `OPEN_*` constants for what the values can do. + The default value for the backing pref is `-1`. When that pref has the default + value, the code will fall back to the value of the general pref that handles + opening links, i.e. `browser.link.open_newwindow`, which defaults to `3`. + type: int + enum: + - -1 # use default browser behavior + - 2 # `OPEN_NEWWINDOW` open external links in a new window + - 3 # `OPEN_NEWTAB` open in a new tab in the topmost window at the end of the tab strip + - 6 # `OPEN_NEWTAB_FOREGROUND` open in a new tab in the topmost window at the end of the tab strip and select it + - 7 # `OPEN_NEWTAB_AFTER_CURRENT` open in a new tab in the topmost window next to the active tab + fallbackPref: browser.link.open_newwindow.override.external + smartblockEmbeds: description: Pref to control Smartblock Embeds owner: emz@mozilla.com