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:
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();