tor-browser

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

commit 59709991dc3f229b938da79c16f8ccd1f0cad32f
parent c469612c4a239e79d88ebdfb536d722faaa38dc0
Author: Andrea Marchesini <amarchesini@mozilla.com>
Date:   Thu, 13 Nov 2025 07:03:33 +0000

Bug 1999122 - Improve the IPProtection VPN add-on helper class, r=ip-protection-reviewers,sstreich

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

Diffstat:
Abrowser/components/ipprotection/IPPVPNAddonHelper.sys.mjs | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/ipprotection/IPProtectionHelpers.sys.mjs | 54+++++++++---------------------------------------------
Mbrowser/components/ipprotection/IPProtectionService.sys.mjs | 10++++++++++
Mbrowser/components/ipprotection/docs/Components.rst | 8++++----
Mbrowser/components/ipprotection/moz.build | 1+
Mbrowser/components/ipprotection/tests/browser/browser_IPProtectionService.js | 12+-----------
6 files changed, 89 insertions(+), 60 deletions(-)

diff --git a/browser/components/ipprotection/IPPVPNAddonHelper.sys.mjs b/browser/components/ipprotection/IPPVPNAddonHelper.sys.mjs @@ -0,0 +1,64 @@ +/* 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 lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", + IPProtectionService: + "resource:///modules/ipprotection/IPProtectionService.sys.mjs", +}); + +const VPN_ADDON_ID = "vpn@mozilla.com"; + +/** + * This class monitors the VPN add-on installation. + */ +class VPNAddonHelperSingleton { + #vpnAddonDetected = false; + + init() {} + + initOnStartupCompleted() { + const self = this; + this.addonVPNListener = { + onInstalled(addon) { + if (addon.id === VPN_ADDON_ID) { + self.#vpnAddonDetected = true; + lazy.IPProtectionService.updateState(); + } + }, + + onUninstalled(addon) { + if (addon.id === VPN_ADDON_ID) { + self.#vpnAddonDetected = false; + lazy.IPProtectionService.updateState(); + } + }, + }; + lazy.AddonManager.addAddonListener(this.addonVPNListener); + + lazy.AddonManager.readyPromise.then(() => { + lazy.AddonManager.getAddonByID(VPN_ADDON_ID).then(addon => { + this.#vpnAddonDetected = !!addon; + lazy.IPProtectionService.updateState(); + }); + }); + } + + uninit() { + if (this.addonVPNListener) { + lazy.AddonManager.removeAddonListener(this.addonVPNListener); + this.#vpnAddonDetected = false; + } + } + + get vpnAddonDetected() { + return this.#vpnAddonDetected; + } +} + +const IPPVPNAddonHelper = new VPNAddonHelperSingleton(); + +export { IPPVPNAddonHelper }; diff --git a/browser/components/ipprotection/IPProtectionHelpers.sys.mjs b/browser/components/ipprotection/IPProtectionHelpers.sys.mjs @@ -10,13 +10,9 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - AddonManager: "resource://gre/modules/AddonManager.sys.mjs", - CustomizableUI: - "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs", IPPExceptionsManager: "resource:///modules/ipprotection/IPPExceptionsManager.sys.mjs", IPProtection: "resource:///modules/ipprotection/IPProtection.sys.mjs", - IPProtectionWidget: "resource:///modules/ipprotection/IPProtection.sys.mjs", IPProtectionService: "resource:///modules/ipprotection/IPProtectionService.sys.mjs", IPProtectionStates: @@ -30,8 +26,7 @@ import { IPPNimbusHelper } from "resource:///modules/ipprotection/IPPNimbusHelpe import { IPProtectionServerlist } from "resource:///modules/ipprotection/IPProtectionServerlist.sys.mjs"; import { IPPSignInWatcher } from "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs"; import { IPPStartupCache } from "resource:///modules/ipprotection/IPPStartupCache.sys.mjs"; - -const VPN_ADDON_ID = "vpn@mozilla.com"; +import { IPPVPNAddonHelper } from "resource:///modules/ipprotection/IPPVPNAddonHelper.sys.mjs"; /** * This simple class controls the UI activation/deactivation. @@ -70,49 +65,18 @@ class UIHelper { lazy.IPProtection.init(); lazy.IPPExceptionsManager.init(); } - } -} - -/** - * This class removes the UI widget if the VPN add-on is installed. - */ -class VPNAddonHelper { - init() {} - /** - * Adds an observer to monitor the VPN add-on installation - */ - initOnStartupCompleted() { - this.addonVPNListener = { - onInstallEnded(_install, addon) { - if ( - addon.id === VPN_ADDON_ID && - IPPEnrollAndEntitleManager.hasUpgraded - ) { - // Place the widget in the customization palette. - lazy.CustomizableUI.removeWidgetFromArea( - lazy.IPProtectionWidget.WIDGET_ID - ); - } - }, - }; - - lazy.AddonManager.addInstallListener(this.addonVPNListener); - } - - /** - * Removes the VPN add-on installation observer - */ - uninit() { - if (this.addonVPNListener) { - lazy.AddonManager.removeInstallListener(this.addonVPNListener); + if ( + lazy.IPProtection.isInitialized && + (state === lazy.IPProtectionStates.UNINITIALIZED || + state === lazy.IPProtectionStates.UNAVAILABLE) + ) { + lazy.IPProtection.uninit(); + lazy.IPPExceptionsManager.uninit(); } } } -// 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 = [ IPPStartupCache, IPPSignInWatcher, @@ -120,7 +84,7 @@ const IPPHelpers = [ IPPEnrollAndEntitleManager, IPPProxyManager, new UIHelper(), - new VPNAddonHelper(), + IPPVPNAddonHelper, ...IPPAutoStartHelpers, IPPNimbusHelper, ]; diff --git a/browser/components/ipprotection/IPProtectionService.sys.mjs b/browser/components/ipprotection/IPProtectionService.sys.mjs @@ -14,6 +14,8 @@ ChromeUtils.defineESModuleGetters(lazy, { IPPNimbusHelper: "resource:///modules/ipprotection/IPPNimbusHelper.sys.mjs", IPPSignInWatcher: "resource:///modules/ipprotection/IPPSignInWatcher.sys.mjs", IPPStartupCache: "resource:///modules/ipprotection/IPPStartupCache.sys.mjs", + IPPVPNAddonHelper: + "resource:///modules/ipprotection/IPPVPNAddonHelper.sys.mjs", SpecialMessageActions: "resource://messaging-system/lib/SpecialMessageActions.sys.mjs", }); @@ -159,6 +161,14 @@ class IPProtectionServiceSingleton extends EventTarget { return lazy.IPPStartupCache.state; } + // If the VPN add-on is installed... + if ( + lazy.IPPVPNAddonHelper.vpnAddonDetected && + lazy.IPPEnrollAndEntitleManager.hasUpgraded + ) { + return IPProtectionStates.UNAVAILABLE; + } + // 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). diff --git a/browser/components/ipprotection/docs/Components.rst b/browser/components/ipprotection/docs/Components.rst @@ -30,12 +30,12 @@ A diagram of all the main components is the following: IPPStartupCache["Startup Cache Helper"] IPPSignInWatcher["Sign-in Observer"] IPProtectionServerlist - IPPEarlyStartupFilter["Early Startup Filter Helper"] + IPPEnrollAndEntitleManager["Enroll & Entitle Manager"] IPPProxyManager UIHelper["UI Helper"] - VPNAddonHelper["VPN Add-on Helper"] + IPPVPNAddonHelper["VPN Add-on Helper"] IPPAutoStart["Auto-Start Helper"] - IPPEnrollAndEntitleManager["Enroll & Entitle Manager"] + IPPEarlyStartupFilter["Early Startup Filter Helper"] IPPNimbusHelper["Nimbus Eligibility Helper"] end @@ -125,7 +125,7 @@ AccountResetHelper Resets stored account information and stops the proxy when the account becomes unavailable. -VPNAddonHelper +IPPVPNAddonHelper Monitors the installation of the Mozilla VPN add‑on and removes the UI when appropriate. diff --git a/browser/components/ipprotection/moz.build b/browser/components/ipprotection/moz.build @@ -26,6 +26,7 @@ EXTRA_JS_MODULES.ipprotection += [ "IPProtectionUsage.sys.mjs", "IPPSignInWatcher.sys.mjs", "IPPStartupCache.sys.mjs", + "IPPVPNAddonHelper.sys.mjs", ] BROWSER_CHROME_MANIFESTS += [ diff --git a/browser/components/ipprotection/tests/browser/browser_IPProtectionService.js b/browser/components/ipprotection/tests/browser/browser_IPProtectionService.js @@ -501,9 +501,6 @@ add_task(async function test_IPProtectionService_reload() { add_task(async function test_IPProtectionService_addon() { let cleanupAlpha = await setupExperiment({ enabled: true, variant: "alpha" }); let widget = document.getElementById(IPProtectionWidget.WIDGET_ID); - let prevPosition = CustomizableUI.getPlacementOfWidget( - IPProtectionWidget.WIDGET_ID - ).position; Assert.ok( BrowserTestUtils.isVisible(widget), @@ -542,12 +539,7 @@ add_task(async function test_IPProtectionService_addon() { "IP-Protection toolbaritem is removed" ); - // Reset to the toolbar - CustomizableUI.addWidgetToArea( - IPProtectionWidget.WIDGET_ID, - CustomizableUI.AREA_NAVBAR, - prevPosition - ); + await extension.unload(); widget = document.getElementById(IPProtectionWidget.WIDGET_ID); Assert.ok( @@ -555,8 +547,6 @@ add_task(async function test_IPProtectionService_addon() { "IP-Protection toolbaritem is re-added" ); - await extension.unload(); - cleanupService(); // hasUpgraded=false await IPPEnrollAndEntitleManager.refetchEntitlement();