tor-browser

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

commit dc44d12594f1402e8c4b6e021975d04001b9cb26
parent 8d12d7d653a05372836f671c38faf127ab772218
Author: Harsheet <hsohaney@mozilla.com>
Date:   Fri, 17 Oct 2025 22:18:41 +0000

Bug 1991951 - (part 2) Split up prefs for backup service to backup and restore. r=omc-reviewers,akulyk,nrishel,mviar,fchasen,jprickett

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

Diffstat:
Mbrowser/app/profile/firefox.js | 6++++--
Mbrowser/components/DesktopActorRegistry.sys.mjs | 1-
Mbrowser/components/aboutwelcome/tests/browser/browser_aboutwelcome_restore_backup.js | 4+++-
Mbrowser/components/backup/BackupService.sys.mjs | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/backup/content/backup-settings.mjs | 100+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mbrowser/components/backup/tests/browser/browser.toml | 3++-
Mbrowser/components/backup/tests/browser/browser_settings.js | 41++++++++++++++++++++++++++++++++++++++++-
Mbrowser/components/backup/tests/chrome/chrome.toml | 2++
Mbrowser/components/backup/tests/marionette/test_backup.py | 7++++++-
Mbrowser/components/backup/tests/xpcshell/xpcshell.toml | 2++
Mbrowser/components/preferences/sync.js | 32+++++++++++++++++++-------------
Mbrowser/components/preferences/tests/browser_bug731866.js | 5+++--
Mtoolkit/components/nimbus/FeatureManifest.yaml | 8--------
13 files changed, 192 insertions(+), 70 deletions(-)

diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -3417,8 +3417,10 @@ pref("browser.mailto.dualPrompt.dismissXClickMinutes", 1440); // one day pref("browser.backup.enabled", true); // Pref to control whether scheduled backups run or not. pref("browser.backup.scheduled.enabled", false); -// Pref to control the visibility of the backup section in about:preferences -pref("browser.backup.preferences.ui.enabled", false); +// Pref to control visibility and usability of the create backup feature. +pref("browser.backup.archive.enabled", false); +// Pref to control visibility and usability of the restore from backup feature. +pref("browser.backup.restore.enabled", false); // The number of SQLite database pages to backup per step. pref("browser.backup.sqlite.pages_per_step", 50); // The delay between SQLite database backup steps in milliseconds. diff --git a/browser/components/DesktopActorRegistry.sys.mjs b/browser/components/DesktopActorRegistry.sys.mjs @@ -248,7 +248,6 @@ let JSWINDOWACTORS = { "about:welcome*", "chrome://browser/content/spotlight.html", ], - enablePreference: "browser.backup.preferences.ui.enabled", }, BlockedSite: { diff --git a/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_restore_backup.js b/browser/components/aboutwelcome/tests/browser/browser_aboutwelcome_restore_backup.js @@ -57,7 +57,9 @@ add_task(async function test_aboutwelcome_embedded_backup_restore_properties() { await pushPrefs([ "browser.backup.enabled", true, - "browser.backup.preferences.ui.enabled", + "browser.backup.archive.enabled", + true, + "browser.backup.restore.enabled", true, ]); diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs @@ -22,6 +22,8 @@ import { BackupError } from "resource:///modules/backup/BackupError.mjs"; const BACKUP_DIR_PREF_NAME = "browser.backup.location"; const BACKUP_ERROR_CODE_PREF_NAME = "browser.backup.errorCode"; const SCHEDULED_BACKUPS_ENABLED_PREF_NAME = "browser.backup.scheduled.enabled"; +const BACKUP_ARCHIVE_ENABLED_PREF_NAME = "browser.backup.archive.enabled"; +const BACKUP_RESTORE_ENABLED_PREF_NAME = "browser.backup.restore.enabled"; const IDLE_THRESHOLD_SECONDS_PREF_NAME = "browser.backup.scheduled.idle-threshold-seconds"; const MINIMUM_TIME_BETWEEN_BACKUPS_SECONDS_PREF_NAME = @@ -618,6 +620,13 @@ export class BackupService extends EventTarget { }; } + if (!Services.prefs.getBoolPref(BACKUP_ARCHIVE_ENABLED_PREF_NAME)) { + return { + enabled: false, + reason: "Archiving a profile disabled by user pref.", + }; + } + return { enabled: true }; } @@ -637,6 +646,13 @@ export class BackupService extends EventTarget { }; } + if (!Services.prefs.getBoolPref(BACKUP_RESTORE_ENABLED_PREF_NAME)) { + return { + enabled: false, + reason: "Restoring a profile disabled by user pref.", + }; + } + return { enabled: true }; } @@ -1018,6 +1034,7 @@ export class BackupService extends EventTarget { if (this.#instance) { return this.#instance; } + this.#instance = new BackupService(DefaultBackupResources); this.#instance.checkForPostRecovery(); @@ -1051,6 +1068,7 @@ export class BackupService extends EventTarget { constructor(backupResources = DefaultBackupResources) { super(); lazy.logConsole.debug("Instantiated"); + this.#registerStatusObservers(); for (const resourceName in backupResources) { let resource = backupResources[resourceName]; @@ -3589,6 +3607,7 @@ export class BackupService extends EventTarget { } case "quit-application-granted": { this.uninitBackupScheduler(); + this.#unregisterStatusObservers(); break; } case "passwordmgr-storage-changed": { @@ -3639,6 +3658,38 @@ 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 + ); + lazy.NimbusFeatures.backupService.onUpdate(this.#notifyStatusObservers); + } + + #unregisterStatusObservers() { + Services.prefs.removeObserver( + BACKUP_ARCHIVE_ENABLED_PREF_NAME, + this.#notifyStatusObservers + ); + Services.prefs.removeObserver( + BACKUP_RESTORE_ENABLED_PREF_NAME, + this.#notifyStatusObservers + ); + } + + /** + * Notify any listeners about the availability of the backup service. + */ + #notifyStatusObservers = () => { + Services.obs.notifyObservers(null, "backup-service-status-updated"); + }; + /** * Called when the last known backup should be deleted and a new one * created. This uses the #regenerationDebouncer to debounce clusters of diff --git a/browser/components/backup/content/backup-settings.mjs b/browser/components/backup/content/backup-settings.mjs @@ -7,6 +7,12 @@ import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; import { getErrorL10nId } from "chrome://browser/content/backup/backup-errors.mjs"; import { ERRORS } from "chrome://browser/content/backup/backup-constants.mjs"; +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + BackupService: "resource:///modules/backup/BackupService.sys.mjs", +}); + // eslint-disable-next-line import/no-unassigned-import import "chrome://browser/content/backup/turn-on-scheduled-backups.mjs"; // eslint-disable-next-line import/no-unassigned-import @@ -26,11 +32,14 @@ const BACKUP_ERROR_CODE_PREF_NAME = "browser.backup.errorCode"; */ export default class BackupSettings extends MozLitElement { #placeholderIconURL = "chrome://global/skin/icons/page-portrait.svg"; + #backupService = lazy.BackupService.get(); static properties = { backupServiceState: { type: Object }, backupErrorCode: { type: Number }, _enableEncryptionTypeAttr: { type: String }, + _archiveEnabled: { type: Boolean }, + _restoreEnabled: { type: Boolean }, }; static get queries() { @@ -88,8 +97,19 @@ export default class BackupSettings extends MozLitElement { }; this.backupErrorCode = this.#readBackupErrorPref(); this._enableEncryptionTypeAttr = ""; + this.updateArchiveAndRestoreState(); + + Services.obs.addObserver( + this.updateArchiveAndRestoreState, + "backup-service-status-updated" + ); } + updateArchiveAndRestoreState = () => { + this._archiveEnabled = this.#backupService.archiveEnabledStatus.enabled; + this._restoreEnabled = this.#backupService.restoreEnabledStatus.enabled; + }; + /** * Dispatches the BackupUI:InitWidget custom event upon being attached to the * DOM, which registers with BackupUIChild for BackupService state updates. @@ -458,46 +478,46 @@ export default class BackupSettings extends MozLitElement { ${this.turnOffScheduledBackupsDialogTemplate()} ${this.enableBackupEncryptionDialogTemplate()} ${this.disableBackupEncryptionDialogTemplate()} - - <section id="scheduled-backups"> - <div class="backups-control"> - <span - id="scheduled-backups-enabled" - data-l10n-id=${scheduledBackupsEnabledL10nID} - class="heading-medium" - ></span> - - <moz-button - id="backup-trigger-button" - @click=${this.handleBackupTrigger} - data-l10n-id=${backupTriggerL10nID} - ?disabled=${this.backupServiceState.backupInProgress || - !this.backupServiceState.scheduledBackupsEnabled} - ></moz-button> - - <moz-button - id="backup-toggle-scheduled-button" - @click=${this.handleShowScheduledBackups} - data-l10n-id="settings-data-backup-toggle" - ></moz-button> - - ${this.backupServiceState.scheduledBackupsEnabled - ? null - : this.scheduledBackupsDescriptionTemplate()} - </div> - - ${this.backupServiceState.lastBackupDate - ? this.lastBackupInfoTemplate() - : null} - ${this.backupServiceState.scheduledBackupsEnabled - ? this.backupLocationTemplate() - : null} - ${this.backupServiceState.scheduledBackupsEnabled - ? this.sensitiveDataTemplate() - : null} - </section> - - ${this.restoreFromBackupTemplate()} `; + ${this._archiveEnabled + ? html` <section id="scheduled-backups"> + <div class="backups-control"> + <span + id="scheduled-backups-enabled" + data-l10n-id=${scheduledBackupsEnabledL10nID} + class="heading-medium" + ></span> + + <moz-button + id="backup-trigger-button" + @click=${this.handleBackupTrigger} + data-l10n-id=${backupTriggerL10nID} + ?disabled=${this.backupServiceState.backupInProgress || + !this.backupServiceState.scheduledBackupsEnabled} + ></moz-button> + + <moz-button + id="backup-toggle-scheduled-button" + @click=${this.handleShowScheduledBackups} + data-l10n-id="settings-data-backup-toggle" + ></moz-button> + + ${this.backupServiceState.scheduledBackupsEnabled + ? null + : this.scheduledBackupsDescriptionTemplate()} + </div> + + ${this.backupServiceState.lastBackupDate + ? this.lastBackupInfoTemplate() + : null} + ${this.backupServiceState.scheduledBackupsEnabled + ? this.backupLocationTemplate() + : null} + ${this.backupServiceState.scheduledBackupsEnabled + ? this.sensitiveDataTemplate() + : null} + </section>` + : null} + ${this._restoreEnabled ? this.restoreFromBackupTemplate() : null} `; } } diff --git a/browser/components/backup/tests/browser/browser.toml b/browser/components/backup/tests/browser/browser.toml @@ -1,7 +1,8 @@ [DEFAULT] prefs = [ "browser.backup.enabled=true", - "browser.backup.preferences.ui.enabled=true", + "browser.backup.archive.enabled=true", + "browser.backup.restore.enabled=true", "browser.backup.scheduled.enabled=false", ] support-files = [ diff --git a/browser/components/backup/tests/browser/browser_settings.js b/browser/components/backup/tests/browser/browser_settings.js @@ -8,6 +8,8 @@ const { MockRegistrar } = ChromeUtils.importESModule( ); const SCHEDULED_BACKUPS_ENABLED_PREF = "browser.backup.scheduled.enabled"; +const BACKUP_ARCHIVE_ENABLED_PREF = "browser.backup.archive.enabled"; +const BACKUP_RESTORE_ENABLED_PREF = "browser.backup.restore.enabled"; add_setup(async () => { MockFilePicker.init(window.browsingContext); @@ -35,7 +37,7 @@ add_task(async function test_preferences_visibility() { }); await SpecialPowers.pushPrefEnv({ - set: [["browser.backup.preferences.ui.enabled", false]], + set: [[BACKUP_ARCHIVE_ENABLED_PREF, false]], }); await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { @@ -44,12 +46,40 @@ add_task(async function test_preferences_visibility() { Assert.ok(backupSection, "Found backup preferences section"); Assert.ok( + BrowserTestUtils.isVisible(backupSection), + "Backup section is still visible" + ); + + let settings = browser.contentDocument.querySelector("backup-settings"); + let backupArchiveSection = settings.querySelector("#scheduled-backups"); + + Assert.ok(!backupArchiveSection, "Backup archive section is not available"); + + Assert.ok( + settings.restoreFromBackupEl, + "Backup restore section is available" + ); + }); + await SpecialPowers.pushPrefEnv({ + set: [[BACKUP_RESTORE_ENABLED_PREF, false]], + }); + await BrowserTestUtils.withNewTab("about:preferences#sync", async browser => { + let settings = browser.contentDocument.querySelector("backup-settings"); + Assert.ok( + !settings.restoreFromBackupEl, + "Backup Restore section is not available" + ); + + let backupSection = + browser.contentDocument.querySelector("#dataBackupGroup"); + Assert.ok( BrowserTestUtils.isHidden(backupSection), "Backup section is now hidden" ); }); await SpecialPowers.popPrefEnv(); + await SpecialPowers.popPrefEnv(); }); /** @@ -66,6 +96,15 @@ add_task(async function test_disable_backup_encryption_confirm() { .stub(BackupService.prototype, "disableEncryption") .resolves(true); + Assert.ok( + Services.prefs.getBoolPref(BACKUP_RESTORE_ENABLED_PREF), + "Restore pref is back to true" + ); + Assert.ok( + Services.prefs.getBoolPref(BACKUP_ARCHIVE_ENABLED_PREF), + "Archive pref is back to true" + ); + await SpecialPowers.pushPrefEnv({ set: [[SCHEDULED_BACKUPS_ENABLED_PREF, true]], }); diff --git a/browser/components/backup/tests/chrome/chrome.toml b/browser/components/backup/tests/chrome/chrome.toml @@ -1,6 +1,8 @@ [DEFAULT] prefs = [ "browser.backup.scheduled.enabled=false", + "browser.backup.archive.enabled=true", + "browser.backup.restore.enabled=true", ] support-files = ["head.js"] diff --git a/browser/components/backup/tests/marionette/test_backup.py b/browser/components/backup/tests/marionette/test_backup.py @@ -22,7 +22,12 @@ class BackupTest(MarionetteTestCase): # by default for Marionette. Also "browser.backup.log" has to be set # to true before Firefox starts in order for it to be displayed. self.marionette.enforce_gecko_prefs( - {"browser.backup.enabled": True, "browser.backup.log": True} + { + "browser.backup.enabled": True, + "browser.backup.log": True, + "browser.backup.archive.enabled": True, + "browser.backup.restore.enabled": True, + } ) self.marionette.set_context("chrome") diff --git a/browser/components/backup/tests/xpcshell/xpcshell.toml b/browser/components/backup/tests/xpcshell/xpcshell.toml @@ -4,6 +4,8 @@ head = "head.js" firefox-appdir = "browser" prefs = [ "browser.backup.log=true", + "browser.backup.archive.enabled=true", + "browser.backup.restore.enabled=true", ] ["test_AddonsBackupResource.js"] diff --git a/browser/components/preferences/sync.js b/browser/components/preferences/sync.js @@ -19,7 +19,12 @@ const FXA_LOGIN_FAILED = 2; const SYNC_DISCONNECTED = 0; const SYNC_CONNECTED = 1; -const BACKUP_UI_ENABLED_PREF = "browser.backup.preferences.ui.enabled"; +const BACKUP_ARCHIVE_ENABLED_PREF_NAME = "browser.backup.archive.enabled"; +const BACKUP_RESTORE_ENABLED_PREF_NAME = "browser.backup.restore.enabled"; + +ChromeUtils.defineESModuleGetters(lazy, { + BackupService: "resource:///modules/backup/BackupService.sys.mjs", +}); var gSyncPane = { get page() { @@ -293,10 +298,9 @@ var gSyncPane = { }, updateBackupUIVisibility() { - const isBackupUIEnabled = Services.prefs.getBoolPref( - BACKUP_UI_ENABLED_PREF, - false - ); + let bs = lazy.BackupService.get(); + let isBackupUIEnabled = + bs.archiveEnabledStatus.enabled || bs.restoreEnabledStatus.enabled; let dataBackupSectionEl = document.getElementById("dataBackupSection"); @@ -308,23 +312,25 @@ var gSyncPane = { let dataBackupGroupEl = document.getElementById("dataBackupGroup"); let backupGroupHeaderEl = document.getElementById("backupCategory"); + dataBackupSectionEl.hidden = !isBackupUIEnabled; dataBackupGroupEl.hidden = !isBackupUIEnabled; backupGroupHeaderEl.hidden = !isBackupUIEnabled; }, _addPrefObservers() { - Services.prefs.addObserver( - BACKUP_UI_ENABLED_PREF, - this.updateBackupUIVisibility + Services.obs.addObserver( + this.updateBackupUIVisibility, + "backup-service-status-updated" ); window.addEventListener( "unload", - () => - Services.prefs.removeObserver( - BACKUP_UI_ENABLED_PREF, - this.updateBackupUIVisibility - ), + () => { + Services.obs.removeObserver( + this.updateBackupUIVisibility, + "backup-service-status-updated" + ); + }, { once: true } ); }, diff --git a/browser/components/preferences/tests/browser_bug731866.js b/browser/components/preferences/tests/browser_bug731866.js @@ -7,8 +7,9 @@ const browserContainersGroupDisabled = !SpecialPowers.getBoolPref( const cookieBannerHandlingDisabled = !SpecialPowers.getBoolPref( "cookiebanners.ui.desktop.enabled" ); -const backupSectionDisabled = !SpecialPowers.getBoolPref( - "browser.backup.preferences.ui.enabled" +const backupSectionDisabled = !( + SpecialPowers.getBoolPref("browser.backup.archive.enabled") || + SpecialPowers.getBoolPref("browser.backup.restore.enabled") ); const profilesGroupDisabled = !SelectableProfileService.isEnabled; const updatePrefContainers = ["updatesCategory", "updateApp"]; diff --git a/toolkit/components/nimbus/FeatureManifest.yaml b/toolkit/components/nimbus/FeatureManifest.yaml @@ -4778,14 +4778,6 @@ backupService: description: >- When true, the profile backup service will be initialized soon after startup. - prefsUIEnabled: - type: boolean - setPref: - branch: default - pref: browser.backup.preferences.ui.enabled - description: >- - When true, the section in about:preferences to control the backup - feature is visible. sqlitePagesPerStep: description: >- The number of database pages to backup per step when backing up an