tor-browser

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

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:
Mbrowser/app/profile/firefox.js | 1+
Mbrowser/components/ipprotection/IPPAutoStart.sys.mjs | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mbrowser/components/ipprotection/IPProtectionService.sys.mjs | 3++-
Abrowser/components/ipprotection/tests/xpcshell/test_IPPAutoStart.js | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/ipprotection/tests/xpcshell/xpcshell.toml | 2++
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"]