tor-browser

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

commit 9625ece3b94b184997f55f085d85290aed595e6d
parent 2ba5de45ca0e296051c085063633a30f018b014c
Author: Harsheet <hsohaney@mozilla.com>
Date:   Fri,  7 Nov 2025 16:22:49 +0000

Bug 1998176 - Clean up any backup items and disable any backup operations if backup is turned off by pref flips. r=cdupuis

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

Diffstat:
Mbrowser/components/backup/BackupService.sys.mjs | 47+++++++++++++++++++++++++++++++++++++++++++----
Mbrowser/components/backup/actors/BackupUIParent.sys.mjs | 9+--------
Mbrowser/components/backup/tests/xpcshell/test_BackupService.js | 42+++++++++++++++++++++++++++++++++++++++---
Mbrowser/components/backup/tests/xpcshell/test_BackupService_regeneration.js | 20++++++++++++++++++++
4 files changed, 103 insertions(+), 15 deletions(-)

diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs @@ -1195,7 +1195,10 @@ export class BackupService extends EventTarget { this.#postRecoveryResolver = resolve; this.#backupWriteAbortController = new AbortController(); this.#regenerationDebouncer = new lazy.DeferredTask(async () => { - if (!this.#backupWriteAbortController.signal.aborted) { + if ( + !this.#backupWriteAbortController.signal.aborted && + this.archiveEnabledStatus.enabled + ) { await this.deleteLastBackup(); if (lazy.scheduledBackupsPref) { await this.createBackupOnIdleDispatch({ @@ -3938,14 +3941,14 @@ export class BackupService extends EventTarget { // immediately reflect across any observers, instead of waiting on idle. this.#statusPrefObserver = () => { // Wrap in an arrow function so 'this' is preserved. - this.#notifyStatusObservers(); + this.#handleStatusChange(); }; for (let pref of BackupService.STATUS_OBSERVER_PREFS) { Services.prefs.addObserver(pref, this.#statusPrefObserver); } lazy.NimbusFeatures.backupService.onUpdate(this.#statusPrefObserver); - this.#notifyStatusObservers(); + this.#handleStatusChange(); } /** @@ -3967,10 +3970,30 @@ export class BackupService extends EventTarget { } /** + * Performs tasks required whenever archive or restore change their status + * + * 1. Notifies any observers that a change has taken place + * 2. If archive is disabled, clean up any backup files + */ + #handleStatusChange() { + this.#notifyStatusObservers(); + + if (!this.archiveEnabledStatus.enabled) { + // We won't wait for this promise to accept/reject since rejections are + // ignored anyways + this.cleanupBackupFiles(); + } + } + + /** * Notify any listeners about the availability of the backup service, then * update relevant telemetry metrics. */ #notifyStatusObservers() { + lazy.logConsole.log( + "Notifying observers about a BackupService state change" + ); + Services.obs.notifyObservers(null, "backup-service-status-updated"); let status = this.archiveEnabledStatus; @@ -3992,6 +4015,22 @@ export class BackupService extends EventTarget { } } + async cleanupBackupFiles() { + lazy.logConsole.debug("Cleaning up backup data"); + try { + if (this.state.encryptionEnabled) { + await this.disableEncryption(); + } + this.deleteLastBackup(); + } catch (e) { + // Ignore any exceptions + lazy.logConsole.error( + "There was an error when cleaning up backup files: ", + e + ); + } + } + /** * Called when the last known backup should be deleted and a new one * created. This uses the #regenerationDebouncer to debounce clusters of @@ -4014,7 +4053,7 @@ export class BackupService extends EventTarget { this.#takenMeasurements = true; } - if (lazy.scheduledBackupsPref) { + if (lazy.scheduledBackupsPref && this.archiveEnabledStatus.enabled) { lazy.logConsole.debug("Scheduled backups enabled."); let now = Math.floor(Date.now() / 1000); let lastBackupDate = this.#_state.lastBackupDate; diff --git a/browser/components/backup/actors/BackupUIParent.sys.mjs b/browser/components/backup/actors/BackupUIParent.sys.mjs @@ -137,14 +137,7 @@ export class BackupUIParent extends JSWindowActorParent { */ return { success: true }; } else if (message.name == "DisableScheduledBackups") { - try { - if (this.#bs.state.encryptionEnabled) { - await this.#bs.disableEncryption(); - } - await this.#bs.deleteLastBackup(); - } catch (e) { - // no-op so that scheduled backups can still be turned off - } + await this.#bs.cleanupBackupFiles(); this.#bs.setScheduledBackups(false); } else if (message.name == "ShowFilepicker") { let { win, filter, existingBackupPath } = message.data; diff --git a/browser/components/backup/tests/xpcshell/test_BackupService.js b/browser/components/backup/tests/xpcshell/test_BackupService.js @@ -19,10 +19,16 @@ const { ERRORS } = ChromeUtils.importESModule( "chrome://browser/content/backup/backup-constants.mjs" ); +const { TestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TestUtils.sys.mjs" +); + const LAST_BACKUP_TIMESTAMP_PREF_NAME = "browser.backup.scheduled.last-backup-timestamp"; const LAST_BACKUP_FILE_NAME_PREF_NAME = "browser.backup.scheduled.last-backup-file"; +const BACKUP_ARCHIVE_ENABLED_PREF_NAME = "browser.backup.archive.enabled"; +const BACKUP_RESTORE_ENABLED_PREF_NAME = "browser.backup.restore.enabled"; /** @type {nsIToolkitProfile} */ let currentProfile; @@ -877,9 +883,6 @@ async function testSelectableProfilesPreventBackup( const SELECTABLE_PROFILES_CREATED_PREF = "browser.profiles.created"; - // Make sure backup and restore are enabled. - const BACKUP_ARCHIVE_ENABLED_PREF_NAME = "browser.backup.archive.enabled"; - const BACKUP_RESTORE_ENABLED_PREF_NAME = "browser.backup.restore.enabled"; Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, true); Services.prefs.setBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME, true); @@ -1199,3 +1202,36 @@ add_task(async function test_getBackupFileInfo_error_handling() { sandbox.restore(); } }); + +/** + * Tests changing the status prefs to ensure that backup is cleaned up if being disabled. + */ +add_task(async function test_changing_prefs_cleanup() { + let sandbox = sinon.createSandbox(); + let bs = BackupService.init(); + + let cleanupStub = sandbox.stub(bs, "cleanupBackupFiles"); + let statusUpdatePromise = TestUtils.topicObserved( + "backup-service-status-updated" + ); + + Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, false); + + await statusUpdatePromise; + + Assert.equal( + cleanupStub.callCount, + 1, + "Cleanup backup files was called on pref change" + ); + + Services.prefs.setBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME, true); + + Assert.equal( + cleanupStub.callCount, + 1, + "Cleanup backup files should not have been called when enabling backups" + ); + + Services.prefs.clearUserPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME); +}); diff --git a/browser/components/backup/tests/xpcshell/test_BackupService_regeneration.js b/browser/components/backup/tests/xpcshell/test_BackupService_regeneration.js @@ -637,3 +637,23 @@ add_task(async function test_newtab_link_blocked() { NewTabUtils.activityStreamLinks.blockURL("https://example.com"); }, "Saw regeneration on the blocking of a newtab link"); }); + +/** + * Tests that setting up a regeneration without archiveEnabledStatus being true + * leads to NO regeneration upon data mutation + */ +add_task(async function test_enabledStatus_no_regeneration() { + let bookmark = await PlacesUtils.bookmarks.insert({ + parentGuid: PlacesUtils.bookmarks.unfiledGuid, + url: "data:text/plain,Content", + title: "Regeneration Test Bookmark", + }); + + Services.prefs.setBoolPref("browser.backup.archive.enabled", false); + + await expectNoRegeneration(async () => { + await PlacesUtils.bookmarks.remove(bookmark); + }, "Saw no regeneration on bookmark removed."); + + Services.prefs.clearUserPref("browser.backup.archive.enabled"); +});