mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
Bug 1885609
- implement backup method for PlacesBackupResource. r=backup-reviewers,places-reviewers,mak,mconley
Implements `PlacesBackupResource.backup` to store a copy of `places.sqlite` and `favicons.sqlite` in the staging folder. If users don't want history remembered or use permanent private browsing mode, we will backup bookmarks instead to a file called `bookmarks.json`. Automatic backup is not yet implemented, so to test changes locally, go to `chrome://browser/content/backup/debug.html` to view where we store the staging folder and to manually run the backup methods for all available backup resources. Backup files for `PlacesBackupResource` should be under the `places` subfolder in the staging folder. Differential Revision: https://phabricator.services.mozilla.com/D206532
This commit is contained in:
parent
c3a4b0e099
commit
ccd602ba69
@ -3,6 +3,28 @@
|
||||
* 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, {
|
||||
BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.sys.mjs",
|
||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
|
||||
Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"isBrowsingHistoryEnabled",
|
||||
"places.history.enabled",
|
||||
true
|
||||
);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
lazy,
|
||||
"isSanitizeOnShutdownEnabled",
|
||||
"privacy.sanitize.sanitizeOnShutdown",
|
||||
false
|
||||
);
|
||||
|
||||
/**
|
||||
* Class representing Places database related files within a user profile.
|
||||
@ -16,6 +38,47 @@ export class PlacesBackupResource extends BackupResource {
|
||||
return false;
|
||||
}
|
||||
|
||||
async backup(stagingPath, profilePath = PathUtils.profileDir) {
|
||||
const sqliteDatabases = ["places.sqlite", "favicons.sqlite"];
|
||||
let canBackupHistory =
|
||||
!lazy.PrivateBrowsingUtils.permanentPrivateBrowsing &&
|
||||
!lazy.isSanitizeOnShutdownEnabled &&
|
||||
lazy.isBrowsingHistoryEnabled;
|
||||
|
||||
/**
|
||||
* 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 (!canBackupHistory) {
|
||||
let bookmarksBackupFile = PathUtils.join(
|
||||
stagingPath,
|
||||
"bookmarks.jsonlz4"
|
||||
);
|
||||
await lazy.BookmarkJSONUtils.exportToFile(bookmarksBackupFile, {
|
||||
compress: true,
|
||||
});
|
||||
return { bookmarksOnly: true };
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async measure(profilePath = PathUtils.profileDir) {
|
||||
let placesDBPath = PathUtils.join(profilePath, "places.sqlite");
|
||||
let faviconsDBPath = PathUtils.join(profilePath, "favicons.sqlite");
|
||||
|
@ -0,0 +1,226 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { PlacesBackupResource } = ChromeUtils.importESModule(
|
||||
"resource:///modules/backup/PlacesBackupResource.sys.mjs"
|
||||
);
|
||||
const { PrivateBrowsingUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/PrivateBrowsingUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const HISTORY_ENABLED_PREF = "places.history.enabled";
|
||||
const SANITIZE_ON_SHUTDOWN_PREF = "privacy.sanitize.sanitizeOnShutdown";
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
/**
|
||||
* Even though test_backup_no_saved_history clears user prefs too,
|
||||
* clear them here as well in case that test fails and we don't
|
||||
* reach the end of the test, which handles the cleanup.
|
||||
*/
|
||||
Services.prefs.clearUserPref(HISTORY_ENABLED_PREF);
|
||||
Services.prefs.clearUserPref(SANITIZE_ON_SHUTDOWN_PREF);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that we can measure Places DB related files in the profile directory.
|
||||
*/
|
||||
add_task(async function test_measure() {
|
||||
Services.fog.testResetFOG();
|
||||
|
||||
const EXPECTED_PLACES_DB_SIZE = 5240;
|
||||
const EXPECTED_FAVICONS_DB_SIZE = 5240;
|
||||
|
||||
// Create resource files in temporary directory
|
||||
const tempDir = PathUtils.tempDir;
|
||||
let tempPlacesDBPath = PathUtils.join(tempDir, "places.sqlite");
|
||||
let tempFaviconsDBPath = PathUtils.join(tempDir, "favicons.sqlite");
|
||||
await createKilobyteSizedFile(tempPlacesDBPath, EXPECTED_PLACES_DB_SIZE);
|
||||
await createKilobyteSizedFile(tempFaviconsDBPath, EXPECTED_FAVICONS_DB_SIZE);
|
||||
|
||||
let placesBackupResource = new PlacesBackupResource();
|
||||
await placesBackupResource.measure(tempDir);
|
||||
|
||||
let placesMeasurement = Glean.browserBackup.placesSize.testGetValue();
|
||||
let faviconsMeasurement = Glean.browserBackup.faviconsSize.testGetValue();
|
||||
let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
|
||||
|
||||
// Compare glean vs telemetry measurements
|
||||
TelemetryTestUtils.assertScalar(
|
||||
scalars,
|
||||
"browser.backup.places_size",
|
||||
placesMeasurement,
|
||||
"Glean and telemetry measurements for places.sqlite should be equal"
|
||||
);
|
||||
TelemetryTestUtils.assertScalar(
|
||||
scalars,
|
||||
"browser.backup.favicons_size",
|
||||
faviconsMeasurement,
|
||||
"Glean and telemetry measurements for favicons.sqlite should be equal"
|
||||
);
|
||||
|
||||
// Compare glean measurements vs actual file sizes
|
||||
Assert.equal(
|
||||
placesMeasurement,
|
||||
EXPECTED_PLACES_DB_SIZE,
|
||||
"Should have collected the correct glean measurement for places.sqlite"
|
||||
);
|
||||
Assert.equal(
|
||||
faviconsMeasurement,
|
||||
EXPECTED_FAVICONS_DB_SIZE,
|
||||
"Should have collected the correct glean measurement for favicons.sqlite"
|
||||
);
|
||||
|
||||
await maybeRemovePath(tempPlacesDBPath);
|
||||
await maybeRemovePath(tempFaviconsDBPath);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that the backup method correctly copies places.sqlite and
|
||||
* favicons.sqlite from the profile directory into the staging directory.
|
||||
*/
|
||||
add_task(async function test_backup() {
|
||||
let sandbox = sinon.createSandbox();
|
||||
|
||||
let placesBackupResource = new PlacesBackupResource();
|
||||
let sourcePath = await IOUtils.createUniqueDirectory(
|
||||
PathUtils.tempDir,
|
||||
"PlacesBackupResource-source-test"
|
||||
);
|
||||
let stagingPath = await IOUtils.createUniqueDirectory(
|
||||
PathUtils.tempDir,
|
||||
"PlacesBackupResource-staging-test"
|
||||
);
|
||||
|
||||
let fakeConnection = {
|
||||
backup: sandbox.stub().resolves(true),
|
||||
close: sandbox.stub().resolves(true),
|
||||
};
|
||||
sandbox.stub(Sqlite, "openConnection").returns(fakeConnection);
|
||||
|
||||
await placesBackupResource.backup(stagingPath, sourcePath);
|
||||
|
||||
Assert.ok(
|
||||
fakeConnection.backup.calledTwice,
|
||||
"Backup should have been called twice"
|
||||
);
|
||||
Assert.ok(
|
||||
fakeConnection.backup.firstCall.calledWith(
|
||||
PathUtils.join(stagingPath, "places.sqlite")
|
||||
),
|
||||
"places.sqlite should have been backed up first"
|
||||
);
|
||||
Assert.ok(
|
||||
fakeConnection.backup.secondCall.calledWith(
|
||||
PathUtils.join(stagingPath, "favicons.sqlite")
|
||||
),
|
||||
"favicons.sqlite should have been backed up second"
|
||||
);
|
||||
|
||||
await maybeRemovePath(stagingPath);
|
||||
await maybeRemovePath(sourcePath);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that the backup method correctly creates a compressed bookmarks JSON file when users
|
||||
* don't want history saved, even on shutdown.
|
||||
*/
|
||||
add_task(async function test_backup_no_saved_history() {
|
||||
let sandbox = sinon.createSandbox();
|
||||
|
||||
let placesBackupResource = new PlacesBackupResource();
|
||||
let sourcePath = await IOUtils.createUniqueDirectory(
|
||||
PathUtils.tempDir,
|
||||
"PlacesBackupResource-source-test"
|
||||
);
|
||||
let stagingPath = await IOUtils.createUniqueDirectory(
|
||||
PathUtils.tempDir,
|
||||
"PlacesBackupResource-staging-test"
|
||||
);
|
||||
|
||||
let fakeConnection = {
|
||||
backup: sandbox.stub().resolves(true),
|
||||
close: sandbox.stub().resolves(true),
|
||||
};
|
||||
sandbox.stub(Sqlite, "openConnection").returns(fakeConnection);
|
||||
|
||||
/**
|
||||
* First verify that remember history pref alone affects backup file type for places,
|
||||
* despite sanitize on shutdown pref value.
|
||||
*/
|
||||
Services.prefs.setBoolPref(HISTORY_ENABLED_PREF, false);
|
||||
Services.prefs.setBoolPref(SANITIZE_ON_SHUTDOWN_PREF, false);
|
||||
|
||||
await placesBackupResource.backup(stagingPath, sourcePath);
|
||||
|
||||
Assert.ok(
|
||||
fakeConnection.backup.notCalled,
|
||||
"No sqlite connections should have been made with remember history disabled"
|
||||
);
|
||||
await assertFilesExist(stagingPath, [{ path: "bookmarks.jsonlz4" }]);
|
||||
await IOUtils.remove(PathUtils.join(stagingPath, "bookmarks.jsonlz4"));
|
||||
|
||||
/**
|
||||
* Now verify that the sanitize shutdown pref alone affects backup file type for places,
|
||||
* even if the user is okay with remembering history while browsing.
|
||||
*/
|
||||
Services.prefs.setBoolPref(HISTORY_ENABLED_PREF, true);
|
||||
Services.prefs.setBoolPref(SANITIZE_ON_SHUTDOWN_PREF, true);
|
||||
|
||||
fakeConnection.backup.resetHistory();
|
||||
await placesBackupResource.backup(stagingPath, sourcePath);
|
||||
|
||||
Assert.ok(
|
||||
fakeConnection.backup.notCalled,
|
||||
"No sqlite connections should have been made with sanitize shutdown enabled"
|
||||
);
|
||||
await assertFilesExist(stagingPath, [{ path: "bookmarks.jsonlz4" }]);
|
||||
|
||||
await maybeRemovePath(stagingPath);
|
||||
await maybeRemovePath(sourcePath);
|
||||
|
||||
sandbox.restore();
|
||||
Services.prefs.clearUserPref(HISTORY_ENABLED_PREF);
|
||||
Services.prefs.clearUserPref(SANITIZE_ON_SHUTDOWN_PREF);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that the backup method correctly creates a compressed bookmarks JSON file when
|
||||
* permanent private browsing mode is enabled.
|
||||
*/
|
||||
add_task(async function test_backup_private_browsing() {
|
||||
let sandbox = sinon.createSandbox();
|
||||
|
||||
let placesBackupResource = new PlacesBackupResource();
|
||||
let sourcePath = await IOUtils.createUniqueDirectory(
|
||||
PathUtils.tempDir,
|
||||
"PlacesBackupResource-source-test"
|
||||
);
|
||||
let stagingPath = await IOUtils.createUniqueDirectory(
|
||||
PathUtils.tempDir,
|
||||
"PlacesBackupResource-staging-test"
|
||||
);
|
||||
|
||||
let fakeConnection = {
|
||||
backup: sandbox.stub().resolves(true),
|
||||
close: sandbox.stub().resolves(true),
|
||||
};
|
||||
sandbox.stub(Sqlite, "openConnection").returns(fakeConnection);
|
||||
sandbox.stub(PrivateBrowsingUtils, "permanentPrivateBrowsing").value(true);
|
||||
|
||||
await placesBackupResource.backup(stagingPath, sourcePath);
|
||||
|
||||
Assert.ok(
|
||||
fakeConnection.backup.notCalled,
|
||||
"No sqlite connections should have been made with permanent private browsing enabled"
|
||||
);
|
||||
await assertFilesExist(stagingPath, [{ path: "bookmarks.jsonlz4" }]);
|
||||
|
||||
await maybeRemovePath(stagingPath);
|
||||
await maybeRemovePath(sourcePath);
|
||||
|
||||
sandbox.restore();
|
||||
});
|
@ -6,9 +6,6 @@ http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
const { CredentialsAndSecurityBackupResource } = ChromeUtils.importESModule(
|
||||
"resource:///modules/backup/CredentialsAndSecurityBackupResource.sys.mjs"
|
||||
);
|
||||
const { PlacesBackupResource } = ChromeUtils.importESModule(
|
||||
"resource:///modules/backup/PlacesBackupResource.sys.mjs"
|
||||
);
|
||||
const { AddonsBackupResource } = ChromeUtils.importESModule(
|
||||
"resource:///modules/backup/AddonsBackupResource.sys.mjs"
|
||||
);
|
||||
@ -79,59 +76,6 @@ add_task(async function test_profDDiskSpace() {
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that we can measure Places DB related files in the profile directory.
|
||||
*/
|
||||
add_task(async function test_placesBackupResource() {
|
||||
Services.fog.testResetFOG();
|
||||
|
||||
const EXPECTED_PLACES_DB_SIZE = 5240;
|
||||
const EXPECTED_FAVICONS_DB_SIZE = 5240;
|
||||
|
||||
// Create resource files in temporary directory
|
||||
const tempDir = PathUtils.tempDir;
|
||||
let tempPlacesDBPath = PathUtils.join(tempDir, "places.sqlite");
|
||||
let tempFaviconsDBPath = PathUtils.join(tempDir, "favicons.sqlite");
|
||||
await createKilobyteSizedFile(tempPlacesDBPath, EXPECTED_PLACES_DB_SIZE);
|
||||
await createKilobyteSizedFile(tempFaviconsDBPath, EXPECTED_FAVICONS_DB_SIZE);
|
||||
|
||||
let placesBackupResource = new PlacesBackupResource();
|
||||
await placesBackupResource.measure(tempDir);
|
||||
|
||||
let placesMeasurement = Glean.browserBackup.placesSize.testGetValue();
|
||||
let faviconsMeasurement = Glean.browserBackup.faviconsSize.testGetValue();
|
||||
let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
|
||||
|
||||
// Compare glean vs telemetry measurements
|
||||
TelemetryTestUtils.assertScalar(
|
||||
scalars,
|
||||
"browser.backup.places_size",
|
||||
placesMeasurement,
|
||||
"Glean and telemetry measurements for places.sqlite should be equal"
|
||||
);
|
||||
TelemetryTestUtils.assertScalar(
|
||||
scalars,
|
||||
"browser.backup.favicons_size",
|
||||
faviconsMeasurement,
|
||||
"Glean and telemetry measurements for favicons.sqlite should be equal"
|
||||
);
|
||||
|
||||
// Compare glean measurements vs actual file sizes
|
||||
Assert.equal(
|
||||
placesMeasurement,
|
||||
EXPECTED_PLACES_DB_SIZE,
|
||||
"Should have collected the correct glean measurement for places.sqlite"
|
||||
);
|
||||
Assert.equal(
|
||||
faviconsMeasurement,
|
||||
EXPECTED_FAVICONS_DB_SIZE,
|
||||
"Should have collected the correct glean measurement for favicons.sqlite"
|
||||
);
|
||||
|
||||
await IOUtils.remove(tempPlacesDBPath);
|
||||
await IOUtils.remove(tempFaviconsDBPath);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests that we can measure credentials related files in the profile directory.
|
||||
*/
|
||||
|
@ -11,6 +11,8 @@ support-files = ["data/test_xulstore.json"]
|
||||
|
||||
["test_MiscDataBackupResource.js"]
|
||||
|
||||
["test_PlacesBackupResource.js"]
|
||||
|
||||
["test_PreferencesBackupResource.js"]
|
||||
|
||||
["test_createBackup.js"]
|
||||
|
Loading…
Reference in New Issue
Block a user