tor-browser

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

test_PreferencesBackupResource.js (11322B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { PreferencesBackupResource } = ChromeUtils.importESModule(
      7  "resource:///modules/backup/PreferencesBackupResource.sys.mjs"
      8 );
      9 const { SearchUtils } = ChromeUtils.importESModule(
     10  "moz-src:///toolkit/components/search/SearchUtils.sys.mjs"
     11 );
     12 
     13 /**
     14 * Test that the measure method correctly collects the disk-sizes of things that
     15 * the PreferencesBackupResource is meant to back up.
     16 */
     17 add_task(async function test_measure() {
     18  Services.fog.testResetFOG();
     19 
     20  const EXPECTED_PREFERENCES_KILOBYTES_SIZE = 55;
     21  const tempDir = await IOUtils.createUniqueDirectory(
     22    PathUtils.tempDir,
     23    "PreferencesBackupResource-measure-test"
     24  );
     25  const mockFiles = [
     26    { path: "prefs.js", sizeInKB: 20 },
     27    { path: "xulstore.json", sizeInKB: 1 },
     28    { path: "containers.json", sizeInKB: 1 },
     29    { path: "handlers.json", sizeInKB: 1 },
     30    { path: "search.json.mozlz4", sizeInKB: 1 },
     31    { path: "user.js", sizeInKB: 2 },
     32    { path: ["chrome", "userChrome.css"], sizeInKB: 5 },
     33    { path: ["chrome", "userContent.css"], sizeInKB: 5 },
     34    { path: ["chrome", "css", "mockStyles.css"], sizeInKB: 5 },
     35  ];
     36 
     37  await createTestFiles(tempDir, mockFiles);
     38 
     39  let preferencesBackupResource = new PreferencesBackupResource();
     40 
     41  await preferencesBackupResource.measure(tempDir);
     42 
     43  let measurement = Glean.browserBackup.preferencesSize.testGetValue();
     44  let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
     45 
     46  TelemetryTestUtils.assertScalar(
     47    scalars,
     48    "browser.backup.preferences_size",
     49    measurement,
     50    "Glean and telemetry measurements for preferences data should be equal"
     51  );
     52  Assert.equal(
     53    measurement,
     54    EXPECTED_PREFERENCES_KILOBYTES_SIZE,
     55    "Should have collected the correct glean measurement for preferences files"
     56  );
     57 
     58  await maybeRemovePath(tempDir);
     59 });
     60 
     61 /**
     62 * Test that the backup method correctly copies items from the profile directory
     63 * into the staging directory.
     64 */
     65 add_task(async function test_backup() {
     66  let sandbox = sinon.createSandbox();
     67 
     68  let preferencesBackupResource = new PreferencesBackupResource();
     69  let sourcePath = await IOUtils.createUniqueDirectory(
     70    PathUtils.tempDir,
     71    "PreferencesBackupResource-source-test"
     72  );
     73  let stagingPath = await IOUtils.createUniqueDirectory(
     74    PathUtils.tempDir,
     75    "PreferencesBackupResource-staging-test"
     76  );
     77 
     78  const simpleCopyFiles = [
     79    { path: "xulstore.json" },
     80    { path: "containers.json" },
     81    { path: "handlers.json" },
     82    { path: "search.json.mozlz4" },
     83    { path: "user.js" },
     84    { path: ["chrome", "userChrome.css"] },
     85    { path: ["chrome", "userContent.css"] },
     86    { path: ["chrome", "childFolder", "someOtherStylesheet.css"] },
     87  ];
     88  await createTestFiles(sourcePath, simpleCopyFiles);
     89 
     90  // We have no need to test that Sqlite.sys.mjs's backup method is working -
     91  // this is something that is tested in Sqlite's own tests. We can just make
     92  // sure that it's being called using sinon. Unfortunately, we cannot do the
     93  // same thing with IOUtils.copy, as its methods are not stubbable.
     94  let fakeConnection = {
     95    backup: sandbox.stub().resolves(true),
     96    close: sandbox.stub().resolves(true),
     97  };
     98  sandbox.stub(Sqlite, "openConnection").returns(fakeConnection);
     99 
    100  let manifestEntry = await preferencesBackupResource.backup(
    101    stagingPath,
    102    sourcePath
    103  );
    104  Assert.deepEqual(
    105    manifestEntry,
    106    { profilePath: sourcePath },
    107    "PreferencesBackupResource.backup should return the original profile path " +
    108      "in its ManifestEntry"
    109  );
    110 
    111  await assertFilesExist(stagingPath, simpleCopyFiles);
    112 
    113  Assert.ok(
    114    fakeConnection.backup.notCalled,
    115    "No sqlite connections should have been made"
    116  );
    117 
    118  // And we'll make sure that preferences were properly written out.
    119  Assert.ok(
    120    await IOUtils.exists(PathUtils.join(stagingPath, "prefs.js")),
    121    "prefs.js should exist in the staging folder"
    122  );
    123 
    124  await maybeRemovePath(stagingPath);
    125  await maybeRemovePath(sourcePath);
    126 
    127  sandbox.restore();
    128 });
    129 
    130 /**
    131 * Check that prefs.js has "browser.backup.profile-restoration-date".  Due to
    132 * concerns over potential time skips in automation, we only check that the
    133 * timestamp is not more than a week before/after now (we would expect the
    134 * difference to be more like a few milliseconds).
    135 *
    136 * @param {string} prefsJsPath
    137 */
    138 async function checkPrefsJsHasValidRecoveryTime(prefsJsPath) {
    139  Assert.equal(
    140    Services.prefs.getPrefType("browser.backup.profile-restoration-date"),
    141    Services.prefs.PREF_INVALID,
    142    "Restoration pref not set since current profile was not restored"
    143  );
    144 
    145  // NB: The non-profile-restoration-date part of the prefs file is junk made
    146  // by `createTestFiles`.  We don't care about that here.
    147  const contents = await IOUtils.readUTF8(prefsJsPath);
    148  const dateRegex =
    149    /pref\("browser\.backup\.profile-restoration-date", (\d+)\);/;
    150  let restoreDate = contents.match(dateRegex);
    151  Assert.equal(restoreDate.length, 2, "found the restoration date");
    152 
    153  const kOneWeekAgoInSec =
    154    60 /* sec/min */ * 60 /* min/hr */ * 24 /* hr/day */ * 7; /* day/wk */
    155  const nowInSeconds = Math.round(Date.now() / 1000);
    156  Assert.lessOrEqual(
    157    Math.abs(nowInSeconds - Number(restoreDate[1])),
    158    kOneWeekAgoInSec,
    159    "timestamp was within one week of now"
    160  );
    161 }
    162 
    163 /**
    164 * Test that the recover method correctly copies items from the recovery
    165 * directory into the destination profile directory.
    166 */
    167 add_task(async function test_recover() {
    168  let sandbox = sinon.createSandbox();
    169  let preferencesBackupResource = new PreferencesBackupResource();
    170  let recoveryPath = await IOUtils.createUniqueDirectory(
    171    PathUtils.tempDir,
    172    "PreferencesBackupResource-recovery-test"
    173  );
    174  let destProfilePath = await IOUtils.createUniqueDirectory(
    175    PathUtils.tempDir,
    176    "PreferencesBackupResource-test-profile"
    177  );
    178 
    179  const simpleCopyFiles = [
    180    { path: "prefs.js" },
    181    { path: "xulstore.json" },
    182    { path: "containers.json" },
    183    { path: "handlers.json" },
    184    { path: "user.js" },
    185    { path: ["chrome", "userChrome.css"] },
    186    { path: ["chrome", "userContent.css"] },
    187    { path: ["chrome", "childFolder", "someOtherStylesheet.css"] },
    188  ];
    189  await createTestFiles(recoveryPath, simpleCopyFiles);
    190 
    191  // We'll now hand-prepare enough of a search.json.mozlz4 file that we can
    192  // ensure that PreferencesBackupResource knows how to update the
    193  // verification hashes for non-default engines.
    194  const TEST_SEARCH_ENGINE_LOAD_PATH = "some/path/on/disk";
    195  const TEST_SEARCH_ENGINE_LOAD_PATH_HASH = "some pre-existing hash";
    196  const TEST_DEFAULT_ENGINE_ID = "bugle";
    197  const TEST_DEFAULT_ENGINE_ID_HASH = "default engine original hash";
    198  const TEST_PRIVATE_DEFAULT_ENGINE_ID = "goose";
    199  const TEST_PRIVATE_DEFAULT_ENGINE_ID_HASH =
    200    "private default engine original hash";
    201 
    202  let fakeSearchPrefs = {
    203    metaData: {
    204      defaultEngineId: TEST_DEFAULT_ENGINE_ID,
    205      defaultEngineIdHash: TEST_DEFAULT_ENGINE_ID_HASH,
    206      privateDefaultEngineId: TEST_PRIVATE_DEFAULT_ENGINE_ID,
    207      privateDefaultEngineIdHash: TEST_PRIVATE_DEFAULT_ENGINE_ID_HASH,
    208    },
    209    engines: [
    210      {
    211        _loadPath: TEST_SEARCH_ENGINE_LOAD_PATH,
    212        _metaData: {
    213          loadPathHash: TEST_SEARCH_ENGINE_LOAD_PATH_HASH,
    214        },
    215      },
    216    ],
    217  };
    218 
    219  const SEARCH_PREFS_FILENAME = "search.json.mozlz4";
    220  await IOUtils.writeJSON(
    221    PathUtils.join(recoveryPath, SEARCH_PREFS_FILENAME),
    222    fakeSearchPrefs,
    223    {
    224      compress: true,
    225    }
    226  );
    227 
    228  const EXPECTED_HASH = "this is some newly generated hash";
    229  sandbox
    230    .stub(SearchUtils, "getVerificationHash")
    231    .onCall(0)
    232    .returns(TEST_SEARCH_ENGINE_LOAD_PATH_HASH)
    233    .onCall(1)
    234    .returns(EXPECTED_HASH)
    235    .onCall(2)
    236    .returns(TEST_DEFAULT_ENGINE_ID_HASH)
    237    .onCall(3)
    238    .returns(EXPECTED_HASH)
    239    .onCall(4)
    240    .returns(TEST_PRIVATE_DEFAULT_ENGINE_ID_HASH)
    241    .onCall(5)
    242    .returns(EXPECTED_HASH);
    243 
    244  const PRETEND_ORIGINAL_PATH = "some/original/path";
    245 
    246  // The backup method is expected to have returned a null ManifestEntry
    247  let postRecoveryEntry = await preferencesBackupResource.recover(
    248    { profilePath: PRETEND_ORIGINAL_PATH },
    249    recoveryPath,
    250    destProfilePath
    251  );
    252  Assert.equal(
    253    postRecoveryEntry,
    254    null,
    255    "PreferencesBackupResource.recover should return null as its post recovery entry"
    256  );
    257 
    258  await assertFilesExist(destProfilePath, simpleCopyFiles);
    259  await checkPrefsJsHasValidRecoveryTime(
    260    PathUtils.join(destProfilePath, "prefs.js")
    261  );
    262 
    263  // Now ensure that the verification was properly recomputed. We should
    264  // Have called getVerificationHash 6 times - twice each for:
    265  //
    266  // - The single engine in the engines list
    267  // - The defaultEngineId
    268  // - The privateDefaultEngineId
    269  //
    270  // The first call is to verify the hash against the original profile path,
    271  // and the second call is to generate the hash for the new profile path.
    272  Assert.equal(
    273    SearchUtils.getVerificationHash.callCount,
    274    6,
    275    "SearchUtils.getVerificationHash was called the right number of times."
    276  );
    277  Assert.ok(
    278    SearchUtils.getVerificationHash
    279      .getCall(0)
    280      .calledWith(TEST_SEARCH_ENGINE_LOAD_PATH, PRETEND_ORIGINAL_PATH),
    281    "SearchUtils.getVerificationHash first call called with the right arguments."
    282  );
    283  Assert.ok(
    284    SearchUtils.getVerificationHash
    285      .getCall(1)
    286      .calledWith(TEST_SEARCH_ENGINE_LOAD_PATH, destProfilePath),
    287    "SearchUtils.getVerificationHash second call called with the right arguments."
    288  );
    289  Assert.ok(
    290    SearchUtils.getVerificationHash
    291      .getCall(2)
    292      .calledWith(TEST_DEFAULT_ENGINE_ID, PRETEND_ORIGINAL_PATH),
    293    "SearchUtils.getVerificationHash third call called with the right arguments."
    294  );
    295  Assert.ok(
    296    SearchUtils.getVerificationHash
    297      .getCall(3)
    298      .calledWith(TEST_DEFAULT_ENGINE_ID, destProfilePath),
    299    "SearchUtils.getVerificationHash fourth call called with the right arguments."
    300  );
    301  Assert.ok(
    302    SearchUtils.getVerificationHash
    303      .getCall(4)
    304      .calledWith(TEST_PRIVATE_DEFAULT_ENGINE_ID, PRETEND_ORIGINAL_PATH),
    305    "SearchUtils.getVerificationHash fifth call called with the right arguments."
    306  );
    307  Assert.ok(
    308    SearchUtils.getVerificationHash
    309      .getCall(5)
    310      .calledWith(TEST_PRIVATE_DEFAULT_ENGINE_ID, destProfilePath),
    311    "SearchUtils.getVerificationHash sixth call called with the right arguments."
    312  );
    313 
    314  let recoveredSearchPrefs = await IOUtils.readJSON(
    315    PathUtils.join(destProfilePath, SEARCH_PREFS_FILENAME),
    316    { decompress: true }
    317  );
    318  Assert.equal(
    319    recoveredSearchPrefs.engines.length,
    320    1,
    321    "Should still have 1 search engine"
    322  );
    323  Assert.equal(
    324    recoveredSearchPrefs.engines[0]._metaData.loadPathHash,
    325    EXPECTED_HASH,
    326    "The expected hash was written for the single engine."
    327  );
    328  Assert.equal(
    329    recoveredSearchPrefs.metaData.defaultEngineIdHash,
    330    EXPECTED_HASH,
    331    "The expected hash was written for the default engine."
    332  );
    333  Assert.equal(
    334    recoveredSearchPrefs.metaData.privateDefaultEngineIdHash,
    335    EXPECTED_HASH,
    336    "The expected hash was written for the private default engine."
    337  );
    338 
    339  await maybeRemovePath(recoveryPath);
    340  await maybeRemovePath(destProfilePath);
    341  sandbox.restore();
    342 });