commit 9f50838225c7945c02eab2680cbd2179c12dd7cd
parent d7ed9110cee36833ac790927fee7e6547bb79b7c
Author: Fred Chasen <fchasen@mozilla.com>
Date: Thu, 8 Jan 2026 01:00:00 +0000
Bug 2005086 - Auto start IPP Proxy on session restore. r=ip-protection-reviewers,baku
If the previous session had the VPN turned on by the user and will be restored, this will hold requests until the VPN has activated again.
- Uses the existing `browser.ipProtection.userEnabled` pref to check if the VPN was previously active.
- Adds an `IPPAutoRestoreSingleton` helper to check for `userEnabled` and if the session will restore on startup.
- If it should restore the previous session, it will hold requests via the `IPPEarlyStartupFilter` and start the proxy when ready.
- Disables the restore in `autoStartPref` is true, as the VPN will always auto start in `IPPAutoStartSingleton`.
- Adds and turns on `browser.ipProtection.autoRestoreEnabled` by default.
Differential Revision: https://phabricator.services.mozilla.com/D277470
Diffstat:
5 files changed, 256 insertions(+), 4 deletions(-)
diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js
@@ -3554,6 +3554,7 @@ pref("browser.ipProtection.features.autoStart", false);
// Prefs to track the user turning on autostart preference
pref("browser.ipProtection.autoStartEnabled", false);
pref("browser.ipProtection.autoStartPrivateEnabled", false);
+pref("browser.ipProtection.autoRestoreEnabled", true);
// Pref to track whether the user has turned IP protection on
pref("browser.ipProtection.userEnabled", false);
// Pref to track which experiment version the user is enrolled in
diff --git a/browser/components/ipprotection/IPPAutoStart.sys.mjs b/browser/components/ipprotection/IPPAutoStart.sys.mjs
@@ -17,10 +17,13 @@ ChromeUtils.defineESModuleGetters(lazy, {
"moz-src:///browser/components/ipprotection/IPProtectionService.sys.mjs",
IPProtectionStates:
"moz-src:///browser/components/ipprotection/IPProtectionService.sys.mjs",
+ SessionStartup: "resource:///modules/sessionstore/SessionStartup.sys.mjs",
});
const AUTOSTART_FEATURE_ENABLE_PREF = "browser.ipProtection.features.autoStart";
const AUTOSTART_PREF = "browser.ipProtection.autoStartEnabled";
+const USER_ENABLED_PREF = "browser.ipProtection.userEnabled";
+const AUTO_RESTORE_PREF = "browser.ipProtection.autoRestoreEnabled";
/**
* This class monitors the auto-start pref and if it sees a READY state, it
@@ -113,16 +116,116 @@ class IPPAutoStartSingleton {
const IPPAutoStart = new IPPAutoStartSingleton();
/**
+ * A helper that manages the auto-restore of the VPN connection on session restore.
+ * If the user had the VPN active before closing the browser and the session is
+ * being restored, this class will start the VPN again once the IPProtectionService
+ * reaches the READY state.
+ */
+export class IPPAutoRestoreSingleton {
+ #willRestore = false;
+ #forceRestore = false;
+
+ /**
+ * @class
+ * @param {boolean} forceRestore
+ */
+ constructor(forceRestore = false) {
+ this.#forceRestore = forceRestore;
+ this.handleEvent = this.#handleEvent.bind(this);
+
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "userEnabled",
+ USER_ENABLED_PREF,
+ false
+ );
+
+ // If auto-start is enabled, auto-restore is not needed.
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "autoStartPref",
+ AUTOSTART_PREF,
+ false
+ );
+
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "autoRestorePref",
+ AUTO_RESTORE_PREF,
+ false
+ );
+ }
+
+ init() {
+ if (!this.shouldRestore) {
+ return;
+ }
+ this.#willRestore = true;
+ lazy.IPProtectionService.addEventListener(
+ "IPProtectionService:StateChanged",
+ this.handleEvent
+ );
+ }
+
+ initOnStartupCompleted() {}
+
+ uninit() {
+ if (!this.#willRestore) {
+ return;
+ }
+ this.#willRestore = false;
+ lazy.IPProtectionService.removeEventListener(
+ "IPProtectionService:StateChanged",
+ this.handleEvent
+ );
+ }
+
+ get shouldRestore() {
+ if (!this.autoRestorePref || this.autoStartPref) {
+ return false;
+ }
+
+ if (this.#forceRestore) {
+ return this.userEnabled;
+ }
+
+ let willRestore =
+ lazy.SessionStartup.willRestore() &&
+ !lazy.SessionStartup.willRestoreAsCrashed();
+
+ return (
+ lazy.IPProtectionServerlist.hasList && willRestore && this.userEnabled
+ );
+ }
+
+ #handleEvent(_event) {
+ switch (lazy.IPProtectionService.state) {
+ case lazy.IPProtectionStates.READY:
+ lazy.IPPProxyManager.start(/* user action: */ false);
+ break;
+
+ default:
+ break;
+ }
+ // Only get the cached state.
+ this.uninit();
+ }
+}
+
+const IPPAutoRestore = new IPPAutoRestoreSingleton();
+
+/**
* This class monitors the startup phases and registers/unregisters the channel
* filter to avoid data leak. The activation of the VPN is done by the
- * IPPAutoStart object above.
+ * IPPAutoStart and IPPAutoRestore objects above.
*/
class IPPEarlyStartupFilter {
#autoStartAndAtStartup = false;
constructor() {
this.handleEvent = this.#handleEvent.bind(this);
- this.#autoStartAndAtStartup = IPPAutoStart.autoStart;
+ this.#autoStartAndAtStartup =
+ IPPAutoStart.autoStart || IPPAutoRestore.shouldRestore;
}
init() {
@@ -182,6 +285,10 @@ class IPPEarlyStartupFilter {
}
}
-const IPPAutoStartHelpers = [IPPAutoStart, new IPPEarlyStartupFilter()];
+const IPPAutoStartHelpers = [
+ IPPAutoRestore,
+ IPPAutoStart,
+ new IPPEarlyStartupFilter(),
+];
export { IPPAutoStartHelpers };
diff --git a/browser/components/ipprotection/IPProtectionService.sys.mjs b/browser/components/ipprotection/IPProtectionService.sys.mjs
@@ -98,7 +98,8 @@ class IPProtectionServiceSingleton extends EventTarget {
async maybeEarlyInit() {
if (
this.featureEnabled &&
- Services.prefs.getBoolPref("browser.ipProtection.autoStartEnabled")
+ (Services.prefs.getBoolPref("browser.ipProtection.autoStartEnabled") ||
+ Services.prefs.getBoolPref("browser.ipProtection.userEnabled"))
) {
await this.init();
}
diff --git a/browser/components/ipprotection/tests/xpcshell/test_IPPAutoStart.js b/browser/components/ipprotection/tests/xpcshell/test_IPPAutoStart.js
@@ -0,0 +1,141 @@
+/* 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/. */
+
+"use strict";
+
+const { IPPAutoRestoreSingleton } = ChromeUtils.importESModule(
+ "moz-src:///browser/components/ipprotection/IPPAutoStart.sys.mjs"
+);
+
+add_setup(async function () {
+ await putServerInRemoteSettings();
+ Services.prefs.setBoolPref("browser.ipProtection.autoRestoreEnabled", true);
+ IPProtectionService.uninit();
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("browser.ipProtection.userEnabled");
+ Services.prefs.clearUserPref("browser.ipProtection.autoRestoreEnabled");
+ IPProtectionService.init();
+ });
+});
+
+/**
+ * Tests that the VPN auto-starts when if the user had previously enabled it.
+ */
+add_task(async function test_IPPStart_AutoRestore_if_userEnabled() {
+ // Simulate user having previously enabled the VPN
+ Services.prefs.setBoolPref("browser.ipProtection.userEnabled", true);
+
+ let sandbox = sinon.createSandbox();
+ setupStubs(sandbox);
+
+ const autoRestore = new IPPAutoRestoreSingleton(true);
+
+ const waitForReady = waitForEvent(
+ IPProtectionService,
+ "IPProtectionService:StateChanged",
+ () => IPProtectionService.state === IPProtectionStates.READY
+ );
+
+ IPProtectionService.init();
+ autoRestore.init();
+
+ await waitForReady;
+
+ Assert.ok(
+ autoRestore.shouldRestore,
+ "Will auto-start when userEnabled is true"
+ );
+
+ Assert.equal(
+ IPPProxyManager.state,
+ IPPProxyStates.ACTIVATING,
+ "Proxy is activating"
+ );
+
+ autoRestore.uninit();
+ IPProtectionService.uninit();
+ sandbox.restore();
+});
+
+/**
+ * Tests that the VPN does not auto-start if the user had previously disabled it.
+ */
+add_task(async function test_IPPAutoStart_restore_if_userDisabled() {
+ // Simulate user having previously disabled the VPN
+ Services.prefs.setBoolPref("browser.ipProtection.userEnabled", false);
+
+ let sandbox = sinon.createSandbox();
+ setupStubs(sandbox);
+
+ const autoRestore = new IPPAutoRestoreSingleton(true);
+
+ const waitForReady = waitForEvent(
+ IPProtectionService,
+ "IPProtectionService:StateChanged",
+ () => IPProtectionService.state === IPProtectionStates.READY
+ );
+
+ IPProtectionService.init();
+ autoRestore.init();
+
+ await waitForReady;
+
+ Assert.ok(
+ !autoRestore.shouldRestore,
+ "Will not auto-start when userEnabled is false"
+ );
+
+ Assert.equal(
+ IPPProxyManager.state,
+ IPPProxyStates.READY,
+ "Proxy is still ready"
+ );
+
+ await IPPProxyManager.stop(false);
+
+ autoRestore.uninit();
+ IPProtectionService.uninit();
+ sandbox.restore();
+});
+
+/**
+ * Tests that the VPN does not auto-start if the state is not READY.
+ */
+add_task(async function test_IPPAutoStart_restore_if_notReady() {
+ // Simulate user having previously enabled the VPN
+ Services.prefs.setBoolPref("browser.ipProtection.userEnabled", true);
+
+ let sandbox = sinon.createSandbox();
+ setupStubs(sandbox);
+
+ const autoRestore = new IPPAutoRestoreSingleton(true);
+
+ const waitForUnavailable = waitForEvent(
+ IPProtectionService,
+ "IPProtectionService:StateChanged",
+ () => IPProtectionService.state === IPProtectionStates.UNAVAILABLE
+ );
+
+ IPProtectionService.init();
+ IPProtectionService.setState(IPProtectionStates.UNAVAILABLE);
+
+ autoRestore.init();
+ await waitForUnavailable;
+
+ Assert.ok(
+ autoRestore.shouldRestore,
+ "Can auto-start when userEnabled is true"
+ );
+
+ Assert.equal(
+ IPPProxyManager.state,
+ IPPProxyStates.READY,
+ "Proxy is still not ready"
+ );
+
+ autoRestore.uninit();
+ IPProtectionService.uninit();
+ sandbox.restore();
+});
diff --git a/browser/components/ipprotection/tests/xpcshell/xpcshell.toml b/browser/components/ipprotection/tests/xpcshell/xpcshell.toml
@@ -11,6 +11,8 @@ prefs = [
["test_GuardianClient.js"]
+["test_IPPAutoStart.js"]
+
["test_IPPChannelFilter.js"]
["test_IPPExceptionsManager.js"]