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:
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