commit c9c2eac140638278c40890696158332216abcd29 parent 56f574eb42901fed95f7c05308bdd32f64afea54 Author: Harsheet <hsohaney@mozilla.com> Date: Wed, 17 Dec 2025 02:02:21 +0000 Bug 1994875 - Respect user's choice of data that should clear on exit when backing up. r=mconley,mak Differential Revision: https://phabricator.services.mozilla.com/D269490 Diffstat:
11 files changed, 330 insertions(+), 93 deletions(-)
diff --git a/browser/components/backup/BackupResources.sys.mjs b/browser/components/backup/BackupResources.sys.mjs @@ -10,19 +10,23 @@ */ import { AddonsBackupResource } from "resource:///modules/backup/AddonsBackupResource.sys.mjs"; +import { BookmarksBackupResource } from "resource:///modules/backup/BookmarksBackupResource.sys.mjs"; import { CredentialsAndSecurityBackupResource } from "resource:///modules/backup/CredentialsAndSecurityBackupResource.sys.mjs"; import { FormHistoryBackupResource } from "resource:///modules/backup/FormHistoryBackupResource.sys.mjs"; import { MiscDataBackupResource } from "resource:///modules/backup/MiscDataBackupResource.sys.mjs"; import { PlacesBackupResource } from "resource:///modules/backup/PlacesBackupResource.sys.mjs"; import { PreferencesBackupResource } from "resource:///modules/backup/PreferencesBackupResource.sys.mjs"; import { SessionStoreBackupResource } from "resource:///modules/backup/SessionStoreBackupResource.sys.mjs"; +import { SiteSettingsBackupResource } from "resource:///modules/backup/SiteSettingsBackupResource.sys.mjs"; export { AddonsBackupResource, + BookmarksBackupResource, CredentialsAndSecurityBackupResource, FormHistoryBackupResource, MiscDataBackupResource, PlacesBackupResource, PreferencesBackupResource, SessionStoreBackupResource, + SiteSettingsBackupResource, }; diff --git a/browser/components/backup/BackupService.sys.mjs b/browser/components/backup/BackupService.sys.mjs @@ -1559,6 +1559,13 @@ export class BackupService extends EventTarget { continue; } + if (!resourceClass.canBackupResource) { + lazy.logConsole.debug( + `We cannot backup ${resourceClass.key}. Skipping.` + ); + continue; + } + let resourcePath = PathUtils.join(stagingPath, resourceClass.key); await IOUtils.makeDirectory(resourcePath); @@ -1726,7 +1733,6 @@ export class BackupService extends EventTarget { ); let result = await this.createAndPopulateStagingFolder(profilePath); - this.#backupInProgress = true; currentStep = result.currentStep; if (result.error) { // Re-throw the error so we can catch it below for telemetry diff --git a/browser/components/backup/docs/backup-resources.rst b/browser/components/backup/docs/backup-resources.rst @@ -48,3 +48,11 @@ Each ``BackupResource`` subclass is registered for use by the .. js:autoclass:: SessionStoreBackupResource :members: :private-members: + +.. js:autoclass:: SiteSettingsBackupResource + :members: + :private-members: + +.. js:autoclass:: BookmarksBackupResource + :members: + :private-members: diff --git a/browser/components/backup/moz.build b/browser/components/backup/moz.build @@ -32,6 +32,7 @@ EXTRA_JS_MODULES.backup += [ "MeasurementUtils.sys.mjs", "resources/AddonsBackupResource.sys.mjs", "resources/BackupResource.sys.mjs", + "resources/BookmarksBackupResource.sys.mjs", "resources/CookiesBackupResource.sys.mjs", "resources/CredentialsAndSecurityBackupResource.sys.mjs", "resources/FormHistoryBackupResource.sys.mjs", @@ -39,4 +40,5 @@ EXTRA_JS_MODULES.backup += [ "resources/PlacesBackupResource.sys.mjs", "resources/PreferencesBackupResource.sys.mjs", "resources/SessionStoreBackupResource.sys.mjs", + "resources/SiteSettingsBackupResource.sys.mjs", ] diff --git a/browser/components/backup/resources/BackupResource.sys.mjs b/browser/components/backup/resources/BackupResource.sys.mjs @@ -10,22 +10,8 @@ ChromeUtils.defineESModuleGetters(lazy, { Sqlite: "resource://gre/modules/Sqlite.sys.mjs", BackupError: "resource:///modules/backup/BackupError.mjs", ERRORS: "chrome://browser/content/backup/backup-constants.mjs", - PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", }); -XPCOMUtils.defineLazyPreferenceGetter( - lazy, - "isBrowsingHistoryEnabled", - "places.history.enabled", - true -); -XPCOMUtils.defineLazyPreferenceGetter( - lazy, - "isSanitizeOnShutdownEnabled", - "privacy.sanitize.sanitizeOnShutdown", - false -); - // Convert from bytes to kilobytes (not kibibytes). export const BYTES_IN_KB = 1000; @@ -245,12 +231,16 @@ export class BackupResource { * * @returns {boolean} */ - static canBackupHistory() { - return ( - !lazy.PrivateBrowsingUtils.permanentPrivateBrowsing && - !lazy.isSanitizeOnShutdownEnabled && - lazy.isBrowsingHistoryEnabled - ); + + /** + * Returns true if the resource is enabled for backup based on different + * browser preferences and configurations. Otherwise, returns false. + * + * @returns {boolean} + */ + static get canBackupResource() { + // This is meant to be overridden if a resource requires checks; default is true. + return true; } constructor() {} diff --git a/browser/components/backup/resources/BookmarksBackupResource.sys.mjs b/browser/components/backup/resources/BookmarksBackupResource.sys.mjs @@ -0,0 +1,72 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.sys.mjs", +}); + +const BOOKMARKS_BACKUP_FILENAME = "bookmarks.jsonlz4"; + +/** + * Class representing Bookmarks database related files within a user profile. + */ +export class BookmarksBackupResource extends BackupResource { + static get key() { + return "bookmarks"; + } + + static get requiresEncryption() { + return false; + } + + static get priority() { + return 1; + } + + async backup( + stagingPath, + _profilePath = PathUtils.profileDir, + _isEncrypting = false + ) { + let bookmarksBackupFile = PathUtils.join( + stagingPath, + BOOKMARKS_BACKUP_FILENAME + ); + await lazy.BookmarkJSONUtils.exportToFile(bookmarksBackupFile, { + compress: true, + }); + return null; + } + + async recover(_manifestEntry, recoveryPath, _destProfilePath) { + /** + * pass the file path to postRecovery() so that we can import all bookmarks into the new + * profile once it's been launched and restored. + */ + let bookmarksBackupPath = PathUtils.join( + recoveryPath, + BOOKMARKS_BACKUP_FILENAME + ); + return { bookmarksBackupPath }; + } + + async postRecovery(postRecoveryEntry) { + if (postRecoveryEntry?.bookmarksBackupPath) { + await lazy.BookmarkJSONUtils.importFromFile( + postRecoveryEntry.bookmarksBackupPath, + { + replace: true, + } + ); + } + } + + async measure(_profilePath = PathUtils.profileDir) { + // Unsure how to measure this!! + } +} diff --git a/browser/components/backup/resources/CookiesBackupResource.sys.mjs b/browser/components/backup/resources/CookiesBackupResource.sys.mjs @@ -16,15 +16,16 @@ export class CookiesBackupResource extends BackupResource { return true; } + static get canBackupResource() { + // We don't backup cookies right now + return false; + } + async backup( stagingPath, profilePath = PathUtils.profileDir, _isEncrypting = false ) { - if (!BackupResource.canBackupHistory()) { - return null; - } - await BackupResource.copySqliteDatabases(profilePath, stagingPath, [ "cookies.sqlite", ]); diff --git a/browser/components/backup/resources/FormHistoryBackupResource.sys.mjs b/browser/components/backup/resources/FormHistoryBackupResource.sys.mjs @@ -3,6 +3,48 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs"; +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isBrowsingHistoryEnabled", + "places.history.enabled", + true +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isSanitizeOnShutdownEnabled", + "privacy.sanitize.sanitizeOnShutdown", + false +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isFormDataClearedOnShutdown", + "privacy.clearOnShutdown.formdata", + false +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isFormDataClearedOnShutdown2", + "privacy.clearOnShutdown_v2.formdata", + false +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "useOldClearHistoryDialog", + "privacy.sanitize.useOldClearHistoryDialog", + false +); /** * Class representing Form history database within a user profile. @@ -16,15 +58,29 @@ export class FormHistoryBackupResource extends BackupResource { return false; } + static get canBackupResource() { + if ( + lazy.PrivateBrowsingUtils.permanentPrivateBrowsing || + !lazy.isBrowsingHistoryEnabled + ) { + return false; + } + + if (!lazy.isSanitizeOnShutdownEnabled) { + return true; + } + + if (lazy.useOldClearHistoryDialog) { + return !lazy.isFormDataClearedOnShutdown; + } + return !lazy.isFormDataClearedOnShutdown2; + } + async backup( stagingPath, profilePath = PathUtils.profileDir, _isEncrypting = false ) { - if (!BackupResource.canBackupHistory()) { - return null; - } - await BackupResource.copySqliteDatabases(profilePath, stagingPath, [ "formhistory.sqlite", ]); diff --git a/browser/components/backup/resources/PlacesBackupResource.sys.mjs b/browser/components/backup/resources/PlacesBackupResource.sys.mjs @@ -2,17 +2,51 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs"; import { MeasurementUtils } from "resource:///modules/backup/MeasurementUtils.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { - BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.sys.mjs", PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", }); -const BOOKMARKS_BACKUP_FILENAME = "bookmarks.jsonlz4"; +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isBrowsingHistoryEnabled", + "places.history.enabled", + true +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isSanitizeOnShutdownEnabled", + "privacy.sanitize.sanitizeOnShutdown", + false +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isHistoryClearedOnShutdown2", + "privacy.clearOnShutdown_v2.browsingHistoryAndDownloads", + false +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "useOldClearHistoryDialog", + "privacy.sanitize.useOldClearHistoryDialog", + false +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isHistoryClearedOnShutdown", + "privacy.clearOnShutdown.history", + false +); /** * Class representing Places database related files within a user profile. @@ -30,26 +64,34 @@ export class PlacesBackupResource extends BackupResource { return 1; } + static get canBackupResource() { + if ( + lazy.PrivateBrowsingUtils.permanentPrivateBrowsing || + !lazy.isBrowsingHistoryEnabled + ) { + return false; + } + + if (!lazy.isSanitizeOnShutdownEnabled) { + return true; + } + + if (!lazy.useOldClearHistoryDialog) { + if (lazy.isHistoryClearedOnShutdown2) { + return false; + } + } else if (lazy.isHistoryClearedOnShutdown) { + return false; + } + + return true; + } + async backup( stagingPath, profilePath = PathUtils.profileDir, _isEncrypting = false ) { - /** - * Do not backup places.sqlite and favicons.sqlite if users have history disabled, want history cleared on shutdown or are using permanent private browsing mode. - * Instead, export all existing bookmarks to a compressed JSON file that we can read when restoring the backup. - */ - if (!BackupResource.canBackupHistory()) { - let bookmarksBackupFile = PathUtils.join( - stagingPath, - BOOKMARKS_BACKUP_FILENAME - ); - await lazy.BookmarkJSONUtils.exportToFile(bookmarksBackupFile, { - compress: true, - }); - return { bookmarksOnly: true }; - } - // These are copied in parallel because they're attached[1], and we don't // want them to get out of sync with one another. // @@ -80,43 +122,15 @@ export class PlacesBackupResource extends BackupResource { } async recover(manifestEntry, recoveryPath, destProfilePath) { - if (!manifestEntry) { - const simpleCopyFiles = ["places.sqlite", "favicons.sqlite"]; - await BackupResource.copyFiles( - recoveryPath, - destProfilePath, - simpleCopyFiles - ); - } else { - const { bookmarksOnly } = manifestEntry; - - /** - * If the recovery file only has bookmarks backed up, pass the file path to postRecovery() - * so that we can import all bookmarks into the new profile once it's been launched and restored. - */ - if (bookmarksOnly) { - let bookmarksBackupPath = PathUtils.join( - recoveryPath, - BOOKMARKS_BACKUP_FILENAME - ); - return { bookmarksBackupPath }; - } - } - + const simpleCopyFiles = ["places.sqlite", "favicons.sqlite"]; + await BackupResource.copyFiles( + recoveryPath, + destProfilePath, + simpleCopyFiles + ); return null; } - async postRecovery(postRecoveryEntry) { - if (postRecoveryEntry?.bookmarksBackupPath) { - await lazy.BookmarkJSONUtils.importFromFile( - postRecoveryEntry.bookmarksBackupPath, - { - replace: true, - } - ); - } - } - async measure(profilePath = PathUtils.profileDir) { let placesDBPath = PathUtils.join(profilePath, "places.sqlite"); let faviconsDBPath = PathUtils.join(profilePath, "favicons.sqlite"); diff --git a/browser/components/backup/resources/PreferencesBackupResource.sys.mjs b/browser/components/backup/resources/PreferencesBackupResource.sys.mjs @@ -80,15 +80,6 @@ export class PreferencesBackupResource extends BackupResource { ]; await BackupResource.copyFiles(profilePath, stagingPath, simpleCopyFiles); - if (BackupResource.canBackupHistory()) { - const sqliteDatabases = ["permissions.sqlite", "content-prefs.sqlite"]; - await BackupResource.copySqliteDatabases( - profilePath, - stagingPath, - sqliteDatabases - ); - } - // prefs.js is a special case - we have a helper function to flush the // current prefs state to disk off of the main thread. let prefsDestPath = PathUtils.join(stagingPath, "prefs.js"); @@ -186,8 +177,6 @@ export class PreferencesBackupResource extends BackupResource { const simpleCopyFiles = [ "prefs.js", "xulstore.json", - "permissions.sqlite", - "content-prefs.sqlite", "containers.json", "handlers.json", "user.js", @@ -220,8 +209,6 @@ export class PreferencesBackupResource extends BackupResource { const files = [ "prefs.js", "xulstore.json", - "permissions.sqlite", - "content-prefs.sqlite", "containers.json", "handlers.json", "search.json.mozlz4", diff --git a/browser/components/backup/resources/SiteSettingsBackupResource.sys.mjs b/browser/components/backup/resources/SiteSettingsBackupResource.sys.mjs @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs"; +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isSanitizeOnShutdownEnabled", + "privacy.sanitize.sanitizeOnShutdown", + false +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isSiteSettingsClearedOnShutdown", + "privacy.clearOnShutdown.siteSettings", + false +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "isSiteSettingsClearedOnShutdown2", + "privacy.clearOnShutdown_v2.siteSettings", + false +); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "useOldClearHistoryDialog", + "privacy.sanitize.useOldClearHistoryDialog", + false +); + +/** + * Class representing the backup and restore of a user's site settings + */ +export class SiteSettingsBackupResource extends BackupResource { + static get key() { + return "site_settings"; + } + + static get requiresEncryption() { + return false; + } + + static get priority() { + return 1; + } + + static get canBackupResource() { + if (!lazy.isSanitizeOnShutdownEnabled) { + return true; + } + + if (lazy.useOldClearHistoryDialog) { + return !lazy.isSiteSettingsClearedOnShutdown; + } + return !lazy.isSiteSettingsClearedOnShutdown2; + } + + async backup( + stagingPath, + profilePath = PathUtils.profileDir, + _isEncrypting = false + ) { + const sqliteDatabases = ["permissions.sqlite", "content-prefs.sqlite"]; + await BackupResource.copySqliteDatabases( + profilePath, + stagingPath, + sqliteDatabases + ); + return null; + } + + async recover(_manifestEntry, recoveryPath, destProfilePath) { + /** + * pass the file path to postRecovery() so that we can import all bookmarks into the new + * profile once it's been launched and restored. + */ + const simpleCopyFiles = ["permissions.sqlite", "content-prefs.sqlite"]; + await BackupResource.copyFiles( + recoveryPath, + destProfilePath, + simpleCopyFiles + ); + + return null; + } + + async measure(_profilePath = PathUtils.profileDir) { + // Unsure how to measure this!! + } +}