summaryrefslogtreecommitdiffstats
path: root/services/sync/tests/unit/test_bookmark_tracker.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/sync/tests/unit/test_bookmark_tracker.js')
-rw-r--r--services/sync/tests/unit/test_bookmark_tracker.js1275
1 files changed, 1275 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_bookmark_tracker.js b/services/sync/tests/unit/test_bookmark_tracker.js
new file mode 100644
index 0000000000..9cfbb4de78
--- /dev/null
+++ b/services/sync/tests/unit/test_bookmark_tracker.js
@@ -0,0 +1,1275 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { Service } = ChromeUtils.importESModule(
+ "resource://services-sync/service.sys.mjs"
+);
+const { PlacesTransactions } = ChromeUtils.importESModule(
+ "resource://gre/modules/PlacesTransactions.sys.mjs"
+);
+
+let engine;
+let store;
+let tracker;
+
+const DAY_IN_MS = 24 * 60 * 60 * 1000;
+
+add_task(async function setup() {
+ await Service.engineManager.switchAlternatives();
+ engine = Service.engineManager.get("bookmarks");
+ store = engine._store;
+ tracker = engine._tracker;
+});
+
+// Test helpers.
+async function verifyTrackerEmpty() {
+ await PlacesTestUtils.promiseAsyncUpdates();
+ let changes = await tracker.getChangedIDs();
+ deepEqual(changes, {});
+ equal(tracker.score, 0);
+}
+
+async function resetTracker() {
+ await PlacesTestUtils.markBookmarksAsSynced();
+ tracker.resetScore();
+}
+
+async function cleanup() {
+ await engine.setLastSync(0);
+ await store.wipe();
+ await resetTracker();
+ await tracker.stop();
+}
+
+// startTracking is a signal that the test wants to notice things that happen
+// after this is called (ie, things already tracked should be discarded.)
+async function startTracking() {
+ engine._tracker.start();
+ await PlacesTestUtils.markBookmarksAsSynced();
+}
+
+async function verifyTrackedItems(tracked) {
+ await PlacesTestUtils.promiseAsyncUpdates();
+ let changedIDs = await tracker.getChangedIDs();
+ let trackedIDs = new Set(Object.keys(changedIDs));
+ for (let guid of tracked) {
+ ok(guid in changedIDs, `${guid} should be tracked`);
+ ok(changedIDs[guid].modified > 0, `${guid} should have a modified time`);
+ ok(changedIDs[guid].counter >= -1, `${guid} should have a change counter`);
+ trackedIDs.delete(guid);
+ }
+ equal(
+ trackedIDs.size,
+ 0,
+ `Unhandled tracked IDs: ${JSON.stringify(Array.from(trackedIDs))}`
+ );
+}
+
+async function verifyTrackedCount(expected) {
+ await PlacesTestUtils.promiseAsyncUpdates();
+ let changedIDs = await tracker.getChangedIDs();
+ do_check_attribute_count(changedIDs, expected);
+}
+
+// A debugging helper that dumps the full bookmarks tree.
+// Currently unused, but might come in handy
+// eslint-disable-next-line no-unused-vars
+async function dumpBookmarks() {
+ let columns = [
+ "id",
+ "title",
+ "guid",
+ "syncStatus",
+ "syncChangeCounter",
+ "position",
+ ];
+ return PlacesUtils.promiseDBConnection().then(connection => {
+ let all = [];
+ return connection
+ .executeCached(
+ `SELECT ${columns.join(", ")} FROM moz_bookmarks;`,
+ {},
+ row => {
+ let repr = {};
+ for (let column of columns) {
+ repr[column] = row.getResultByName(column);
+ }
+ all.push(repr);
+ }
+ )
+ .then(() => {
+ dump("All bookmarks:\n");
+ dump(JSON.stringify(all, undefined, 2));
+ });
+ });
+}
+
+add_task(async function test_tracking() {
+ _("Test starting and stopping the tracker");
+
+ // Remove existing tracking information for roots.
+ await startTracking();
+
+ let folder = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "Test Folder",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ });
+
+ // creating the folder should have made 2 changes - the folder itself and
+ // the parent of the folder.
+ await verifyTrackedCount(2);
+ // Reset the changes as the rest of the test doesn't want to see these.
+ await resetTracker();
+
+ function createBmk() {
+ return PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ }
+
+ try {
+ _("Tell the tracker to start tracking changes.");
+ await startTracking();
+ await createBmk();
+ // We expect two changed items because the containing folder
+ // changed as well (new child).
+ await verifyTrackedCount(2);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+
+ _("Notifying twice won't do any harm.");
+ await createBmk();
+ await verifyTrackedCount(3);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_tracker_sql_batching() {
+ _(
+ "Test tracker does the correct thing when it is forced to batch SQL queries"
+ );
+
+ const SQLITE_MAX_VARIABLE_NUMBER = 999;
+ let numItems = SQLITE_MAX_VARIABLE_NUMBER * 2 + 10;
+
+ await startTracking();
+
+ let children = [];
+ for (let i = 0; i < numItems; i++) {
+ children.push({
+ url: "https://example.org/" + i,
+ title: "Sync Bookmark " + i,
+ });
+ }
+ let inserted = await PlacesUtils.bookmarks.insertTree({
+ guid: PlacesUtils.bookmarks.unfiledGuid,
+ children: [
+ {
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ children,
+ },
+ ],
+ });
+
+ Assert.equal(children.length, numItems);
+ Assert.equal(inserted.length, numItems + 1);
+ await verifyTrackedCount(numItems + 2); // The parent and grandparent are also tracked.
+ await resetTracker();
+
+ await PlacesUtils.bookmarks.remove(inserted[0]);
+ await verifyTrackedCount(numItems + 2);
+
+ await cleanup();
+});
+
+add_task(async function test_bookmarkAdded() {
+ _("Items inserted via the synchronous bookmarks API should be tracked");
+
+ try {
+ await startTracking();
+
+ _("Insert a folder using the sync API");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ let syncFolder = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "Sync Folder",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ });
+ await verifyTrackedItems(["menu", syncFolder.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2);
+
+ await resetTracker();
+ await startTracking();
+
+ _("Insert a bookmark using the sync API");
+ totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ let syncBmk = await PlacesUtils.bookmarks.insert({
+ parentGuid: syncFolder.guid,
+ url: "https://example.org/sync",
+ title: "Sync Bookmark",
+ });
+ await verifyTrackedItems([syncFolder.guid, syncBmk.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_bookmarkAdded() {
+ _("Items inserted via the asynchronous bookmarks API should be tracked");
+
+ try {
+ await startTracking();
+
+ _("Insert a folder using the async API");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ let asyncFolder = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "Async Folder",
+ });
+ await verifyTrackedItems(["menu", asyncFolder.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2);
+
+ await resetTracker();
+ await startTracking();
+
+ _("Insert a bookmark using the async API");
+ totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ let asyncBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: asyncFolder.guid,
+ url: "https://example.org/async",
+ title: "Async Bookmark",
+ });
+ await verifyTrackedItems([asyncFolder.guid, asyncBmk.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2);
+
+ await resetTracker();
+ await startTracking();
+
+ _("Insert a separator using the async API");
+ totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ let asyncSep = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: asyncFolder.index,
+ });
+ await verifyTrackedItems(["menu", asyncSep.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemChanged() {
+ _("Items updated using the asynchronous bookmarks API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert a bookmark");
+ let fxBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ _(`Firefox GUID: ${fxBmk.guid}`);
+
+ await startTracking();
+
+ _("Update the bookmark using the async API");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.update({
+ guid: fxBmk.guid,
+ title: "Download Firefox",
+ url: "https://www.mozilla.org/firefox",
+ // PlacesUtils.bookmarks.update rejects last modified dates older than
+ // the added date.
+ lastModified: new Date(Date.now() + DAY_IN_MS),
+ });
+
+ await verifyTrackedItems([fxBmk.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_onItemChanged_itemDates() {
+ _("Changes to item dates should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert a bookmark");
+ let fx_bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ _(`Firefox GUID: ${fx_bm.guid}`);
+
+ await startTracking();
+
+ _("Reset the bookmark's added date, should not be tracked");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ let dateAdded = new Date(Date.now() - DAY_IN_MS);
+ await PlacesUtils.bookmarks.update({
+ guid: fx_bm.guid,
+ dateAdded,
+ });
+ await verifyTrackedCount(0);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges);
+
+ await resetTracker();
+
+ _(
+ "Reset the bookmark's added date and another property, should be tracked"
+ );
+ totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ dateAdded = new Date();
+ await PlacesUtils.bookmarks.update({
+ guid: fx_bm.guid,
+ dateAdded,
+ title: "test",
+ });
+ await verifyTrackedItems([fx_bm.guid]);
+ Assert.equal(tracker.score, 2 * SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1);
+
+ await resetTracker();
+
+ _("Set the bookmark's last modified date");
+ totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ let fx_id = await PlacesTestUtils.promiseItemId(fx_bm.guid);
+ let dateModified = Date.now() * 1000;
+ PlacesUtils.bookmarks.setItemLastModified(fx_id, dateModified);
+ await verifyTrackedItems([fx_bm.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_onItemTagged() {
+ _("Items tagged using the synchronous API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Create a folder");
+ let folder = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "Parent",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ });
+ _("Folder ID: " + folder);
+ _("Folder GUID: " + folder.guid);
+
+ _("Track changes to tags");
+ let uri = CommonUtils.makeURI("http://getfirefox.com");
+ let b = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ url: uri,
+ title: "Get Firefox!",
+ });
+ _("New item is " + b);
+ _("GUID: " + b.guid);
+
+ await startTracking();
+
+ _("Tag the item");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ PlacesUtils.tagging.tagURI(uri, ["foo"]);
+
+ // bookmark should be tracked, folder should not be.
+ await verifyTrackedItems([b.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 6);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_onItemUntagged() {
+ _("Items untagged using the synchronous API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert tagged bookmarks");
+ let uri = CommonUtils.makeURI("http://getfirefox.com");
+ let fx1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: uri,
+ title: "Get Firefox!",
+ });
+ // Different parent and title; same URL.
+ let fx2 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: uri,
+ title: "Download Firefox",
+ });
+ PlacesUtils.tagging.tagURI(uri, ["foo"]);
+
+ await startTracking();
+
+ _("Remove the tag");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ PlacesUtils.tagging.untagURI(uri, ["foo"]);
+
+ await verifyTrackedItems([fx1.guid, fx2.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 4);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 5);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemUntagged() {
+ _("Items untagged using the asynchronous API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert tagged bookmarks");
+ let fxBmk1 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ let fxBmk2 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "http://getfirefox.com",
+ title: "Download Firefox",
+ });
+ let tag = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.tagsGuid,
+ title: "some tag",
+ });
+ let fxTag = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: tag.guid,
+ url: "http://getfirefox.com",
+ });
+
+ await startTracking();
+
+ _("Remove the tag using the async bookmarks API");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.remove(fxTag.guid);
+
+ await verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 4);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 5);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemTagged() {
+ _("Items tagged using the asynchronous API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert untagged bookmarks");
+ let folder1 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "Folder 1",
+ });
+ let fxBmk1 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: folder1.guid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ let folder2 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "Folder 2",
+ });
+ // Different parent and title; same URL.
+ let fxBmk2 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: folder2.guid,
+ url: "http://getfirefox.com",
+ title: "Download Firefox",
+ });
+
+ await startTracking();
+
+ // This will change once tags are moved into a separate table (bug 424160).
+ // We specifically test this case because Bookmarks.jsm updates tagged
+ // bookmarks and notifies observers.
+ _("Insert a tag using the async bookmarks API");
+ let tag = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.tagsGuid,
+ title: "some tag",
+ });
+
+ _("Tag an item using the async bookmarks API");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: tag.guid,
+ url: "http://getfirefox.com",
+ });
+
+ await verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 4);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 5);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemKeywordChanged() {
+ _("Keyword changes via the asynchronous API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert two bookmarks with the same URL");
+ let fxBmk1 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ let fxBmk2 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "http://getfirefox.com",
+ title: "Download Firefox",
+ });
+
+ await startTracking();
+
+ _("Add a keyword for both items");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.keywords.insert({
+ keyword: "the_keyword",
+ url: "http://getfirefox.com",
+ postData: "postData",
+ });
+
+ await verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemKeywordDeleted() {
+ _("Keyword deletions via the asynchronous API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert two bookmarks with the same URL and keywords");
+ let fxBmk1 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ let fxBmk2 = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "http://getfirefox.com",
+ title: "Download Firefox",
+ });
+ await PlacesUtils.keywords.insert({
+ keyword: "the_keyword",
+ url: "http://getfirefox.com",
+ });
+
+ await startTracking();
+
+ _("Remove the keyword");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.keywords.remove("the_keyword");
+
+ await verifyTrackedItems([fxBmk1.guid, fxBmk2.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_bookmarkAdded_filtered_root() {
+ _("Items outside the change roots should not be tracked");
+
+ try {
+ await startTracking();
+
+ _("Create a new root");
+ let root = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.rootGuid,
+ title: "New root",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ });
+ _(`New root GUID: ${root.guid}`);
+
+ _("Insert a bookmark underneath the new root");
+ let untrackedBmk = await PlacesUtils.bookmarks.insert({
+ parentGuid: root.guid,
+ url: "http://getthunderbird.com",
+ title: "Get Thunderbird!",
+ });
+ _(`New untracked bookmark GUID: ${untrackedBmk.guid}`);
+
+ _("Insert a bookmark underneath the Places root");
+ let rootBmk = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.rootGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ _(`New Places root bookmark GUID: ${rootBmk.guid}`);
+
+ _("New root and bookmark should be ignored");
+ await verifyTrackedItems([]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_onItemDeleted_filtered_root() {
+ _("Deleted items outside the change roots should not be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert a bookmark underneath the Places root");
+ let rootBmk = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.rootGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ _(`New Places root bookmark GUID: ${rootBmk.guid}`);
+
+ await startTracking();
+
+ await PlacesUtils.bookmarks.remove(rootBmk);
+
+ await verifyTrackedItems([]);
+ // We'll still increment the counter for the removed item.
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_onPageAnnoChanged() {
+ _("Page annotations should not be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert a bookmark without an annotation");
+ let pageURI = "http://getfirefox.com";
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: pageURI,
+ title: "Get Firefox!",
+ });
+
+ await startTracking();
+
+ _("Add a page annotation");
+ await PlacesUtils.history.update({
+ url: pageURI,
+ annotations: new Map([[PlacesUtils.CHARSET_ANNO, "UTF-16"]]),
+ });
+ await verifyTrackedItems([]);
+ Assert.equal(tracker.score, 0);
+ await resetTracker();
+
+ _("Remove the page annotation");
+ await PlacesUtils.history.update({
+ url: pageURI,
+ annotations: new Map([[PlacesUtils.CHARSET_ANNO, null]]),
+ });
+ await verifyTrackedItems([]);
+ Assert.equal(tracker.score, 0);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_onFaviconChanged() {
+ _("Favicon changes should not be tracked");
+
+ try {
+ await tracker.stop();
+
+ let pageURI = CommonUtils.makeURI("http://getfirefox.com");
+ let iconURI = CommonUtils.makeURI("http://getfirefox.com/icon");
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: pageURI,
+ title: "Get Firefox!",
+ });
+
+ await PlacesTestUtils.addVisits(pageURI);
+
+ await startTracking();
+
+ _("Favicon annotations should be ignored");
+ let iconURL =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
+ "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
+
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ iconURI,
+ iconURL,
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ await new Promise(resolve => {
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ pageURI,
+ iconURI,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ (uri, dataLen, data, mimeType) => {
+ resolve();
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+ await verifyTrackedItems([]);
+ Assert.equal(tracker.score, 0);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemMoved_moveToFolder() {
+ _("Items moved via `moveToFolder` should be tracked");
+
+ try {
+ await tracker.stop();
+
+ await PlacesUtils.bookmarks.insertTree({
+ guid: PlacesUtils.bookmarks.menuGuid,
+ children: [
+ {
+ guid: "bookmarkAAAA",
+ title: "A",
+ url: "http://example.com/a",
+ },
+ {
+ guid: "bookmarkBBBB",
+ title: "B",
+ url: "http://example.com/b",
+ },
+ {
+ guid: "bookmarkCCCC",
+ title: "C",
+ url: "http://example.com/c",
+ },
+ {
+ guid: "bookmarkDDDD",
+ title: "D",
+ url: "http://example.com/d",
+ },
+ ],
+ });
+ await PlacesUtils.bookmarks.insertTree({
+ guid: PlacesUtils.bookmarks.toolbarGuid,
+ children: [
+ {
+ guid: "bookmarkEEEE",
+ title: "E",
+ url: "http://example.com/e",
+ },
+ ],
+ });
+
+ await startTracking();
+
+ _("Move (A B D) to the toolbar");
+ await PlacesUtils.bookmarks.moveToFolder(
+ ["bookmarkAAAA", "bookmarkBBBB", "bookmarkDDDD"],
+ PlacesUtils.bookmarks.toolbarGuid,
+ PlacesUtils.bookmarks.DEFAULT_INDEX
+ );
+
+ // Moving multiple bookmarks between two folders should track the old
+ // folder, new folder, and moved bookmarks.
+ await verifyTrackedItems([
+ "menu",
+ "toolbar",
+ "bookmarkAAAA",
+ "bookmarkBBBB",
+ "bookmarkDDDD",
+ ]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3);
+ await resetTracker();
+
+ _("Reorder toolbar children: (D A B E)");
+ await PlacesUtils.bookmarks.moveToFolder(
+ ["bookmarkDDDD", "bookmarkAAAA", "bookmarkBBBB"],
+ PlacesUtils.bookmarks.toolbarGuid,
+ 0
+ );
+
+ // Reordering bookmarks in a folder should only track the folder, not the
+ // bookmarks.
+ await verifyTrackedItems(["toolbar"]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 2);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemMoved_update() {
+ _("Items moved via the asynchronous API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ let tbBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getthunderbird.com",
+ title: "Get Thunderbird!",
+ });
+
+ await startTracking();
+
+ _("Repositioning a bookmark should track the folder");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.update({
+ guid: tbBmk.guid,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: 0,
+ });
+ await verifyTrackedItems(["menu"]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1);
+ await resetTracker();
+
+ _("Reparenting a bookmark should track both folders and the bookmark");
+ totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.update({
+ guid: tbBmk.guid,
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+ });
+ await verifyTrackedItems(["menu", "toolbar", tbBmk.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 3);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemMoved_reorder() {
+ _("Items reordered via the asynchronous API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Insert out-of-order bookmarks");
+ let fxBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ _(`Firefox GUID: ${fxBmk.guid}`);
+
+ let tbBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getthunderbird.com",
+ title: "Get Thunderbird!",
+ });
+ _(`Thunderbird GUID: ${tbBmk.guid}`);
+
+ let mozBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "https://mozilla.org",
+ title: "Mozilla",
+ });
+ _(`Mozilla GUID: ${mozBmk.guid}`);
+
+ await startTracking();
+
+ _("Reorder bookmarks");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.reorder(PlacesUtils.bookmarks.menuGuid, [
+ mozBmk.guid,
+ fxBmk.guid,
+ tbBmk.guid,
+ ]);
+
+ // We only track the folder if we reorder its children, but we should
+ // bump the score for every changed item.
+ await verifyTrackedItems(["menu"]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 1);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_onItemDeleted_removeFolderTransaction() {
+ _("Folders removed in a transaction should be tracked");
+
+ try {
+ await tracker.stop();
+
+ _("Create a folder with two children");
+ let folder = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "Test folder",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ });
+ _(`Folder GUID: ${folder.guid}`);
+ let fx = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ _(`Firefox GUID: ${fx.guid}`);
+ let tb = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder.guid,
+ url: "http://getthunderbird.com",
+ title: "Get Thunderbird!",
+ });
+ _(`Thunderbird GUID: ${tb.guid}`);
+
+ await startTracking();
+
+ let txn = PlacesTransactions.Remove({ guid: folder.guid });
+ // We haven't executed the transaction yet.
+ await verifyTrackerEmpty();
+
+ _("Execute the remove folder transaction");
+ await txn.transact();
+ await verifyTrackedItems(["menu", folder.guid, fx.guid, tb.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3);
+ await resetTracker();
+
+ _("Undo the remove folder transaction");
+ await PlacesTransactions.undo();
+
+ await verifyTrackedItems(["menu", folder.guid, fx.guid, tb.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3);
+ await resetTracker();
+
+ _("Redo the transaction");
+ await PlacesTransactions.redo();
+ await verifyTrackedItems(["menu", folder.guid, fx.guid, tb.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_treeMoved() {
+ _("Moving an entire tree of bookmarks should track the parents");
+
+ try {
+ // Create a couple of parent folders.
+ let folder1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ test: "First test folder",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ });
+
+ // A second folder in the first.
+ let folder2 = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder1.guid,
+ title: "Second test folder",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ });
+
+ // Create a couple of bookmarks in the second folder.
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: folder2.guid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: folder2.guid,
+ url: "http://getthunderbird.com",
+ title: "Get Thunderbird!",
+ });
+
+ await startTracking();
+
+ // Move folder 2 to be a sibling of folder1.
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.update({
+ guid: folder2.guid,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: 0,
+ });
+
+ // the menu and both folders should be tracked, the children should not be.
+ await verifyTrackedItems(["menu", folder1.guid, folder2.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 3);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_onItemDeleted() {
+ _("Bookmarks deleted via the synchronous API should be tracked");
+
+ try {
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ let tb = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getthunderbird.com",
+ title: "Get Thunderbird!",
+ });
+
+ await startTracking();
+
+ // Delete the last item - the item and parent should be tracked.
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.remove(tb);
+
+ await verifyTrackedItems(["menu", tb.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemDeleted() {
+ _("Bookmarks deleted via the asynchronous API should be tracked");
+
+ try {
+ await tracker.stop();
+
+ let fxBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "http://getthunderbird.com",
+ title: "Get Thunderbird!",
+ });
+
+ await startTracking();
+
+ _("Delete the first item");
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.remove(fxBmk.guid);
+
+ await verifyTrackedItems(["menu", fxBmk.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 2);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_async_onItemDeleted_eraseEverything() {
+ _("Erasing everything should track all deleted items");
+
+ try {
+ await tracker.stop();
+
+ let fxBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.mobileGuid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ _(`Firefox GUID: ${fxBmk.guid}`);
+ let tbBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.mobileGuid,
+ url: "http://getthunderbird.com",
+ title: "Get Thunderbird!",
+ });
+ _(`Thunderbird GUID: ${tbBmk.guid}`);
+ let mozBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "https://mozilla.org",
+ title: "Mozilla",
+ });
+ _(`Mozilla GUID: ${mozBmk.guid}`);
+ let mdnBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ url: "https://developer.mozilla.org",
+ title: "MDN",
+ });
+ _(`MDN GUID: ${mdnBmk.guid}`);
+ let bugsFolder = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ title: "Bugs",
+ });
+ _(`Bugs folder GUID: ${bugsFolder.guid}`);
+ let bzBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: bugsFolder.guid,
+ url: "https://bugzilla.mozilla.org",
+ title: "Bugzilla",
+ });
+ _(`Bugzilla GUID: ${bzBmk.guid}`);
+ let bugsChildFolder = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ parentGuid: bugsFolder.guid,
+ title: "Bugs child",
+ });
+ _(`Bugs child GUID: ${bugsChildFolder.guid}`);
+ let bugsGrandChildBmk = await PlacesUtils.bookmarks.insert({
+ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ parentGuid: bugsChildFolder.guid,
+ url: "https://example.com",
+ title: "Bugs grandchild",
+ });
+ _(`Bugs grandchild GUID: ${bugsGrandChildBmk.guid}`);
+
+ await startTracking();
+ // Simulate moving a synced item into a new folder. Deleting the folder
+ // should write a tombstone for the item, but not the folder.
+ await PlacesTestUtils.setBookmarkSyncFields({
+ guid: bugsChildFolder.guid,
+ syncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NEW,
+ });
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.eraseEverything();
+
+ // bugsChildFolder's sync status is still "NEW", so it shouldn't be
+ // tracked. bugsGrandChildBmk is "NORMAL", so we *should* write a
+ // tombstone and track it.
+ await verifyTrackedItems([
+ "menu",
+ mozBmk.guid,
+ mdnBmk.guid,
+ "toolbar",
+ bugsFolder.guid,
+ "mobile",
+ fxBmk.guid,
+ tbBmk.guid,
+ "unfiled",
+ bzBmk.guid,
+ bugsGrandChildBmk.guid,
+ ]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 8);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 11);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});
+
+add_task(async function test_onItemDeleted_tree() {
+ _("Deleting a tree of bookmarks should track all items");
+
+ try {
+ // Create a couple of parent folders.
+ let folder1 = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "First test folder",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ });
+
+ // A second folder in the first.
+ let folder2 = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder1.guid,
+ title: "Second test folder",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ });
+
+ // Create a couple of bookmarks in the second folder.
+ let fx = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder2.guid,
+ url: "http://getfirefox.com",
+ title: "Get Firefox!",
+ });
+ let tb = await PlacesUtils.bookmarks.insert({
+ parentGuid: folder2.guid,
+ url: "http://getthunderbird.com",
+ title: "Get Thunderbird!",
+ });
+
+ await startTracking();
+
+ // Delete folder2 - everything we created should be tracked.
+ let totalSyncChanges = PlacesUtils.bookmarks.totalSyncChanges;
+ await PlacesUtils.bookmarks.remove(folder2);
+
+ await verifyTrackedItems([fx.guid, tb.guid, folder1.guid, folder2.guid]);
+ Assert.equal(tracker.score, SCORE_INCREMENT_XLARGE * 3);
+ Assert.equal(PlacesUtils.bookmarks.totalSyncChanges, totalSyncChanges + 4);
+ } finally {
+ _("Clean up.");
+ await cleanup();
+ }
+});