--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -274,17 +274,19 @@ const BookmarkSyncUtils = PlacesSyncUtil
fetch: Task.async(function* (syncId) {
let guid = BookmarkSyncUtils.syncIdToGuid(syncId);
let bookmarkItem = yield PlacesUtils.bookmarks.fetch(guid);
if (!bookmarkItem) {
return null;
}
// Convert the Places bookmark object to a Sync bookmark and add
- // kind-specific properties.
+ // kind-specific properties. Titles are required for bookmarks,
+ // folders, and livemarks; optional for queries, and omitted for
+ // separators.
let kind = yield getKindForItem(bookmarkItem);
let item;
switch (kind) {
case BookmarkSyncUtils.KINDS.BOOKMARK:
case BookmarkSyncUtils.KINDS.MICROSUMMARY:
item = yield fetchBookmarkItem(bookmarkItem);
break;
@@ -304,22 +306,21 @@ const BookmarkSyncUtils = PlacesSyncUtil
item = yield placesBookmarkToSyncBookmark(bookmarkItem);
item.index = bookmarkItem.index;
break;
default:
throw new Error(`Unknown bookmark kind: ${kind}`);
}
- // Sync uses the parent title for de-duping.
+ // Sync uses the parent title for de-duping. All Sync bookmark objects
+ // except the Places root should have this property.
if (bookmarkItem.parentGuid) {
let parent = yield PlacesUtils.bookmarks.fetch(bookmarkItem.parentGuid);
- if ("title" in parent) {
- item.parentTitle = parent.title;
- }
+ item.parentTitle = parent.title || "";
}
return item;
}),
/**
* Get the sync record kind for the record with provided sync id.
*
@@ -1035,16 +1036,20 @@ function syncBookmarkToPlacesBookmark(in
return bookmarkInfo;
}
// Creates and returns a Sync bookmark object containing the bookmark's
// tags, keyword, description, and whether it loads in the sidebar.
var fetchBookmarkItem = Task.async(function* (bookmarkItem) {
let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+ if (!item.title) {
+ item.title = "";
+ }
+
item.tags = PlacesUtils.tagging.getTagsForURI(
PlacesUtils.toURI(bookmarkItem.url), {});
let keywordEntry = yield PlacesUtils.keywords.fetch({
url: bookmarkItem.url,
});
if (keywordEntry) {
item.keyword = keywordEntry.keyword;
@@ -1062,16 +1067,20 @@ var fetchBookmarkItem = Task.async(funct
return item;
});
// Creates and returns a Sync bookmark object containing the folder's
// description and children.
var fetchFolderItem = Task.async(function* (bookmarkItem) {
let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+ if (!item.title) {
+ item.title = "";
+ }
+
let description = yield getAnno(bookmarkItem.guid,
BookmarkSyncUtils.DESCRIPTION_ANNO);
if (description) {
item.description = description;
}
let db = yield PlacesUtils.promiseDBConnection();
let children = yield fetchAllChildren(db, bookmarkItem.guid);
@@ -1082,16 +1091,20 @@ var fetchFolderItem = Task.async(functio
return item;
});
// Creates and returns a Sync bookmark object containing the livemark's
// description, children (none), feed URI, and site URI.
var fetchLivemarkItem = Task.async(function* (bookmarkItem) {
let item = yield placesBookmarkToSyncBookmark(bookmarkItem);
+ if (!item.title) {
+ item.title = "";
+ }
+
let description = yield getAnno(bookmarkItem.guid,
BookmarkSyncUtils.DESCRIPTION_ANNO);
if (description) {
item.description = description;
}
let feedAnno = yield getAnno(bookmarkItem.guid, PlacesUtils.LMANNO_FEEDURI);
item.feed = new URL(feedAnno);
--- a/toolkit/components/places/tests/unit/test_sync_utils.js
+++ b/toolkit/components/places/tests/unit/test_sync_utils.js
@@ -1053,43 +1053,47 @@ add_task(function* test_fetch() {
let item = yield PlacesSyncUtils.bookmarks.fetch(folder.syncId);
deepEqual(item, {
syncId: folder.syncId,
kind: "folder",
parentSyncId: "menu",
description: "Folder description",
childSyncIds: [folderBmk.syncId, folderSep.syncId],
parentTitle: "Bookmarks Menu",
- }, "Should include description, children, and parent title in folder");
+ title: "",
+ }, "Should include description, children, title, and parent title in folder");
}
do_print("Fetch bookmark with description, sidebar anno, and tags");
{
let item = yield PlacesSyncUtils.bookmarks.fetch(bmk.syncId);
- deepEqual(Object.keys(item).sort(), ["syncId", "kind", "parentSyncId", "url",
- "tags", "description", "loadInSidebar", "parentTitle"].sort(),
+ deepEqual(Object.keys(item).sort(), ["syncId", "kind", "parentSyncId",
+ "url", "tags", "description", "loadInSidebar", "parentTitle", "title"].sort(),
"Should include bookmark-specific properties");
equal(item.syncId, bmk.syncId, "Sync ID should match");
equal(item.url.href, "https://example.com/", "Should return URL");
equal(item.parentSyncId, "menu", "Should return parent sync ID");
deepEqual(item.tags, ["taggy"], "Should return tags");
equal(item.description, "Bookmark description", "Should return bookmark description");
strictEqual(item.loadInSidebar, true, "Should return sidebar anno");
equal(item.parentTitle, "Bookmarks Menu", "Should return parent title");
+ strictEqual(item.title, "", "Should return empty title");
}
do_print("Fetch bookmark with keyword; without parent title or annos");
{
let item = yield PlacesSyncUtils.bookmarks.fetch(folderBmk.syncId);
deepEqual(Object.keys(item).sort(), ["syncId", "kind", "parentSyncId",
- "url", "keyword", "tags", "loadInSidebar"].sort(),
+ "url", "keyword", "tags", "loadInSidebar", "parentTitle", "title"].sort(),
"Should omit blank bookmark-specific properties");
strictEqual(item.loadInSidebar, false, "Should not load bookmark in sidebar");
deepEqual(item.tags, [], "Tags should be empty");
equal(item.keyword, "kw", "Should return keyword");
+ strictEqual(item.parentTitle, "", "Should include parent title even if empty");
+ strictEqual(item.title, "", "Should include bookmark title even if empty");
}
do_print("Fetch separator");
{
let item = yield PlacesSyncUtils.bookmarks.fetch(folderSep.syncId);
strictEqual(item.index, 1, "Should return separator position");
}
@@ -1127,19 +1131,20 @@ add_task(function* test_fetch_livemark()
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
});
PlacesUtils.annotations.setItemAnnotation(livemark.id, DESCRIPTION_ANNO,
"Livemark description", 0, PlacesUtils.annotations.EXPIRE_NEVER);
do_print("Fetch livemark");
let item = yield PlacesSyncUtils.bookmarks.fetch(livemark.guid);
deepEqual(Object.keys(item).sort(), ["syncId", "kind", "parentSyncId",
- "description", "feed", "site", "parentTitle"].sort(),
+ "description", "feed", "site", "parentTitle", "title"].sort(),
"Should include livemark-specific properties");
equal(item.description, "Livemark description", "Should return description");
equal(item.feed.href, site + "/feed/1", "Should return feed URL");
equal(item.site.href, site + "/", "Should return site URL");
+ strictEqual(item.title, "", "Should include livemark title even if empty");
} finally {
yield stopServer();
}
yield PlacesUtils.bookmarks.eraseEverything();
});