tor-browser

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

commit c9014d1918128ac900065d31c8f9f5b0e805ca9e
parent 4fd36a25bb8ca453b819ae0f3bf019ea7dece108
Author: Duncan McIntosh <dmcintosh@mozilla.com>
Date:   Mon,  3 Nov 2025 21:53:03 +0000

Bug 1997389 - Part 1: Ensure that BackupService.#notifyStatusObservers is called, with correct 'this', when any disable reason changes. r=cdupuis

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

Diffstat:
Mbrowser/components/backup/BackupService.sys.mjs | 66++++++++++++++++++++++++++++++++++++++++--------------------------
Mbrowser/components/backup/tests/xpcshell/test_BackupService_enabled.js | 15+++++++++++++++
2 files changed, 55 insertions(+), 26 deletions(-)

diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs @@ -38,6 +38,7 @@ const DISABLED_ON_IDLE_RETRY_PREF_NAME = const BACKUP_DEBUG_INFO_PREF_NAME = "browser.backup.backup-debug-info"; const MAXIMUM_NUMBER_OF_UNREMOVABLE_STAGING_ITEMS_PREF_NAME = "browser.backup.max-num-unremovable-staging-items"; +const CREATED_MANAGED_PROFILES_PREF_NAME = "browser.profiles.created"; const SCHEMAS = Object.freeze({ BACKUP_MANIFEST: 1, @@ -842,6 +843,12 @@ export class BackupService extends EventTarget { #wasArchivePreviouslyDisabled = false; /** + * Monitors prefs that are relevant to the status of the backup service. + * Unlike #observer, this does not wait for an idle tick. + */ + #statusPrefObserver = null; + + /** * The path of the default parent directory for saving backups. * The current default is the Documents directory. * @@ -988,6 +995,20 @@ export class BackupService extends EventTarget { } /** + * Prefs that should be monitored. When one of these prefs changes, the + * 'backup-service-status-changed' observers are notified and telemetry + * updates. + */ + static get #STATUS_OBSERVER_PREFS() { + return [ + BACKUP_ARCHIVE_ENABLED_PREF_NAME, + BACKUP_RESTORE_ENABLED_PREF_NAME, + "privacy.sanitize.sanitizeOnShutdown", + CREATED_MANAGED_PROFILES_PREF_NAME, + ]; + } + + /** * Returns the schema for the schemaType for a given version. * * @param {number} schemaType @@ -3873,35 +3894,28 @@ export class BackupService extends EventTarget { #registerStatusObservers() { // We don't use this.#observer since any changes to the prefs or nimbus should - // immediately reflect across any observers, instead of waiting on idle - Services.prefs.addObserver( - BACKUP_ARCHIVE_ENABLED_PREF_NAME, - this.#notifyStatusObservers - ); - Services.prefs.addObserver( - BACKUP_RESTORE_ENABLED_PREF_NAME, - this.#notifyStatusObservers - ); - Services.prefs.addObserver( - "privacy.sanitize.sanitizeOnShutdown", - this.#notifyStatusObservers - ); - lazy.NimbusFeatures.backupService.onUpdate(this.#notifyStatusObservers); + // immediately reflect across any observers, instead of waiting on idle. + this.#statusPrefObserver = () => { + // Wrap in an arrow function so 'this' is preserved. + this.#notifyStatusObservers(); + }; + + for (let pref of BackupService.#STATUS_OBSERVER_PREFS) { + Services.prefs.addObserver(pref, this.#statusPrefObserver); + } + lazy.NimbusFeatures.backupService.onUpdate(this.#statusPrefObserver); } #unregisterStatusObservers() { - Services.prefs.removeObserver( - BACKUP_ARCHIVE_ENABLED_PREF_NAME, - this.#notifyStatusObservers - ); - Services.prefs.removeObserver( - BACKUP_RESTORE_ENABLED_PREF_NAME, - this.#notifyStatusObservers - ); - Services.prefs.removeObserver( - "privacy.sanitize.sanitizeOnShutdown", - this.#notifyStatusObservers - ); + if (this.#statusPrefObserver == null) { + return; + } + + for (let pref of BackupService.#STATUS_OBSERVER_PREFS) { + Services.prefs.removeObserver(pref, this.#statusPrefObserver); + } + lazy.NimbusFeatures.backupService.offUpdate(this.#statusPrefObserver); + this.#statusPrefObserver = null; } /** diff --git a/browser/components/backup/tests/xpcshell/test_BackupService_enabled.js b/browser/components/backup/tests/xpcshell/test_BackupService_enabled.js @@ -154,7 +154,12 @@ add_task(async function test_restore_selectable_profiles() { async function archiveTemplate({ internalReason, disable, enable }) { const bs = BackupService.get(); + let calledCount = 0; + let callback = () => calledCount++; + Services.obs.addObserver(callback, "backup-service-status-updated"); + await disable(); + Assert.equal(calledCount, 1, "Observers were notified on disable"); Assert.ok( !bs.archiveEnabledStatus.enabled, @@ -181,6 +186,7 @@ async function archiveTemplate({ internalReason, disable, enable }) { ); await enable(); + Assert.equal(calledCount, 2, "Observers were notified on re-enable"); Assert.ok( bs.archiveEnabledStatus.enabled, @@ -205,6 +211,7 @@ async function archiveTemplate({ internalReason, disable, enable }) { ); await IOUtils.remove(backup.archivePath); + Services.obs.removeObserver(callback, "backup-service-status-updated"); } async function restoreTemplate({ internalReason, disable, enable }) { @@ -216,7 +223,12 @@ async function restoreTemplate({ internalReason, disable, enable }) { "Archive should have been created on disk." ); + let calledCount = 0; + let callback = () => calledCount++; + Services.obs.addObserver(callback, "backup-service-status-updated"); + await disable(); + Assert.equal(calledCount, 1, "Observers were notified on disable"); const recoveryDir = await IOUtils.createUniqueDirectory( PathUtils.profileDir, @@ -246,6 +258,7 @@ async function restoreTemplate({ internalReason, disable, enable }) { ); await enable(); + Assert.equal(calledCount, 2, "Observers were notified on re-enable"); Assert.ok( bs.restoreEnabledStatus.enabled, @@ -267,4 +280,6 @@ async function restoreTemplate({ internalReason, disable, enable }) { await IOUtils.exists(recoveredProfile.rootDir.path), "Recovered profile directory should exist on disk." ); + + Services.obs.removeObserver(callback, "backup-service-status-updated"); }