tor-browser

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

commit 29023a6ade3da48075f4084350fd61067f405fbc
parent 84ad56baf96e2a6167ccb4e7d6ece2bde731a6db
Author: Harsheet <hsohaney@mozilla.com>
Date:   Tue,  7 Oct 2025 20:21:47 +0000

Bug 1990517 - Show restore errors if any restore resource's fail. r=sthompson

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

Diffstat:
Mbrowser/components/backup/BackupService.sys.mjs | 2++
Mbrowser/components/backup/tests/xpcshell/head.js | 93+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mbrowser/components/backup/tests/xpcshell/test_BackupService.js | 44+-------------------------------------------
Mbrowser/components/backup/tests/xpcshell/test_BackupService_retryHeuristic.js | 39+--------------------------------------
Abrowser/components/backup/tests/xpcshell/test_ResourceFailures.js | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/backup/tests/xpcshell/xpcshell.toml | 2++
6 files changed, 176 insertions(+), 116 deletions(-)

diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs @@ -2684,6 +2684,7 @@ export class BackupService extends EventTarget { } catch (_) { lazy.logConsole.warn("Could not remove ", RECOVERY_FILE_DEST_PATH); } + try { // We're using a try/finally here to clean up the temporary OSKeyStore. // We need to make sure that cleanup occurs _after_ the recovery has @@ -2877,6 +2878,7 @@ export class BackupService extends EventTarget { `Failed to recover resource: ${resourceKey}`, e ); + throw e; } } diff --git a/browser/components/backup/tests/xpcshell/head.js b/browser/components/backup/tests/xpcshell/head.js @@ -4,41 +4,18 @@ "use strict"; -const { BackupService } = ChromeUtils.importESModule( - "resource:///modules/backup/BackupService.sys.mjs" -); - -const { BackupResource } = ChromeUtils.importESModule( - "resource:///modules/backup/BackupResource.sys.mjs" -); - -const { MeasurementUtils } = ChromeUtils.importESModule( - "resource:///modules/backup/MeasurementUtils.sys.mjs" -); - -const { TelemetryTestUtils } = ChromeUtils.importESModule( - "resource://testing-common/TelemetryTestUtils.sys.mjs" -); - -const { Sqlite } = ChromeUtils.importESModule( - "resource://gre/modules/Sqlite.sys.mjs" -); - -const { sinon } = ChromeUtils.importESModule( - "resource://testing-common/Sinon.sys.mjs" -); - -const { OSKeyStoreTestUtils } = ChromeUtils.importESModule( - "resource://testing-common/OSKeyStoreTestUtils.sys.mjs" -); - -const { MockRegistrar } = ChromeUtils.importESModule( - "resource://testing-common/MockRegistrar.sys.mjs" -); - -const { PrivateBrowsingUtils } = ChromeUtils.importESModule( - "resource://gre/modules/PrivateBrowsingUtils.sys.mjs" -); +ChromeUtils.defineESModuleGetters(this, { + BackupService: "resource:///modules/backup/BackupService.sys.mjs", + BackupResource: "resource:///modules/backup/BackupResource.sys.mjs", + MeasurementUtils: "resource:///modules/backup/MeasurementUtils.sys.mjs", + TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs", + Sqlite: "resource://gre/modules/Sqlite.sys.mjs", + sinon: "resource://testing-common/Sinon.sys.mjs", + OSKeyStoreTestUtils: "resource://testing-common/OSKeyStoreTestUtils.sys.mjs", + MockRegistrar: "resource://testing-common/MockRegistrar.sys.mjs", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", + AppConstants: "resource://gre/modules/AppConstants.sys.mjs", +}); const HISTORY_ENABLED_PREF = "places.history.enabled"; const SANITIZE_ON_SHUTDOWN_PREF = "privacy.sanitize.sanitizeOnShutdown"; @@ -349,6 +326,52 @@ function countHistogramMeasurements(histogram) { return countsPerBucket.reduce((sum, count) => sum + count, 0); } +function setupProfile() { + // FOG needs to be initialized in order for data to flow. + Services.fog.initializeFOG(); + + // Much of this setup is copied from toolkit/profile/xpcshell/head.js. It is + // needed in order to put the xpcshell test environment into the state where + // it thinks its profile is the one pointed at by + // nsIToolkitProfileService.currentProfile. + let gProfD = do_get_profile(); + let gDataHome = gProfD.clone(); + gDataHome.append("data"); + gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + let gDataHomeLocal = gProfD.clone(); + gDataHomeLocal.append("local"); + gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + + let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService( + Ci.nsIXREDirProvider + ); + xreDirProvider.setUserDataDirectory(gDataHome, false); + xreDirProvider.setUserDataDirectory(gDataHomeLocal, true); + + let profileSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService( + Ci.nsIToolkitProfileService + ); + + let createdProfile = {}; + let didCreate = profileSvc.selectStartupProfile( + ["xpcshell"], + false, + AppConstants.UPDATE_CHANNEL, + "", + {}, + {}, + createdProfile + ); + Assert.ok(didCreate, "Created a testing profile and set it to current."); + Assert.equal( + profileSvc.currentProfile, + createdProfile.value, + "Profile set to current" + ); + + return createdProfile.value; +} + /** * Asserts that a histogram received a certain number of measurements, regardless * of the values of the measurements themselves. diff --git a/browser/components/backup/tests/xpcshell/test_BackupService.js b/browser/components/backup/tests/xpcshell/test_BackupService.js @@ -28,49 +28,7 @@ const LAST_BACKUP_FILE_NAME_PREF_NAME = let currentProfile; add_setup(function () { - // FOG needs to be initialized in order for data to flow. - Services.fog.initializeFOG(); - - // Much of this setup is copied from toolkit/profile/xpcshell/head.js. It is - // needed in order to put the xpcshell test environment into the state where - // it thinks its profile is the one pointed at by - // nsIToolkitProfileService.currentProfile. - let gProfD = do_get_profile(); - let gDataHome = gProfD.clone(); - gDataHome.append("data"); - gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); - let gDataHomeLocal = gProfD.clone(); - gDataHomeLocal.append("local"); - gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); - - let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService( - Ci.nsIXREDirProvider - ); - xreDirProvider.setUserDataDirectory(gDataHome, false); - xreDirProvider.setUserDataDirectory(gDataHomeLocal, true); - - let profileSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService( - Ci.nsIToolkitProfileService - ); - - let createdProfile = {}; - let didCreate = profileSvc.selectStartupProfile( - ["xpcshell"], - false, - AppConstants.UPDATE_CHANNEL, - "", - {}, - {}, - createdProfile - ); - Assert.ok(didCreate, "Created a testing profile and set it to current."); - Assert.equal( - profileSvc.currentProfile, - createdProfile.value, - "Profile set to current" - ); - - currentProfile = createdProfile.value; + currentProfile = setupProfile(); }); /** diff --git a/browser/components/backup/tests/xpcshell/test_BackupService_retryHeuristic.js b/browser/components/backup/tests/xpcshell/test_BackupService_retryHeuristic.js @@ -50,44 +50,7 @@ add_setup(async () => { Services.prefs.setIntPref(BACKUP_RETRY_LIMIT_PREF_NAME, 2); Services.prefs.setBoolPref(DISABLED_ON_IDLE_RETRY_PREF_NAME, false); - // Much of this setup is copied from toolkit/profile/xpcshell/head.js. It is - // needed in order to put the xpcshell test environment into the state where - // it thinks its profile is the one pointed at by - // nsIToolkitProfileService.currentProfile. - let gProfD = do_get_profile(); - let gDataHome = gProfD.clone(); - gDataHome.append("data"); - gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); - let gDataHomeLocal = gProfD.clone(); - gDataHomeLocal.append("local"); - gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755); - - let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService( - Ci.nsIXREDirProvider - ); - xreDirProvider.setUserDataDirectory(gDataHome, false); - xreDirProvider.setUserDataDirectory(gDataHomeLocal, true); - - let profileSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService( - Ci.nsIToolkitProfileService - ); - - let createdProfile = {}; - let didCreate = profileSvc.selectStartupProfile( - ["xpcshell"], - false, - AppConstants.UPDATE_CHANNEL, - "", - {}, - {}, - createdProfile - ); - Assert.ok(didCreate, "Created a testing profile and set it to current."); - Assert.equal( - profileSvc.currentProfile, - createdProfile.value, - "Profile set to current" - ); + setupProfile(); registerCleanupFunction(async () => { Services.prefs.clearUserPref(BACKUP_DEFAULT_LOCATION_PREF_NAME); diff --git a/browser/components/backup/tests/xpcshell/test_ResourceFailures.js b/browser/components/backup/tests/xpcshell/test_ResourceFailures.js @@ -0,0 +1,112 @@ +/* Any copyright is dedicated to the Public Domain. +https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + BackupError: "resource:///modules/backup/BackupError.mjs", + ERRORS: "chrome://browser/content/backup/backup-constants.mjs", + AppConstants: "resource://gre/modules/AppConstants.sys.mjs", +}); + +add_setup(function () { + setupProfile(); +}); + +/** + * Test's the case where a resource fails to recover successfully. We expect + * the complete recover process to error out. + */ +add_task(async function testResourceFailure() { + let sandbox = sinon.createSandbox(); + registerCleanupFunction(() => { + sandbox.restore(); + }); + + let testBackupPath = await IOUtils.createUniqueDirectory( + PathUtils.tempDir, + "checkForErrorsTestBackup" + ); + + let fake1ManifestEntry = { fake1: "hello from 1" }; + sandbox + .stub(FakeBackupResource1.prototype, "backup") + .resolves(fake1ManifestEntry); + sandbox.stub(FakeBackupResource1.prototype, "recover").resolves(); + + let fake2ManifestEntry = { fake1: "hello from 2" }; + sandbox + .stub(FakeBackupResource2.prototype, "backup") + .resolves(fake2ManifestEntry); + sandbox + .stub(FakeBackupResource2.prototype, "recover") + .rejects(new BackupError("recovery failed", ERRORS.RECOVERY_FAILED)); + + let fake3ManifestEntry = { fake1: "hello from 3" }; + sandbox + .stub(FakeBackupResource3.prototype, "backup") + .resolves(fake3ManifestEntry); + sandbox.stub(FakeBackupResource3.prototype, "recover").resolves(); + + let bs = new BackupService({ + FakeBackupResource1, + FakeBackupResource2, + FakeBackupResource3, + }); + + let { manifest, archivePath: backupFilePath } = await bs.createBackup({ + profilePath: testBackupPath, + }); + Assert.ok(await IOUtils.exists(backupFilePath), "The backup file exists"); + + let archiveDateSuffix = bs.generateArchiveDateSuffix( + new Date(manifest.meta.date) + ); + + // We also expect the HTML file to have been written to the folder pointed + // at by browser.backups.location, within backupDirPath folder. + const EXPECTED_ARCHIVE_PATH = PathUtils.join( + bs.state.backupDirPath, + `${BackupService.BACKUP_FILE_NAME}_${manifest.meta.profileName}_${archiveDateSuffix}.html` + ); + Assert.ok( + await IOUtils.exists(EXPECTED_ARCHIVE_PATH), + "Single-file backup archive was written." + ); + Assert.equal( + backupFilePath, + EXPECTED_ARCHIVE_PATH, + "Backup was written to the configured destination folder" + ); + Assert.deepEqual( + Object.keys(manifest.resources).sort(), + ["fake1", "fake2", "fake3"], + "Manifest contains all expected BackupResource keys" + ); + + let recoveredProfilePath = await IOUtils.createUniqueDirectory( + PathUtils.tempDir, + "createBackupTestRecoveredProfile" + ); + + await Assert.rejects( + bs.recoverFromBackupArchive( + backupFilePath, + null, + false, + testBackupPath, + recoveredProfilePath + ), + err => err.cause == ERRORS.RECOVERY_FAILED + ); + + // Since we fail on backupResource2, backupResource1 should never be called + sinon.assert.notCalled(FakeBackupResource1.prototype.recover); + + // Ideally, we should be removing any left over data when we fail. + // The current behavior does not do this, so let's clear the paths + // manually in the test. + await maybeRemovePath(backupFilePath); + await maybeRemovePath(testBackupPath); + await maybeRemovePath(recoveredProfilePath); +}); diff --git a/browser/components/backup/tests/xpcshell/xpcshell.toml b/browser/components/backup/tests/xpcshell/xpcshell.toml @@ -77,6 +77,8 @@ run-sequentially = ["true"] # Mock Windows registry interferes with normal opera ["test_PreferencesBackupResource_searchEngines.js"] +["test_ResourceFailures.js"] + ["test_SessionStoreBackupResource.js"] ["test_backupService_findBackupsInWellKnownLocations.js"]