416 lines
13 KiB
JavaScript
416 lines
13 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
https://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
const { AddonsBackupResource } = ChromeUtils.importESModule(
|
|
"resource:///modules/backup/AddonsBackupResource.sys.mjs"
|
|
);
|
|
|
|
/**
|
|
* Tests that we can measure the size of all the addons & extensions data.
|
|
*/
|
|
add_task(async function test_measure() {
|
|
Services.fog.testResetFOG();
|
|
Services.telemetry.clearScalars();
|
|
|
|
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON = 250;
|
|
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE = 500;
|
|
const EXPECTED_KILOBYTES_FOR_STORAGE_SYNC = 50;
|
|
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_A = 600;
|
|
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_B = 400;
|
|
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C = 150;
|
|
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_DIRECTORY = 1000;
|
|
const EXPECTED_KILOBYTES_FOR_EXTENSION_DATA = 100;
|
|
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE = 200;
|
|
|
|
let tempDir = PathUtils.tempDir;
|
|
|
|
// Create extensions json files (all the same size).
|
|
const extensionsFilePath = PathUtils.join(tempDir, "extensions.json");
|
|
await createKilobyteSizedFile(
|
|
extensionsFilePath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
|
|
);
|
|
const extensionSettingsFilePath = PathUtils.join(
|
|
tempDir,
|
|
"extension-settings.json"
|
|
);
|
|
await createKilobyteSizedFile(
|
|
extensionSettingsFilePath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
|
|
);
|
|
const extensionsPrefsFilePath = PathUtils.join(
|
|
tempDir,
|
|
"extension-preferences.json"
|
|
);
|
|
await createKilobyteSizedFile(
|
|
extensionsPrefsFilePath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
|
|
);
|
|
const addonStartupFilePath = PathUtils.join(tempDir, "addonStartup.json.lz4");
|
|
await createKilobyteSizedFile(
|
|
addonStartupFilePath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
|
|
);
|
|
|
|
// Create the extension store permissions data file.
|
|
let extensionStorePermissionsDataSize = PathUtils.join(
|
|
tempDir,
|
|
"extension-store-permissions",
|
|
"data.safe.bin"
|
|
);
|
|
await createKilobyteSizedFile(
|
|
extensionStorePermissionsDataSize,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE
|
|
);
|
|
|
|
// Create the storage sync database file.
|
|
let storageSyncPath = PathUtils.join(tempDir, "storage-sync-v2.sqlite");
|
|
await createKilobyteSizedFile(
|
|
storageSyncPath,
|
|
EXPECTED_KILOBYTES_FOR_STORAGE_SYNC
|
|
);
|
|
|
|
// Create the extensions directory with XPI files.
|
|
let extensionsXPIAPath = PathUtils.join(
|
|
tempDir,
|
|
"extensions",
|
|
"extension-b.xpi"
|
|
);
|
|
let extensionsXPIBPath = PathUtils.join(
|
|
tempDir,
|
|
"extensions",
|
|
"extension-a.xpi"
|
|
);
|
|
await createKilobyteSizedFile(
|
|
extensionsXPIAPath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_A
|
|
);
|
|
await createKilobyteSizedFile(
|
|
extensionsXPIBPath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_B
|
|
);
|
|
// Should be ignored.
|
|
let extensionsXPIStagedPath = PathUtils.join(
|
|
tempDir,
|
|
"extensions",
|
|
"staged",
|
|
"staged-test-extension.xpi"
|
|
);
|
|
let extensionsXPITrashPath = PathUtils.join(
|
|
tempDir,
|
|
"extensions",
|
|
"trash",
|
|
"trashed-test-extension.xpi"
|
|
);
|
|
let extensionsXPIUnpackedPath = PathUtils.join(
|
|
tempDir,
|
|
"extensions",
|
|
"unpacked-extension.xpi",
|
|
"manifest.json"
|
|
);
|
|
await createKilobyteSizedFile(
|
|
extensionsXPIStagedPath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C
|
|
);
|
|
await createKilobyteSizedFile(
|
|
extensionsXPITrashPath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C
|
|
);
|
|
await createKilobyteSizedFile(
|
|
extensionsXPIUnpackedPath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C
|
|
);
|
|
|
|
// Create the browser extension data directory.
|
|
let browserExtensionDataPath = PathUtils.join(
|
|
tempDir,
|
|
"browser-extension-data",
|
|
"test-file"
|
|
);
|
|
await createKilobyteSizedFile(
|
|
browserExtensionDataPath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSION_DATA
|
|
);
|
|
|
|
// Create the extensions storage directory.
|
|
let extensionsStoragePath = PathUtils.join(
|
|
tempDir,
|
|
"storage",
|
|
"default",
|
|
"moz-extension+++test-extension-id",
|
|
"idb",
|
|
"data.sqlite"
|
|
);
|
|
// Other storage files that should not be counted.
|
|
let otherStoragePath = PathUtils.join(
|
|
tempDir,
|
|
"storage",
|
|
"default",
|
|
"https+++accounts.firefox.com",
|
|
"ls",
|
|
"data.sqlite"
|
|
);
|
|
|
|
await createKilobyteSizedFile(
|
|
extensionsStoragePath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE
|
|
);
|
|
await createKilobyteSizedFile(
|
|
otherStoragePath,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE
|
|
);
|
|
|
|
// Measure all the extensions data.
|
|
let extensionsBackupResource = new AddonsBackupResource();
|
|
await extensionsBackupResource.measure(tempDir);
|
|
|
|
let extensionsJsonSizeMeasurement =
|
|
Glean.browserBackup.extensionsJsonSize.testGetValue();
|
|
Assert.equal(
|
|
extensionsJsonSizeMeasurement,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON * 4, // There are 4 equally sized files.
|
|
"Should have collected the correct measurement of the total size of all extensions JSON files"
|
|
);
|
|
|
|
let extensionStorePermissionsDataSizeMeasurement =
|
|
Glean.browserBackup.extensionStorePermissionsDataSize.testGetValue();
|
|
Assert.equal(
|
|
extensionStorePermissionsDataSizeMeasurement,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE,
|
|
"Should have collected the correct measurement of the size of the extension store permissions data"
|
|
);
|
|
|
|
let storageSyncSizeMeasurement =
|
|
Glean.browserBackup.storageSyncSize.testGetValue();
|
|
Assert.equal(
|
|
storageSyncSizeMeasurement,
|
|
EXPECTED_KILOBYTES_FOR_STORAGE_SYNC,
|
|
"Should have collected the correct measurement of the size of the storage sync database"
|
|
);
|
|
|
|
let extensionsXPIDirectorySizeMeasurement =
|
|
Glean.browserBackup.extensionsXpiDirectorySize.testGetValue();
|
|
Assert.equal(
|
|
extensionsXPIDirectorySizeMeasurement,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_DIRECTORY,
|
|
"Should have collected the correct measurement of the size 2 equally sized XPI files in the extensions directory"
|
|
);
|
|
|
|
let browserExtensionDataSizeMeasurement =
|
|
Glean.browserBackup.browserExtensionDataSize.testGetValue();
|
|
Assert.equal(
|
|
browserExtensionDataSizeMeasurement,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSION_DATA,
|
|
"Should have collected the correct measurement of the size of the browser extension data directory"
|
|
);
|
|
|
|
let extensionsStorageSizeMeasurement =
|
|
Glean.browserBackup.extensionsStorageSize.testGetValue();
|
|
Assert.equal(
|
|
extensionsStorageSizeMeasurement,
|
|
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE,
|
|
"Should have collected the correct measurement of all the extensions storage"
|
|
);
|
|
|
|
// Compare glean vs telemetry measurements
|
|
let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
|
|
TelemetryTestUtils.assertScalar(
|
|
scalars,
|
|
"browser.backup.extensions_json_size",
|
|
extensionsJsonSizeMeasurement,
|
|
"Glean and telemetry measurements for extensions JSON should be equal"
|
|
);
|
|
TelemetryTestUtils.assertScalar(
|
|
scalars,
|
|
"browser.backup.extension_store_permissions_data_size",
|
|
extensionStorePermissionsDataSizeMeasurement,
|
|
"Glean and telemetry measurements for extension store permissions data should be equal"
|
|
);
|
|
TelemetryTestUtils.assertScalar(
|
|
scalars,
|
|
"browser.backup.storage_sync_size",
|
|
storageSyncSizeMeasurement,
|
|
"Glean and telemetry measurements for storage sync database should be equal"
|
|
);
|
|
TelemetryTestUtils.assertScalar(
|
|
scalars,
|
|
"browser.backup.extensions_xpi_directory_size",
|
|
extensionsXPIDirectorySizeMeasurement,
|
|
"Glean and telemetry measurements for extensions directory should be equal"
|
|
);
|
|
TelemetryTestUtils.assertScalar(
|
|
scalars,
|
|
"browser.backup.browser_extension_data_size",
|
|
browserExtensionDataSizeMeasurement,
|
|
"Glean and telemetry measurements for browser extension data should be equal"
|
|
);
|
|
TelemetryTestUtils.assertScalar(
|
|
scalars,
|
|
"browser.backup.extensions_storage_size",
|
|
extensionsStorageSizeMeasurement,
|
|
"Glean and telemetry measurements for extensions storage should be equal"
|
|
);
|
|
|
|
await maybeRemovePath(tempDir);
|
|
});
|
|
|
|
/**
|
|
* Tests that we can handle the extension store permissions data
|
|
* and moz-extension IndexedDB databases not existing.
|
|
*/
|
|
add_task(async function test_measure_missing_data() {
|
|
Services.fog.testResetFOG();
|
|
|
|
let tempDir = PathUtils.tempDir;
|
|
|
|
let extensionsBackupResource = new AddonsBackupResource();
|
|
await extensionsBackupResource.measure(tempDir);
|
|
|
|
let extensionStorePermissionsDataSizeMeasurement =
|
|
Glean.browserBackup.extensionStorePermissionsDataSize.testGetValue();
|
|
Assert.equal(
|
|
extensionStorePermissionsDataSizeMeasurement,
|
|
null,
|
|
"Should NOT have collected a measurement for the missing permissions data"
|
|
);
|
|
|
|
let extensionsStorageSizeMeasurement =
|
|
Glean.browserBackup.extensionsStorageSize.testGetValue();
|
|
Assert.equal(
|
|
extensionsStorageSizeMeasurement,
|
|
null,
|
|
"Should NOT have collected a measurement for the missing storage data"
|
|
);
|
|
});
|
|
|
|
/**
|
|
* Test that the backup method correctly copies items from the profile directory
|
|
* into the staging directory.
|
|
*/
|
|
add_task(async function test_backup() {
|
|
let sandbox = sinon.createSandbox();
|
|
|
|
let addonsBackupResource = new AddonsBackupResource();
|
|
let sourcePath = await IOUtils.createUniqueDirectory(
|
|
PathUtils.tempDir,
|
|
"AddonsBackupResource-source-test"
|
|
);
|
|
let stagingPath = await IOUtils.createUniqueDirectory(
|
|
PathUtils.tempDir,
|
|
"AddonsBackupResource-staging-test"
|
|
);
|
|
|
|
const simpleCopyFiles = [
|
|
{ path: "extensions.json" },
|
|
{ path: "extension-settings.json" },
|
|
{ path: "extension-preferences.json" },
|
|
{ path: "addonStartup.json.lz4" },
|
|
{
|
|
path: [
|
|
"browser-extension-data",
|
|
"{11aa1234-f111-1234-abcd-a9b8c7654d32}",
|
|
],
|
|
},
|
|
{ path: ["extension-store-permissions", "data.safe.bin"] },
|
|
{ path: ["extensions", "{11aa1234-f111-1234-abcd-a9b8c7654d32}.xpi"] },
|
|
];
|
|
await createTestFiles(sourcePath, simpleCopyFiles);
|
|
|
|
const junkFiles = [{ path: ["extensions", "junk"] }];
|
|
await createTestFiles(sourcePath, junkFiles);
|
|
|
|
// Create a fake storage-sync-v2 database file. We don't expect this to
|
|
// be copied to the staging directory in this test due to our stubbing
|
|
// of the backup method, so we don't include it in `simpleCopyFiles`.
|
|
await createTestFiles(sourcePath, [{ path: "storage-sync-v2.sqlite" }]);
|
|
|
|
let fakeConnection = {
|
|
backup: sandbox.stub().resolves(true),
|
|
close: sandbox.stub().resolves(true),
|
|
};
|
|
sandbox.stub(Sqlite, "openConnection").returns(fakeConnection);
|
|
|
|
let manifestEntry = await addonsBackupResource.backup(
|
|
stagingPath,
|
|
sourcePath
|
|
);
|
|
Assert.equal(
|
|
manifestEntry,
|
|
null,
|
|
"AddonsBackupResource.backup should return null as its ManifestEntry"
|
|
);
|
|
|
|
await assertFilesExist(stagingPath, simpleCopyFiles);
|
|
|
|
let junkFile = PathUtils.join(stagingPath, "extensions", "junk");
|
|
Assert.equal(
|
|
await IOUtils.exists(junkFile),
|
|
false,
|
|
`${junkFile} should not exist in the staging folder`
|
|
);
|
|
|
|
// Make sure storage-sync-v2 database is backed up.
|
|
Assert.ok(
|
|
fakeConnection.backup.calledOnce,
|
|
"Called backup the expected number of times for all connections"
|
|
);
|
|
Assert.ok(
|
|
fakeConnection.backup.calledWith(
|
|
PathUtils.join(stagingPath, "storage-sync-v2.sqlite")
|
|
),
|
|
"Called backup on the storage-sync-v2 Sqlite connection"
|
|
);
|
|
|
|
await maybeRemovePath(stagingPath);
|
|
await maybeRemovePath(sourcePath);
|
|
|
|
sandbox.restore();
|
|
});
|
|
|
|
/**
|
|
* Test that the recover method correctly copies items from the recovery
|
|
* directory into the destination profile directory.
|
|
*/
|
|
add_task(async function test_recover() {
|
|
let addonsBackupResource = new AddonsBackupResource();
|
|
let recoveryPath = await IOUtils.createUniqueDirectory(
|
|
PathUtils.tempDir,
|
|
"addonsBackupResource-recovery-test"
|
|
);
|
|
let destProfilePath = await IOUtils.createUniqueDirectory(
|
|
PathUtils.tempDir,
|
|
"addonsBackupResource-test-profile"
|
|
);
|
|
|
|
const files = [
|
|
{ path: "extensions.json" },
|
|
{ path: "extension-settings.json" },
|
|
{ path: "extension-preferences.json" },
|
|
{ path: "addonStartup.json.lz4" },
|
|
{ path: "storage-sync-v2.sqlite" },
|
|
{ path: ["browser-extension-data", "addon@darkreader.org.xpi", "data"] },
|
|
{ path: ["extensions", "addon@darkreader.org.xpi"] },
|
|
{ path: ["extension-store-permissions", "data.safe.bin"] },
|
|
];
|
|
await createTestFiles(recoveryPath, files);
|
|
|
|
// The backup method is expected to have returned a null ManifestEntry
|
|
let postRecoveryEntry = await addonsBackupResource.recover(
|
|
null /* manifestEntry */,
|
|
recoveryPath,
|
|
destProfilePath
|
|
);
|
|
Assert.equal(
|
|
postRecoveryEntry,
|
|
null,
|
|
"AddonsBackupResource.recover should return null as its post " +
|
|
"recovery entry"
|
|
);
|
|
|
|
await assertFilesExist(destProfilePath, files);
|
|
|
|
await maybeRemovePath(recoveryPath);
|
|
await maybeRemovePath(destProfilePath);
|
|
});
|