--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -24,82 +24,135 @@ XPCOMUtils.defineLazyModuleGetter(this,
/**
* This module exports functions for Sync to use when applying remote
* records. The calls are similar to those in `Bookmarks.jsm` and
* `nsINavBookmarksService`, with special handling for smart bookmarks,
* tags, keywords, synced annotations, and missing parents.
*/
var PlacesSyncUtils = {};
-const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
-const DESCRIPTION_ANNO = "bookmarkProperties/description";
-const SIDEBAR_ANNO = "bookmarkProperties/loadInSidebar";
-const PARENT_ANNO = "sync/parent";
-
const { SOURCE_SYNC } = Ci.nsINavBookmarksService;
+// These are defined as lazy getters to defer initializing the bookmarks
+// service until it's needed.
+XPCOMUtils.defineLazyGetter(this, "ROOT_SYNC_ID_TO_GUID", () => ({
+ menu: PlacesUtils.bookmarks.menuGuid,
+ places: PlacesUtils.bookmarks.rootGuid,
+ tags: PlacesUtils.bookmarks.tagsGuid,
+ toolbar: PlacesUtils.bookmarks.toolbarGuid,
+ unfiled: PlacesUtils.bookmarks.unfiledGuid,
+ mobile: PlacesUtils.bookmarks.mobileGuid,
+}));
+
+XPCOMUtils.defineLazyGetter(this, "ROOT_GUID_TO_SYNC_ID", () => ({
+ [PlacesUtils.bookmarks.menuGuid]: "menu",
+ [PlacesUtils.bookmarks.rootGuid]: "places",
+ [PlacesUtils.bookmarks.tagsGuid]: "tags",
+ [PlacesUtils.bookmarks.toolbarGuid]: "toolbar",
+ [PlacesUtils.bookmarks.unfiledGuid]: "unfiled",
+ [PlacesUtils.bookmarks.mobileGuid]: "mobile",
+}));
+
+XPCOMUtils.defineLazyGetter(this, "ROOTS", () =>
+ Object.keys(ROOT_SYNC_ID_TO_GUID)
+);
+
const BookmarkSyncUtils = PlacesSyncUtils.bookmarks = Object.freeze({
+ SMART_BOOKMARKS_ANNO: "Places/SmartBookmark",
+ DESCRIPTION_ANNO: "bookmarkProperties/description",
+ SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
+ SYNC_PARENT_ANNO: "sync/parent",
+ SYNC_MOBILE_ROOT_ANNO: "mobile/bookmarksRoot",
+
KINDS: {
BOOKMARK: "bookmark",
// Microsummaries were removed from Places in bug 524091. For now, Sync
// treats them identically to bookmarks. Bug 745410 tracks removing them
// entirely.
MICROSUMMARY: "microsummary",
QUERY: "query",
FOLDER: "folder",
LIVEMARK: "livemark",
SEPARATOR: "separator",
},
+ get ROOTS() {
+ return ROOTS;
+ },
+
/**
- * Fetches a folder's children, ordered by their position within the folder.
+ * Converts a Places GUID to a Sync ID. Sync IDs are identical to Places
+ * GUIDs for all items except roots.
*/
- fetchChildGuids: Task.async(function* (parentGuid) {
- PlacesUtils.SYNC_BOOKMARK_VALIDATORS.guid(parentGuid);
+ guidToSyncId(guid) {
+ return ROOT_GUID_TO_SYNC_ID[guid] || guid;
+ },
+
+ /**
+ * Converts a Sync record ID to a Places GUID.
+ */
+ syncIdToGuid(syncId) {
+ return ROOT_SYNC_ID_TO_GUID[syncId] || syncId;
+ },
+
+ /**
+ * Fetches the sync IDs for a folder's children, ordered by their position
+ * within the folder.
+ */
+ fetchChildSyncIds: Task.async(function* (parentSyncId) {
+ PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(parentSyncId);
+ let parentGuid = BookmarkSyncUtils.syncIdToGuid(parentSyncId);
let db = yield PlacesUtils.promiseDBConnection();
let children = yield fetchAllChildren(db, parentGuid);
- return children.map(child => child.guid);
+ return children.map(child =>
+ BookmarkSyncUtils.guidToSyncId(child.guid)
+ );
}),
/**
* Reorders a folder's children, based on their order in the array of GUIDs.
* This method is similar to `Bookmarks.reorder`, but leaves missing entries
* in place instead of moving them to the end of the folder.
*
* Sync uses this method to reorder all synced children after applying all
* incoming records.
*
*/
- order: Task.async(function* (parentGuid, childGuids) {
- PlacesUtils.SYNC_BOOKMARK_VALIDATORS.guid(parentGuid);
- for (let guid of childGuids) {
- PlacesUtils.SYNC_BOOKMARK_VALIDATORS.guid(guid);
+ order: Task.async(function* (parentSyncId, childSyncIds) {
+ PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(parentSyncId);
+ if (!childSyncIds.length) {
+ return;
}
+ for (let syncId of childSyncIds) {
+ PlacesUtils.SYNC_BOOKMARK_VALIDATORS.syncId(syncId);
+ }
+ let parentGuid = BookmarkSyncUtils.syncIdToGuid(parentSyncId);
if (parentGuid == PlacesUtils.bookmarks.rootGuid) {
// Reordering roots doesn't make sense, but Sync will do this on the
// first sync.
- return Promise.resolve();
+ return;
}
- return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: order",
+ yield PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: order",
Task.async(function* (db) {
let children = yield fetchAllChildren(db, parentGuid);
if (!children.length) {
return;
}
// Reorder the list, ignoring missing children.
let delta = 0;
- for (let i = 0; i < childGuids.length; ++i) {
- let guid = childGuids[i];
+ for (let i = 0; i < childSyncIds.length; ++i) {
+ let guid = BookmarkSyncUtils.syncIdToGuid(childSyncIds[i]);
let child = findChildByGuid(children, guid);
if (!child) {
delta++;
- BookmarkSyncLog.trace(`order: Ignoring missing child ${guid}`);
+ BookmarkSyncLog.trace(`order: Ignoring missing child ${
+ childSyncIds[i]}`);
continue;
}
let newIndex = i - delta;
updateChildIndex(children, child, newIndex);
}
children.sort((a, b) => a.index - b.index);
// Update positions.
@@ -107,42 +160,38 @@ const BookmarkSyncUtils = PlacesSyncUtil
yield PlacesUtils.bookmarks.reorder(parentGuid, orderedChildrenGuids);
})
);
}),
/**
* Removes an item from the database.
*/
- remove: Task.async(function* (guid) {
+ remove: Task.async(function* (syncId) {
+ let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
+ if (guid in ROOT_GUID_TO_SYNC_ID) {
+ BookmarkSyncLog.warn(`remove: Refusing to remove root ${syncId}`);
+ return null;
+ }
return PlacesUtils.bookmarks.remove(guid, {
source: SOURCE_SYNC,
});
}),
/**
- * Removes a folder's children. This is a temporary method that can be
- * replaced by `eraseEverything` once Places supports the Sync-specific
- * mobile root.
- */
- clear: Task.async(function* (folderGuid) {
- let folderId = yield PlacesUtils.promiseItemId(folderGuid);
- PlacesUtils.bookmarks.removeFolderChildren(folderId, SOURCE_SYNC);
- }),
-
- /**
- * Changes the GUID of an existing item.
+ * Changes the GUID of an existing item. This method only allows Places GUIDs
+ * because root sync IDs cannot be changed.
*
* @return {Promise} resolved once the GUID has been changed.
* @resolves to the new GUID.
* @rejects if the old GUID does not exist.
*/
changeGuid: Task.async(function* (oldGuid, newGuid) {
- PlacesUtils.SYNC_BOOKMARK_VALIDATORS.guid(oldGuid);
- PlacesUtils.SYNC_BOOKMARK_VALIDATORS.guid(newGuid);
+ PlacesUtils.BOOKMARK_VALIDATORS.guid(oldGuid);
+ PlacesUtils.BOOKMARK_VALIDATORS.guid(newGuid);
let itemId = yield PlacesUtils.promiseItemId(oldGuid);
if (PlacesUtils.isRootItem(itemId)) {
throw new Error(`Cannot change GUID of Places root ${oldGuid}`);
}
return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: changeGuid",
Task.async(function* (db) {
yield db.executeCached(`UPDATE moz_bookmarks SET guid = :newGuid
@@ -174,22 +223,18 @@ const BookmarkSyncUtils = PlacesSyncUtil
*
* @return {Promise} resolved when the update is complete.
* @resolves to an object representing the updated bookmark.
* @rejects if it's not possible to update the given bookmark.
* @throws if the arguments are invalid.
*/
update: Task.async(function* (info) {
let updateInfo = validateSyncBookmarkObject(info,
- { guid: { required: true }
- , type: { validIf: () => false }
- , index: { validIf: () => false }
- , source: { validIf: () => false }
+ { syncId: { required: true }
});
- updateInfo.source = SOURCE_SYNC;
return updateSyncBookmark(updateInfo);
}),
/**
* Inserts a synced bookmark into the tree. Only Sync should call this
* method; other callers should use `Bookmarks.insert`.
*
@@ -288,200 +333,200 @@ var GUIDMissing = Task.async(function* (
}
throw ex;
}
});
// Tag queries use a `place:` URL that refers to the tag folder ID. When we
// apply a synced tag query from a remote client, we need to update the URL to
// point to the local tag folder.
-var updateTagQueryFolder = Task.async(function* (item) {
- if (item.kind != BookmarkSyncUtils.KINDS.QUERY || !item.folder || !item.url ||
- item.url.protocol != "place:") {
- return item;
+var updateTagQueryFolder = Task.async(function* (info) {
+ if (info.kind != BookmarkSyncUtils.KINDS.QUERY || !info.folder || !info.url ||
+ info.url.protocol != "place:") {
+ return info;
}
- let params = new URLSearchParams(item.url.pathname);
+ let params = new URLSearchParams(info.url.pathname);
let type = +params.get("type");
if (type != Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
- return item;
+ return info;
}
- let id = yield getOrCreateTagFolder(item.folder);
+ let id = yield getOrCreateTagFolder(info.folder);
BookmarkSyncLog.debug(`updateTagQueryFolder: Tag query folder: ${
- item.folder} = ${id}`);
+ info.folder} = ${id}`);
// Rewrite the query to reference the new ID.
params.set("folder", id);
- item.url = new URL(item.url.protocol + params);
+ info.url = new URL(info.url.protocol + params);
- return item;
+ return info;
});
-var annotateOrphan = Task.async(function* (item, requestedParentGuid) {
- let itemId = yield PlacesUtils.promiseItemId(item.guid);
+var annotateOrphan = Task.async(function* (item, requestedParentSyncId) {
+ let guid = BookmarkSyncUtils.syncIdToGuid(item.syncId);
+ let itemId = yield PlacesUtils.promiseItemId(guid);
PlacesUtils.annotations.setItemAnnotation(itemId,
- PARENT_ANNO, requestedParentGuid, 0,
+ BookmarkSyncUtils.SYNC_PARENT_ANNO, requestedParentSyncId, 0,
PlacesUtils.annotations.EXPIRE_NEVER,
SOURCE_SYNC);
});
var reparentOrphans = Task.async(function* (item) {
- if (item.type != PlacesUtils.bookmarks.TYPE_FOLDER) {
+ if (item.kind != BookmarkSyncUtils.KINDS.FOLDER) {
return;
}
- let orphanIds = findAnnoItems(PARENT_ANNO, item.guid);
+ let orphanIds = findAnnoItems(BookmarkSyncUtils.SYNC_PARENT_ANNO, item.syncId);
// The annotations API returns item IDs, but the asynchronous bookmarks
// API uses GUIDs. We can remove the `promiseItemGuid` calls and parallel
// arrays once we implement a GUID-aware annotations API.
let orphanGuids = yield Promise.all(orphanIds.map(id =>
PlacesUtils.promiseItemGuid(id)));
+ let folderGuid = BookmarkSyncUtils.syncIdToGuid(item.syncId);
BookmarkSyncLog.debug(`reparentOrphans: Reparenting ${
- JSON.stringify(orphanGuids)} to ${item.guid}`);
+ JSON.stringify(orphanGuids)} to ${item.syncId}`);
for (let i = 0; i < orphanGuids.length; ++i) {
let isReparented = false;
try {
// Reparenting can fail if we have a corrupted or incomplete tree
// where an item's parent is one of its descendants.
BookmarkSyncLog.trace(`reparentOrphans: Attempting to move item ${
- orphanGuids[i]} to new parent ${item.guid}`);
+ orphanGuids[i]} to new parent ${item.syncId}`);
yield PlacesUtils.bookmarks.update({
guid: orphanGuids[i],
- parentGuid: item.guid,
+ parentGuid: folderGuid,
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
source: SOURCE_SYNC,
});
isReparented = true;
} catch (ex) {
BookmarkSyncLog.error(`reparentOrphans: Failed to reparent item ${
- orphanGuids[i]} to ${item.guid}`, ex);
+ orphanGuids[i]} to ${item.syncId}`, ex);
}
if (isReparented) {
// Remove the annotation once we've reparented the item.
PlacesUtils.annotations.removeItemAnnotation(orphanIds[i],
- PARENT_ANNO, SOURCE_SYNC);
+ BookmarkSyncUtils.SYNC_PARENT_ANNO, SOURCE_SYNC);
}
}
});
// Inserts a synced bookmark into the database.
var insertSyncBookmark = Task.async(function* (insertInfo) {
- let requestedParentGuid = insertInfo.parentGuid;
- let isOrphan = yield GUIDMissing(insertInfo.parentGuid);
+ let requestedParentSyncId = insertInfo.parentSyncId;
+ let requestedParentGuid =
+ BookmarkSyncUtils.syncIdToGuid(insertInfo.parentSyncId);
+ let isOrphan = yield GUIDMissing(requestedParentGuid);
// Default to "unfiled" for new bookmarks if the parent doesn't exist.
if (!isOrphan) {
BookmarkSyncLog.debug(`insertSyncBookmark: Item ${
- insertInfo.guid} is not an orphan`);
+ insertInfo.syncId} is not an orphan`);
} else {
BookmarkSyncLog.debug(`insertSyncBookmark: Item ${
- insertInfo.guid} is an orphan: parent ${
- requestedParentGuid} doesn't exist; reparenting to unfiled`);
- insertInfo.parentGuid = PlacesUtils.bookmarks.unfiledGuid;
+ insertInfo.syncId} is an orphan: parent ${
+ insertInfo.parentSyncId} doesn't exist; reparenting to unfiled`);
+ insertInfo.parentSyncId = "unfiled";
}
// If we're inserting a tag query, make sure the tag exists and fix the
// folder ID to refer to the local tag folder.
insertInfo = yield updateTagQueryFolder(insertInfo);
let newItem;
if (insertInfo.kind == BookmarkSyncUtils.KINDS.LIVEMARK) {
newItem = yield insertSyncLivemark(insertInfo);
} else {
- let item = yield PlacesUtils.bookmarks.insert(insertInfo);
- let newId = yield PlacesUtils.promiseItemId(item.guid);
- newItem = yield insertBookmarkMetadata(newId, item, insertInfo);
+ let bookmarkInfo = syncBookmarkToPlacesBookmark(insertInfo);
+ let bookmarkItem = yield PlacesUtils.bookmarks.insert(bookmarkInfo);
+ newItem = yield insertBookmarkMetadata(bookmarkItem, insertInfo);
}
if (!newItem) {
return null;
}
- // If the item is an orphan, annotate it with its real parent ID.
+ // If the item is an orphan, annotate it with its real parent sync ID.
if (isOrphan) {
- yield annotateOrphan(newItem, requestedParentGuid);
+ yield annotateOrphan(newItem, requestedParentSyncId);
}
// Reparent all orphans that expect this folder as the parent.
yield reparentOrphans(newItem);
return newItem;
});
// Inserts a synced livemark.
var insertSyncLivemark = Task.async(function* (insertInfo) {
- let parentId = yield PlacesUtils.promiseItemId(insertInfo.parentGuid);
+ let livemarkInfo = syncBookmarkToPlacesBookmark(insertInfo);
+ let parentId = yield PlacesUtils.promiseItemId(livemarkInfo.parentGuid);
let parentIsLivemark = PlacesUtils.annotations.itemHasAnnotation(parentId,
PlacesUtils.LMANNO_FEEDURI);
if (parentIsLivemark) {
// A livemark can't be a descendant of another livemark.
BookmarkSyncLog.debug(`insertSyncLivemark: Invalid parent ${
- insertInfo.parentGuid}; skipping livemark record ${insertInfo.guid}`);
+ insertInfo.parentSyncId}; skipping livemark record ${
+ insertInfo.syncId}`);
return null;
}
- let feedURI = PlacesUtils.toURI(insertInfo.feed);
- let siteURI = insertInfo.site ? PlacesUtils.toURI(insertInfo.site) : null;
- let item = yield PlacesUtils.livemarks.addLivemark({
- title: insertInfo.title,
- parentGuid: insertInfo.parentGuid,
- index: PlacesUtils.bookmarks.DEFAULT_INDEX,
- feedURI,
- siteURI,
- guid: insertInfo.guid,
- source: SOURCE_SYNC,
- });
+ let livemarkItem = yield PlacesUtils.livemarks.addLivemark(livemarkInfo);
- return insertBookmarkMetadata(item.id, item, insertInfo);
+ return insertBookmarkMetadata(livemarkItem, insertInfo);
});
-// Sets annotations, keywords, and tags on a new synced bookmark.
-var insertBookmarkMetadata = Task.async(function* (itemId, item, insertInfo) {
+// Sets annotations, keywords, and tags on a new bookmark. Returns a Sync
+// bookmark object.
+var insertBookmarkMetadata = Task.async(function* (bookmarkItem, insertInfo) {
+ let itemId = yield PlacesUtils.promiseItemId(bookmarkItem.guid);
+ let newItem = yield placesBookmarkToSyncBookmark(bookmarkItem);
+
if (insertInfo.query) {
PlacesUtils.annotations.setItemAnnotation(itemId,
- SMART_BOOKMARKS_ANNO, insertInfo.query, 0,
+ BookmarkSyncUtils.SMART_BOOKMARKS_ANNO, insertInfo.query, 0,
PlacesUtils.annotations.EXPIRE_NEVER,
SOURCE_SYNC);
- item.query = insertInfo.query;
+ newItem.query = insertInfo.query;
}
try {
- item.tags = yield tagItem(item, insertInfo.tags);
+ newItem.tags = yield tagItem(bookmarkItem, insertInfo.tags);
} catch (ex) {
BookmarkSyncLog.warn(`insertBookmarkMetadata: Error tagging item ${
- item.guid}`, ex);
+ insertInfo.syncId}`, ex);
}
if (insertInfo.keyword) {
yield PlacesUtils.keywords.insert({
keyword: insertInfo.keyword,
- url: item.url.href,
+ url: bookmarkItem.url.href,
source: SOURCE_SYNC,
});
- item.keyword = insertInfo.keyword;
+ newItem.keyword = insertInfo.keyword;
}
if (insertInfo.description) {
PlacesUtils.annotations.setItemAnnotation(itemId,
- DESCRIPTION_ANNO, insertInfo.description, 0,
+ BookmarkSyncUtils.DESCRIPTION_ANNO, insertInfo.description, 0,
PlacesUtils.annotations.EXPIRE_NEVER,
SOURCE_SYNC);
- item.description = insertInfo.description;
+ newItem.description = insertInfo.description;
}
if (insertInfo.loadInSidebar) {
PlacesUtils.annotations.setItemAnnotation(itemId,
- SIDEBAR_ANNO, insertInfo.loadInSidebar, 0,
+ BookmarkSyncUtils.SIDEBAR_ANNO, insertInfo.loadInSidebar, 0,
PlacesUtils.annotations.EXPIRE_NEVER,
SOURCE_SYNC);
- item.loadInSidebar = insertInfo.loadInSidebar;
+ newItem.loadInSidebar = insertInfo.loadInSidebar;
}
- return item;
+ return newItem;
});
// Determines the Sync record kind for an existing bookmark.
var getKindForItem = Task.async(function* (item) {
switch (item.type) {
case PlacesUtils.bookmarks.TYPE_FOLDER: {
let itemId = yield PlacesUtils.promiseItemId(item.guid);
let isLivemark = PlacesUtils.annotations.itemHasAnnotation(itemId,
@@ -522,18 +567,19 @@ function getTypeForKind(kind) {
// Determines if a livemark should be reinserted. Returns true if `updateInfo`
// specifies different feed or site URLs; false otherwise.
var shouldReinsertLivemark = Task.async(function* (updateInfo) {
let hasFeed = updateInfo.hasOwnProperty("feed");
let hasSite = updateInfo.hasOwnProperty("site");
if (!hasFeed && !hasSite) {
return false;
}
+ let guid = BookmarkSyncUtils.syncIdToGuid(updateInfo.syncId);
let livemark = yield PlacesUtils.livemarks.getLivemark({
- guid: updateInfo.guid,
+ guid,
});
if (hasFeed) {
let feedURI = PlacesUtils.toURI(updateInfo.feed);
if (!livemark.feedURI.equals(feedURI)) {
return true;
}
}
if (hasSite) {
@@ -544,119 +590,134 @@ var shouldReinsertLivemark = Task.async(
if (!livemark.siteURI || !siteURI.equals(livemark.siteURI)) {
return true;
}
}
return false;
});
var updateSyncBookmark = Task.async(function* (updateInfo) {
- let oldItem = yield PlacesUtils.bookmarks.fetch(updateInfo.guid);
- if (!oldItem) {
- throw new Error(`Bookmark with GUID ${updateInfo.guid} does not exist`);
+ let guid = BookmarkSyncUtils.syncIdToGuid(updateInfo.syncId);
+ let oldBookmarkItem = yield PlacesUtils.bookmarks.fetch(guid);
+ if (!oldBookmarkItem) {
+ throw new Error(`Bookmark with sync ID ${
+ updateInfo.syncId} does not exist`);
}
let shouldReinsert = false;
- let oldKind = yield getKindForItem(oldItem);
+ let oldKind = yield getKindForItem(oldBookmarkItem);
if (updateInfo.hasOwnProperty("kind") && updateInfo.kind != oldKind) {
// If the item's aren't the same kind, we can't update the record;
// we must remove and reinsert.
shouldReinsert = true;
- BookmarkSyncLog.warn(`updateSyncBookmark: Local ${
- oldItem.guid} kind = (${oldKind}); remote ${
- updateInfo.guid} kind = ${updateInfo.kind}. Deleting and recreating`);
+ if (BookmarkSyncLog.level <= Log.Level.Warn) {
+ let oldSyncId = BookmarkSyncUtils.guidToSyncId(oldBookmarkItem.guid);
+ BookmarkSyncLog.warn(`updateSyncBookmark: Local ${
+ oldSyncId} kind = ${oldKind}; remote ${
+ updateInfo.syncId} kind = ${
+ updateInfo.kind}. Deleting and recreating`);
+ }
} else if (oldKind == BookmarkSyncUtils.KINDS.LIVEMARK) {
// Similarly, if we're changing a livemark's site or feed URL, we need to
// reinsert.
shouldReinsert = yield shouldReinsertLivemark(updateInfo);
- if (shouldReinsert) {
+ if (BookmarkSyncLog.level <= Log.Level.Debug) {
+ let oldSyncId = BookmarkSyncUtils.guidToSyncId(oldBookmarkItem.guid);
BookmarkSyncLog.debug(`updateSyncBookmark: Local ${
- oldItem.guid} and remote ${
- updateInfo.guid} livemarks have different URLs`);
+ oldSyncId} and remote ${
+ updateInfo.syncId} livemarks have different URLs`);
}
}
+
if (shouldReinsert) {
- delete updateInfo.source;
- let newItem = validateNewBookmark(updateInfo);
+ let newInfo = validateNewBookmark(updateInfo);
yield PlacesUtils.bookmarks.remove({
- guid: oldItem.guid,
+ guid,
source: SOURCE_SYNC,
});
// A reinsertion likely indicates a confused client, since there aren't
// public APIs for changing livemark URLs or an item's kind (e.g., turning
// a folder into a separator while preserving its annos and position).
// This might be a good case to repair later; for now, we assume Sync has
// passed a complete record for the new item, and don't try to merge
- // `oldItem` with `updateInfo`.
- return insertSyncBookmark(newItem);
+ // `oldBookmarkItem` with `updateInfo`.
+ return insertSyncBookmark(newInfo);
}
- let isOrphan = false, requestedParentGuid;
- if (updateInfo.hasOwnProperty("parentGuid")) {
- requestedParentGuid = updateInfo.parentGuid;
- if (requestedParentGuid != oldItem.parentGuid) {
- let oldId = yield PlacesUtils.promiseItemId(oldItem.guid);
+ let isOrphan = false, requestedParentSyncId;
+ if (updateInfo.hasOwnProperty("parentSyncId")) {
+ requestedParentSyncId = updateInfo.parentSyncId;
+ let oldParentSyncId =
+ BookmarkSyncUtils.guidToSyncId(oldBookmarkItem.parentGuid);
+ if (requestedParentSyncId != oldParentSyncId) {
+ let oldId = yield PlacesUtils.promiseItemId(oldBookmarkItem.guid);
if (PlacesUtils.isRootItem(oldId)) {
throw new Error(`Cannot move Places root ${oldId}`);
}
+ let requestedParentGuid =
+ BookmarkSyncUtils.syncIdToGuid(requestedParentSyncId);
isOrphan = yield GUIDMissing(requestedParentGuid);
if (!isOrphan) {
BookmarkSyncLog.debug(`updateSyncBookmark: Item ${
- updateInfo.guid} is not an orphan`);
+ updateInfo.syncId} is not an orphan`);
} else {
// Don't move the item if the new parent doesn't exist. Instead, mark
// the item as an orphan. We'll annotate it with its real parent after
// updating.
BookmarkSyncLog.trace(`updateSyncBookmark: Item ${
- updateInfo.guid} is an orphan: could not find parent ${
- requestedParentGuid}`);
- delete updateInfo.parentGuid;
+ updateInfo.syncId} is an orphan: could not find parent ${
+ requestedParentSyncId}`);
+ delete updateInfo.parentSyncId;
}
- // If we're reparenting the item, pass the default index so that
- // `PlacesUtils.bookmarks.update` doesn't throw. Sync will reorder
- // children at the end of the sync.
- updateInfo.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
} else {
- // `PlacesUtils.bookmarks.update` requires us to specify an index if we
- // pass a parent, so we remove the parent if it's the same.
- delete updateInfo.parentGuid;
+ // If the parent is the same, just omit it so that `update` doesn't do
+ // extra work.
+ delete updateInfo.parentSyncId;
}
}
updateInfo = yield updateTagQueryFolder(updateInfo);
- let newItem = shouldUpdateBookmark(updateInfo) ?
- yield PlacesUtils.bookmarks.update(updateInfo) : oldItem;
- let itemId = yield PlacesUtils.promiseItemId(newItem.guid);
+ let bookmarkInfo = syncBookmarkToPlacesBookmark(updateInfo);
+ let newBookmarkItem = shouldUpdateBookmark(bookmarkInfo) ?
+ yield PlacesUtils.bookmarks.update(bookmarkInfo) :
+ oldBookmarkItem;
+ let newItem = yield updateBookmarkMetadata(oldBookmarkItem, newBookmarkItem,
+ updateInfo);
- newItem = yield updateBookmarkMetadata(itemId, oldItem, newItem, updateInfo);
-
- // If the item is an orphan, annotate it with its real parent ID.
+ // If the item is an orphan, annotate it with its real parent sync ID.
if (isOrphan) {
- yield annotateOrphan(newItem, requestedParentGuid);
+ yield annotateOrphan(newItem, requestedParentSyncId);
}
// Reparent all orphans that expect this folder as the parent.
yield reparentOrphans(newItem);
return newItem;
});
-var updateBookmarkMetadata = Task.async(function* (itemId, oldItem, newItem, updateInfo) {
+// Updates tags, keywords, and annotations for an existing bookmark. Returns a
+// Sync bookmark object.
+var updateBookmarkMetadata = Task.async(function* (oldBookmarkItem,
+ newBookmarkItem,
+ updateInfo) {
+ let itemId = yield PlacesUtils.promiseItemId(newBookmarkItem.guid);
+ let newItem = yield placesBookmarkToSyncBookmark(newBookmarkItem);
+
try {
- newItem.tags = yield tagItem(newItem, updateInfo.tags);
+ newItem.tags = yield tagItem(newBookmarkItem, updateInfo.tags);
} catch (ex) {
BookmarkSyncLog.warn(`updateBookmarkMetadata: Error tagging item ${
- newItem.guid}`, ex);
+ updateInfo.syncId}`, ex);
}
if (updateInfo.hasOwnProperty("keyword")) {
// Unconditionally remove the old keyword.
let entry = yield PlacesUtils.keywords.fetch({
- url: oldItem.url.href,
+ url: oldBookmarkItem.url.href,
});
if (entry) {
yield PlacesUtils.keywords.remove({
keyword: entry.keyword,
source: SOURCE_SYNC,
});
}
if (updateInfo.keyword) {
@@ -667,42 +728,42 @@ var updateBookmarkMetadata = Task.async(
});
}
newItem.keyword = updateInfo.keyword;
}
if (updateInfo.hasOwnProperty("description")) {
if (updateInfo.description) {
PlacesUtils.annotations.setItemAnnotation(itemId,
- DESCRIPTION_ANNO, updateInfo.description, 0,
+ BookmarkSyncUtils.DESCRIPTION_ANNO, updateInfo.description, 0,
PlacesUtils.annotations.EXPIRE_NEVER,
SOURCE_SYNC);
} else {
PlacesUtils.annotations.removeItemAnnotation(itemId,
- DESCRIPTION_ANNO, SOURCE_SYNC);
+ BookmarkSyncUtils.DESCRIPTION_ANNO, SOURCE_SYNC);
}
newItem.description = updateInfo.description;
}
if (updateInfo.hasOwnProperty("loadInSidebar")) {
if (updateInfo.loadInSidebar) {
PlacesUtils.annotations.setItemAnnotation(itemId,
- SIDEBAR_ANNO, updateInfo.loadInSidebar, 0,
+ BookmarkSyncUtils.SIDEBAR_ANNO, updateInfo.loadInSidebar, 0,
PlacesUtils.annotations.EXPIRE_NEVER,
SOURCE_SYNC);
} else {
PlacesUtils.annotations.removeItemAnnotation(itemId,
- SIDEBAR_ANNO, SOURCE_SYNC);
+ BookmarkSyncUtils.SIDEBAR_ANNO, SOURCE_SYNC);
}
newItem.loadInSidebar = updateInfo.loadInSidebar;
}
if (updateInfo.hasOwnProperty("query")) {
PlacesUtils.annotations.setItemAnnotation(itemId,
- SMART_BOOKMARKS_ANNO, updateInfo.query, 0,
+ BookmarkSyncUtils.SMART_BOOKMARKS_ANNO, updateInfo.query, 0,
PlacesUtils.annotations.EXPIRE_NEVER,
SOURCE_SYNC);
newItem.query = updateInfo.query;
}
return newItem;
});
@@ -711,33 +772,24 @@ var setGuid = Task.async(function* (db,
WHERE id = :itemId`, { newGuid, itemId });
PlacesUtils.invalidateCachedGuidFor(itemId);
return newGuid;
});
function validateNewBookmark(info) {
let insertInfo = validateSyncBookmarkObject(info,
{ kind: { required: true }
- // Explicitly prevent callers from passing types.
- , type: { validIf: () => false }
- // Because Sync applies bookmarks as it receives them, it doesn't pass
- // an index. Instead, Sync calls `BookmarkSyncUtils.order` at the end of
- // the sync, which orders children according to their placement in the
- // `BookmarkFolder::children` array.
- , index: { validIf: () => false }
- // This module always uses `nsINavBookmarksService::SOURCE_SYNC`.
- , source: { validIf: () => false }
- , guid: { required: true }
+ , syncId: { required: true }
, url: { requiredIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
, BookmarkSyncUtils.KINDS.MICROSUMMARY
, BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind)
, validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
, BookmarkSyncUtils.KINDS.MICROSUMMARY
, BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) }
- , parentGuid: { required: true }
+ , parentSyncId: { required: true }
, title: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
, BookmarkSyncUtils.KINDS.MICROSUMMARY
, BookmarkSyncUtils.KINDS.QUERY
, BookmarkSyncUtils.KINDS.FOLDER
, BookmarkSyncUtils.KINDS.LIVEMARK ].includes(b.kind) }
, query: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.QUERY }
, folder: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.QUERY }
, tags: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
@@ -747,29 +799,23 @@ function validateNewBookmark(info) {
, BookmarkSyncUtils.KINDS.MICROSUMMARY
, BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) }
, description: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
, BookmarkSyncUtils.KINDS.MICROSUMMARY
, BookmarkSyncUtils.KINDS.QUERY
, BookmarkSyncUtils.KINDS.FOLDER
, BookmarkSyncUtils.KINDS.LIVEMARK ].includes(b.kind) }
, loadInSidebar: { validIf: b => [ BookmarkSyncUtils.KINDS.BOOKMARK
- , BookmarkSyncUtils.KINDS.MICROSUMMARY ].includes(b.kind) }
+ , BookmarkSyncUtils.KINDS.MICROSUMMARY
+ , BookmarkSyncUtils.KINDS.QUERY ].includes(b.kind) }
, feed: { requiredIf: b => b.kind == BookmarkSyncUtils.KINDS.LIVEMARK
, validIf: b => b.kind == BookmarkSyncUtils.KINDS.LIVEMARK }
, site: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.LIVEMARK }
});
- // Sync doesn't track modification times, so use the default.
- let time = new Date();
- insertInfo.dateAdded = insertInfo.lastModified = time;
-
- insertInfo.type = getTypeForKind(insertInfo.kind);
- insertInfo.source = SOURCE_SYNC;
-
return insertInfo;
}
function findAnnoItems(anno, val) {
let annos = PlacesUtils.annotations;
return annos.getItemsWithAnnotation(anno, {}).filter(id =>
annos.getItemAnnotation(id, anno) == val);
}
@@ -790,32 +836,22 @@ var tagItem = Task.async(function (item,
PlacesUtils.tagging.untagURI(bookmarkURI, null, SOURCE_SYNC);
PlacesUtils.tagging.tagURI(bookmarkURI, newTags, SOURCE_SYNC);
PlacesUtils.tagging.untagURI(dummyURI, null, SOURCE_SYNC);
return newTags;
});
// `PlacesUtils.bookmarks.update` checks if we've supplied enough properties,
-// but doesn't know about additional Sync record properties. We check this to
-// avoid having it throw in case we only pass Sync-specific properties, like
-// `{ guid, tags }`.
-function shouldUpdateBookmark(updateInfo) {
- let propsToUpdate = 0;
- for (let prop in PlacesUtils.BOOKMARK_VALIDATORS) {
- if (!updateInfo.hasOwnProperty(prop)) {
- continue;
- }
- // We should have at least one more property, in addition to `guid` and
- // `source`.
- if (++propsToUpdate >= 3) {
- return true;
- }
- }
- return false;
+// but doesn't know about additional livemark properties. We check this to avoid
+// having it throw in case we only pass properties like `{ guid, feedURI }`.
+function shouldUpdateBookmark(bookmarkInfo) {
+ return bookmarkInfo.hasOwnProperty("parentGuid") ||
+ bookmarkInfo.hasOwnProperty("title") ||
+ bookmarkInfo.hasOwnProperty("url");
}
var getTagFolder = Task.async(function* (tag) {
let db = yield PlacesUtils.promiseDBConnection();
let results = yield db.executeCached(`SELECT id FROM moz_bookmarks
WHERE parent = :tagsFolder AND title = :tag LIMIT 1`,
{ tagsFolder: PlacesUtils.bookmarks.tagsFolder, tag });
return results.length ? results[0].getResultByName("id") : null;
@@ -829,8 +865,106 @@ var getOrCreateTagFolder = Task.async(fu
// Create the tag if it doesn't exist.
let item = yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.tagsGuid,
title: tag,
});
return PlacesUtils.promiseItemId(item.guid);
});
+
+// Converts a Places bookmark or livemark to a Sync bookmark. This function
+// maps Places GUIDs to sync IDs and filters out extra Places properties like
+// date added, last modified, and index.
+var placesBookmarkToSyncBookmark = Task.async(function* (bookmarkItem) {
+ let item = {};
+
+ for (let prop in bookmarkItem) {
+ switch (prop) {
+ // Sync IDs are identical to Places GUIDs for all items except roots.
+ case "guid":
+ item.syncId = BookmarkSyncUtils.guidToSyncId(bookmarkItem.guid);
+ break;
+
+ case "parentGuid":
+ item.parentSyncId =
+ BookmarkSyncUtils.guidToSyncId(bookmarkItem.parentGuid);
+ break;
+
+ // Sync uses kinds instead of types, which distinguish between folders,
+ // livemarks, bookmarks, and queries.
+ case "type":
+ item.kind = yield getKindForItem(bookmarkItem);
+ break;
+
+ case "title":
+ case "url":
+ item[prop] = bookmarkItem[prop];
+ break;
+
+ // Livemark objects contain additional properties. The feed URL is
+ // required; the site URL is optional.
+ case "feedURI":
+ item.feed = new URL(bookmarkItem.feedURI.spec);
+ break;
+
+ case "siteURI":
+ if (bookmarkItem.siteURI) {
+ item.site = new URL(bookmarkItem.siteURI.spec);
+ }
+ break;
+ }
+ }
+
+ return item;
+});
+
+// Converts a Sync bookmark object to a Places bookmark or livemark object.
+// This function maps sync IDs to Places GUIDs, and filters out extra Sync
+// properties like keywords, tags, and descriptions. Returns an object that can
+// be passed to `PlacesUtils.livemarks.addLivemark` or
+// `PlacesUtils.bookmarks.{insert, update}`.
+function syncBookmarkToPlacesBookmark(info) {
+ let bookmarkInfo = {
+ source: SOURCE_SYNC,
+ };
+
+ for (let prop in info) {
+ switch (prop) {
+ case "kind":
+ bookmarkInfo.type = getTypeForKind(info.kind);
+ break;
+
+ // Convert sync IDs to Places GUIDs for roots.
+ case "syncId":
+ bookmarkInfo.guid = BookmarkSyncUtils.syncIdToGuid(info.syncId);
+ break;
+
+ case "parentSyncId":
+ bookmarkInfo.parentGuid =
+ BookmarkSyncUtils.syncIdToGuid(info.parentSyncId);
+ // Instead of providing an index, Sync reorders children at the end of
+ // the sync using `BookmarkSyncUtils.order`. We explicitly specify the
+ // default index here to prevent `PlacesUtils.bookmarks.update` and
+ // `PlacesUtils.livemarks.addLivemark` from throwing.
+ bookmarkInfo.index = PlacesUtils.bookmarks.DEFAULT_INDEX;
+ break;
+
+ case "title":
+ case "url":
+ bookmarkInfo[prop] = info[prop];
+ break;
+
+ // Livemark-specific properties.
+ case "feed":
+ bookmarkInfo.feedURI = PlacesUtils.toURI(info.feed);
+ break;
+
+ case "site":
+ if (info.site) {
+ bookmarkInfo.siteURI = PlacesUtils.toURI(info.site);
+ }
+ break;
+ }
+ }
+
+ return bookmarkInfo;
+}
--- a/toolkit/components/places/tests/unit/test_sync_utils.js
+++ b/toolkit/components/places/tests/unit/test_sync_utils.js
@@ -108,16 +108,21 @@ var populateTree = Task.async(function*
}
guids[item.title] = guid;
}
return guids;
});
+var syncIdToId = Task.async(function* syncIdToId(syncId) {
+ let guid = yield PlacesSyncUtils.bookmarks.syncIdToGuid(syncId);
+ return PlacesUtils.promiseItemId(guid);
+});
+
add_task(function* test_order() {
do_print("Insert some bookmarks");
let guids = yield populateTree(PlacesUtils.bookmarks.menuGuid, {
kind: "bookmark",
title: "childBmk",
url: "http://getfirefox.com",
}, {
kind: "bookmark",
@@ -131,40 +136,40 @@ add_task(function* test_order() {
title: "siblingSep",
});
do_print("Reorder inserted bookmarks");
{
let order = [guids.siblingFolder, guids.siblingSep, guids.childBmk,
guids.siblingBmk];
yield PlacesSyncUtils.bookmarks.order(PlacesUtils.bookmarks.menuGuid, order);
- let childGuids = yield PlacesSyncUtils.bookmarks.fetchChildGuids(PlacesUtils.bookmarks.menuGuid);
- deepEqual(childGuids, order, "New bookmarks should be reordered according to array");
+ let childSyncIds = yield PlacesSyncUtils.bookmarks.fetchChildSyncIds(PlacesUtils.bookmarks.menuGuid);
+ deepEqual(childSyncIds, order, "New bookmarks should be reordered according to array");
}
do_print("Reorder with unspecified children");
{
yield PlacesSyncUtils.bookmarks.order(PlacesUtils.bookmarks.menuGuid, [
guids.siblingSep, guids.siblingBmk,
]);
- let childGuids = yield PlacesSyncUtils.bookmarks.fetchChildGuids(
+ let childSyncIds = yield PlacesSyncUtils.bookmarks.fetchChildSyncIds(
PlacesUtils.bookmarks.menuGuid);
- deepEqual(childGuids, [guids.siblingSep, guids.siblingBmk,
+ deepEqual(childSyncIds, [guids.siblingSep, guids.siblingBmk,
guids.siblingFolder, guids.childBmk],
"Unordered children should be moved to end");
}
do_print("Reorder with nonexistent children");
{
yield PlacesSyncUtils.bookmarks.order(PlacesUtils.bookmarks.menuGuid, [
guids.childBmk, makeGuid(), guids.siblingBmk, guids.siblingSep,
makeGuid(), guids.siblingFolder, makeGuid()]);
- let childGuids = yield PlacesSyncUtils.bookmarks.fetchChildGuids(
+ let childSyncIds = yield PlacesSyncUtils.bookmarks.fetchChildSyncIds(
PlacesUtils.bookmarks.menuGuid);
- deepEqual(childGuids, [guids.childBmk, guids.siblingBmk, guids.siblingSep,
+ deepEqual(childSyncIds, [guids.childBmk, guids.siblingBmk, guids.siblingSep,
guids.siblingFolder], "Nonexistent children should be ignored");
}
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_changeGuid_invalid() {
yield rejects(PlacesSyncUtils.bookmarks.changeGuid(makeGuid()),
@@ -192,141 +197,141 @@ add_task(function* test_changeGuid() {
equal(result, newGuid, "Should return new GUID");
equal(yield PlacesUtils.promiseItemId(newGuid), id, "Should map ID to new GUID");
yield rejects(PlacesUtils.promiseItemId(item.guid), "Should not map ID to old GUID");
equal(yield PlacesUtils.promiseItemGuid(id), newGuid, "Should map new GUID to ID");
});
add_task(function* test_order_roots() {
- let oldOrder = yield PlacesSyncUtils.bookmarks.fetchChildGuids(
+ let oldOrder = yield PlacesSyncUtils.bookmarks.fetchChildSyncIds(
PlacesUtils.bookmarks.rootGuid);
yield PlacesSyncUtils.bookmarks.order(PlacesUtils.bookmarks.rootGuid,
shuffle(oldOrder));
- let newOrder = yield PlacesSyncUtils.bookmarks.fetchChildGuids(
+ let newOrder = yield PlacesSyncUtils.bookmarks.fetchChildSyncIds(
PlacesUtils.bookmarks.rootGuid);
deepEqual(oldOrder, newOrder, "Should ignore attempts to reorder roots");
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_update_tags() {
do_print("Insert item without tags");
let item = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
url: "https://mozilla.org",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
});
do_print("Add tags");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: item.guid,
+ syncId: item.syncId,
tags: ["foo", "bar"],
});
deepEqual(updatedItem.tags, ["foo", "bar"], "Should return new tags");
assertURLHasTags("https://mozilla.org", ["bar", "foo"],
"Should set new tags for URL");
}
do_print("Add new tag, remove existing tag");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: item.guid,
+ syncId: item.syncId,
tags: ["foo", "baz"],
});
deepEqual(updatedItem.tags, ["foo", "baz"], "Should return updated tags");
assertURLHasTags("https://mozilla.org", ["baz", "foo"],
"Should update tags for URL");
assertTagForURLs("bar", [], "Should remove existing tag");
}
do_print("Tags with whitespace");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: item.guid,
+ syncId: item.syncId,
tags: [" leading", "trailing ", " baz ", " "],
});
deepEqual(updatedItem.tags, ["leading", "trailing", "baz"],
"Should return filtered tags");
assertURLHasTags("https://mozilla.org", ["baz", "leading", "trailing"],
"Should trim whitespace and filter blank tags");
}
do_print("Remove all tags");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: item.guid,
+ syncId: item.syncId,
tags: null,
});
deepEqual(updatedItem.tags, [], "Should return empty tag array");
assertURLHasTags("https://mozilla.org", [],
"Should remove all existing tags");
}
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_update_keyword() {
do_print("Insert item without keyword");
let item = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ parentSyncId: "menu",
url: "https://mozilla.org",
- guid: makeGuid(),
+ syncId: makeGuid(),
});
do_print("Add item keyword");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: item.guid,
+ syncId: item.syncId,
keyword: "moz",
});
equal(updatedItem.keyword, "moz", "Should return new keyword");
let entryByKeyword = yield PlacesUtils.keywords.fetch("moz");
equal(entryByKeyword.url.href, "https://mozilla.org/",
"Should set new keyword for URL");
let entryByURL = yield PlacesUtils.keywords.fetch({
url: "https://mozilla.org",
});
equal(entryByURL.keyword, "moz", "Looking up URL should return new keyword");
}
do_print("Change item keyword");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: item.guid,
+ syncId: item.syncId,
keyword: "m",
});
equal(updatedItem.keyword, "m", "Should return updated keyword");
let newEntry = yield PlacesUtils.keywords.fetch("m");
equal(newEntry.url.href, "https://mozilla.org/", "Should update keyword for URL");
let oldEntry = yield PlacesUtils.keywords.fetch("moz");
ok(!oldEntry, "Should remove old keyword");
}
do_print("Remove existing keyword");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: item.guid,
+ syncId: item.syncId,
keyword: null,
});
ok(!updatedItem.keyword,
"Should not include removed keyword in properties");
let entry = yield PlacesUtils.keywords.fetch({
url: "https://mozilla.org",
});
ok(!entry, "Should remove new keyword from URL");
}
do_print("Remove keyword for item without keyword");
{
yield PlacesSyncUtils.bookmarks.update({
- guid: item.guid,
+ syncId: item.syncId,
keyword: null,
});
let entry = yield PlacesUtils.keywords.fetch({
url: "https://mozilla.org",
});
ok(!entry,
"Removing keyword for URL without existing keyword should succeed");
}
@@ -345,181 +350,184 @@ add_task(function* test_update_annos() {
url: "https://example.com",
description: "Bookmark description",
loadInSidebar: true,
});
do_print("Add folder description");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: guids.folder,
+ syncId: guids.folder,
description: "Folder description",
});
equal(updatedItem.description, "Folder description",
"Should return new description");
- let id = yield PlacesUtils.promiseItemId(updatedItem.guid);
+ let id = yield syncIdToId(updatedItem.syncId);
equal(PlacesUtils.annotations.getItemAnnotation(id, DESCRIPTION_ANNO),
"Folder description", "Should set description anno");
}
do_print("Clear folder description");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: guids.folder,
+ syncId: guids.folder,
description: null,
});
ok(!updatedItem.description, "Should not return cleared description");
- let id = yield PlacesUtils.promiseItemId(updatedItem.guid);
+ let id = yield syncIdToId(updatedItem.syncId);
ok(!PlacesUtils.annotations.itemHasAnnotation(id, DESCRIPTION_ANNO),
"Should remove description anno");
}
do_print("Add bookmark sidebar anno");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: guids.bmk,
+ syncId: guids.bmk,
loadInSidebar: true,
});
ok(updatedItem.loadInSidebar, "Should return sidebar anno");
- let id = yield PlacesUtils.promiseItemId(updatedItem.guid);
+ let id = yield syncIdToId(updatedItem.syncId);
ok(PlacesUtils.annotations.itemHasAnnotation(id, LOAD_IN_SIDEBAR_ANNO),
"Should set sidebar anno for existing bookmark");
}
do_print("Clear bookmark sidebar anno");
{
let updatedItem = yield PlacesSyncUtils.bookmarks.update({
- guid: guids.bmk,
+ syncId: guids.bmk,
loadInSidebar: false,
});
ok(!updatedItem.loadInSidebar, "Should not return cleared sidebar anno");
- let id = yield PlacesUtils.promiseItemId(updatedItem.guid);
+ let id = yield syncIdToId(updatedItem.syncId);
ok(!PlacesUtils.annotations.itemHasAnnotation(id, LOAD_IN_SIDEBAR_ANNO),
"Should clear sidebar anno for existing bookmark");
}
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_update_move_root() {
do_print("Move root to same parent");
{
// This should be a no-op.
let sameRoot = yield PlacesSyncUtils.bookmarks.update({
- guid: PlacesUtils.bookmarks.menuGuid,
- parentGuid: PlacesUtils.bookmarks.rootGuid,
+ syncId: "menu",
+ parentSyncId: "places",
});
- equal(sameRoot.guid, PlacesUtils.bookmarks.menuGuid,
+ equal(sameRoot.syncId, "menu",
"Menu root GUID should not change");
- equal(sameRoot.parentGuid, PlacesUtils.bookmarks.rootGuid,
+ equal(sameRoot.parentSyncId, "places",
"Parent Places root GUID should not change");
}
do_print("Try reparenting root");
yield rejects(PlacesSyncUtils.bookmarks.update({
- guid: PlacesUtils.bookmarks.menuGuid,
- parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ syncId: "menu",
+ parentSyncId: "toolbar",
}));
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_insert() {
do_print("Insert bookmark");
{
let item = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
url: "https://example.org",
});
- equal(item.type, PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ let { type } = yield PlacesUtils.bookmarks.fetch({ guid: item.syncId });
+ equal(type, PlacesUtils.bookmarks.TYPE_BOOKMARK,
"Bookmark should have correct type");
}
do_print("Insert query");
{
let item = yield PlacesSyncUtils.bookmarks.insert({
kind: "query",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
url: "place:terms=term&folder=TOOLBAR&queryType=1",
folder: "Saved search",
});
- equal(item.type, PlacesUtils.bookmarks.TYPE_BOOKMARK,
+ let { type } = yield PlacesUtils.bookmarks.fetch({ guid: item.syncId });
+ equal(type, PlacesUtils.bookmarks.TYPE_BOOKMARK,
"Queries should be stored as bookmarks");
}
do_print("Insert folder");
{
let item = yield PlacesSyncUtils.bookmarks.insert({
kind: "folder",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
title: "New folder",
});
- equal(item.type, PlacesUtils.bookmarks.TYPE_FOLDER,
+ let { type } = yield PlacesUtils.bookmarks.fetch({ guid: item.syncId });
+ equal(type, PlacesUtils.bookmarks.TYPE_FOLDER,
"Folder should have correct type");
}
do_print("Insert separator");
{
let item = yield PlacesSyncUtils.bookmarks.insert({
kind: "separator",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
});
- equal(item.type, PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ let { type } = yield PlacesUtils.bookmarks.fetch({ guid: item.syncId });
+ equal(type, PlacesUtils.bookmarks.TYPE_SEPARATOR,
"Separator should have correct type");
}
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_insert_livemark() {
let { server, site, stopServer } = makeLivemarkServer();
try {
do_print("Insert livemark with feed URL");
{
let livemark = yield PlacesSyncUtils.bookmarks.insert({
kind: "livemark",
- guid: makeGuid(),
+ syncId: makeGuid(),
feed: site + "/feed/1",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ parentSyncId: "menu",
});
let bmk = yield PlacesUtils.bookmarks.fetch({
- guid: livemark.guid,
+ guid: yield PlacesSyncUtils.bookmarks.syncIdToGuid(livemark.syncId),
})
equal(bmk.type, PlacesUtils.bookmarks.TYPE_FOLDER,
"Livemarks should be stored as folders");
}
- let livemarkGuid;
+ let livemarkSyncId;
do_print("Insert livemark with site and feed URLs");
{
let livemark = yield PlacesSyncUtils.bookmarks.insert({
kind: "livemark",
- guid: makeGuid(),
+ syncId: makeGuid(),
site,
feed: site + "/feed/1",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ parentSyncId: "menu",
});
- livemarkGuid = livemark.guid;
-
+ livemarkSyncId = livemark.syncId;
}
do_print("Try inserting livemark into livemark");
{
let livemark = yield PlacesSyncUtils.bookmarks.insert({
kind: "livemark",
- guid: makeGuid(),
+ syncId: makeGuid(),
site,
feed: site + "/feed/1",
- parentGuid: livemarkGuid,
+ parentSyncId: livemarkSyncId,
});
ok(!livemark, "Should not insert livemark as child of livemark");
}
} finally {
yield stopServer();
}
yield PlacesUtils.bookmarks.eraseEverything();
@@ -536,35 +544,35 @@ add_task(function* test_update_livemark(
let livemark = yield PlacesUtils.livemarks.addLivemark({
parentGuid: PlacesUtils.bookmarks.menuGuid,
feedURI,
siteURI: uri(site),
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
});
yield PlacesSyncUtils.bookmarks.update({
- guid: livemark.guid,
+ syncId: livemark.guid,
feed: feedURI,
});
// `nsLivemarkService` returns references to `Livemark` instances, so we
// can compare them with `==` to make sure they haven't been replaced.
equal(yield PlacesUtils.livemarks.getLivemark({
guid: livemark.guid,
}), livemark, "Livemark with same feed URL should not be replaced");
yield PlacesSyncUtils.bookmarks.update({
- guid: livemark.guid,
+ syncId: livemark.guid,
site,
});
equal(yield PlacesUtils.livemarks.getLivemark({
guid: livemark.guid,
}), livemark, "Livemark with same site URL should not be replaced");
yield PlacesSyncUtils.bookmarks.update({
- guid: livemark.guid,
+ syncId: livemark.guid,
feed: feedURI,
site,
});
equal(yield PlacesUtils.livemarks.getLivemark({
guid: livemark.guid,
}), livemark, "Livemark with same feed and site URLs should not be replaced");
}
@@ -574,164 +582,164 @@ add_task(function* test_update_livemark(
parentGuid: PlacesUtils.bookmarks.menuGuid,
feedURI,
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
});
// Since we're reinserting, we need to pass all properties required
// for a new livemark. `update` won't merge the old and new ones.
yield rejects(PlacesSyncUtils.bookmarks.update({
- guid: livemark.guid,
+ syncId: livemark.guid,
feed: site + "/feed/2",
}), "Reinserting livemark with changed feed URL requires full record");
let newLivemark = yield PlacesSyncUtils.bookmarks.update({
kind: "livemark",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
- guid: livemark.guid,
+ parentSyncId: "menu",
+ syncId: livemark.guid,
feed: site + "/feed/2",
});
- equal(newLivemark.guid, livemark.guid,
+ equal(newLivemark.syncId, livemark.guid,
"GUIDs should match for reinserted livemark with changed feed URL");
- equal(newLivemark.feedURI.spec, site + "/feed/2",
+ equal(newLivemark.feed.href, site + "/feed/2",
"Reinserted livemark should have changed feed URI");
}
do_print("Add livemark site URL");
{
let livemark = yield PlacesUtils.livemarks.addLivemark({
parentGuid: PlacesUtils.bookmarks.menuGuid,
feedURI,
});
ok(livemark.feedURI.equals(feedURI), "Livemark feed URI should match");
ok(!livemark.siteURI, "Livemark should not have site URI");
yield rejects(PlacesSyncUtils.bookmarks.update({
- guid: livemark.guid,
+ syncId: livemark.guid,
site,
}), "Reinserting livemark with new site URL requires full record");
let newLivemark = yield PlacesSyncUtils.bookmarks.update({
kind: "livemark",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
- guid: livemark.guid,
+ parentSyncId: "menu",
+ syncId: livemark.guid,
feed: feedURI,
site,
});
notEqual(newLivemark, livemark,
"Livemark with new site URL should replace old livemark");
- equal(newLivemark.guid, livemark.guid,
+ equal(newLivemark.syncId, livemark.guid,
"GUIDs should match for reinserted livemark with new site URL");
- equal(newLivemark.siteURI.spec, site + "/",
+ equal(newLivemark.site.href, site + "/",
"Reinserted livemark should have new site URI");
- ok(newLivemark.feedURI.equals(feedURI),
+ equal(newLivemark.feed.href, feedURI.spec,
"Reinserted livemark with new site URL should have same feed URI");
}
do_print("Remove livemark site URL");
{
let livemark = yield PlacesUtils.livemarks.addLivemark({
parentGuid: PlacesUtils.bookmarks.menuGuid,
feedURI,
siteURI: uri(site),
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
});
yield rejects(PlacesSyncUtils.bookmarks.update({
- guid: livemark.guid,
+ syncId: livemark.guid,
site: null,
}), "Reinserting livemark witout site URL requires full record");
let newLivemark = yield PlacesSyncUtils.bookmarks.update({
kind: "livemark",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
- guid: livemark.guid,
+ parentSyncId: "menu",
+ syncId: livemark.guid,
feed: feedURI,
site: null,
});
notEqual(newLivemark, livemark,
"Livemark without site URL should replace old livemark");
- equal(newLivemark.guid, livemark.guid,
+ equal(newLivemark.syncId, livemark.guid,
"GUIDs should match for reinserted livemark without site URL");
- ok(!newLivemark.siteURI, "Reinserted livemark should not have site URI");
+ ok(!newLivemark.site, "Reinserted livemark should not have site URI");
}
do_print("Change livemark site URL");
{
let livemark = yield PlacesUtils.livemarks.addLivemark({
parentGuid: PlacesUtils.bookmarks.menuGuid,
feedURI,
siteURI: uri(site),
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
});
yield rejects(PlacesSyncUtils.bookmarks.update({
- guid: livemark.guid,
+ syncId: livemark.guid,
site: site + "/new",
}), "Reinserting livemark with changed site URL requires full record");
let newLivemark = yield PlacesSyncUtils.bookmarks.update({
kind: "livemark",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
- guid: livemark.guid,
+ parentSyncId: "menu",
+ syncId: livemark.guid,
feed:feedURI,
site: site + "/new",
});
notEqual(newLivemark, livemark,
"Livemark with changed site URL should replace old livemark");
- equal(newLivemark.guid, livemark.guid,
+ equal(newLivemark.syncId, livemark.guid,
"GUIDs should match for reinserted livemark with changed site URL");
- equal(newLivemark.siteURI.spec, site + "/new",
+ equal(newLivemark.site.href, site + "/new",
"Reinserted livemark should have changed site URI");
}
// Livemarks are stored as folders, but have different kinds. We should
// remove the folder and insert a livemark with the same GUID instead of
// trying to update the folder in-place.
do_print("Replace folder with livemark");
{
let folder = yield PlacesUtils.bookmarks.insert({
type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: PlacesUtils.bookmarks.menuGuid,
title: "Plain folder",
});
let livemark = yield PlacesSyncUtils.bookmarks.update({
kind: "livemark",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
- guid: folder.guid,
+ parentSyncId: "menu",
+ syncId: folder.guid,
feed: feedURI,
});
- equal(livemark.guid, folder.guid,
+ equal(livemark.guid, folder.syncId,
"Livemark should have same GUID as replaced folder");
}
} finally {
yield stopServer();
}
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_insert_tags() {
let newItems = yield Promise.all([{
kind: "bookmark",
url: "https://example.com",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
tags: ["foo", "bar"],
}, {
kind: "bookmark",
url: "https://example.org",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ syncId: makeGuid(),
+ parentSyncId: "toolbar",
tags: ["foo", "baz"],
}, {
kind: "query",
url: "place:queryType=1&sort=12&maxResults=10",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ syncId: makeGuid(),
+ parentSyncId: "toolbar",
folder: "bar",
tags: ["baz", "qux"],
title: "bar",
}].map(info => PlacesSyncUtils.bookmarks.insert(info)));
assertTagForURLs("foo", ["https://example.com/", "https://example.org/"],
"2 URLs with new tag");
assertTagForURLs("bar", ["https://example.com/"], "1 URL with existing tag");
@@ -744,31 +752,31 @@ add_task(function* test_insert_tags() {
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_insert_tags_whitespace() {
do_print("Untrimmed and blank tags");
let taggedBlanks = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
url: "https://example.org",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
tags: [" untrimmed ", " ", "taggy"],
});
deepEqual(taggedBlanks.tags, ["untrimmed", "taggy"],
"Should not return empty tags");
assertURLHasTags("https://example.org/", ["taggy", "untrimmed"],
"Should set trimmed tags and ignore dupes");
do_print("Dupe tags");
let taggedDupes = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
url: "https://example.net",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ syncId: makeGuid(),
+ parentSyncId: "toolbar",
tags: [" taggy", "taggy ", " taggy ", "taggy"],
});
deepEqual(taggedDupes.tags, ["taggy", "taggy", "taggy", "taggy"],
"Should return trimmed and dupe tags");
assertURLHasTags("https://example.net/", ["taggy"],
"Should ignore dupes when setting tags");
assertTagForURLs("taggy", ["https://example.net/", "https://example.org/"],
@@ -777,119 +785,119 @@ add_task(function* test_insert_tags_whit
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_insert_keyword() {
do_print("Insert item with new keyword");
{
let bookmark = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ parentSyncId: "menu",
url: "https://example.com",
keyword: "moz",
- guid: makeGuid(),
+ syncId: makeGuid(),
});
let entry = yield PlacesUtils.keywords.fetch("moz");
equal(entry.url.href, "https://example.com/",
"Should add keyword for item");
}
do_print("Insert item with existing keyword");
{
let bookmark = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ parentSyncId: "menu",
url: "https://mozilla.org",
keyword: "moz",
- guid: makeGuid(),
+ syncId: makeGuid(),
});
let entry = yield PlacesUtils.keywords.fetch("moz");
equal(entry.url.href, "https://mozilla.org/",
"Should reassign keyword to new item");
}
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_insert_annos() {
do_print("Bookmark with description");
let descBmk = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
url: "https://example.com",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
description: "Bookmark description",
});
{
equal(descBmk.description, "Bookmark description",
"Should return new bookmark description");
- let id = yield PlacesUtils.promiseItemId(descBmk.guid);
+ let id = yield syncIdToId(descBmk.syncId);
equal(PlacesUtils.annotations.getItemAnnotation(id, DESCRIPTION_ANNO),
"Bookmark description", "Should set new bookmark description");
}
do_print("Folder with description");
let descFolder = yield PlacesSyncUtils.bookmarks.insert({
kind: "folder",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
description: "Folder description",
});
{
equal(descFolder.description, "Folder description",
"Should return new folder description");
- let id = yield PlacesUtils.promiseItemId(descFolder.guid);
+ let id = yield syncIdToId(descFolder.syncId);
equal(PlacesUtils.annotations.getItemAnnotation(id, DESCRIPTION_ANNO),
"Folder description", "Should set new folder description");
}
do_print("Bookmark with sidebar anno");
let sidebarBmk = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
url: "https://example.com",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
loadInSidebar: true,
});
{
ok(sidebarBmk.loadInSidebar, "Should return sidebar anno for new bookmark");
- let id = yield PlacesUtils.promiseItemId(sidebarBmk.guid);
+ let id = yield syncIdToId(sidebarBmk.syncId);
ok(PlacesUtils.annotations.itemHasAnnotation(id, LOAD_IN_SIDEBAR_ANNO),
"Should set sidebar anno for new bookmark");
}
do_print("Bookmark without sidebar anno");
let noSidebarBmk = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
url: "https://example.org",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ syncId: makeGuid(),
+ parentSyncId: "toolbar",
loadInSidebar: false,
});
{
ok(!noSidebarBmk.loadInSidebar,
"Should not return sidebar anno for new bookmark");
- let id = yield PlacesUtils.promiseItemId(noSidebarBmk.guid);
+ let id = yield syncIdToId(noSidebarBmk.syncId);
ok(!PlacesUtils.annotations.itemHasAnnotation(id, LOAD_IN_SIDEBAR_ANNO),
"Should not set sidebar anno for new bookmark");
}
yield PlacesUtils.bookmarks.eraseEverything();
});
add_task(function* test_insert_tag_query() {
let tagFolder = -1;
do_print("Insert tag query for new tag");
{
deepEqual(PlacesUtils.tagging.allTags, [], "New tag should not exist yet");
let query = yield PlacesSyncUtils.bookmarks.insert({
kind: "query",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ syncId: makeGuid(),
+ parentSyncId: "toolbar",
url: "place:type=7&folder=90",
folder: "taggy",
title: "Tagged stuff",
});
notEqual(query.url.href, "place:type=7&folder=90",
"Tag query URL for new tag should differ");
[, tagFolder] = /\bfolder=(\d+)\b/.exec(query.url.pathname);
@@ -900,18 +908,18 @@ add_task(function* test_insert_tag_query
do_print("Insert tag query for existing tag");
{
let url = "place:type=7&folder=90&maxResults=15";
let query = yield PlacesSyncUtils.bookmarks.insert({
kind: "query",
url,
folder: "taggy",
title: "Sorted and tagged",
- guid: makeGuid(),
- parentGuid: PlacesUtils.bookmarks.menuGuid,
+ syncId: makeGuid(),
+ parentSyncId: "menu",
});
notEqual(query.url.href, url, "Tag query URL for existing tag should differ");
let params = new URLSearchParams(query.url.pathname);
equal(params.get("type"), "7", "Should preserve query type");
equal(params.get("maxResults"), "15", "Should preserve additional params");
equal(params.get("folder"), tagFolder, "Should update tag folder");
deepEqual(PlacesUtils.tagging.allTags, ["taggy"], "Should not duplicate existing tags");
}
@@ -944,52 +952,52 @@ add_task(function* test_insert_orphans()
let parentGuid = makeGuid();
let childGuid = makeGuid();
let childId;
do_print("Insert an orphaned child");
{
let child = yield PlacesSyncUtils.bookmarks.insert({
kind: "bookmark",
- parentGuid,
- guid: childGuid,
+ parentSyncId: parentGuid,
+ syncId: childGuid,
url: "https://mozilla.org",
});
- equal(child.guid, childGuid,
+ equal(child.syncId, childGuid,
"Should insert orphan with requested GUID");
- equal(child.parentGuid, PlacesUtils.bookmarks.unfiledGuid,
+ equal(child.parentSyncId, "unfiled",
"Should reparent orphan to unfiled");
childId = yield PlacesUtils.promiseItemId(childGuid);
equal(PlacesUtils.annotations.getItemAnnotation(childId, SYNC_PARENT_ANNO),
parentGuid, "Should set anno to missing parent GUID");
}
do_print("Insert the grandparent");
{
let grandParent = yield PlacesSyncUtils.bookmarks.insert({
kind: "folder",
- parentGuid: PlacesUtils.bookmarks.menuGuid,
- guid: grandParentGuid,
+ parentSyncId: "menu",
+ syncId: grandParentGuid,
});
equal(PlacesUtils.annotations.getItemAnnotation(childId, SYNC_PARENT_ANNO),
parentGuid, "Child should still have orphan anno");
}
// Note that only `PlacesSyncUtils` reparents orphans, though Sync adds an
// observer that removes the orphan anno if the orphan is manually moved.
do_print("Insert the missing parent");
{
let parent = yield PlacesSyncUtils.bookmarks.insert({
kind: "folder",
- parentGuid: grandParentGuid,
- guid: parentGuid,
+ parentSyncId: grandParentGuid,
+ syncId: parentGuid,
});
- equal(parent.guid, parentGuid, "Should insert parent with requested GUID");
- equal(parent.parentGuid, grandParentGuid,
+ equal(parent.syncId, parentGuid, "Should insert parent with requested GUID");
+ equal(parent.parentSyncId, grandParentGuid,
"Parent should be child of grandparent");
ok(!PlacesUtils.annotations.itemHasAnnotation(childId, SYNC_PARENT_ANNO),
"Orphan anno should be removed after reparenting");
let child = yield PlacesUtils.bookmarks.fetch({ guid: childGuid });
equal(child.parentGuid, parentGuid,
"Should reparent child after inserting missing parent");
}