tor-browser

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

commit 186822e44db0341697995e2ea9c32268ee039f18
parent 165c9f04a88e3d694b4d06f72d2c3492d69f8aec
Author: Andrea Marchesini <amarchesini@mozilla.com>
Date:   Sat, 18 Oct 2025 08:13:49 +0000

Bug 1994974 - Introduce IPPNimbusHelper to entralize all the Nimbus calls, r=ip-protection-reviewers,fchasen

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

Diffstat:
Abrowser/components/ipprotection/IPPNimbusHelper.sys.mjs | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/ipprotection/IPProtectionHelpers.sys.mjs | 25+++----------------------
Mbrowser/components/ipprotection/IPProtectionService.sys.mjs | 22++--------------------
Mbrowser/components/ipprotection/docs/Components.rst | 12+++++++-----
Mbrowser/components/ipprotection/moz.build | 1+
Mbrowser/components/ipprotection/tests/xpcshell/test_IPProtectionStates.js | 9++++++---
6 files changed, 75 insertions(+), 50 deletions(-)

diff --git a/browser/components/ipprotection/IPPNimbusHelper.sys.mjs b/browser/components/ipprotection/IPPNimbusHelper.sys.mjs @@ -0,0 +1,56 @@ +/* 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/. */ + +/** + * Note: If you add or modify the list of helpers, make sure to update the + * corresponding documentation in the `docs` folder as well. + */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + IPProtectionService: + "resource:///modules/ipprotection/IPProtectionService.sys.mjs", + NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", +}); +/** + * This class monitors the eligibility flag from Nimbus + */ +class IPPNimbusHelperSingleton { + init() {} + + initOnStartupCompleted() { + lazy.NimbusFeatures.ipProtection.onUpdate( + lazy.IPProtectionService.updateState + ); + } + + uninit() { + lazy.NimbusFeatures.ipProtection.offUpdate( + lazy.IPProtectionService.updateState + ); + } + + /** + * Check if this device is in the experiment with a variant branch. + * + * @returns {boolean} + */ + get isEligible() { + let inExperiment = lazy.NimbusFeatures.ipProtection.getEnrollmentMetadata(); + let isEligible = inExperiment?.branch && inExperiment.branch !== "control"; + + if (inExperiment) { + lazy.NimbusFeatures.ipProtection.recordExposureEvent({ + once: true, + }); + } + + return isEligible; + } +} + +const IPPNimbusHelper = new IPPNimbusHelperSingleton(); + +export { IPPNimbusHelper }; diff --git a/browser/components/ipprotection/IPProtectionHelpers.sys.mjs b/browser/components/ipprotection/IPProtectionHelpers.sys.mjs @@ -21,10 +21,10 @@ ChromeUtils.defineESModuleGetters(lazy, { "resource:///modules/ipprotection/IPProtectionService.sys.mjs", IPProtectionStates: "resource:///modules/ipprotection/IPProtectionService.sys.mjs", - NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", }); import { IPPAutoStartHelpers } from "resource:///modules/ipprotection/IPPAutoStart.sys.mjs"; +import { IPPNimbusHelper } from "resource:///modules/ipprotection/IPPNimbusHelper.sys.mjs"; import { IPPSignInWatcher } from "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs"; import { IPPStartupCache } from "resource:///modules/ipprotection/IPPStartupCache.sys.mjs"; @@ -142,26 +142,7 @@ class VPNAddonHelper { } } -/** - * This class monitors the eligibility flag from Nimbus - */ -class EligibilityHelper { - init() {} - - initOnStartupCompleted() { - lazy.NimbusFeatures.ipProtection.onUpdate( - lazy.IPProtectionService.updateState - ); - } - - uninit() { - lazy.NimbusFeatures.ipProtection.offUpdate( - lazy.IPProtectionService.updateState - ); - } -} - -// The order is important! Eligibility must be the last one because nimbus +// The order is important! NimbusHelper must be the last one because nimbus // triggers the callback immdiately, which could compute a new state for all // the helpers. const IPPHelpers = [ @@ -170,8 +151,8 @@ const IPPHelpers = [ new UIHelper(), new AccountResetHelper(), new VPNAddonHelper(), - new EligibilityHelper(), ...IPPAutoStartHelpers, + IPPNimbusHelper, ]; export { IPPHelpers }; diff --git a/browser/components/ipprotection/IPProtectionService.sys.mjs b/browser/components/ipprotection/IPProtectionService.sys.mjs @@ -9,12 +9,12 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { GuardianClient: "resource:///modules/ipprotection/GuardianClient.sys.mjs", IPPHelpers: "resource:///modules/ipprotection/IPProtectionHelpers.sys.mjs", + IPPNimbusHelper: "resource:///modules/ipprotection/IPPNimbusHelper.sys.mjs", IPPProxyManager: "resource:///modules/ipprotection/IPPProxyManager.sys.mjs", IPPSignInWatcher: "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs", IPPStartupCache: "resource:///modules/ipprotection/IPPStartupCache.sys.mjs", SpecialMessageActions: "resource://messaging-system/lib/SpecialMessageActions.sys.mjs", - NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", }); import { @@ -322,24 +322,6 @@ class IPProtectionServiceSingleton extends EventTarget { } /** - * Check if this device is in the experiment with a variant branch. - * - * @returns {boolean} - */ - get isEligible() { - let inExperiment = lazy.NimbusFeatures.ipProtection.getEnrollmentMetadata(); - let isEligible = inExperiment?.branch && inExperiment.branch !== "control"; - - if (inExperiment) { - lazy.NimbusFeatures.ipProtection.recordExposureEvent({ - once: true, - }); - } - - return isEligible; - } - - /** * Clear the current entitlement and requests a state update to dispatch * the current hasUpgraded status. * @@ -461,7 +443,7 @@ class IPProtectionServiceSingleton extends EventTarget { // For non authenticated users, we can check if they are eligible (the UI // is shown and they have to login) or we don't know yet their current // enroll state (no UI is shown). - let eligible = this.isEligible; + let eligible = lazy.IPPNimbusHelper.isEligible; if (!lazy.IPPSignInWatcher.isSignedIn) { return !eligible ? IPProtectionStates.UNAVAILABLE diff --git a/browser/components/ipprotection/docs/Components.rst b/browser/components/ipprotection/docs/Components.rst @@ -32,7 +32,7 @@ A diagram of all the main components is the following: UIHelper["UI Helper"] AccountResetHelper["Account Reset Helper"] VPNAddonHelper["VPN Add-on Helper"] - EligibilityHelper["Nimbus Eligibility Helper"] + IPPNimbusHelper["Nimbus Eligibility Helper"] IPPAutoStart["Auto-Start Helper"] IPPEarlyStartupFilter["Early Startup Filter Helper"] end @@ -132,8 +132,9 @@ VPNAddonHelper Monitors the installation of the Mozilla VPN add‑on and removes the UI when appropriate. -EligibilityHelper - Monitors the Nimbus experiment flag and triggers state updates when it changes. +IPPNimbusHelper + Monitors the Nimbus feature (``NimbusFeatures.ipProtection``) and triggers a + state recomputation on updates. How to implement new components ------------------------------- @@ -148,7 +149,8 @@ Recommended steps: 2. If your helper reacts to state changes, listen to the ``IPProtectionService:StateChanged`` event. 3. Add your helper to the ``IPPHelpers`` array in ``IPProtectionHelpers.sys.mjs``. - Be mindful of ordering if your helper depends on others (e.g. Nimbus - eligibility is registered last to avoid premature updates). + Be mindful of ordering if your helper depends on others. For example, + ``IPPNimbusHelper`` is registered last to avoid premature state updates + triggered by Nimbus’ immediate callback. 4. If your component needs to trigger a recomputation, call ``IPProtectionService.updateState``. diff --git a/browser/components/ipprotection/moz.build b/browser/components/ipprotection/moz.build @@ -15,6 +15,7 @@ EXTRA_JS_MODULES.ipprotection += [ "IPPChannelFilter.sys.mjs", "IPPExceptionsManager.sys.mjs", "IPPNetworkErrorObserver.sys.mjs", + "IPPNimbusHelper.sys.mjs", "IPPProxyManager.sys.mjs", "IPProtection.sys.mjs", "IPProtectionHelpers.sys.mjs", diff --git a/browser/components/ipprotection/tests/xpcshell/test_IPProtectionStates.js b/browser/components/ipprotection/tests/xpcshell/test_IPProtectionStates.js @@ -6,6 +6,9 @@ https://creativecommons.org/publicdomain/zero/1.0/ */ const { IPProtectionService, IPProtectionStates } = ChromeUtils.importESModule( "resource:///modules/ipprotection/IPProtectionService.sys.mjs" ); +const { IPPNimbusHelper } = ChromeUtils.importESModule( + "resource:///modules/ipprotection/IPPNimbusHelper.sys.mjs" +); const { IPPSignInWatcher } = ChromeUtils.importESModule( "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs" ); @@ -66,7 +69,7 @@ add_task(async function test_IPProtectionStates_uninitialized() { "IP Protection service should be unavailable" ); - sandbox.stub(IPProtectionService, "isEligible").get(() => true); + sandbox.stub(IPPNimbusHelper, "isEligible").get(() => true); await IPProtectionService.updateState(); @@ -98,7 +101,7 @@ add_task(async function test_IPProtectionStates_unauthenticated() { "IP Protection service should be unavailable" ); - sandbox.stub(IPProtectionService, "isEligible").get(() => true); + sandbox.stub(IPPNimbusHelper, "isEligible").get(() => true); await IPProtectionService.updateState(); @@ -131,7 +134,7 @@ add_task(async function test_IPProtectionStates_enrolling() { sandbox .stub(IPProtectionService.guardian, "isLinkedToGuardian") .resolves(false); - sandbox.stub(IPProtectionService, "isEligible").get(() => true); + sandbox.stub(IPPNimbusHelper, "isEligible").get(() => true); sandbox.stub(IPProtectionService.guardian, "enroll").resolves({ ok: true }); sandbox.stub(IPProtectionService.guardian, "fetchUserInfo").resolves({ status: 200,