gecko/services/sync/tests/unit/test_bookmark_tracker.js

320 lines
11 KiB
JavaScript

Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/util.js");
try {
Cu.import("resource://gre/modules/PlacesUtils.jsm");
}
catch(ex) {
Cu.import("resource://gre/modules/utils.js");
}
// Grab a backstage pass so we can fetch the anno constant. Evil but useful.
let bsp = Cu.import("resource://services-sync/engines/bookmarks.js");
const SYNC_GUID_ANNO = bsp.GUID_ANNO; // Not the same as Places' GUID_ANNO!
Engines.register(BookmarksEngine);
let engine = Engines.get("bookmarks");
let store = engine._store;
store.wipe();
function test_copying_places() {
// Gecko <2.0
if (store._haveGUIDColumn) {
_("We have a GUID column; not testing anno GUID fixing.");
return;
}
//
// Copied and simplified from PlacesUIUtils, to which we don't have easy
// access.
//
let ptm;
try {
ptm = Components.classes["@mozilla.org/browser/placesTransactionsService;1"]
.getService(Ci.nsIPlacesTransactionsService);
} catch (ex) {
_("Can't test transactions -- not running with a browser context.");
return;
}
function getBookmarkItemCopyTransaction(aData, aContainer, aIndex,
aExcludeAnnotations) {
var itemURL = PlacesUtils._uri(aData.uri);
var itemTitle = aData.title;
var keyword = aData.keyword || null;
var annos = aData.annos || [];
// always exclude GUID when copying any item
var excludeAnnos = [PlacesUtils.GUID_ANNO];
if (aExcludeAnnotations)
excludeAnnos = excludeAnnos.concat(aExcludeAnnotations);
annos = annos.filter(function(aValue, aIndex, aArray) {
return excludeAnnos.indexOf(aValue.name) == -1;
});
var childTxns = [];
if (aData.dateAdded)
childTxns.push(ptm.editItemDateAdded(null, aData.dateAdded));
if (aData.lastModified)
childTxns.push(ptm.editItemLastModified(null, aData.lastModified));
if (aData.tags) {
var tags = aData.tags.split(", ");
var storedTags = PlacesUtils.tagging.getTagsForURI(itemURL, {});
tags = tags.filter(function (aTag) {
return (storedTags.indexOf(aTag) == -1);
}, this);
if (tags.length)
childTxns.push(ptm.tagURI(itemURL, tags));
}
return ptm.createItem(itemURL, aContainer, aIndex, itemTitle, keyword,
annos, childTxns);
}
function getFolderCopyTransaction(aData, aContainer, aIndex) {
function getChildItemsTransactions(aChildren) {
var childItemsTransactions = [];
var cc = aChildren.length;
var index = aIndex;
for (var i = 0; i < cc; ++i) {
var txn = null;
var node = aChildren[i];
// Make sure that items are given the correct index, this will be
// passed by the transaction manager to the backend for the insertion.
// Insertion behaves differently if index == DEFAULT_INDEX (append)
if (aIndex != PlacesUtils.bookmarks.DEFAULT_INDEX)
index = i;
if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER)
txn = getFolderCopyTransaction(node, aContainer, index);
else // (node.type == PlacesUtils.TYPE_X_MOZ_PLACE)
txn = getBookmarkItemCopyTransaction(node, -1, index);
if (txn)
childItemsTransactions.push(txn);
}
return childItemsTransactions;
}
// tag folders use tag transactions
if (aContainer == PlacesUtils.tagsFolderId) {
var txns = [];
if (aData.children) {
aData.children.forEach(function(aChild) {
txns.push(ptm.tagURI(PlacesUtils._uri(aChild.uri), [aData.title]));
}, this);
}
return ptm.aggregateTransactions("addTags", txns);
}
else {
var childItems = getChildItemsTransactions(aData.children);
if (aData.dateAdded)
childItems.push(ptm.editItemDateAdded(null, aData.dateAdded));
if (aData.lastModified)
childItems.push(ptm.editItemLastModified(null, aData.lastModified));
var annos = aData.annos || [];
annos = annos.filter(function(aAnno) {
// always exclude GUID when copying any item
return aAnno.name != PlacesUtils.GUID_ANNO;
});
return ptm.createFolder(aData.title, aContainer, aIndex, annos, childItems);
}
}
//
// End simplified PlacesUIUtils code.
//
let tracker = engine._tracker;
tracker.clearChangedIDs();
try {
_("Test copying using Places transactions.");
Svc.Obs.notify("weave:engine:start-tracking");
_("Set up source and destination folders.");
let f1 = Svc.Bookmark.createFolder(PlacesUtils.toolbarFolderId, "Folder One",
Svc.Bookmark.DEFAULT_INDEX);
let f2 = Svc.Bookmark.createFolder(PlacesUtils.toolbarFolderId, "Folder Two",
Svc.Bookmark.DEFAULT_INDEX);
let b1 = Svc.Bookmark.insertBookmark(f1,
Utils.makeURI("http://example.com/"),
Svc.Bookmark.DEFAULT_INDEX,
"Example!");
_("F1: " + f1 + "; F2: " + f2 + "; B1: " + b1);
_("Fetch GUIDs so our anno exists.");
let f1GUID = store.GUIDForId(f1);
let f2GUID = store.GUIDForId(f2);
let b1GUID = store.GUIDForId(b1);
do_check_true(!!f1GUID);
do_check_true(!!f2GUID);
do_check_true(!!b1GUID);
_("Make sure the destination folder is empty.");
do_check_eq(PlacesUtils.getFolderContents(f2, false, true).root.childCount, 0);
let f1Node = PlacesUtils.getFolderContents(f1, false, false);
_("Node to copy: " + f1Node.root.itemId);
let serialized = PlacesUtils.wrapNode(f1Node.root, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
_("Serialized to " + serialized);
let raw = PlacesUtils.unwrapNodes(serialized, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER).shift();
let transaction = getFolderCopyTransaction(raw, f2, Svc.Bookmark.DEFAULT_INDEX, true);
_("Run the copy transaction.");
ptm.doTransaction(transaction);
_("Verify that items have been copied.");
let f2Node = PlacesUtils.getFolderContents(f2, false, true);
do_check_eq(f2Node.root.childCount, 1);
_("Verify that the copied folder has different GUIDs.");
let c0 = f2Node.root.getChild(0);
do_check_eq(c0.title, "Folder One");
do_check_neq(c0.itemId, f1);
do_check_neq(store.GUIDForId(c0.itemId), f1GUID);
_("Verify that this copied folder contains a copied bookmark with different GUIDs.");
c0 = c0.QueryInterface(Ci.nsINavHistoryContainerResultNode);
c0.containerOpen = true;
do_check_eq(c0.childCount, 1);
let b0 = c0.getChild(0);
do_check_eq(b0.title, "Example!");
do_check_eq(b0.uri, "http://example.com/");
do_check_neq(b0.itemId, b1);
do_check_neq(store.GUIDForId(b0.itemId), b1GUID);
} finally {
tracker.clearChangedIDs();
Svc.Obs.notify("weave:engine:stop-tracking");
}
}
function test_tracking() {
_("Verify we've got an empty tracker to work with.");
let tracker = engine._tracker;
do_check_eq([id for (id in tracker.changedIDs)].length, 0);
let folder = Svc.Bookmark.createFolder(Svc.Bookmark.bookmarksMenuFolder,
"Test Folder",
Svc.Bookmark.DEFAULT_INDEX);
function createBmk() {
return Svc.Bookmark.insertBookmark(folder,
Utils.makeURI("http://getfirefox.com"),
Svc.Bookmark.DEFAULT_INDEX,
"Get Firefox!");
}
try {
_("Create bookmark. Won't show because we haven't started tracking yet");
createBmk();
do_check_eq([id for (id in tracker.changedIDs)].length, 0);
_("Tell the tracker to start tracking changes.");
Svc.Obs.notify("weave:engine:start-tracking");
createBmk();
// We expect two changed items because the containing folder
// changed as well (new child).
do_check_eq([id for (id in tracker.changedIDs)].length, 2);
_("Notifying twice won't do any harm.");
Svc.Obs.notify("weave:engine:start-tracking");
createBmk();
do_check_eq([id for (id in tracker.changedIDs)].length, 3);
_("Let's stop tracking again.");
tracker.clearChangedIDs();
Svc.Obs.notify("weave:engine:stop-tracking");
createBmk();
do_check_eq([id for (id in tracker.changedIDs)].length, 0);
_("Notifying twice won't do any harm.");
Svc.Obs.notify("weave:engine:stop-tracking");
createBmk();
do_check_eq([id for (id in tracker.changedIDs)].length, 0);
} finally {
_("Clean up.");
store.wipe();
tracker.clearChangedIDs();
Svc.Obs.notify("weave:engine:stop-tracking");
}
}
function test_guid_stripping() {
// Gecko <2.0
if (store._haveGUIDColumn) {
_("We have a GUID column; not testing anno GUID fixing.");
return;
}
_("Verify we've got an empty tracker to work with.");
let tracker = engine._tracker;
let folder = Svc.Bookmark.createFolder(Svc.Bookmark.bookmarksMenuFolder,
"Test Folder",
Svc.Bookmark.DEFAULT_INDEX);
function createBmk() {
return Svc.Bookmark.insertBookmark(folder,
Utils.makeURI("http://getfirefox.com"),
Svc.Bookmark.DEFAULT_INDEX,
"Get Firefox!");
}
do_check_eq([id for (id in tracker.changedIDs)].length, 0);
try {
_("Directly testing GUID stripping.");
Svc.Obs.notify("weave:engine:start-tracking");
tracker.ignoreAll = false;
let suspect = createBmk();
let victim = createBmk();
_("Suspect: " + suspect + ", victim: " + victim);
let suspectGUID = store.GUIDForId(suspect);
let victimGUID = store.GUIDForId(victim);
_("Set the GUID on one entry to be the same as another.");
do_check_neq(suspectGUID, victimGUID);
Svc.Annos.setItemAnnotation(suspect, SYNC_GUID_ANNO, store.GUIDForId(victim),
0, Svc.Annos.EXPIRE_NEVER);
_("Tracker changed it to something else.");
let newGUID = store.GUIDForId(suspect);
do_check_neq(newGUID, victimGUID);
do_check_neq(newGUID, suspectGUID);
_("Victim GUID remains unchanged.");
do_check_eq(victimGUID, store.GUIDForId(victim));
_("New GUID is in the tracker.");
_(JSON.stringify(tracker.changedIDs));
do_check_neq(tracker.changedIDs[newGUID], null);
} finally {
_("Clean up.");
store.wipe();
tracker.clearChangedIDs();
Svc.Obs.notify("weave:engine:stop-tracking");
}
}
function run_test() {
initTestLogging("Trace");
Log4Moz.repository.getLogger("Engine.Bookmarks").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Store.Bookmarks").level = Log4Moz.Level.Trace;
Log4Moz.repository.getLogger("Tracker.Bookmarks").level = Log4Moz.Level.Trace;
test_copying_places();
test_tracking();
test_guid_stripping();
}