Bug 1295521 - Replace GUIDs with sync IDs in `PlacesSyncUtils`. r=markh,tcsc draft
authorKit Cambridge <kcambridge@mozilla.com>
Fri, 16 Sep 2016 14:55:20 -0700
changeset 423751 f13b90a122d0bcb375b2c0d4d7ae64b9fb43ce4c
parent 423750 bf54d3ff51da1d9121be07d87c8eaa05e990ca10
child 423752 40f23512b976595a094bd4491db434958ae381a1
push id31975
push userbmo:kcambridge@mozilla.com
push dateTue, 11 Oct 2016 15:53:22 +0000
reviewersmarkh, tcsc
bugs1295521
milestone52.0a1
Bug 1295521 - Replace GUIDs with sync IDs in `PlacesSyncUtils`. r=markh,tcsc MozReview-Commit-ID: Lg3SJx2uGNq
toolkit/components/places/PlacesSyncUtils.jsm
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/tests/unit/test_sync_utils.js
--- 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/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -251,19 +251,24 @@ const BOOKMARK_VALIDATORS = Object.freez
       return new URL(v.spec);
     return v;
   },
   source: simpleValidateFunc(v => Number.isInteger(v) &&
                                   Object.values(PlacesUtils.bookmarks.SOURCES).includes(v)),
 });
 
 // Sync bookmark records can contain additional properties.
-const SYNC_BOOKMARK_VALIDATORS = Object.freeze(Object.assign({
-  // Sync uses kinds instead of types, which distinguish between livemarks
-  // and smart bookmarks.
+const SYNC_BOOKMARK_VALIDATORS = Object.freeze({
+  // Sync uses Places GUIDs for all records except roots.
+  syncId: simpleValidateFunc(v => typeof v == "string" && (
+                                  (PlacesSyncUtils.bookmarks.ROOTS.includes(v) ||
+                                   PlacesUtils.isValidGuid(v)))),
+  parentSyncId: v => SYNC_BOOKMARK_VALIDATORS.syncId(v),
+  // Sync uses kinds instead of types, which distinguish between livemarks,
+  // queries, and smart bookmarks.
   kind: simpleValidateFunc(v => typeof v == "string" &&
                                 Object.values(PlacesSyncUtils.bookmarks.KINDS).includes(v)),
   query: simpleValidateFunc(v => v === null || (typeof v == "string" && v)),
   folder: simpleValidateFunc(v => typeof v == "string" && v &&
                                   v.length <= Ci.nsITaggingService.MAX_TAG_LENGTH),
   tags: v => {
     if (v === null) {
       return [];
@@ -279,17 +284,19 @@ const SYNC_BOOKMARK_VALIDATORS = Object.
     }
     return v;
   },
   keyword: simpleValidateFunc(v => v === null || typeof v == "string"),
   description: simpleValidateFunc(v => v === null || typeof v == "string"),
   loadInSidebar: simpleValidateFunc(v => v === true || v === false),
   feed: BOOKMARK_VALIDATORS.url,
   site: v => v === null ? v : BOOKMARK_VALIDATORS.url(v),
-}, BOOKMARK_VALIDATORS));
+  title: BOOKMARK_VALIDATORS.title,
+  url: BOOKMARK_VALIDATORS.url,
+});
 
 this.PlacesUtils = {
   // Place entries that are containers, e.g. bookmark folders or queries.
   TYPE_X_MOZ_PLACE_CONTAINER: "text/x-moz-place-container",
   // Place entries that are bookmark separators.
   TYPE_X_MOZ_PLACE_SEPARATOR: "text/x-moz-place-separator",
   // Place entries that are not containers or separators
   TYPE_X_MOZ_PLACE: "text/x-moz-place",
--- 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");
   }