diff options
Diffstat (limited to 'services/sync/tests/unit/test_bookmark_tracker.js')
-rw-r--r-- | services/sync/tests/unit/test_bookmark_tracker.js | 1275 |
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..2ac482183d --- /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 PlacesUtils.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(); + } +}); |