tor-browser

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

commit 75ff40df4021290701c453be1eaaf120637d04b6
parent 35362df915a1b7c7cbca9ff65d73f473e548d2ae
Author: Nicholas Rishel <nrishel@mozilla.com>
Date:   Mon,  3 Nov 2025 22:45:36 +0000

Bug 1993688 - Flush tab state before getting setting store state to backup. r=mconley,sthompson

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

Diffstat:
Mbrowser/app/profile/firefox.js | 3+++
Mbrowser/components/backup/resources/SessionStoreBackupResource.sys.mjs | 42++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/backup/tests/marionette/test_backup.py | 32++++++++++++++++++++++++++++++++
3 files changed, 77 insertions(+), 0 deletions(-)

diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -3464,6 +3464,9 @@ pref("browser.backup.disabled-on-idle-backup-retry", false); // removing for any reason. pref("browser.backup.max-num-unremovable-staging-items", 5); pref("browser.backup.scheduled.user-disabled", false); +// How many milliseconds to wait for tab state to flush before continuing the +// backup process. +pref("browser.backup.tab-flush-timeout", 5000) #ifdef NIGHTLY_BUILD // Pref to enable the new profiles diff --git a/browser/components/backup/resources/SessionStoreBackupResource.sys.mjs b/browser/components/backup/resources/SessionStoreBackupResource.sys.mjs @@ -2,6 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + import { BackupResource, bytesToFuzzyKilobytes, @@ -10,9 +12,28 @@ import { const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { + BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", + setTimeout: "resource://gre/modules/Timer.sys.mjs", + TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs", +}); + +ChromeUtils.defineLazyGetter(lazy, "logConsole", function () { + return console.createInstance({ + prefix: "SessionStoreBackupResource", + maxLogLevel: Services.prefs.getBoolPref("browser.backup.log", false) + ? "Debug" + : "Warn", + }); }); +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "TAB_FLUSH_TIMEOUT", + "browser.backup.tab-flush-timeout", + 5000 +); + /** * Class representing Session store related files within a user profile. */ @@ -44,6 +65,27 @@ export class SessionStoreBackupResource extends BackupResource { profilePath = PathUtils.profileDir, _isEncrypting = false ) { + // Flush tab state so backups receive the correct url to restore. + await Promise.race([ + Promise.allSettled( + lazy.BrowserWindowTracker.orderedWindows.map( + lazy.TabStateFlusher.flushWindow + ) + ), + new Promise((_, reject) => + lazy.setTimeout(reject, lazy.TAB_FLUSH_TIMEOUT, { timeout: true }) + ), + ]).catch(e => { + if (e?.timeout) { + lazy.logConsole.warn("Timed out waiting while flushing tab state."); + } else { + lazy.logConsole.error( + "Unrecognized error while flushing tab state.", + e + ); + } + }); + let sessionStoreState = this.#sessionStore.getCurrentState(true); let sessionStorePath = PathUtils.join(stagingPath, "sessionstore.jsonlz4"); diff --git a/browser/components/backup/tests/marionette/test_backup.py b/browser/components/backup/tests/marionette/test_backup.py @@ -27,6 +27,9 @@ class BackupTest(MarionetteTestCase): "browser.backup.log": True, "browser.backup.archive.enabled": True, "browser.backup.restore.enabled": True, + # Necessary to test Session Restore from backup, which relies on + # the crash restore mechanism. + "browser.sessionstore.resume_from_crash": True, } ) @@ -94,6 +97,11 @@ class BackupTest(MarionetteTestCase): # to be flushed to disk and to be made ready for backup self.marionette.restart() + # We want to validate that TabState is flushed before serializing the + # backup, so run this test in the same browser instance we invoke the + # backup in. + self.add_test_sessionstore() + # Put the OSKeyStore label back, since it would have been cleared # from memory during the restart. self.marionette.execute_script( @@ -254,6 +262,7 @@ class BackupTest(MarionetteTestCase): self.verify_recovered_preferences() self.verify_recovered_permissions() self.verify_recovered_payment_methods(osKeyStoreLabel) + self.verify_recovered_sessionstore() # Clean up the temporary OSKeyStore label self.marionette.execute_async_script( @@ -859,3 +868,26 @@ class BackupTest(MarionetteTestCase): script_args=[osKeyStoreLabel], ) self.assertTrue(cardExists) + + def add_test_sessionstore(self): + with self.marionette.using_context("content"): + self.marionette.navigate("about:mozilla") + + def verify_recovered_sessionstore(self): + [tabCount, url] = self.marionette.execute_script( + """ + const { SessionStore } = ChromeUtils.importESModule( + "resource:///modules/sessionstore/SessionStore.sys.mjs" + ); + const session = SessionStore.getCurrentState(true); + const win = session.windows[0]; + const tabLen = win.tabs.length; + const tab = win.tabs[0]; + const entry = tab.entries[0]; + const url = entry.url; + return [tabLen, url]; + """ + ) + + self.assertEqual(tabCount, 1) + self.assertEqual(url, "about:mozilla")