diff options
Diffstat (limited to 'browser/components/backup/resources')
9 files changed, 502 insertions, 91 deletions
diff --git a/browser/components/backup/resources/AddonsBackupResource.sys.mjs b/browser/components/backup/resources/AddonsBackupResource.sys.mjs index 83b97ed2f2..29b51b8a7f 100644 --- a/browser/components/backup/resources/AddonsBackupResource.sys.mjs +++ b/browser/components/backup/resources/AddonsBackupResource.sys.mjs @@ -16,6 +16,73 @@ export class AddonsBackupResource extends BackupResource { return false; } + async backup(stagingPath, profilePath = PathUtils.profileDir) { + // Files and directories to backup. + let toCopy = [ + "extensions.json", + "extension-settings.json", + "extension-preferences.json", + "addonStartup.json.lz4", + "browser-extension-data", + "extension-store-permissions", + ]; + await BackupResource.copyFiles(profilePath, stagingPath, toCopy); + + // Backup only the XPIs in the extensions directory. + let xpiFiles = []; + let extensionsXPIDirectoryPath = PathUtils.join(profilePath, "extensions"); + let xpiDirectoryChildren = await IOUtils.getChildren( + extensionsXPIDirectoryPath, + { + ignoreAbsent: true, + } + ); + for (const childFilePath of xpiDirectoryChildren) { + if (childFilePath.endsWith(".xpi")) { + let childFileName = PathUtils.filename(childFilePath); + xpiFiles.push(childFileName); + } + } + // Create the extensions directory in the staging directory. + let stagingExtensionsXPIDirectoryPath = PathUtils.join( + stagingPath, + "extensions" + ); + await IOUtils.makeDirectory(stagingExtensionsXPIDirectoryPath); + // Copy all found XPIs to the staging directory. + await BackupResource.copyFiles( + extensionsXPIDirectoryPath, + stagingExtensionsXPIDirectoryPath, + xpiFiles + ); + + // Copy storage sync database. + let databases = ["storage-sync-v2.sqlite"]; + await BackupResource.copySqliteDatabases( + profilePath, + stagingPath, + databases + ); + + return null; + } + + async recover(_manifestEntry, recoveryPath, destProfilePath) { + const files = [ + "extensions.json", + "extension-settings.json", + "extension-preferences.json", + "addonStartup.json.lz4", + "browser-extension-data", + "extension-store-permissions", + "extensions", + "storage-sync-v2.sqlite", + ]; + await BackupResource.copyFiles(recoveryPath, destProfilePath, files); + + return null; + } + async measure(profilePath = PathUtils.profileDir) { // Report the total size of the extension json files. const jsonFiles = [ @@ -55,16 +122,16 @@ export class AddonsBackupResource extends BackupResource { Glean.browserBackup.storageSyncSize.set(storageSyncSize); // Report the total size of XPI files in the extensions directory. - let extensionsXpiDirectoryPath = PathUtils.join(profilePath, "extensions"); - let extensionsXpiDirectorySize = await BackupResource.getDirectorySize( - extensionsXpiDirectoryPath, + let extensionsXPIDirectoryPath = PathUtils.join(profilePath, "extensions"); + let extensionsXPIDirectorySize = await BackupResource.getDirectorySize( + extensionsXPIDirectoryPath, { shouldExclude: (filePath, fileType) => fileType !== "regular" || !filePath.endsWith(".xpi"), } ); Glean.browserBackup.extensionsXpiDirectorySize.set( - extensionsXpiDirectorySize + extensionsXPIDirectorySize ); // Report the total size of the browser extension data. diff --git a/browser/components/backup/resources/BackupResource.sys.mjs b/browser/components/backup/resources/BackupResource.sys.mjs index d851eb5199..5be6314a60 100644 --- a/browser/components/backup/resources/BackupResource.sys.mjs +++ b/browser/components/backup/resources/BackupResource.sys.mjs @@ -2,6 +2,14 @@ * 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"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Sqlite: "resource://gre/modules/Sqlite.sys.mjs", +}); + // Convert from bytes to kilobytes (not kibibytes). export const BYTES_IN_KB = 1000; @@ -50,6 +58,19 @@ export class BackupResource { } /** + * This can be overridden to return a number indicating the priority the + * resource should have in the backup order. + * + * Resources with a higher priority will be backed up first. + * The default priority of 0 indicates it can be processed in any order. + * + * @returns {number} + */ + static get priority() { + return 0; + } + + /** * Get the size of a file. * * @param {string} filePath - path to a file. @@ -129,6 +150,75 @@ export class BackupResource { return size; } + /** + * Copy a set of SQLite databases safely from a source directory to a + * destination directory. A new read-only connection is opened for each + * database, and then a backup is created. If the source database does not + * exist, it is ignored. + * + * @param {string} sourcePath + * Path to the source directory of the SQLite databases. + * @param {string} destPath + * Path to the destination directory where the SQLite databases should be + * copied to. + * @param {Array<string>} sqliteDatabases + * An array of filenames of the SQLite databases to copy. + * @returns {Promise<undefined>} + */ + static async copySqliteDatabases(sourcePath, destPath, sqliteDatabases) { + for (let fileName of sqliteDatabases) { + let sourceFilePath = PathUtils.join(sourcePath, fileName); + + if (!(await IOUtils.exists(sourceFilePath))) { + continue; + } + + let destFilePath = PathUtils.join(destPath, fileName); + let connection; + + try { + connection = await lazy.Sqlite.openConnection({ + path: sourceFilePath, + readOnly: true, + }); + + await connection.backup( + destFilePath, + BackupResource.SQLITE_PAGES_PER_STEP, + BackupResource.SQLITE_STEP_DELAY_MS + ); + } finally { + await connection?.close(); + } + } + } + + /** + * A helper function to copy a set of files from a source directory to a + * destination directory. Callers should ensure that the source files can be + * copied safely before invoking this function. Files that do not exist will + * be ignored. Callers that wish to copy SQLite databases should use + * copySqliteDatabases() instead. + * + * @param {string} sourcePath + * Path to the source directory of the files to be copied. + * @param {string} destPath + * Path to the destination directory where the files should be + * copied to. + * @param {string[]} fileNames + * An array of filenames of the files to copy. + * @returns {Promise<undefined>} + */ + static async copyFiles(sourcePath, destPath, fileNames) { + for (let fileName of fileNames) { + let sourceFilePath = PathUtils.join(sourcePath, fileName); + let destFilePath = PathUtils.join(destPath, fileName); + if (await IOUtils.exists(sourceFilePath)) { + await IOUtils.copy(sourceFilePath, destFilePath, { recursive: true }); + } + } + } + constructor() {} /** @@ -144,12 +234,12 @@ export class BackupResource { } /** - * Perform a safe copy of the resource(s) and write them into the backup - * database. The Promise should resolve with an object that can be serialized - * to JSON, as it will be written to the manifest file. This same object will - * be deserialized and passed to restore() when restoring the backup. This - * object can be null if no additional information is needed to restore the - * backup. + * Perform a safe copy of the datastores that this resource manages and write + * them into the backup database. The Promise should resolve with an object + * that can be serialized to JSON, as it will be written to the manifest file. + * This same object will be deserialized and passed to restore() when + * restoring the backup. This object can be null if no additional information + * is needed to restore the backup. * * @param {string} stagingPath * The path to the staging folder where copies of the datastores for this @@ -167,4 +257,72 @@ export class BackupResource { async backup(stagingPath, profilePath = null) { throw new Error("BackupResource::backup must be overridden"); } + + /** + * Recovers the datastores that this resource manages from a backup archive + * that has been decompressed into the recoveryPath. A pre-existing unlocked + * user profile should be available to restore into, and destProfilePath + * should point at its location on the file system. + * + * This method is not expected to be running in an app connected to the + * destProfilePath. If the BackupResource needs to run some operations + * while attached to the recovery profile, it should do that work inside of + * postRecovery(). If data needs to be transferred to postRecovery(), it + * should be passed as a JSON serializable object in the return value of this + * method. + * + * @see BackupResource.postRecovery() + * @param {object|null} manifestEntry + * The object that was returned by the backup() method when the backup was + * created. This object can be null if no additional information was needed + * for recovery. + * @param {string} recoveryPath + * The path to the resource directory where the backup archive has been + * decompressed. + * @param {string} destProfilePath + * The path to the profile directory where the backup should be restored to. + * @returns {Promise<object|null>} + * This should return a JSON serializable object that will be passed to + * postRecovery() if any data needs to be passed to it. This object can be + * null if no additional information is needed for postRecovery(). + */ + // eslint-disable-next-line no-unused-vars + async recover(manifestEntry, recoveryPath, destProfilePath) { + throw new Error("BackupResource::recover must be overridden"); + } + + /** + * Perform any post-recovery operations that need to be done after the + * recovery has been completed and the recovered profile has been attached + * to. + * + * This method is running in an app connected to the recovered profile. The + * profile is locked, but this postRecovery method can be used to insert + * data into connected datastores, or perform any other operations that can + * only occur within the context of the recovered profile. + * + * @see BackupResource.recover() + * @param {object|null} postRecoveryEntry + * The object that was returned by the recover() method when the recovery + * was originally done. This object can be null if no additional information + * is needed for post-recovery. + */ + // eslint-disable-next-line no-unused-vars + async postRecovery(postRecoveryEntry) { + // no-op by default + } } + +XPCOMUtils.defineLazyPreferenceGetter( + BackupResource, + "SQLITE_PAGES_PER_STEP", + "browser.backup.sqlite.pages_per_step", + 5 +); + +XPCOMUtils.defineLazyPreferenceGetter( + BackupResource, + "SQLITE_STEP_DELAY_MS", + "browser.backup.sqlite.step_delay_ms", + 250 +); diff --git a/browser/components/backup/resources/CookiesBackupResource.sys.mjs b/browser/components/backup/resources/CookiesBackupResource.sys.mjs index 8b988fd532..87ac27757c 100644 --- a/browser/components/backup/resources/CookiesBackupResource.sys.mjs +++ b/browser/components/backup/resources/CookiesBackupResource.sys.mjs @@ -16,6 +16,20 @@ export class CookiesBackupResource extends BackupResource { return true; } + async backup(stagingPath, profilePath = PathUtils.profileDir) { + await BackupResource.copySqliteDatabases(profilePath, stagingPath, [ + "cookies.sqlite", + ]); + return null; + } + + async recover(_manifestEntry, recoveryPath, destProfilePath) { + await BackupResource.copyFiles(recoveryPath, destProfilePath, [ + "cookies.sqlite", + ]); + return null; + } + async measure(profilePath = PathUtils.profileDir) { let cookiesDBPath = PathUtils.join(profilePath, "cookies.sqlite"); let cookiesSize = await BackupResource.getFileSize(cookiesDBPath); diff --git a/browser/components/backup/resources/CredentialsAndSecurityBackupResource.sys.mjs b/browser/components/backup/resources/CredentialsAndSecurityBackupResource.sys.mjs index 89069de826..03a0267f33 100644 --- a/browser/components/backup/resources/CredentialsAndSecurityBackupResource.sys.mjs +++ b/browser/components/backup/resources/CredentialsAndSecurityBackupResource.sys.mjs @@ -16,6 +16,42 @@ export class CredentialsAndSecurityBackupResource extends BackupResource { return true; } + async backup(stagingPath, profilePath = PathUtils.profileDir) { + const simpleCopyFiles = [ + "pkcs11.txt", + "logins.json", + "logins-backup.json", + "autofill-profiles.json", + "signedInUser.json", + ]; + await BackupResource.copyFiles(profilePath, stagingPath, simpleCopyFiles); + + const sqliteDatabases = ["cert9.db", "key4.db", "credentialstate.sqlite"]; + await BackupResource.copySqliteDatabases( + profilePath, + stagingPath, + sqliteDatabases + ); + + return null; + } + + async recover(_manifestEntry, recoveryPath, destProfilePath) { + const files = [ + "pkcs11.txt", + "logins.json", + "logins-backup.json", + "autofill-profiles.json", + "signedInUser.json", + "cert9.db", + "key4.db", + "credentialstate.sqlite", + ]; + await BackupResource.copyFiles(recoveryPath, destProfilePath, files); + + return null; + } + async measure(profilePath = PathUtils.profileDir) { const securityFiles = ["cert9.db", "pkcs11.txt"]; let securitySize = 0; diff --git a/browser/components/backup/resources/FormHistoryBackupResource.sys.mjs b/browser/components/backup/resources/FormHistoryBackupResource.sys.mjs index cb314eb34d..8e35afc66b 100644 --- a/browser/components/backup/resources/FormHistoryBackupResource.sys.mjs +++ b/browser/components/backup/resources/FormHistoryBackupResource.sys.mjs @@ -16,6 +16,22 @@ export class FormHistoryBackupResource extends BackupResource { return false; } + async backup(stagingPath, profilePath = PathUtils.profileDir) { + await BackupResource.copySqliteDatabases(profilePath, stagingPath, [ + "formhistory.sqlite", + ]); + + return null; + } + + async recover(_manifestEntry, recoveryPath, destProfilePath) { + await BackupResource.copyFiles(recoveryPath, destProfilePath, [ + "formhistory.sqlite", + ]); + + return null; + } + async measure(profilePath = PathUtils.profileDir) { let formHistoryDBPath = PathUtils.join(profilePath, "formhistory.sqlite"); let formHistorySize = await BackupResource.getFileSize(formHistoryDBPath); diff --git a/browser/components/backup/resources/MiscDataBackupResource.sys.mjs b/browser/components/backup/resources/MiscDataBackupResource.sys.mjs index 97224f0e31..3d66114599 100644 --- a/browser/components/backup/resources/MiscDataBackupResource.sys.mjs +++ b/browser/components/backup/resources/MiscDataBackupResource.sys.mjs @@ -5,11 +5,19 @@ import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs"; const lazy = {}; - ChromeUtils.defineESModuleGetters(lazy, { - Sqlite: "resource://gre/modules/Sqlite.sys.mjs", + ActivityStreamStorage: + "resource://activity-stream/lib/ActivityStreamStorage.sys.mjs", + ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs", }); +const SNIPPETS_TABLE_NAME = "snippets"; +const FILES_FOR_BACKUP = [ + "enumerate_devices.txt", + "protections.sqlite", + "SiteSecurityServiceState.bin", +]; + /** * Class representing miscellaneous files for telemetry, site storage, * media device origin mapping, chrome privileged IndexedDB databases, @@ -25,57 +33,102 @@ export class MiscDataBackupResource extends BackupResource { } async backup(stagingPath, profilePath = PathUtils.profileDir) { - const files = [ - "times.json", - "enumerate_devices.txt", - "SiteSecurityServiceState.bin", - ]; - - for (let fileName of files) { - let sourcePath = PathUtils.join(profilePath, fileName); - let destPath = PathUtils.join(stagingPath, fileName); - if (await IOUtils.exists(sourcePath)) { - await IOUtils.copy(sourcePath, destPath, { recursive: true }); - } - } + const files = ["enumerate_devices.txt", "SiteSecurityServiceState.bin"]; + await BackupResource.copyFiles(profilePath, stagingPath, files); const sqliteDatabases = ["protections.sqlite"]; - - for (let fileName of sqliteDatabases) { - let sourcePath = PathUtils.join(profilePath, fileName); - let destPath = PathUtils.join(stagingPath, fileName); - let connection; - - try { - connection = await lazy.Sqlite.openConnection({ - path: sourcePath, - readOnly: true, - }); - - await connection.backup(destPath); - } finally { - await connection.close(); - } - } + await BackupResource.copySqliteDatabases( + profilePath, + stagingPath, + sqliteDatabases + ); // Bug 1890585 - we don't currently have the ability to copy the - // chrome-privileged IndexedDB databases under storage/permanent/chrome, so - // we'll just skip that for now. + // chrome-privileged IndexedDB databases under storage/permanent/chrome. + // Instead, we'll manually export any IndexedDB data we need to backup + // to a separate JSON file. + + // The first IndexedDB database we want to back up is the ActivityStream + // one - specifically, the "snippets" table, as this contains information + // on ASRouter impressions, blocked messages, message group impressions, + // etc. + let storage = new lazy.ActivityStreamStorage({ + storeNames: [SNIPPETS_TABLE_NAME], + }); + let snippetsTable = await storage.getDbTable(SNIPPETS_TABLE_NAME); + let snippetsObj = {}; + for (let key of await snippetsTable.getAllKeys()) { + snippetsObj[key] = await snippetsTable.get(key); + } + let snippetsBackupFile = PathUtils.join( + stagingPath, + "activity-stream-snippets.json" + ); + await IOUtils.writeJSON(snippetsBackupFile, snippetsObj); return null; } - async measure(profilePath = PathUtils.profileDir) { - const files = [ + async recover(_manifestEntry, recoveryPath, destProfilePath) { + await BackupResource.copyFiles( + recoveryPath, + destProfilePath, + FILES_FOR_BACKUP + ); + + // The times.json file, the one that powers ProfileAge, works hand in hand + // with the Telemetry client ID. We don't want to accidentally _overwrite_ + // a pre-existing times.json with data from a different profile, because + // then the client ID wouldn't match the times.json data anymore. + // + // The rule that we're following for backups and recoveries is that the + // recovered profile always inherits the client ID (and therefore the + // times.json) from the profile that _initiated recovery_. + // + // This means we want to copy the times.json file from the profile that's + // currently in use to the destProfilePath. + await BackupResource.copyFiles(PathUtils.profileDir, destProfilePath, [ "times.json", - "enumerate_devices.txt", - "protections.sqlite", - "SiteSecurityServiceState.bin", - ]; + ]); + + // We also want to write the recoveredFromBackup timestamp now. + let profileAge = await lazy.ProfileAge(destProfilePath); + await profileAge.recordRecoveredFromBackup(); + + // The activity-stream-snippets data will need to be written during the + // postRecovery phase, so we'll stash the path to the JSON file in the + // post recovery entry. + let snippetsBackupFile = PathUtils.join( + recoveryPath, + "activity-stream-snippets.json" + ); + return { snippetsBackupFile }; + } + + async postRecovery(postRecoveryEntry) { + let { snippetsBackupFile } = postRecoveryEntry; + // If for some reason, the activity-stream-snippets data file has been + // removed already, there's nothing to do. + if (!IOUtils.exists(snippetsBackupFile)) { + return; + } + + let snippetsData = await IOUtils.readJSON(snippetsBackupFile); + let storage = new lazy.ActivityStreamStorage({ + storeNames: [SNIPPETS_TABLE_NAME], + }); + let snippetsTable = await storage.getDbTable(SNIPPETS_TABLE_NAME); + for (let key in snippetsData) { + let value = snippetsData[key]; + await snippetsTable.set(key, value); + } + } + + async measure(profilePath = PathUtils.profileDir) { let fullSize = 0; - for (let filePath of files) { + for (let filePath of FILES_FOR_BACKUP) { let resourcePath = PathUtils.join(profilePath, filePath); let resourceSize = await BackupResource.getFileSize(resourcePath); if (Number.isInteger(resourceSize)) { diff --git a/browser/components/backup/resources/PlacesBackupResource.sys.mjs b/browser/components/backup/resources/PlacesBackupResource.sys.mjs index 1955406f51..3a9433e67c 100644 --- a/browser/components/backup/resources/PlacesBackupResource.sys.mjs +++ b/browser/components/backup/resources/PlacesBackupResource.sys.mjs @@ -10,7 +10,6 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.sys.mjs", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", - Sqlite: "resource://gre/modules/Sqlite.sys.mjs", }); XPCOMUtils.defineLazyPreferenceGetter( @@ -26,6 +25,8 @@ XPCOMUtils.defineLazyPreferenceGetter( false ); +const BOOKMARKS_BACKUP_FILENAME = "bookmarks.jsonlz4"; + /** * Class representing Places database related files within a user profile. */ @@ -38,8 +39,11 @@ export class PlacesBackupResource extends BackupResource { return false; } + static get priority() { + return 1; + } + async backup(stagingPath, profilePath = PathUtils.profileDir) { - const sqliteDatabases = ["places.sqlite", "favicons.sqlite"]; let canBackupHistory = !lazy.PrivateBrowsingUtils.permanentPrivateBrowsing && !lazy.isSanitizeOnShutdownEnabled && @@ -52,7 +56,7 @@ export class PlacesBackupResource extends BackupResource { if (!canBackupHistory) { let bookmarksBackupFile = PathUtils.join( stagingPath, - "bookmarks.jsonlz4" + BOOKMARKS_BACKUP_FILENAME ); await lazy.BookmarkJSONUtils.exportToFile(bookmarksBackupFile, { compress: true, @@ -60,25 +64,60 @@ export class PlacesBackupResource extends BackupResource { return { bookmarksOnly: true }; } - for (let fileName of sqliteDatabases) { - let sourcePath = PathUtils.join(profilePath, fileName); - let destPath = PathUtils.join(stagingPath, fileName); - let connection; + // These are copied in parallel because they're attached[1], and we don't + // want them to get out of sync with one another. + // + // [1]: https://www.sqlite.org/lang_attach.html + await Promise.all([ + BackupResource.copySqliteDatabases(profilePath, stagingPath, [ + "places.sqlite", + ]), + BackupResource.copySqliteDatabases(profilePath, stagingPath, [ + "favicons.sqlite", + ]), + ]); + + return null; + } - try { - connection = await lazy.Sqlite.openConnection({ - path: sourcePath, - readOnly: true, - }); + async recover(manifestEntry, recoveryPath, destProfilePath) { + if (!manifestEntry) { + const simpleCopyFiles = ["places.sqlite", "favicons.sqlite"]; + await BackupResource.copyFiles( + recoveryPath, + destProfilePath, + simpleCopyFiles + ); + } else { + const { bookmarksOnly } = manifestEntry; - await connection.backup(destPath); - } finally { - await connection.close(); + /** + * 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 }; } } + 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 index 012c0bf91e..80196cab74 100644 --- a/browser/components/backup/resources/PreferencesBackupResource.sys.mjs +++ b/browser/components/backup/resources/PreferencesBackupResource.sys.mjs @@ -3,7 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { BackupResource } from "resource:///modules/backup/BackupResource.sys.mjs"; -import { Sqlite } from "resource://gre/modules/Sqlite.sys.mjs"; /** * Class representing files that modify preferences and permissions within a user profile. @@ -28,32 +27,14 @@ export class PreferencesBackupResource extends BackupResource { "user.js", "chrome", ]; - - for (let fileName of simpleCopyFiles) { - let sourcePath = PathUtils.join(profilePath, fileName); - let destPath = PathUtils.join(stagingPath, fileName); - if (await IOUtils.exists(sourcePath)) { - await IOUtils.copy(sourcePath, destPath, { recursive: true }); - } - } + await BackupResource.copyFiles(profilePath, stagingPath, simpleCopyFiles); const sqliteDatabases = ["permissions.sqlite", "content-prefs.sqlite"]; - - for (let fileName of sqliteDatabases) { - let sourcePath = PathUtils.join(profilePath, fileName); - let destPath = PathUtils.join(stagingPath, fileName); - let connection; - - try { - connection = await Sqlite.openConnection({ - path: sourcePath, - }); - - await connection.backup(destPath); - } finally { - await connection.close(); - } - } + 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. @@ -64,6 +45,27 @@ export class PreferencesBackupResource extends BackupResource { return null; } + async recover(_manifestEntry, recoveryPath, destProfilePath) { + const simpleCopyFiles = [ + "prefs.js", + "xulstore.json", + "permissions.sqlite", + "content-prefs.sqlite", + "containers.json", + "handlers.json", + "search.json.mozlz4", + "user.js", + "chrome", + ]; + await BackupResource.copyFiles( + recoveryPath, + destProfilePath, + simpleCopyFiles + ); + + return null; + } + async measure(profilePath = PathUtils.profileDir) { const files = [ "prefs.js", diff --git a/browser/components/backup/resources/SessionStoreBackupResource.sys.mjs b/browser/components/backup/resources/SessionStoreBackupResource.sys.mjs index fa5dcca848..d28598944f 100644 --- a/browser/components/backup/resources/SessionStoreBackupResource.sys.mjs +++ b/browser/components/backup/resources/SessionStoreBackupResource.sys.mjs @@ -28,6 +28,32 @@ export class SessionStoreBackupResource extends BackupResource { return false; } + async backup(stagingPath, profilePath = PathUtils.profileDir) { + let sessionStoreState = lazy.SessionStore.getCurrentState(true); + let sessionStorePath = PathUtils.join(stagingPath, "sessionstore.jsonlz4"); + + /* Bug 1891854 - remove cookies from session store state if the backup file is + * not encrypted. */ + + await IOUtils.writeJSON(sessionStorePath, sessionStoreState, { + compress: true, + }); + await BackupResource.copyFiles(profilePath, stagingPath, [ + "sessionstore-backups", + ]); + + return null; + } + + async recover(_manifestEntry, recoveryPath, destProfilePath) { + await BackupResource.copyFiles(recoveryPath, destProfilePath, [ + "sessionstore.jsonlz4", + "sessionstore-backups", + ]); + + return null; + } + async measure(profilePath = PathUtils.profileDir) { // Get the current state of the session store JSON and // measure it's uncompressed size. |