tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_AddonsBackupResource.js (12922B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { AddonsBackupResource } = ChromeUtils.importESModule(
      7  "resource:///modules/backup/AddonsBackupResource.sys.mjs"
      8 );
      9 
     10 /**
     11 * Tests that we can measure the size of all the addons & extensions data.
     12 */
     13 add_task(async function test_measure() {
     14  Services.fog.testResetFOG();
     15  Services.telemetry.clearScalars();
     16 
     17  const EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON = 250;
     18  const EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE = 500;
     19  const EXPECTED_KILOBYTES_FOR_STORAGE_SYNC = 50;
     20  const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_A = 600;
     21  const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_B = 400;
     22  const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C = 150;
     23  const EXPECTED_KILOBYTES_FOR_EXTENSIONS_DIRECTORY = 1000;
     24  const EXPECTED_KILOBYTES_FOR_EXTENSION_DATA = 100;
     25  const EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE = 200;
     26 
     27  let tempDir = PathUtils.tempDir;
     28 
     29  // Create extensions json files (all the same size).
     30  const extensionsFilePath = PathUtils.join(tempDir, "extensions.json");
     31  await createKilobyteSizedFile(
     32    extensionsFilePath,
     33    EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
     34  );
     35  const extensionSettingsFilePath = PathUtils.join(
     36    tempDir,
     37    "extension-settings.json"
     38  );
     39  await createKilobyteSizedFile(
     40    extensionSettingsFilePath,
     41    EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
     42  );
     43  const extensionsPrefsFilePath = PathUtils.join(
     44    tempDir,
     45    "extension-preferences.json"
     46  );
     47  await createKilobyteSizedFile(
     48    extensionsPrefsFilePath,
     49    EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
     50  );
     51  const addonStartupFilePath = PathUtils.join(tempDir, "addonStartup.json.lz4");
     52  await createKilobyteSizedFile(
     53    addonStartupFilePath,
     54    EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
     55  );
     56 
     57  // Create the extension store permissions data file.
     58  let extensionStorePermissionsDataSize = PathUtils.join(
     59    tempDir,
     60    "extension-store-permissions",
     61    "data.safe.bin"
     62  );
     63  await createKilobyteSizedFile(
     64    extensionStorePermissionsDataSize,
     65    EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE
     66  );
     67 
     68  // Create the storage sync database file.
     69  let storageSyncPath = PathUtils.join(tempDir, "storage-sync-v2.sqlite");
     70  await createKilobyteSizedFile(
     71    storageSyncPath,
     72    EXPECTED_KILOBYTES_FOR_STORAGE_SYNC
     73  );
     74 
     75  // Create the extensions directory with XPI files.
     76  let extensionsXPIAPath = PathUtils.join(
     77    tempDir,
     78    "extensions",
     79    "extension-b.xpi"
     80  );
     81  let extensionsXPIBPath = PathUtils.join(
     82    tempDir,
     83    "extensions",
     84    "extension-a.xpi"
     85  );
     86  await createKilobyteSizedFile(
     87    extensionsXPIAPath,
     88    EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_A
     89  );
     90  await createKilobyteSizedFile(
     91    extensionsXPIBPath,
     92    EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_B
     93  );
     94  // Should be ignored.
     95  let extensionsXPIStagedPath = PathUtils.join(
     96    tempDir,
     97    "extensions",
     98    "staged",
     99    "staged-test-extension.xpi"
    100  );
    101  let extensionsXPITrashPath = PathUtils.join(
    102    tempDir,
    103    "extensions",
    104    "trash",
    105    "trashed-test-extension.xpi"
    106  );
    107  let extensionsXPIUnpackedPath = PathUtils.join(
    108    tempDir,
    109    "extensions",
    110    "unpacked-extension.xpi",
    111    "manifest.json"
    112  );
    113  await createKilobyteSizedFile(
    114    extensionsXPIStagedPath,
    115    EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C
    116  );
    117  await createKilobyteSizedFile(
    118    extensionsXPITrashPath,
    119    EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C
    120  );
    121  await createKilobyteSizedFile(
    122    extensionsXPIUnpackedPath,
    123    EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C
    124  );
    125 
    126  // Create the browser extension data directory.
    127  let browserExtensionDataPath = PathUtils.join(
    128    tempDir,
    129    "browser-extension-data",
    130    "test-file"
    131  );
    132  await createKilobyteSizedFile(
    133    browserExtensionDataPath,
    134    EXPECTED_KILOBYTES_FOR_EXTENSION_DATA
    135  );
    136 
    137  // Create the extensions storage directory.
    138  let extensionsStoragePath = PathUtils.join(
    139    tempDir,
    140    "storage",
    141    "default",
    142    "moz-extension+++test-extension-id",
    143    "idb",
    144    "data.sqlite"
    145  );
    146  // Other storage files that should not be counted.
    147  let otherStoragePath = PathUtils.join(
    148    tempDir,
    149    "storage",
    150    "default",
    151    "https+++accounts.firefox.com",
    152    "ls",
    153    "data.sqlite"
    154  );
    155 
    156  await createKilobyteSizedFile(
    157    extensionsStoragePath,
    158    EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE
    159  );
    160  await createKilobyteSizedFile(
    161    otherStoragePath,
    162    EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE
    163  );
    164 
    165  // Measure all the extensions data.
    166  let extensionsBackupResource = new AddonsBackupResource();
    167  await extensionsBackupResource.measure(tempDir);
    168 
    169  let extensionsJsonSizeMeasurement =
    170    Glean.browserBackup.extensionsJsonSize.testGetValue();
    171  Assert.equal(
    172    extensionsJsonSizeMeasurement,
    173    EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON * 4, // There are 4 equally sized files.
    174    "Should have collected the correct measurement of the total size of all extensions JSON files"
    175  );
    176 
    177  let extensionStorePermissionsDataSizeMeasurement =
    178    Glean.browserBackup.extensionStorePermissionsDataSize.testGetValue();
    179  Assert.equal(
    180    extensionStorePermissionsDataSizeMeasurement,
    181    EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE,
    182    "Should have collected the correct measurement of the size of the extension store permissions data"
    183  );
    184 
    185  let storageSyncSizeMeasurement =
    186    Glean.browserBackup.storageSyncSize.testGetValue();
    187  Assert.equal(
    188    storageSyncSizeMeasurement,
    189    EXPECTED_KILOBYTES_FOR_STORAGE_SYNC,
    190    "Should have collected the correct measurement of the size of the storage sync database"
    191  );
    192 
    193  let extensionsXPIDirectorySizeMeasurement =
    194    Glean.browserBackup.extensionsXpiDirectorySize.testGetValue();
    195  Assert.equal(
    196    extensionsXPIDirectorySizeMeasurement,
    197    EXPECTED_KILOBYTES_FOR_EXTENSIONS_DIRECTORY,
    198    "Should have collected the correct measurement of the size 2 equally sized XPI files in the extensions directory"
    199  );
    200 
    201  let browserExtensionDataSizeMeasurement =
    202    Glean.browserBackup.browserExtensionDataSize.testGetValue();
    203  Assert.equal(
    204    browserExtensionDataSizeMeasurement,
    205    EXPECTED_KILOBYTES_FOR_EXTENSION_DATA,
    206    "Should have collected the correct measurement of the size of the browser extension data directory"
    207  );
    208 
    209  let extensionsStorageSizeMeasurement =
    210    Glean.browserBackup.extensionsStorageSize.testGetValue();
    211  Assert.equal(
    212    extensionsStorageSizeMeasurement,
    213    EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE,
    214    "Should have collected the correct measurement of all the extensions storage"
    215  );
    216 
    217  // Compare glean vs telemetry measurements
    218  let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
    219  TelemetryTestUtils.assertScalar(
    220    scalars,
    221    "browser.backup.extensions_json_size",
    222    extensionsJsonSizeMeasurement,
    223    "Glean and telemetry measurements for extensions JSON should be equal"
    224  );
    225  TelemetryTestUtils.assertScalar(
    226    scalars,
    227    "browser.backup.extension_store_permissions_data_size",
    228    extensionStorePermissionsDataSizeMeasurement,
    229    "Glean and telemetry measurements for extension store permissions data should be equal"
    230  );
    231  TelemetryTestUtils.assertScalar(
    232    scalars,
    233    "browser.backup.storage_sync_size",
    234    storageSyncSizeMeasurement,
    235    "Glean and telemetry measurements for storage sync database should be equal"
    236  );
    237  TelemetryTestUtils.assertScalar(
    238    scalars,
    239    "browser.backup.extensions_xpi_directory_size",
    240    extensionsXPIDirectorySizeMeasurement,
    241    "Glean and telemetry measurements for extensions directory should be equal"
    242  );
    243  TelemetryTestUtils.assertScalar(
    244    scalars,
    245    "browser.backup.browser_extension_data_size",
    246    browserExtensionDataSizeMeasurement,
    247    "Glean and telemetry measurements for browser extension data should be equal"
    248  );
    249  TelemetryTestUtils.assertScalar(
    250    scalars,
    251    "browser.backup.extensions_storage_size",
    252    extensionsStorageSizeMeasurement,
    253    "Glean and telemetry measurements for extensions storage should be equal"
    254  );
    255 
    256  await maybeRemovePath(tempDir);
    257 });
    258 
    259 /**
    260 * Tests that we can handle the extension store permissions data
    261 * and moz-extension IndexedDB databases not existing.
    262 */
    263 add_task(async function test_measure_missing_data() {
    264  Services.fog.testResetFOG();
    265 
    266  let tempDir = PathUtils.tempDir;
    267 
    268  let extensionsBackupResource = new AddonsBackupResource();
    269  await extensionsBackupResource.measure(tempDir);
    270 
    271  let extensionStorePermissionsDataSizeMeasurement =
    272    Glean.browserBackup.extensionStorePermissionsDataSize.testGetValue();
    273  Assert.equal(
    274    extensionStorePermissionsDataSizeMeasurement,
    275    null,
    276    "Should NOT have collected a measurement for the missing permissions data"
    277  );
    278 
    279  let extensionsStorageSizeMeasurement =
    280    Glean.browserBackup.extensionsStorageSize.testGetValue();
    281  Assert.equal(
    282    extensionsStorageSizeMeasurement,
    283    null,
    284    "Should NOT have collected a measurement for the missing storage data"
    285  );
    286 });
    287 
    288 /**
    289 * Test that the backup method correctly copies items from the profile directory
    290 * into the staging directory.
    291 */
    292 add_task(async function test_backup() {
    293  let sandbox = sinon.createSandbox();
    294 
    295  let addonsBackupResource = new AddonsBackupResource();
    296  let sourcePath = await IOUtils.createUniqueDirectory(
    297    PathUtils.tempDir,
    298    "AddonsBackupResource-source-test"
    299  );
    300  let stagingPath = await IOUtils.createUniqueDirectory(
    301    PathUtils.tempDir,
    302    "AddonsBackupResource-staging-test"
    303  );
    304 
    305  const simpleCopyFiles = [
    306    { path: "extensions.json" },
    307    { path: "extension-settings.json" },
    308    { path: "extension-preferences.json" },
    309    { path: "addonStartup.json.lz4" },
    310    {
    311      path: [
    312        "browser-extension-data",
    313        "{11aa1234-f111-1234-abcd-a9b8c7654d32}",
    314      ],
    315    },
    316    { path: ["extension-store-permissions", "data.safe.bin"] },
    317    { path: ["extensions", "{11aa1234-f111-1234-abcd-a9b8c7654d32}.xpi"] },
    318  ];
    319  await createTestFiles(sourcePath, simpleCopyFiles);
    320 
    321  const junkFiles = [{ path: ["extensions", "junk"] }];
    322  await createTestFiles(sourcePath, junkFiles);
    323 
    324  // Create a fake storage-sync-v2 database file. We don't expect this to
    325  // be copied to the staging directory in this test due to our stubbing
    326  // of the backup method, so we don't include it in `simpleCopyFiles`.
    327  await createTestFiles(sourcePath, [{ path: "storage-sync-v2.sqlite" }]);
    328 
    329  let fakeConnection = {
    330    backup: sandbox.stub().resolves(true),
    331    close: sandbox.stub().resolves(true),
    332  };
    333  sandbox.stub(Sqlite, "openConnection").returns(fakeConnection);
    334 
    335  let manifestEntry = await addonsBackupResource.backup(
    336    stagingPath,
    337    sourcePath
    338  );
    339  Assert.equal(
    340    manifestEntry,
    341    null,
    342    "AddonsBackupResource.backup should return null as its ManifestEntry"
    343  );
    344 
    345  await assertFilesExist(stagingPath, simpleCopyFiles);
    346 
    347  let junkFile = PathUtils.join(stagingPath, "extensions", "junk");
    348  Assert.equal(
    349    await IOUtils.exists(junkFile),
    350    false,
    351    `${junkFile} should not exist in the staging folder`
    352  );
    353 
    354  // Make sure storage-sync-v2 database is backed up.
    355  Assert.ok(
    356    fakeConnection.backup.calledOnce,
    357    "Called backup the expected number of times for all connections"
    358  );
    359  Assert.ok(
    360    fakeConnection.backup.calledWith(
    361      PathUtils.join(stagingPath, "storage-sync-v2.sqlite")
    362    ),
    363    "Called backup on the storage-sync-v2 Sqlite connection"
    364  );
    365 
    366  await maybeRemovePath(stagingPath);
    367  await maybeRemovePath(sourcePath);
    368 
    369  sandbox.restore();
    370 });
    371 
    372 /**
    373 * Test that the recover method correctly copies items from the recovery
    374 * directory into the destination profile directory.
    375 */
    376 add_task(async function test_recover() {
    377  let addonsBackupResource = new AddonsBackupResource();
    378  let recoveryPath = await IOUtils.createUniqueDirectory(
    379    PathUtils.tempDir,
    380    "addonsBackupResource-recovery-test"
    381  );
    382  let destProfilePath = await IOUtils.createUniqueDirectory(
    383    PathUtils.tempDir,
    384    "addonsBackupResource-test-profile"
    385  );
    386 
    387  const files = [
    388    { path: "extensions.json" },
    389    { path: "extension-settings.json" },
    390    { path: "extension-preferences.json" },
    391    { path: "addonStartup.json.lz4" },
    392    { path: "storage-sync-v2.sqlite" },
    393    { path: ["browser-extension-data", "addon@darkreader.org.xpi", "data"] },
    394    { path: ["extensions", "addon@darkreader.org.xpi"] },
    395    { path: ["extension-store-permissions", "data.safe.bin"] },
    396  ];
    397  await createTestFiles(recoveryPath, files);
    398 
    399  // The backup method is expected to have returned a null ManifestEntry
    400  let postRecoveryEntry = await addonsBackupResource.recover(
    401    null /* manifestEntry */,
    402    recoveryPath,
    403    destProfilePath
    404  );
    405  Assert.equal(
    406    postRecoveryEntry,
    407    null,
    408    "AddonsBackupResource.recover should return null as its post " +
    409      "recovery entry"
    410  );
    411 
    412  await assertFilesExist(destProfilePath, files);
    413 
    414  await maybeRemovePath(recoveryPath);
    415  await maybeRemovePath(destProfilePath);
    416 });