summaryrefslogtreecommitdiffstats
path: root/browser/components/backup/resources
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
commit59203c63bb777a3bacec32fb8830fba33540e809 (patch)
tree58298e711c0ff0575818c30485b44a2f21bf28a0 /browser/components/backup/resources
parentAdding upstream version 126.0.1. (diff)
downloadfirefox-59203c63bb777a3bacec32fb8830fba33540e809.tar.xz
firefox-59203c63bb777a3bacec32fb8830fba33540e809.zip
Adding upstream version 127.0.upstream/127.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/backup/resources')
-rw-r--r--browser/components/backup/resources/AddonsBackupResource.sys.mjs75
-rw-r--r--browser/components/backup/resources/BackupResource.sys.mjs170
-rw-r--r--browser/components/backup/resources/CookiesBackupResource.sys.mjs14
-rw-r--r--browser/components/backup/resources/CredentialsAndSecurityBackupResource.sys.mjs36
-rw-r--r--browser/components/backup/resources/FormHistoryBackupResource.sys.mjs16
-rw-r--r--browser/components/backup/resources/MiscDataBackupResource.sys.mjs135
-rw-r--r--browser/components/backup/resources/PlacesBackupResource.sys.mjs69
-rw-r--r--browser/components/backup/resources/PreferencesBackupResource.sys.mjs52
-rw-r--r--browser/components/backup/resources/SessionStoreBackupResource.sys.mjs26
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.