tor-browser

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

test_engine_changes_during_sync.js (19323B)


      1 const { FormHistory } = ChromeUtils.importESModule(
      2  "resource://gre/modules/FormHistory.sys.mjs"
      3 );
      4 const { Service } = ChromeUtils.importESModule(
      5  "resource://services-sync/service.sys.mjs"
      6 );
      7 const { Bookmark, BookmarkFolder, BookmarkQuery } = ChromeUtils.importESModule(
      8  "resource://services-sync/engines/bookmarks.sys.mjs"
      9 );
     10 const { HistoryRec } = ChromeUtils.importESModule(
     11  "resource://services-sync/engines/history.sys.mjs"
     12 );
     13 const { FormRec } = ChromeUtils.importESModule(
     14  "resource://services-sync/engines/forms.sys.mjs"
     15 );
     16 const { LoginRec } = ChromeUtils.importESModule(
     17  "resource://services-sync/engines/passwords.sys.mjs"
     18 );
     19 const { PrefRec } = ChromeUtils.importESModule(
     20  "resource://services-sync/engines/prefs.sys.mjs"
     21 );
     22 
     23 const LoginInfo = Components.Constructor(
     24  "@mozilla.org/login-manager/loginInfo;1",
     25  Ci.nsILoginInfo,
     26  "init"
     27 );
     28 
     29 /**
     30 * We don't test the clients or tabs engines because neither has conflict
     31 * resolution logic. The clients engine syncs twice per global sync, and
     32 * custom conflict resolution logic for commands that doesn't use
     33 * timestamps. Tabs doesn't have conflict resolution at all, since it's
     34 * read-only.
     35 */
     36 
     37 async function assertChildGuids(folderGuid, expectedChildGuids, message) {
     38  let tree = await PlacesUtils.promiseBookmarksTree(folderGuid);
     39  let childGuids = tree.children.map(child => child.guid);
     40  deepEqual(childGuids, expectedChildGuids, message);
     41 }
     42 
     43 async function cleanup(engine, server) {
     44  await engine._tracker.stop();
     45  await engine._store.wipe();
     46  for (const pref of Svc.PrefBranch.getChildList("")) {
     47    Svc.PrefBranch.clearUserPref(pref);
     48  }
     49  Service.recordManager.clearCache();
     50  await promiseStopServer(server);
     51 }
     52 
     53 add_task(async function test_history_change_during_sync() {
     54  _("Ensure that we don't bump the score when applying history records.");
     55 
     56  enableValidationPrefs();
     57 
     58  let engine = Service.engineManager.get("history");
     59  let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]);
     60  await SyncTestingInfrastructure(server);
     61  let collection = server.user("foo").collection("history");
     62 
     63  // Override `uploadOutgoing` to insert a record while we're applying
     64  // changes. The tracker should ignore this change.
     65  let uploadOutgoing = engine._uploadOutgoing;
     66  engine._uploadOutgoing = async function () {
     67    engine._uploadOutgoing = uploadOutgoing;
     68    try {
     69      await uploadOutgoing.call(this);
     70    } finally {
     71      _("Inserting local history visit");
     72      await addVisit("during_sync");
     73      await engine._tracker.asyncObserver.promiseObserversComplete();
     74    }
     75  };
     76 
     77  engine._tracker.start();
     78 
     79  try {
     80    let remoteRec = new HistoryRec("history", "UrOOuzE5QM-e");
     81    remoteRec.histUri = "http://getfirefox.com/";
     82    remoteRec.title = "Get Firefox!";
     83    remoteRec.visits = [
     84      {
     85        date: PlacesUtils.toPRTime(Date.now()),
     86        type: PlacesUtils.history.TRANSITION_TYPED,
     87      },
     88    ];
     89    collection.insert(remoteRec.id, encryptPayload(remoteRec.cleartext));
     90 
     91    await sync_engine_and_validate_telem(engine, true);
     92    strictEqual(
     93      Service.scheduler.globalScore,
     94      0,
     95      "Should not bump global score for visits added during sync"
     96    );
     97 
     98    equal(
     99      collection.count(),
    100      1,
    101      "New local visit should not exist on server after first sync"
    102    );
    103 
    104    await sync_engine_and_validate_telem(engine, true);
    105    strictEqual(
    106      Service.scheduler.globalScore,
    107      0,
    108      "Should not bump global score during second history sync"
    109    );
    110 
    111    equal(
    112      collection.count(),
    113      2,
    114      "New local visit should exist on server after second sync"
    115    );
    116  } finally {
    117    engine._uploadOutgoing = uploadOutgoing;
    118    await cleanup(engine, server);
    119  }
    120 });
    121 
    122 add_task(async function test_passwords_change_during_sync() {
    123  _("Ensure that we don't bump the score when applying passwords.");
    124 
    125  enableValidationPrefs();
    126 
    127  let engine = Service.engineManager.get("passwords");
    128  let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]);
    129  await SyncTestingInfrastructure(server);
    130  let collection = server.user("foo").collection("passwords");
    131 
    132  let uploadOutgoing = engine._uploadOutgoing;
    133  engine._uploadOutgoing = async function () {
    134    engine._uploadOutgoing = uploadOutgoing;
    135    try {
    136      await uploadOutgoing.call(this);
    137    } finally {
    138      _("Inserting local password");
    139      let login = new LoginInfo(
    140        "https://example.com",
    141        "",
    142        null,
    143        "username",
    144        "password",
    145        "",
    146        ""
    147      );
    148      await Services.logins.addLoginAsync(login);
    149      await engine._tracker.asyncObserver.promiseObserversComplete();
    150    }
    151  };
    152 
    153  engine._tracker.start();
    154 
    155  try {
    156    let remoteRec = new LoginRec(
    157      "passwords",
    158      "{765e3d6e-071d-d640-a83d-81a7eb62d3ed}"
    159    );
    160    remoteRec.formSubmitURL = "";
    161    remoteRec.httpRealm = "";
    162    remoteRec.hostname = "https://mozilla.org";
    163    remoteRec.username = "username";
    164    remoteRec.password = "sekrit";
    165    remoteRec.timeCreated = Date.now();
    166    remoteRec.timePasswordChanged = Date.now();
    167    collection.insert(remoteRec.id, encryptPayload(remoteRec.cleartext));
    168 
    169    await sync_engine_and_validate_telem(engine, true);
    170    strictEqual(
    171      Service.scheduler.globalScore,
    172      0,
    173      "Should not bump global score for passwords added during first sync"
    174    );
    175 
    176    equal(
    177      collection.count(),
    178      1,
    179      "New local password should not exist on server after first sync"
    180    );
    181 
    182    await sync_engine_and_validate_telem(engine, true);
    183    strictEqual(
    184      Service.scheduler.globalScore,
    185      0,
    186      "Should not bump global score during second passwords sync"
    187    );
    188 
    189    equal(
    190      collection.count(),
    191      2,
    192      "New local password should exist on server after second sync"
    193    );
    194  } finally {
    195    engine._uploadOutgoing = uploadOutgoing;
    196    await cleanup(engine, server);
    197  }
    198 });
    199 
    200 add_task(async function test_prefs_change_during_sync() {
    201  _("Ensure that we don't bump the score when applying prefs.");
    202 
    203  const TEST_PREF = "test.duringSync";
    204  // create a "control pref" for the pref we sync.
    205  Services.prefs.setBoolPref("services.sync.prefs.sync.test.duringSync", true);
    206 
    207  enableValidationPrefs();
    208 
    209  let engine = Service.engineManager.get("prefs");
    210  let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]);
    211  await SyncTestingInfrastructure(server);
    212  let collection = server.user("foo").collection("prefs");
    213 
    214  let uploadOutgoing = engine._uploadOutgoing;
    215  engine._uploadOutgoing = async function () {
    216    engine._uploadOutgoing = uploadOutgoing;
    217    try {
    218      await uploadOutgoing.call(this);
    219    } finally {
    220      _("Updating local pref value");
    221      // Change the value of a synced pref.
    222      Services.prefs.setStringPref(TEST_PREF, "hello");
    223      await engine._tracker.asyncObserver.promiseObserversComplete();
    224    }
    225  };
    226 
    227  engine._tracker.start();
    228 
    229  try {
    230    // All synced prefs are stored in a single record, so we'll only ever
    231    // have one record on the server. This test just checks that we don't
    232    // track or upload prefs changed during the sync.
    233    let guid = CommonUtils.encodeBase64URL(Services.appinfo.ID);
    234    let remoteRec = new PrefRec("prefs", guid);
    235    remoteRec.value = {
    236      [TEST_PREF]: "world",
    237    };
    238    collection.insert(remoteRec.id, encryptPayload(remoteRec.cleartext));
    239 
    240    await sync_engine_and_validate_telem(engine, true);
    241    strictEqual(
    242      Service.scheduler.globalScore,
    243      0,
    244      "Should not bump global score for prefs added during first sync"
    245    );
    246    let payloads = collection.payloads();
    247    equal(
    248      payloads.length,
    249      1,
    250      "Should not upload multiple prefs records after first sync"
    251    );
    252    equal(
    253      payloads[0].value[TEST_PREF],
    254      "world",
    255      "Should not upload pref value changed during first sync"
    256    );
    257 
    258    await sync_engine_and_validate_telem(engine, true);
    259    strictEqual(
    260      Service.scheduler.globalScore,
    261      0,
    262      "Should not bump global score during second prefs sync"
    263    );
    264    payloads = collection.payloads();
    265    equal(
    266      payloads.length,
    267      1,
    268      "Should not upload multiple prefs records after second sync"
    269    );
    270    equal(
    271      payloads[0].value[TEST_PREF],
    272      "hello",
    273      "Should upload changed pref value during second sync"
    274    );
    275  } finally {
    276    engine._uploadOutgoing = uploadOutgoing;
    277    await cleanup(engine, server);
    278    Services.prefs.clearUserPref(TEST_PREF);
    279  }
    280 });
    281 
    282 add_task(async function test_forms_change_during_sync() {
    283  _("Ensure that we don't bump the score when applying form records.");
    284 
    285  enableValidationPrefs();
    286 
    287  let engine = Service.engineManager.get("forms");
    288  let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]);
    289  await SyncTestingInfrastructure(server);
    290  let collection = server.user("foo").collection("forms");
    291 
    292  let uploadOutgoing = engine._uploadOutgoing;
    293  engine._uploadOutgoing = async function () {
    294    engine._uploadOutgoing = uploadOutgoing;
    295    try {
    296      await uploadOutgoing.call(this);
    297    } finally {
    298      _("Inserting local form history entry");
    299      await FormHistory.update([
    300        {
    301          op: "add",
    302          fieldname: "favoriteDrink",
    303          value: "cocoa",
    304        },
    305      ]);
    306      await engine._tracker.asyncObserver.promiseObserversComplete();
    307    }
    308  };
    309 
    310  engine._tracker.start();
    311 
    312  try {
    313    // Add an existing remote form history entry. We shouldn't bump the score when
    314    // we apply this record.
    315    let remoteRec = new FormRec("forms", "Tl9dHgmJSR6FkyxS");
    316    remoteRec.name = "name";
    317    remoteRec.value = "alice";
    318    collection.insert(remoteRec.id, encryptPayload(remoteRec.cleartext));
    319 
    320    await sync_engine_and_validate_telem(engine, true);
    321    strictEqual(
    322      Service.scheduler.globalScore,
    323      0,
    324      "Should not bump global score for forms added during first sync"
    325    );
    326 
    327    equal(
    328      collection.count(),
    329      1,
    330      "New local form should not exist on server after first sync"
    331    );
    332 
    333    await sync_engine_and_validate_telem(engine, true);
    334    strictEqual(
    335      Service.scheduler.globalScore,
    336      0,
    337      "Should not bump global score during second forms sync"
    338    );
    339 
    340    equal(
    341      collection.count(),
    342      2,
    343      "New local form should exist on server after second sync"
    344    );
    345  } finally {
    346    engine._uploadOutgoing = uploadOutgoing;
    347    await cleanup(engine, server);
    348  }
    349 });
    350 
    351 add_task(async function test_bookmark_change_during_sync() {
    352  _("Ensure that we track bookmark changes made during a sync.");
    353 
    354  enableValidationPrefs();
    355  let schedulerProto = Object.getPrototypeOf(Service.scheduler);
    356  let syncThresholdDescriptor = Object.getOwnPropertyDescriptor(
    357    schedulerProto,
    358    "syncThreshold"
    359  );
    360  Object.defineProperty(Service.scheduler, "syncThreshold", {
    361    // Trigger resync if any changes exist, rather than deciding based on the
    362    // normal sync threshold.
    363    get: () => 0,
    364  });
    365 
    366  let engine = Service.engineManager.get("bookmarks");
    367  let server = await serverForEnginesWithKeys({ foo: "password" }, [engine]);
    368  await SyncTestingInfrastructure(server);
    369 
    370  // Already-tracked bookmarks that shouldn't be uploaded during the first sync.
    371  let bzBmk = await PlacesUtils.bookmarks.insert({
    372    parentGuid: PlacesUtils.bookmarks.menuGuid,
    373    url: "https://bugzilla.mozilla.org/",
    374    title: "Bugzilla",
    375  });
    376  _(`Bugzilla GUID: ${bzBmk.guid}`);
    377 
    378  await PlacesTestUtils.setBookmarkSyncFields({
    379    guid: bzBmk.guid,
    380    syncChangeCounter: 0,
    381    syncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NORMAL,
    382  });
    383 
    384  let collection = server.user("foo").collection("bookmarks");
    385 
    386  let bmk3; // New child of Folder 1, created locally during sync.
    387 
    388  let uploadOutgoing = engine._uploadOutgoing;
    389  engine._uploadOutgoing = async function () {
    390    engine._uploadOutgoing = uploadOutgoing;
    391    try {
    392      await uploadOutgoing.call(this);
    393    } finally {
    394      _("Inserting bookmark into local store");
    395      bmk3 = await PlacesUtils.bookmarks.insert({
    396        parentGuid: folder1.guid,
    397        url: "https://mozilla.org/",
    398        title: "Mozilla",
    399      });
    400      await engine._tracker.asyncObserver.promiseObserversComplete();
    401    }
    402  };
    403 
    404  // New bookmarks that should be uploaded during the first sync.
    405  let folder1 = await PlacesUtils.bookmarks.insert({
    406    type: PlacesUtils.bookmarks.TYPE_FOLDER,
    407    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
    408    title: "Folder 1",
    409  });
    410  _(`Folder GUID: ${folder1.guid}`);
    411 
    412  let tbBmk = await PlacesUtils.bookmarks.insert({
    413    parentGuid: folder1.guid,
    414    url: "http://getthunderbird.com/",
    415    title: "Get Thunderbird!",
    416  });
    417  _(`Thunderbird GUID: ${tbBmk.guid}`);
    418 
    419  engine._tracker.start();
    420 
    421  try {
    422    let bmk2_guid = "get-firefox1"; // New child of Folder 1, created remotely.
    423    let folder2_guid = "folder2-1111"; // New folder, created remotely.
    424    let tagQuery_guid = "tag-query111"; // New tag query child of Folder 2, created remotely.
    425    let bmk4_guid = "example-org1"; // New tagged child of Folder 2, created remotely.
    426    {
    427      // An existing record changed on the server that should not trigger
    428      // another sync when applied.
    429      let remoteBzBmk = new Bookmark("bookmarks", bzBmk.guid);
    430      remoteBzBmk.bmkUri = "https://bugzilla.mozilla.org/";
    431      remoteBzBmk.description = "New description";
    432      remoteBzBmk.title = "Bugzilla";
    433      remoteBzBmk.tags = ["new", "tags"];
    434      remoteBzBmk.parentName = "Bookmarks Menu";
    435      remoteBzBmk.parentid = "menu";
    436      collection.insert(bzBmk.guid, encryptPayload(remoteBzBmk.cleartext));
    437 
    438      let remoteFolder = new BookmarkFolder("bookmarks", folder2_guid);
    439      remoteFolder.title = "Folder 2";
    440      remoteFolder.children = [bmk4_guid, tagQuery_guid];
    441      remoteFolder.parentName = "Bookmarks Menu";
    442      remoteFolder.parentid = "menu";
    443      collection.insert(folder2_guid, encryptPayload(remoteFolder.cleartext));
    444 
    445      let remoteFxBmk = new Bookmark("bookmarks", bmk2_guid);
    446      remoteFxBmk.bmkUri = "http://getfirefox.com/";
    447      remoteFxBmk.description = "Firefox is awesome.";
    448      remoteFxBmk.title = "Get Firefox!";
    449      remoteFxBmk.tags = ["firefox", "awesome", "browser"];
    450      remoteFxBmk.keyword = "awesome";
    451      remoteFxBmk.parentName = "Folder 1";
    452      remoteFxBmk.parentid = folder1.guid;
    453      collection.insert(bmk2_guid, encryptPayload(remoteFxBmk.cleartext));
    454 
    455      // A tag query referencing a nonexistent tag folder, which we should
    456      // create locally when applying the record.
    457      let remoteTagQuery = new BookmarkQuery("bookmarks", tagQuery_guid);
    458      remoteTagQuery.bmkUri = "place:type=7&folder=999";
    459      remoteTagQuery.title = "Taggy tags";
    460      remoteTagQuery.folderName = "taggy";
    461      remoteTagQuery.parentName = "Folder 2";
    462      remoteTagQuery.parentid = folder2_guid;
    463      collection.insert(
    464        tagQuery_guid,
    465        encryptPayload(remoteTagQuery.cleartext)
    466      );
    467 
    468      // A bookmark that should appear in the results for the tag query.
    469      let remoteTaggedBmk = new Bookmark("bookmarks", bmk4_guid);
    470      remoteTaggedBmk.bmkUri = "https://example.org/";
    471      remoteTaggedBmk.title = "Tagged bookmark";
    472      remoteTaggedBmk.tags = ["taggy"];
    473      remoteTaggedBmk.parentName = "Folder 2";
    474      remoteTaggedBmk.parentid = folder2_guid;
    475      collection.insert(bmk4_guid, encryptPayload(remoteTaggedBmk.cleartext));
    476 
    477      collection.insert(
    478        "toolbar",
    479        encryptPayload({
    480          id: "toolbar",
    481          type: "folder",
    482          title: "toolbar",
    483          children: [folder1.guid],
    484          parentName: "places",
    485          parentid: "places",
    486        })
    487      );
    488 
    489      collection.insert(
    490        "menu",
    491        encryptPayload({
    492          id: "menu",
    493          type: "folder",
    494          title: "menu",
    495          children: [bzBmk.guid, folder2_guid],
    496          parentName: "places",
    497          parentid: "places",
    498        })
    499      );
    500 
    501      collection.insert(
    502        folder1.guid,
    503        encryptPayload({
    504          id: folder1.guid,
    505          type: "folder",
    506          title: "Folder 1",
    507          children: [bmk2_guid],
    508          parentName: "toolbar",
    509          parentid: "toolbar",
    510        })
    511      );
    512    }
    513 
    514    await assertChildGuids(
    515      folder1.guid,
    516      [tbBmk.guid],
    517      "Folder should have 1 child before first sync"
    518    );
    519 
    520    let pingsPromise = wait_for_pings(2);
    521 
    522    let changes = await PlacesSyncUtils.bookmarks.pullChanges();
    523    deepEqual(
    524      Object.keys(changes).sort(),
    525      [folder1.guid, tbBmk.guid, "menu", "mobile", "toolbar", "unfiled"].sort(),
    526      "Should track bookmark and folder created before first sync"
    527    );
    528 
    529    // Unlike the tests above, we can't use `sync_engine_and_validate_telem`
    530    // because the bookmarks engine will automatically schedule a follow-up
    531    // sync for us.
    532    _("Perform first sync and immediate follow-up sync");
    533    Service.sync({ engines: ["bookmarks"] });
    534 
    535    let pings = await pingsPromise;
    536    equal(pings.length, 2, "Should submit two pings");
    537    ok(
    538      pings.every(p => {
    539        assert_success_ping(p);
    540        return p.syncs.length == 1;
    541      }),
    542      "Should submit 1 sync per ping"
    543    );
    544 
    545    strictEqual(
    546      Service.scheduler.globalScore,
    547      0,
    548      "Should reset global score after follow-up sync"
    549    );
    550    ok(bmk3, "Should insert bookmark during first sync to simulate change");
    551    ok(
    552      collection.wbo(bmk3.guid),
    553      "Changed bookmark should be uploaded after follow-up sync"
    554    );
    555 
    556    let bmk2 = await PlacesUtils.bookmarks.fetch({
    557      guid: bmk2_guid,
    558    });
    559    ok(bmk2, "Remote bookmark should be applied during first sync");
    560    {
    561      // We only check child GUIDs, and not their order, because the exact
    562      // order is an implementation detail.
    563      let folder1Children = await PlacesSyncUtils.bookmarks.fetchChildRecordIds(
    564        folder1.guid
    565      );
    566      deepEqual(
    567        folder1Children.sort(),
    568        [bmk2_guid, tbBmk.guid, bmk3.guid].sort(),
    569        "Folder 1 should have 3 children after first sync"
    570      );
    571    }
    572    await assertChildGuids(
    573      folder2_guid,
    574      [bmk4_guid, tagQuery_guid],
    575      "Folder 2 should have 2 children after first sync"
    576    );
    577    let taggedURIs = [];
    578    await PlacesUtils.bookmarks.fetch({ tags: ["taggy"] }, b =>
    579      taggedURIs.push(b.url)
    580    );
    581    equal(taggedURIs.length, 1, "Should have 1 tagged URI");
    582    equal(
    583      taggedURIs[0].href,
    584      "https://example.org/",
    585      "Synced tagged bookmark should appear in tagged URI list"
    586    );
    587 
    588    changes = await PlacesSyncUtils.bookmarks.pullChanges();
    589    deepEqual(
    590      changes,
    591      {},
    592      "Should have already uploaded changes in follow-up sync"
    593    );
    594 
    595    // First ping won't include validation data, since we've changed bookmarks
    596    // and `canValidate` will indicate it can't proceed.
    597    let engineData = pings.map(p => {
    598      return p.syncs[0].engines.find(e => e.name == "bookmarks-buffered");
    599    });
    600    ok(engineData[0].validation, "Engine should validate after first sync");
    601    ok(engineData[1].validation, "Engine should validate after second sync");
    602  } finally {
    603    Object.defineProperty(
    604      schedulerProto,
    605      "syncThreshold",
    606      syncThresholdDescriptor
    607    );
    608    engine._uploadOutgoing = uploadOutgoing;
    609    await cleanup(engine, server);
    610  }
    611 });