Bug 1285408 - Add source tracking to the Places methods used by Sync. draft
authorKit Cambridge <kcambridge@mozilla.com>
Fri, 08 Jul 2016 16:46:14 -0700
changeset 385679 a7e2efd95a47170aa33abb6e05018af7454c9376
parent 385678 702a039cc89b510f695ecb7e596982d5bb97b24b
child 385680 2a7394758e9400fea99d7d04f180d72e22b36297
push id22577
push userkcambridge@mozilla.com
push dateSat, 09 Jul 2016 00:25:19 +0000
bugs1285408
milestone50.0a1
Bug 1285408 - Add source tracking to the Places methods used by Sync. MozReview-Commit-ID: I6IWhMWR1bG
toolkit/components/places/Bookmarks.jsm
toolkit/components/places/PlacesSyncUtils.jsm
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/nsAnnotationService.cpp
toolkit/components/places/nsIAnnotationService.idl
toolkit/components/places/nsINavBookmarksService.idl
toolkit/components/places/nsITaggingService.idl
toolkit/components/places/nsLivemarkService.js
toolkit/components/places/nsNavBookmarks.cpp
toolkit/components/places/nsNavBookmarks.h
toolkit/components/places/nsNavHistoryResult.cpp
toolkit/components/places/nsNavHistoryResult.h
toolkit/components/places/nsTaggingService.js
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -153,16 +153,17 @@ var Bookmarks = Object.freeze({
       , title: { validIf: b => [ this.TYPE_BOOKMARK
                                , this.TYPE_FOLDER ].includes(b.type) }
       , dateAdded: { defaultValue: time
                    , validIf: b => !b.lastModified ||
                                     b.dateAdded <= b.lastModified }
       , lastModified: { defaultValue: time,
                         validIf: b => (!b.dateAdded && b.lastModified >= time) ||
                                       (b.dateAdded && b.lastModified >= b.dateAdded) }
+      , source: { defaultValue: this.SOURCE_DEFAULT }
       });
 
     return Task.spawn(function* () {
       // Ensure the parent exists.
       let parent = yield fetchBookmark({ guid: insertInfo.parentGuid });
       if (!parent)
         throw new Error("parentGuid must be valid");
 
@@ -223,20 +224,21 @@ var Bookmarks = Object.freeze({
    */
   update(info) {
     // The info object is first validated here to ensure it's consistent, then
     // it's compared to the existing item to remove any properties that don't
     // need to be updated.
     let updateInfo = validateBookmarkObject(info,
       { guid: { required: true }
       , index: { validIf: b => b.index >= 0 || b.index == this.DEFAULT_INDEX }
+      , source: { defaultValue: this.SOURCE_DEFAULT }
       });
 
-    // There should be at last one more property in addition to guid.
-    if (Object.keys(updateInfo).length < 2)
+    // There should be at last one more property in addition to guid and source.
+    if (Object.keys(updateInfo).length < 3)
       throw new Error("Not enough properties to update");
 
     return Task.spawn(function* () {
       // Ensure the item exists.
       let item = yield fetchBookmark(updateInfo);
       if (!item)
         throw new Error("No bookmarks found for the provided GUID");
       if (updateInfo.hasOwnProperty("type") && updateInfo.type != item.type)
@@ -247,17 +249,17 @@ var Bookmarks = Object.freeze({
       if (updateInfo.hasOwnProperty("parentGuid") &&
           updateInfo.parentGuid != item.parentGuid &&
           !updateInfo.hasOwnProperty("index"))
         throw new Error("An index is required for moving to another folder");
 
       // Remove any property that will stay the same.
       removeSameValueProperties(updateInfo, item);
       // Check if anything should still be updated.
-      if (Object.keys(updateInfo).length < 2) {
+      if (Object.keys(updateInfo).length < 3) {
         // Remove non-enumerable properties.
         return Object.assign({}, item);
       }
 
       let time = (updateInfo && updateInfo.dateAdded) || new Date();
       updateInfo = validateBookmarkObject(updateInfo,
         { url: { validIf: () => item.type == this.TYPE_BOOKMARK }
         , title: { validIf: () => [ this.TYPE_BOOKMARK
@@ -397,24 +399,26 @@ var Bookmarks = Object.freeze({
     // Disallow removing the root folders.
     if ([this.rootGuid, this.menuGuid, this.toolbarGuid, this.unfiledGuid,
          this.tagsGuid].includes(info.guid)) {
       throw new Error("It's not possible to remove Places root folders.");
     }
 
     // Even if we ignore any other unneeded property, we still validate any
     // known property to reduce likelihood of hidden bugs.
-    let removeInfo = validateBookmarkObject(info);
+    let removeInfo = validateBookmarkObject(info,
+      { source: { defaultValue: this.SOURCE_DEFAULT }
+      });
 
     return Task.spawn(function* () {
       let item = yield fetchBookmark(removeInfo);
       if (!item)
         throw new Error("No bookmarks found for the provided GUID.");
 
-      item = yield removeBookmark(item, options);
+      item = yield removeBookmark(removeInfo, item, options);
 
       // Notify onItemRemoved to listeners.
       let observers = PlacesUtils.bookmarks.getObservers();
       let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
       notify(observers, "onItemRemoved", [ item._id, item._parentId, item.index,
                                            item.type, uri, item.guid,
                                            item.parentGuid ]);
 
@@ -1077,18 +1081,18 @@ function fetchBookmarksByParent(info) {
 
     return rowsToItemsArray(rows);
   }));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Remove implementation.
 
-function removeBookmark(item, options) {
-  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: updateBookmark",
+function removeBookmark(info, item, options) {
+  return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: removeBookmark",
     Task.async(function*(db) {
 
     let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
 
     yield db.executeTransaction(function* transaction() {
       // If it's a folder, remove its contents first.
       if (item.type == Bookmarks.TYPE_FOLDER) {
         if (options.preventRemovalOfNonEmptyFolders && item._childCount > 0) {
@@ -1317,17 +1321,20 @@ XPCOMUtils.defineLazyGetter(this, "VALID
                                 (val instanceof Ci.nsIURI && val.spec.length <= DB_URL_LENGTH_MAX) ||
                                 (val instanceof URL && val.href.length <= DB_URL_LENGTH_MAX)
                         ).call(this, v);
       if (typeof(v) === "string")
         return new URL(v);
       if (v instanceof Ci.nsIURI)
         return new URL(v.spec);
       return v;
-    }
+    },
+    source: simpleValidateFunc(v => Number.isInteger(v) &&
+                                    [ Ci.nsINavBookmarksService.SOURCE_DEFAULT
+                                    , Ci.nsINavBookmarksService.SOURCE_SYNC ].includes(v)),
   });
 });
 
 function validateBookmarkObject(input, behavior) {
   return PlacesUtils.validateItemProperties(VALIDATORS, input, behavior);
 }
 
 /**
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -66,16 +66,17 @@ const BookmarkSyncUtils = PlacesSyncUtil
         delta++;
         BookmarkSyncLog.trace(`order: Ignoring missing child ${childGuid}`);
         continue;
       }
       let newIndex = index - delta;
       yield PlacesUtils.bookmarks.update({
         guid: childGuid,
         index: newIndex,
+        source: PlacesUtils.bookmarks.SOURCE_SYNC,
       });
     }
   }),
 
   /**
    * Removes an item from the database.
    */
   remove: Task.async(function* (guid) {
@@ -101,28 +102,31 @@ const BookmarkSyncUtils = PlacesSyncUtil
       case PlacesUtils.bookmarks.TYPE_SEPARATOR:
         BookmarkSyncLog.debug(`remove: Removing separator ${guid}`);
         break;
 
       default:
         BookmarkSyncLog.error(`remove: Unknown item type ${type} for ${guid}`);
         return false;
     }
-    yield PlacesUtils.bookmarks.remove(guid);
+    yield PlacesUtils.bookmarks.remove({
+      guid,
+      source: PlacesUtils.bookmarks.SOURCE_SYNC,
+    });
     return true;
   }),
 
   /**
    * 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);
+    PlacesUtils.bookmarks.removeFolderChildren(folderId, PlacesUtils.bookmarks.SOURCE_SYNC);
   }),
 
   /**
    * Ensures an item with the |itemId| has a GUID, assigning one if necessary.
    * Returns a promise that resolves with the existing or new GUID.
    */
   ensureGuidForId: Task.async(function* (itemId) {
     return PlacesUtils.withConnectionWrapper("BookmarkSyncUtils: ensureGuidForId", Task.async(function* (db) {
@@ -190,17 +194,19 @@ const BookmarkSyncUtils = PlacesSyncUtil
    * @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 }
       });
+    updateInfo.source = PlacesUtils.bookmarks.SOURCE_SYNC;
 
     return yield* updateSyncBookmark(updateInfo);
   }),
 
   /**
    * Inserts a synced bookmark into the tree. Only Sync should call this
    * method; other callers should use `Bookmarks.insert`.
    *
@@ -343,17 +349,18 @@ function updateTagQueryFolder(item) {
     return item;
   }));
 }
 
 function* annotateOrphan(item, requestedParentGuid) {
   let itemId = yield PlacesUtils.promiseItemId(item.guid);
   PlacesUtils.annotations.setItemAnnotation(itemId,
     PARENT_ANNO, requestedParentGuid, 0,
-    PlacesUtils.annotations.EXPIRE_NEVER);
+    PlacesUtils.annotations.EXPIRE_NEVER,
+    PlacesUtils.bookmarks.SOURCE_SYNC);
 }
 
 function* reparentOrphans(item) {
   if (item.type != PlacesUtils.bookmarks.TYPE_FOLDER) {
     return;
   }
   let orphanIds = findAnnoItems(PARENT_ANNO, item.guid);
   // The annotations API returns item IDs, but the asynchronous bookmarks
@@ -369,26 +376,27 @@ function* reparentOrphans(item) {
       // 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}`);
       yield PlacesUtils.bookmarks.update({
         guid: orphanGuids[i],
         parentGuid: item.guid,
         index: PlacesUtils.bookmarks.DEFAULT_INDEX,
+        source: PlacesUtils.bookmarks.SOURCE_SYNC,
       });
       isReparented = true;
     } catch (ex) {
       BookmarkSyncLog.error(`reparentOrphans: Failed to reparent item ${
         orphanGuids[i]} to ${item.guid}: ${ex}`);
     }
     if (isReparented) {
       // Remove the annotation once we've reparented the item.
       PlacesUtils.annotations.removeItemAnnotation(orphanIds[i],
-        PARENT_ANNO);
+        PARENT_ANNO, PlacesUtils.bookmarks.SOURCE_SYNC);
     }
   }
 }
 
 // Inserts a synced bookmark into the database.
 function* insertSyncBookmark(insertInfo) {
   let requestedParentGuid = insertInfo.parentGuid;
   let parent = yield PlacesUtils.bookmarks.fetch(requestedParentGuid);
@@ -448,56 +456,61 @@ function* insertSyncLivemark(requestedPa
   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: PlacesUtils.bookmarks.SOURCE_SYNC,
   });
 
   return yield* insertBookmarkMetadata(item.id, item, insertInfo);
 }
 
 // Sets annotations, keywords, and tags on a new synced bookmark.
 function* insertBookmarkMetadata(itemId, item, insertInfo) {
   if (insertInfo.query) {
     PlacesUtils.annotations.setItemAnnotation(itemId,
       SMART_BOOKMARKS_ANNO, insertInfo.query, 0,
-      PlacesUtils.annotations.EXPIRE_NEVER);
+      PlacesUtils.annotations.EXPIRE_NEVER,
+      PlacesUtils.bookmarks.SOURCE_SYNC);
     item.query = insertInfo.query;
   }
 
   try {
     item.tags = tagItem(item, insertInfo.tags);
   } catch (ex) {
     BookmarkSyncLog.warn(`insertBookmarkMetadata: Error tagging item ${
       item.guid}: ${ex}`);
   }
 
   if (insertInfo.keyword) {
     yield PlacesUtils.keywords.insert({
       keyword: insertInfo.keyword,
       url: item.url.href,
+      source: PlacesUtils.bookmarks.SOURCE_SYNC,
     });
     item.keyword = insertInfo.keyword;
   }
 
   if (insertInfo.description) {
     PlacesUtils.annotations.setItemAnnotation(itemId,
       DESCRIPTION_ANNO, insertInfo.description, 0,
-      PlacesUtils.annotations.EXPIRE_NEVER);
+      PlacesUtils.annotations.EXPIRE_NEVER,
+      PlacesUtils.bookmarks.SOURCE_SYNC);
     item.description = insertInfo.description;
   }
 
   if (insertInfo.loadInSidebar) {
     PlacesUtils.annotations.setItemAnnotation(itemId,
       SIDEBAR_ANNO, insertInfo.loadInSidebar, 0,
-      PlacesUtils.annotations.EXPIRE_NEVER);
+      PlacesUtils.annotations.EXPIRE_NEVER,
+      PlacesUtils.bookmarks.SOURCE_SYNC);
     item.loadInSidebar = insertInfo.loadInSidebar;
   }
 
   return item;
 }
 
 // Determines the Sync record kind for an existing bookmark.
 function* getKindForItem(item) {
@@ -557,17 +570,20 @@ function* updateSyncBookmark(updateInfo)
 
   BookmarkSyncLog.trace(`updateSyncBookmark: Local kind: ${
     oldKind}. Remote kind: ${updateInfo.kind}`);
   if (shouldReinsert) {
     BookmarkSyncLog.debug(`updateSyncBookmark: Local ${
       oldItem.guid} and remote ${
       updateInfo.guid} differ in type. Deleting and recreating`);
     let newItem = validateNewBookmark(updateInfo);
-    yield PlacesUtils.bookmarks.remove(oldItem.guid);
+    yield PlacesUtils.bookmarks.remove({
+      guid: oldItem.guid,
+      source: PlacesUtils.bookmarks.SOURCE_SYNC,
+    });
     return yield* insertSyncBookmark(newItem);
   }
 
   let isOrphan = false, requestedParentGuid;
   if (updateInfo.hasOwnProperty("parentGuid")) {
     requestedParentGuid = updateInfo.parentGuid;
     if (requestedParentGuid != oldItem.parentGuid) {
       let oldId = yield PlacesUtils.promiseItemId(oldItem.guid);
@@ -627,56 +643,63 @@ function* updateBookmarkMetadata(itemId,
       newItem.guid}: ${ex}`);
   }
 
   if (updateInfo.hasOwnProperty("keyword")) {
     if (updateInfo.keyword) {
       yield PlacesUtils.keywords.insert({
         keyword: updateInfo.keyword,
         url: newItem.url.href,
+        source: PlacesUtils.bookmarks.SOURCE_SYNC,
       });
     } else {
       let entry = yield PlacesUtils.keywords.fetch({
         url: oldItem.url.href,
       });
       if (entry) {
-        yield PlacesUtils.keywords.remove(entry.keyword);
+        yield PlacesUtils.keywords.remove({
+          keyword: entry.keyword,
+          source: PlacesUtils.bookmarks.SOURCE_SYNC,
+        });
       }
     }
     newItem.keyword = updateInfo.keyword;
   }
 
   if (updateInfo.hasOwnProperty("description")) {
     if (updateInfo.description) {
       PlacesUtils.annotations.setItemAnnotation(itemId,
         DESCRIPTION_ANNO, updateInfo.description, 0,
-        PlacesUtils.annotations.EXPIRE_NEVER);
+        PlacesUtils.annotations.EXPIRE_NEVER,
+        PlacesUtils.bookmarks.SOURCE_SYNC);
     } else {
       PlacesUtils.annotations.removeItemAnnotation(itemId,
-        DESCRIPTION_ANNO);
+        DESCRIPTION_ANNO, PlacesUtils.bookmarks.SOURCE_SYNC);
     }
     newItem.description = updateInfo.description;
   }
 
   if (updateInfo.hasOwnProperty("loadInSidebar")) {
     if (updateInfo.loadInSidebar) {
       PlacesUtils.annotations.setItemAnnotation(itemId,
         SIDEBAR_ANNO, updateInfo.loadInSidebar, 0,
-        PlacesUtils.annotations.EXPIRE_NEVER);
+        PlacesUtils.annotations.EXPIRE_NEVER,
+        PlacesUtils.bookmarks.SOURCE_SYNC);
     } else {
       PlacesUtils.annotations.removeItemAnnotation(itemId,
-        SIDEBAR_ANNO);
+        SIDEBAR_ANNO, PlacesUtils.bookmarks.SOURCE_SYNC);
     }
     newItem.loadInSidebar = updateInfo.loadInSidebar;
   }
 
   if (updateInfo.hasOwnProperty("query")) {
     PlacesUtils.annotations.setItemAnnotation(itemId,
       SMART_BOOKMARKS_ANNO, updateInfo.query, 0,
-      PlacesUtils.annotations.EXPIRE_NEVER);
+      PlacesUtils.annotations.EXPIRE_NEVER,
+      PlacesUtils.bookmarks.SOURCE_SYNC);
     newItem.query = updateInfo.query;
   }
 
   return newItem;
 }
 
 function* changeGuid(db, itemId, newGuid) {
   if (!newGuid) {
@@ -693,16 +716,18 @@ function validateNewBookmark(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 }
     , url: { requiredIf: b => [ BookmarkSyncUtils.KIND_BOOKMARK
                               , BookmarkSyncUtils.KIND_MICROSUMMARY
                               , BookmarkSyncUtils.KIND_QUERY ].includes(b.kind)
            , validIf: b => [ BookmarkSyncUtils.KIND_BOOKMARK
                            , BookmarkSyncUtils.KIND_MICROSUMMARY
                            , BookmarkSyncUtils.KIND_QUERY ].includes(b.kind) }
     , parentGuid: { required: true }
@@ -731,16 +756,17 @@ function validateNewBookmark(info) {
     , site: { validIf: b => b.kind == BookmarkSyncUtils.KIND_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 = PlacesUtils.bookmarks.SOURCE_SYNC;
 
   return insertInfo;
 }
 
 function findAnnoItems(anno, val) {
   let annos = PlacesUtils.annotations;
   return annos.getItemsWithAnnotation(anno, {}).filter(id =>
     PlacesUtils.annotations.getItemAnnotation(id, anno) == val);
@@ -754,20 +780,20 @@ function tagItem(item, tags) {
   // Filter out any null/undefined/empty tags.
   let newTags = tags.filter(Boolean);
 
   if (newTags.length) {
     // Removing the last tagged item will also remove the tag. To preserve
     // tag IDs, we temporarily tag a dummy URI, ensuring the tags exist.
     let dummyURI = PlacesUtils.toURI("about:weave#BStore_tagURI");
     let bookmarkURI = PlacesUtils.toURI(item.url.href);
-    PlacesUtils.tagging.tagURI(dummyURI, newTags);
-    PlacesUtils.tagging.untagURI(bookmarkURI, null);
-    PlacesUtils.tagging.tagURI(bookmarkURI, newTags);
-    PlacesUtils.tagging.untagURI(dummyURI, null);
+    PlacesUtils.tagging.tagURI(dummyURI, newTags, PlacesUtils.bookmarks.SOURCE_SYNC);
+    PlacesUtils.tagging.untagURI(bookmarkURI, null, PlacesUtils.bookmarks.SOURCE_SYNC);
+    PlacesUtils.tagging.tagURI(bookmarkURI, newTags, PlacesUtils.bookmarks.SOURCE_SYNC);
+    PlacesUtils.tagging.untagURI(dummyURI, null, PlacesUtils.bookmarks.SOURCE_SYNC);
   }
 
   return newTags;
 }
 
 function isRootId(id) {
   return [ PlacesUtils.bookmarks.placesRoot
          , PlacesUtils.bookmarks.bookmarksMenuFolder
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -2178,20 +2178,25 @@ var Keywords = {
   /**
    * Removes a keyword.
    *
    * @param keyword
    *        The keyword to remove.
    * @return {Promise}
    * @resolves when the removal is complete.
    */
-  remove(keyword) {
-    if (!keyword || typeof(keyword) != "string")
+  remove(keywordOrEntry) {
+    if (typeof(keywordOrEntry) == "string")
+      keywordOrEntry = { keyword: keywordOrEntry };
+
+    if (keywordOrEntry === null || typeof(keywordOrEntry) != "object" ||
+        typeof keywordOrEntry.keyword != "string")
       throw new Error("Invalid keyword");
-    keyword = keyword.trim().toLowerCase();
+
+    let keyword = keywordOrEntry.keyword.trim().toLowerCase();
     return PlacesUtils.withConnectionWrapper("Keywords.remove",  Task.async(function*(db) {
       let cache = yield gKeywordsCachePromise;
       if (!cache.has(keyword))
         return;
       let { url } = cache.get(keyword);
       cache.delete(keyword);
 
       yield db.execute(`DELETE FROM moz_keywords WHERE keyword = :keyword`,
--- a/toolkit/components/places/nsAnnotationService.cpp
+++ b/toolkit/components/places/nsAnnotationService.cpp
@@ -258,17 +258,18 @@ nsAnnotationService::SetPageAnnotation(n
 }
 
 
 NS_IMETHODIMP
 nsAnnotationService::SetItemAnnotation(int64_t aItemId,
                                        const nsACString& aName,
                                        nsIVariant* aValue,
                                        int32_t aFlags,
-                                       uint16_t aExpiration)
+                                       uint16_t aExpiration,
+                                       uint16_t aSource)
 {
   PROFILER_LABEL("AnnotationService", "SetItemAnnotation",
     js::ProfileEntry::Category::OTHER);
 
   NS_ENSURE_ARG_MIN(aItemId, 1);
   NS_ENSURE_ARG(aValue);
 
   if (aExpiration == EXPIRE_WITH_HISTORY)
@@ -285,59 +286,59 @@ nsAnnotationService::SetItemAnnotation(i
     case nsIDataType::VTYPE_UINT16:
     case nsIDataType::VTYPE_INT32:
     case nsIDataType::VTYPE_UINT32:
     case nsIDataType::VTYPE_BOOL: {
       int32_t valueInt;
       rv = aValue->GetAsInt32(&valueInt);
       if (NS_SUCCEEDED(rv)) {
         NS_ENSURE_SUCCESS(rv, rv);
-        rv = SetItemAnnotationInt32(aItemId, aName, valueInt, aFlags, aExpiration);
+        rv = SetItemAnnotationInt32(aItemId, aName, valueInt, aFlags, aExpiration, aSource);
         NS_ENSURE_SUCCESS(rv, rv);
         return NS_OK;
       }
       // Fall through int64_t case otherwise.
       MOZ_FALLTHROUGH;
     }
     case nsIDataType::VTYPE_INT64:
     case nsIDataType::VTYPE_UINT64: {
       int64_t valueLong;
       rv = aValue->GetAsInt64(&valueLong);
       if (NS_SUCCEEDED(rv)) {
         NS_ENSURE_SUCCESS(rv, rv);
-        rv = SetItemAnnotationInt64(aItemId, aName, valueLong, aFlags, aExpiration);
+        rv = SetItemAnnotationInt64(aItemId, aName, valueLong, aFlags, aExpiration, aSource);
         NS_ENSURE_SUCCESS(rv, rv);
         return NS_OK;
       }
       // Fall through double case otherwise.
       MOZ_FALLTHROUGH;
     }
     case nsIDataType::VTYPE_FLOAT:
     case nsIDataType::VTYPE_DOUBLE: {
       double valueDouble;
       rv = aValue->GetAsDouble(&valueDouble);
       NS_ENSURE_SUCCESS(rv, rv);
-      rv = SetItemAnnotationDouble(aItemId, aName, valueDouble, aFlags, aExpiration);
+      rv = SetItemAnnotationDouble(aItemId, aName, valueDouble, aFlags, aExpiration, aSource);
       NS_ENSURE_SUCCESS(rv, rv);
       return NS_OK;
     }
     case nsIDataType::VTYPE_CHAR:
     case nsIDataType::VTYPE_WCHAR:
     case nsIDataType::VTYPE_DOMSTRING:
     case nsIDataType::VTYPE_CHAR_STR:
     case nsIDataType::VTYPE_WCHAR_STR:
     case nsIDataType::VTYPE_STRING_SIZE_IS:
     case nsIDataType::VTYPE_WSTRING_SIZE_IS:
     case nsIDataType::VTYPE_UTF8STRING:
     case nsIDataType::VTYPE_CSTRING:
     case nsIDataType::VTYPE_ASTRING: {
       nsAutoString stringValue;
       rv = aValue->GetAsAString(stringValue);
       NS_ENSURE_SUCCESS(rv, rv);
-      rv = SetItemAnnotationString(aItemId, aName, stringValue, aFlags, aExpiration);
+      rv = SetItemAnnotationString(aItemId, aName, stringValue, aFlags, aExpiration, aSource);
       NS_ENSURE_SUCCESS(rv, rv);
       return NS_OK;
     }
   }
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
@@ -361,28 +362,29 @@ nsAnnotationService::SetPageAnnotationSt
 }
 
 
 NS_IMETHODIMP
 nsAnnotationService::SetItemAnnotationString(int64_t aItemId,
                                              const nsACString& aName,
                                              const nsAString& aValue,
                                              int32_t aFlags,
-                                             uint16_t aExpiration)
+                                             uint16_t aExpiration,
+                                             uint16_t aSource)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
 
   if (aExpiration == EXPIRE_WITH_HISTORY)
     return NS_ERROR_INVALID_ARG;
 
   nsresult rv = SetAnnotationStringInternal(nullptr, aItemId, aName, aValue,
                                             aFlags, aExpiration);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName));
+  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource));
 
   return NS_OK;
 }
 
 
 nsresult
 nsAnnotationService::SetAnnotationInt32Internal(nsIURI* aURI,
                                                 int64_t aItemId,
@@ -431,28 +433,29 @@ nsAnnotationService::SetPageAnnotationIn
 }
 
 
 NS_IMETHODIMP
 nsAnnotationService::SetItemAnnotationInt32(int64_t aItemId,
                                             const nsACString& aName,
                                             int32_t aValue,
                                             int32_t aFlags,
-                                            uint16_t aExpiration)
+                                            uint16_t aExpiration,
+                                            uint16_t aSource)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
 
   if (aExpiration == EXPIRE_WITH_HISTORY)
     return NS_ERROR_INVALID_ARG;
 
   nsresult rv = SetAnnotationInt32Internal(nullptr, aItemId, aName, aValue,
                                            aFlags, aExpiration);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName));
+  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource));
 
   return NS_OK;
 }
 
 
 nsresult
 nsAnnotationService::SetAnnotationInt64Internal(nsIURI* aURI,
                                                 int64_t aItemId,
@@ -501,28 +504,29 @@ nsAnnotationService::SetPageAnnotationIn
 }
 
 
 NS_IMETHODIMP
 nsAnnotationService::SetItemAnnotationInt64(int64_t aItemId,
                                             const nsACString& aName,
                                             int64_t aValue,
                                             int32_t aFlags,
-                                            uint16_t aExpiration)
+                                            uint16_t aExpiration,
+                                            uint16_t aSource)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
 
   if (aExpiration == EXPIRE_WITH_HISTORY)
     return NS_ERROR_INVALID_ARG;
 
   nsresult rv = SetAnnotationInt64Internal(nullptr, aItemId, aName, aValue,
                                            aFlags, aExpiration);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName));
+  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource));
 
   return NS_OK;
 }
 
 
 nsresult
 nsAnnotationService::SetAnnotationDoubleInternal(nsIURI* aURI,
                                                  int64_t aItemId,
@@ -571,28 +575,29 @@ nsAnnotationService::SetPageAnnotationDo
 }
 
 
 NS_IMETHODIMP
 nsAnnotationService::SetItemAnnotationDouble(int64_t aItemId,
                                              const nsACString& aName,
                                              double aValue,
                                              int32_t aFlags,
-                                             uint16_t aExpiration)
+                                             uint16_t aExpiration,
+                                             uint16_t aSource)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
 
   if (aExpiration == EXPIRE_WITH_HISTORY)
     return NS_ERROR_INVALID_ARG;
 
   nsresult rv = SetAnnotationDoubleInternal(nullptr, aItemId, aName, aValue,
                                             aFlags, aExpiration);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName));
+  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aItemId, aName, aSource));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAnnotationService::GetPageAnnotationString(nsIURI* aURI,
                                              const nsACString& aName,
                                              nsAString& _retval)
@@ -1420,24 +1425,25 @@ nsAnnotationService::RemovePageAnnotatio
   NOTIFY_ANNOS_OBSERVERS(OnPageAnnotationRemoved(aURI, aName));
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsAnnotationService::RemoveItemAnnotation(int64_t aItemId,
-                                          const nsACString& aName)
+                                          const nsACString& aName,
+                                          uint16_t aSource)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
 
   nsresult rv = RemoveAnnotationInternal(nullptr, aItemId, aName);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationRemoved(aItemId, aName));
+  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationRemoved(aItemId, aName, aSource));
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsAnnotationService::RemovePageAnnotations(nsIURI* aURI)
 {
@@ -1477,17 +1483,18 @@ nsAnnotationService::RemoveItemAnnotatio
   mozStorageStatementScoper scoper(statement);
 
   nsresult rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationRemoved(aItemId, EmptyCString()));
+  NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationRemoved(aItemId, EmptyCString(),
+                                                 nsINavBookmarksService::SOURCE_DEFAULT));
 
   return NS_OK;
 }
 
 
 /**
  * @note If we use annotations for some standard items like GeckoFlags, it
  *       might be a good idea to blacklist these standard annotations from this
@@ -1621,17 +1628,18 @@ nsAnnotationService::CopyItemAnnotations
     nsAutoCString annoName;
     rv = sourceStmt->GetUTF8String(1, annoName);
     NS_ENSURE_SUCCESS(rv, rv);
     int64_t annoExistsOnDest = sourceStmt->AsInt64(2);
 
     if (annoExistsOnDest) {
       if (!aOverwriteDest)
         continue;
-      rv = RemoveItemAnnotation(aDestItemId, annoName);
+      rv = RemoveItemAnnotation(aDestItemId, annoName,
+                                nsINavBookmarksService::SOURCE_DEFAULT);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // Copy the annotation.
     mozStorageStatementScoper scoper(copyStmt);
     rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("dest_item_id"), aDestItemId);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("source_item_id"), aSourceItemId);
@@ -1639,17 +1647,18 @@ nsAnnotationService::CopyItemAnnotations
     rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("name_id"), annoNameID);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = copyStmt->BindInt64ByName(NS_LITERAL_CSTRING("date"), PR_Now());
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = copyStmt->Execute();
     NS_ENSURE_SUCCESS(rv, rv);
 
-    NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aDestItemId, annoName));
+    NOTIFY_ANNOS_OBSERVERS(OnItemAnnotationSet(aDestItemId, annoName,
+                                               nsINavBookmarksService::SOURCE_DEFAULT));
   }
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
--- a/toolkit/components/places/nsIAnnotationService.idl
+++ b/toolkit/components/places/nsIAnnotationService.idl
@@ -14,27 +14,29 @@ interface nsIAnnotationObserver : nsISup
 {
     /**
      * Called when an annotation value is set. It could be a new annotation,
      * or it could be a new value for an existing annotation.
      */
     void onPageAnnotationSet(in nsIURI aPage,
                              in AUTF8String aName);
     void onItemAnnotationSet(in long long aItemId,
-                             in AUTF8String aName);
+                             in AUTF8String aName,
+                             in unsigned short aSource);
 
     /**
      * Called when an annotation is deleted. If aName is empty, then ALL
      * annotations for the given URI have been deleted. This is not called when
      * annotations are expired (normally happens when the app exits).
      */
     void onPageAnnotationRemoved(in nsIURI aURI,
                                  in AUTF8String aName);
     void onItemAnnotationRemoved(in long long aItemId,
-                                 in AUTF8String aName);
+                                 in AUTF8String aName,
+                                 in unsigned short aSource);
 };
 
 [scriptable, uuid(D4CDAAB1-8EEC-47A8-B420-AD7CB333056A)]
 interface nsIAnnotationService : nsISupports
 {
     /**
      * Valid values for aExpiration, which sets the expiration policy for your
      * annotation. The times for the days, weeks and months policies are
@@ -119,82 +121,87 @@ interface nsIAnnotationService : nsISupp
                            in AUTF8String aName,
                            in nsIVariant aValue,
                            in long aFlags,
                            in unsigned short aExpiration);
     void setItemAnnotation(in long long aItemId,
                            in AUTF8String aName,
                            in nsIVariant aValue,
                            in long aFlags,
-                           in unsigned short aExpiration);
+                           in unsigned short aExpiration,
+                           [optional] in unsigned short aSource);
 
     /**
      * @throws NS_ERROR_ILLEGAL_VALUE if the page or the bookmark doesn't exist.
      */
     [noscript] void setPageAnnotationString(in nsIURI aURI,
                                             in AUTF8String aName,
                                             in AString aValue,
                                             in long aFlags,
                                             in unsigned short aExpiration);
     [noscript] void setItemAnnotationString(in long long aItemId,
                                             in AUTF8String aName,
                                             in AString aValue,
                                             in long aFlags,
-                                            in unsigned short aExpiration);
+                                            in unsigned short aExpiration,
+                                            [optional] in unsigned short aSource);
 
     /**
      * Sets an annotation just like setAnnotationString, but takes an Int32 as
      * input.
      *
      * @throws NS_ERROR_ILLEGAL_VALUE if the page or the bookmark doesn't exist.
      */
     [noscript] void setPageAnnotationInt32(in nsIURI aURI,
                                            in AUTF8String aName,
                                            in long aValue,
                                            in long aFlags,
                                            in unsigned short aExpiration);
     [noscript] void setItemAnnotationInt32(in long long aItemId,
                                            in AUTF8String aName,
                                            in long aValue,
                                            in long aFlags,
-                                           in unsigned short aExpiration);
+                                           in unsigned short aExpiration,
+                                           [optional] in unsigned short aSource);
 
     /**
      * Sets an annotation just like setAnnotationString, but takes an Int64 as
      * input.
      *
      * @throws NS_ERROR_ILLEGAL_VALUE if the page or the bookmark doesn't exist.
      */
     [noscript] void setPageAnnotationInt64(in nsIURI aURI,
                                            in AUTF8String aName,
                                            in long long aValue,
                                            in long aFlags,
                                            in unsigned short aExpiration);
     [noscript] void setItemAnnotationInt64(in long long aItemId,
                                            in AUTF8String aName,
                                            in long long aValue,
                                            in long aFlags,
-                                           in unsigned short aExpiration);
+                                           in unsigned short aExpiration,
+                                           [optional] in unsigned short aSource);
 
     /**
      * Sets an annotation just like setAnnotationString, but takes a double as
      * input.
      *
      * @throws NS_ERROR_ILLEGAL_VALUE if the page or the bookmark doesn't exist.
      */
     [noscript] void setPageAnnotationDouble(in nsIURI aURI,
                                             in AUTF8String aName,
                                             in double aValue,
                                             in long aFlags,
                                             in unsigned short aExpiration);
     [noscript] void setItemAnnotationDouble(in long long aItemId,
                                             in AUTF8String aName,
                                             in double aValue,
                                             in long aFlags,
-                                            in unsigned short aExpiration);
+                                            in unsigned short aExpiration,
+                                            [optional] in unsigned short aSource);
 
     /**
      * Retrieves the value of a given annotation. Throws an error if the
      * annotation does not exist. C++ consumers may use the type-specific
      * methods.
      *
      * The type-specific methods throw if the given annotation is set in
      * a different type.
@@ -322,17 +329,18 @@ interface nsIAnnotationService : nsISupp
 
     /**
      * Removes a specific annotation. Succeeds even if the annotation is
      * not found.
      */
     void removePageAnnotation(in nsIURI aURI,
                               in AUTF8String aName);
     void removeItemAnnotation(in long long aItemId,
-                              in AUTF8String aName);
+                              in AUTF8String aName,
+                              [optional] in unsigned short aSource);
 
     /**
      * Removes all annotations for the given page/item.
      * We may want some other similar functions to get annotations with given
      * flags (once we have flags defined).
      */
     void removePageAnnotations(in nsIURI aURI);
     void removeItemAnnotations(in long long aItemId);
--- a/toolkit/components/places/nsINavBookmarksService.idl
+++ b/toolkit/components/places/nsINavBookmarksService.idl
@@ -60,17 +60,18 @@ interface nsINavBookmarkObserver : nsISu
   void onItemAdded(in long long aItemId,
                    in long long aParentId,
                    in long aIndex,
                    in unsigned short aItemType,
                    in nsIURI aURI,
                    in AUTF8String aTitle,
                    in PRTime aDateAdded,
                    in ACString aGuid,
-                   in ACString aParentGuid);
+                   in ACString aParentGuid,
+                   in unsigned short aSource);
 
   /**
    * Notifies that an item was removed.  Called after the actual remove took
    * place.
    * When an item is removed, all the items following it in the same folder
    * will have their index shifted down, but no additional notifications will
    * be sent.
    *
@@ -90,17 +91,18 @@ interface nsINavBookmarkObserver : nsISu
    *        The unique ID associated with the item's parent.
    */
   void onItemRemoved(in long long aItemId,
                      in long long aParentId,
                      in long aIndex,
                      in unsigned short aItemType,
                      in nsIURI aURI,
                      in ACString aGuid,
-                     in ACString aParentGuid);
+                     in ACString aParentGuid,
+                     in unsigned short aSource);
 
   /**
    * Notifies that an item's information has changed.  This will be called
    * whenever any attributes like "title" are changed.
    *
    * @param aItemId
    *        The id of the item that was changed.
    * @param aProperty
@@ -151,17 +153,18 @@ interface nsINavBookmarkObserver : nsISu
                      in ACString aProperty,
                      in boolean aIsAnnotationProperty,
                      in AUTF8String aNewValue,
                      in PRTime aLastModified,
                      in unsigned short aItemType,
                      in long long aParentId,
                      in ACString aGuid,
                      in ACString aParentGuid,
-                     in AUTF8String aOldValue);
+                     in AUTF8String aOldValue,
+                     in unsigned short aSource);
 
   /**
    * Notifies that the item was visited.  Can be invoked only for TYPE_BOOKMARK
    * items.
    *
    * @param aItemId
    *        The id of the bookmark that was visited.
    * @param aVisitId
@@ -273,16 +276,23 @@ interface nsINavBookmarksService : nsISu
 
   const unsigned short TYPE_BOOKMARK = 1;
   const unsigned short TYPE_FOLDER = 2;
   const unsigned short TYPE_SEPARATOR = 3;
   // Dynamic containers are deprecated and unsupported.
   // This const exists just to avoid reusing the value.
   const unsigned short TYPE_DYNAMIC_CONTAINER = 4;
 
+  // Change source constants. These are used to distinguish changes made by
+  // Sync from other Places consumers, though they can be extended to support
+  // other callers. Sources are passed as optional parameters to methods used
+  // by Sync, and forwarded to observers.
+  const unsigned short SOURCE_DEFAULT = 0;
+  const unsigned short SOURCE_SYNC = 1;
+
   /**
    * Inserts a child bookmark into the given folder.
    *
    *  @param aParentId
    *         The id of the parent folder
    *  @param aURI
    *         The URI to insert
    *  @param aIndex
@@ -296,24 +306,25 @@ interface nsINavBookmarksService : nsISu
    *  @return The ID of the newly-created bookmark.
    *
    *  @note aTitle will be truncated to TITLE_LENGTH_MAX and
    *        aURI will be truncated to URI_LENGTH_MAX.
    *  @throws if aGuid is malformed.
    */
   long long insertBookmark(in long long aParentId, in nsIURI aURI,
                            in long aIndex, in AUTF8String aTitle,
-                           [optional] in ACString aGuid);
+                           [optional] in ACString aGuid,
+                           [optional] in unsigned short aSource);
 
   /**
    * Removes a child item. Used to delete a bookmark or separator.
    *  @param aItemId
    *         The child item to remove
    */
-  void removeItem(in long long aItemId);
+  void removeItem(in long long aItemId, [optional] in unsigned short aSource);
 
   /**
    * Creates a new child folder and inserts it under the given parent.
    *  @param aParentFolder
    *         The id of the parent folder
    *  @param aName
    *         The name of the new folder
    *  @param aIndex
@@ -322,17 +333,18 @@ interface nsINavBookmarksService : nsISu
    *         The GUID to be set for the new item.  If not set, a new GUID is
    *         generated.  Unless you've a very sound reason, such as an undo
    *         manager implementation, do not pass this argument.
    *  @return The ID of the newly-inserted folder.
    *  @throws if aGuid is malformed.
    */
   long long createFolder(in long long aParentFolder, in AUTF8String name,
                          in long index,
-                         [optional] in ACString aGuid);
+                         [optional] in ACString aGuid,
+                         [optional] in unsigned short aSource);
 
   /**
    * Gets an undo-able transaction for removing a folder from the bookmarks
    * tree.
    *  @param aItemId
    *         The id of the folder to remove.
    *  @return An object implementing nsITransaction that can be used to undo
    *          or redo the action.
@@ -348,17 +360,18 @@ interface nsINavBookmarksService : nsISu
   nsITransaction getRemoveFolderTransaction(in long long aItemId);
 
   /**
    * Convenience function for container services.  Removes
    * all children of the given folder.
    *  @param aItemId
    *         The id of the folder to remove children from.
    */
-  void removeFolderChildren(in long long aItemId);
+  void removeFolderChildren(in long long aItemId,
+                            [optional] in unsigned short aSource);
 
   /**
    * Moves an item to a different container, preserving its contents.
    *  @param aItemId
    *         The id of the item to move
    *  @param aNewParentId
    *         The id of the new parent
    *  @param aIndex
@@ -402,17 +415,18 @@ interface nsINavBookmarksService : nsISu
    * Set the title for an item.
    *  @param aItemId
    *         The id of the item whose title should be updated.
    *  @param aTitle
    *         The new title for the bookmark.
    *
    *  @note  aTitle will be truncated to TITLE_LENGTH_MAX.
    */
-  void setItemTitle(in long long aItemId, in AUTF8String aTitle);
+  void setItemTitle(in long long aItemId, in AUTF8String aTitle,
+                    [optional] in unsigned short aSource);
 
   /**
    * Get the title for an item.
    *
    * If no item title is available it will return a void string (null in JS).
    *
    *  @param aItemId
    *         The id of the item whose title should be retrieved
--- a/toolkit/components/places/nsITaggingService.idl
+++ b/toolkit/components/places/nsITaggingService.idl
@@ -25,30 +25,30 @@ interface nsITaggingService : nsISupport
    *
    * @param aURI
    *        the URL to tag.
    * @param aTags
    *        Array of tags to set for the given URL.  Each element within the
    *        array can be either a tag name (non-empty string) or a concrete
    *        itemId of a tag container.
    */
-  void tagURI(in nsIURI aURI, in nsIVariant aTags);
+  void tagURI(in nsIURI aURI, in nsIVariant aTags, [optional] in unsigned short aSource);
 
   /**
    * Removes tags from a URL. Tags from aTags which are not set for the
    * given URL are ignored.
    *
    * @param aURI
    *        the URL to un-tag.
    * @param aTags
    *        Array of tags to unset.  Pass null to remove all tags from the given
    *        url.  Each element within the array can be either a tag name
    *        (non-empty string) or a concrete itemId of a tag container.
    */
-  void untagURI(in nsIURI aURI, in nsIVariant aTags);
+  void untagURI(in nsIURI aURI, in nsIVariant aTags, [optional] in unsigned short aSource);
 
   /**
    * Retrieves all URLs tagged with the given tag.
    *
    * @param aTag
    *        tag name
    * @returns Array of uris tagged with aTag.
    */
--- a/toolkit/components/places/nsLivemarkService.js
+++ b/toolkit/components/places/nsLivemarkService.js
@@ -203,17 +203,18 @@ LivemarkService.prototype = {
 
       // Create a new livemark.
       let folder = yield PlacesUtils.bookmarks.insert({
         type: PlacesUtils.bookmarks.TYPE_FOLDER,
         parentGuid: aLivemarkInfo.parentGuid,
         title: aLivemarkInfo.title,
         index: aLivemarkInfo.index,
         guid: aLivemarkInfo.guid,
-        dateAdded: toDate(aLivemarkInfo.dateAdded) || toDate(aLivemarkInfo.lastModified)
+        dateAdded: toDate(aLivemarkInfo.dateAdded) || toDate(aLivemarkInfo.lastModified),
+        source: aLivemarkInfo.source,
       });
 
       // Set feed and site URI annotations.
       let id = yield PlacesUtils.promiseItemId(folder.guid);
 
       // Create the internal Livemark object.
       let livemark = new Livemark({ id
                                   , title:        folder.title
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -463,16 +463,17 @@ nsNavBookmarks::InsertBookmarkInDB(int64
 
 
 NS_IMETHODIMP
 nsNavBookmarks::InsertBookmark(int64_t aFolder,
                                nsIURI* aURI,
                                int32_t aIndex,
                                const nsACString& aTitle,
                                const nsACString& aGUID,
+                               uint16_t aSource,
                                int64_t* aNewBookmarkId)
 {
   NS_ENSURE_ARG(aURI);
   NS_ENSURE_ARG_POINTER(aNewBookmarkId);
   NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
 
   if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
     return NS_ERROR_INVALID_ARG;
@@ -521,17 +522,17 @@ nsNavBookmarks::InsertBookmark(int64_t a
   }
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                    nsINavBookmarkObserver,
                    OnItemAdded(*aNewBookmarkId, aFolder, index, TYPE_BOOKMARK,
-                               aURI, title, dateAdded, guid, folderGuid));
+                               aURI, title, dateAdded, guid, folderGuid, aSource));
 
   // If the bookmark has been added to a tag container, notify all
   // bookmark-folder result nodes which contain a bookmark for the new
   // bookmark's url.
   if (grandParentId == mTagsRoot) {
     // Notify a tags change to all bookmarks for this URI.
     nsTArray<BookmarkData> bookmarks;
     rv = GetBookmarksForURI(aURI, bookmarks);
@@ -547,26 +548,27 @@ nsNavBookmarks::InsertBookmark(int64_t a
                                      NS_LITERAL_CSTRING("tags"),
                                      false,
                                      EmptyCString(),
                                      bookmarks[i].lastModified,
                                      TYPE_BOOKMARK,
                                      bookmarks[i].parentId,
                                      bookmarks[i].guid,
                                      bookmarks[i].parentGuid,
-                                     EmptyCString()));
+                                     EmptyCString(),
+                                     aSource));
     }
   }
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsNavBookmarks::RemoveItem(int64_t aItemId)
+nsNavBookmarks::RemoveItem(int64_t aItemId, uint16_t aSource)
 {
   PROFILER_LABEL("nsNavBookmarks", "RemoveItem",
     js::ProfileEntry::Category::OTHER);
 
   NS_ENSURE_ARG(!IsRoot(aItemId));
 
   BookmarkData bookmark;
   nsresult rv = FetchItemInfo(aItemId, bookmark);
@@ -580,17 +582,17 @@ nsNavBookmarks::RemoveItem(int64_t aItem
     nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
     NS_ENSURE_TRUE(annosvc, NS_ERROR_OUT_OF_MEMORY);
     rv = annosvc->RemoveItemAnnotations(bookmark.id);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (bookmark.type == TYPE_FOLDER) {
     // Remove all of the folder's children.
-    rv = RemoveFolderChildren(bookmark.id);
+    rv = RemoveFolderChildren(bookmark.id, aSource);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
     "DELETE FROM moz_bookmarks WHERE id = :item_id"
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
@@ -633,17 +635,18 @@ nsNavBookmarks::RemoveItem(int64_t aItem
   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                    nsINavBookmarkObserver,
                    OnItemRemoved(bookmark.id,
                                  bookmark.parentId,
                                  bookmark.position,
                                  bookmark.type,
                                  uri,
                                  bookmark.guid,
-                                 bookmark.parentGuid));
+                                 bookmark.parentGuid,
+                                 aSource));
 
   if (bookmark.type == TYPE_BOOKMARK && bookmark.grandParentId == mTagsRoot &&
       uri) {
     // If the removed bookmark was child of a tag container, notify a tags
     // change to all bookmarks for this URI.
     nsTArray<BookmarkData> bookmarks;
     rv = GetBookmarksForURI(uri, bookmarks);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -655,43 +658,44 @@ nsNavBookmarks::RemoveItem(int64_t aItem
                                      NS_LITERAL_CSTRING("tags"),
                                      false,
                                      EmptyCString(),
                                      bookmarks[i].lastModified,
                                      TYPE_BOOKMARK,
                                      bookmarks[i].parentId,
                                      bookmarks[i].guid,
                                      bookmarks[i].parentGuid,
-                                     EmptyCString()));
+                                     EmptyCString(),
+                                     aSource));
     }
 
   }
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::CreateFolder(int64_t aParent, const nsACString& aName,
                              int32_t aIndex, const nsACString& aGUID,
-                             int64_t* aNewFolder)
+                             uint16_t aSource, int64_t* aNewFolder)
 {
   // NOTE: aParent can be null for root creation, so not checked
   NS_ENSURE_ARG_POINTER(aNewFolder);
 
   if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
     return NS_ERROR_INVALID_ARG;
 
   // CreateContainerWithID returns the index of the new folder, but that's not
   // used here.  To avoid any risk of corrupting data should this function
   // be changed, we'll use a local variable to hold it.  The true argument
   // will cause notifications to be sent to bookmark observers.
   int32_t localIndex = aIndex;
   nsresult rv = CreateContainerWithID(-1, aParent, aName, true, &localIndex,
-                                      aGUID, aNewFolder);
+                                      aGUID, aSource, aNewFolder);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 bool nsNavBookmarks::IsLivemark(int64_t aFolderId)
 {
   nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
   NS_ENSURE_TRUE(annosvc, false);
@@ -705,16 +709,17 @@ bool nsNavBookmarks::IsLivemark(int64_t 
 
 nsresult
 nsNavBookmarks::CreateContainerWithID(int64_t aItemId,
                                       int64_t aParent,
                                       const nsACString& aTitle,
                                       bool aIsBookmarkFolder,
                                       int32_t* aIndex,
                                       const nsACString& aGUID,
+                                      uint16_t aSource,
                                       int64_t* aNewFolder)
 {
   NS_ENSURE_ARG_MIN(*aIndex, nsINavBookmarksService::DEFAULT_INDEX);
 
   // Get the correct index for insertion.  This also ensures the parent exists.
   int32_t index, folderCount;
   int64_t grandParentId;
   nsAutoCString folderGuid;
@@ -745,17 +750,18 @@ nsNavBookmarks::CreateContainerWithID(in
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                    nsINavBookmarkObserver,
                    OnItemAdded(*aNewFolder, aParent, index, FOLDER,
-                               nullptr, title, dateAdded, guid, folderGuid));
+                               nullptr, title, dateAdded, guid, folderGuid,
+                               aSource));
 
   *aIndex = index;
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::InsertSeparator(int64_t aParent,
@@ -802,17 +808,18 @@ nsNavBookmarks::InsertSeparator(int64_t 
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
   NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                    nsINavBookmarkObserver,
                    OnItemAdded(*aNewItemId, aParent, index, TYPE_SEPARATOR,
-                               nullptr, voidString, dateAdded, guid, folderGuid));
+                               nullptr, voidString, dateAdded, guid, folderGuid,
+                               SOURCE_DEFAULT));
 
   return NS_OK;
 }
 
 
 nsresult
 nsNavBookmarks::GetLastChildId(int64_t aFolderId, int64_t* aItemId)
 {
@@ -1021,17 +1028,17 @@ nsNavBookmarks::GetDescendantChildren(in
     }
   }
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId)
+nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId, uint16_t aSource)
 {
   PROFILER_LABEL("nsNavBookmarks", "RemoveFolderChilder",
     js::ProfileEntry::Category::OTHER);
 
   NS_ENSURE_ARG_MIN(aFolderId, 1);
   NS_ENSURE_ARG(aFolderId != mRoot);
 
   BookmarkData folder;
@@ -1111,17 +1118,18 @@ nsNavBookmarks::RemoveFolderChildren(int
     NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                      nsINavBookmarkObserver,
                      OnItemRemoved(child.id,
                                    child.parentId,
                                    child.position,
                                    child.type,
                                    uri,
                                    child.guid,
-                                   child.parentGuid));
+                                   child.parentGuid,
+                                   aSource));
 
     if (child.type == TYPE_BOOKMARK && child.grandParentId == mTagsRoot &&
         uri) {
       // If the removed bookmark was a child of a tag container, notify all
       // bookmark-folder result nodes which contain a bookmark for the removed
       // bookmark's url.
       nsTArray<BookmarkData> bookmarks;
       rv = GetBookmarksForURI(uri, bookmarks);
@@ -1134,17 +1142,18 @@ nsNavBookmarks::RemoveFolderChildren(int
                                        NS_LITERAL_CSTRING("tags"),
                                        false,
                                        EmptyCString(),
                                        bookmarks[i].lastModified,
                                        TYPE_BOOKMARK,
                                        bookmarks[i].parentId,
                                        bookmarks[i].guid,
                                        bookmarks[i].parentGuid,
-                                       EmptyCString()));
+                                       EmptyCString(),
+                                       aSource));
       }
     }
   }
 
   return NS_OK;
 }
 
 
@@ -1416,17 +1425,18 @@ nsNavBookmarks::SetItemDateAdded(int64_t
                                  NS_LITERAL_CSTRING("dateAdded"),
                                  false,
                                  nsPrintfCString("%lld", bookmark.dateAdded),
                                  bookmark.dateAdded,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
                                  bookmark.parentGuid,
-                                 EmptyCString()));
+                                 EmptyCString(),
+                                 SOURCE_DEFAULT));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetItemDateAdded(int64_t aItemId, PRTime* _dateAdded)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
@@ -1463,17 +1473,18 @@ nsNavBookmarks::SetItemLastModified(int6
                                  NS_LITERAL_CSTRING("lastModified"),
                                  false,
                                  nsPrintfCString("%lld", bookmark.lastModified),
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
                                  bookmark.parentGuid,
-                                 EmptyCString()));
+                                 EmptyCString(),
+                                 SOURCE_DEFAULT));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetItemLastModified(int64_t aItemId, PRTime* _lastModified)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
@@ -1484,17 +1495,18 @@ nsNavBookmarks::GetItemLastModified(int6
   NS_ENSURE_SUCCESS(rv, rv);
 
   *_lastModified = bookmark.lastModified;
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle)
+nsNavBookmarks::SetItemTitle(int64_t aItemId, const nsACString& aTitle,
+                             uint16_t aSource)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
 
   BookmarkData bookmark;
   nsresult rv = FetchItemInfo(aItemId, bookmark);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
@@ -1532,17 +1544,18 @@ nsNavBookmarks::SetItemTitle(int64_t aIt
                                  NS_LITERAL_CSTRING("title"),
                                  false,
                                  title,
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
                                  bookmark.parentGuid,
-                                 EmptyCString()));
+                                 EmptyCString(),
+                                 aSource));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetItemTitle(int64_t aItemId,
                              nsACString& _title)
 {
@@ -2016,17 +2029,18 @@ nsNavBookmarks::ChangeBookmarkURI(int64_
                                  NS_LITERAL_CSTRING("uri"),
                                  false,
                                  spec,
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
                                  bookmark.parentGuid,
-                                 bookmark.url));
+                                 bookmark.url,
+                                 SOURCE_DEFAULT));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetFolderIdForItem(int64_t aItemId, int64_t* _parentId)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
@@ -2311,17 +2325,18 @@ nsNavBookmarks::SetKeywordForBookmark(in
                                      NS_LITERAL_CSTRING("keyword"),
                                      false,
                                      EmptyCString(),
                                      bookmarks[i].lastModified,
                                      TYPE_BOOKMARK,
                                      bookmarks[i].parentId,
                                      bookmarks[i].guid,
                                      bookmarks[i].parentGuid,
-                                     EmptyCString()));
+                                     EmptyCString(),
+                                     SOURCE_DEFAULT));
     }
 
     return NS_OK;
   }
 
   // A keyword can only be associated to a single URI.  Check if the requested
   // keyword was already associated, in such a case we will need to notify about
   // the change.
@@ -2364,17 +2379,18 @@ nsNavBookmarks::SetKeywordForBookmark(in
                                      NS_LITERAL_CSTRING("keyword"),
                                      false,
                                      EmptyCString(),
                                      bookmarks[i].lastModified,
                                      TYPE_BOOKMARK,
                                      bookmarks[i].parentId,
                                      bookmarks[i].guid,
                                      bookmarks[i].parentGuid,
-                                     EmptyCString()));
+                                     EmptyCString(),
+                                     SOURCE_DEFAULT));
     }
 
     stmt = mDB->GetStatement(
       "UPDATE moz_keywords SET place_id = :place_id WHERE keyword = :keyword"
     );
     NS_ENSURE_STATE(stmt);
   }
   else {
@@ -2404,17 +2420,18 @@ nsNavBookmarks::SetKeywordForBookmark(in
                                    NS_LITERAL_CSTRING("keyword"),
                                    false,
                                    NS_ConvertUTF16toUTF8(keyword),
                                    bookmarks[i].lastModified,
                                    TYPE_BOOKMARK,
                                    bookmarks[i].parentId,
                                    bookmarks[i].guid,
                                    bookmarks[i].parentGuid,
-                                   EmptyCString()));
+                                   EmptyCString(),
+                                   SOURCE_DEFAULT));
   }
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetKeywordForBookmark(int64_t aBookmarkId, nsAString& aKeyword)
@@ -2610,17 +2627,18 @@ nsNavBookmarks::NotifyItemChanged(const 
                                  aData.property,
                                  aData.isAnnotation,
                                  aData.newValue,
                                  aData.bookmark.lastModified,
                                  aData.bookmark.type,
                                  aData.bookmark.parentId,
                                  aData.bookmark.guid,
                                  aData.bookmark.parentGuid,
-                                 aData.oldValue));
+                                 aData.oldValue,
+                                 SOURCE_DEFAULT));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIObserver
 
 NS_IMETHODIMP
 nsNavBookmarks::Observe(nsISupports *aSubject, const char *aTopic,
                         const char16_t *aData)
@@ -2830,17 +2848,18 @@ nsNavBookmarks::OnDeleteVisits(nsIURI* a
 NS_IMETHODIMP
 nsNavBookmarks::OnPageAnnotationSet(nsIURI* aPage, const nsACString& aName)
 {
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsNavBookmarks::OnItemAnnotationSet(int64_t aItemId, const nsACString& aName)
+nsNavBookmarks::OnItemAnnotationSet(int64_t aItemId, const nsACString& aName,
+                                    uint16_t aSource)
 {
   BookmarkData bookmark;
   nsresult rv = FetchItemInfo(aItemId, bookmark);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bookmark.lastModified = RoundedPRNow();
   rv = SetItemDateInternal(LAST_MODIFIED, bookmark.id, bookmark.lastModified);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -2851,29 +2870,31 @@ nsNavBookmarks::OnItemAnnotationSet(int6
                                  aName,
                                  true,
                                  EmptyCString(),
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
                                  bookmark.parentGuid,
-                                 EmptyCString()));
+                                 EmptyCString(),
+                                 aSource));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::OnPageAnnotationRemoved(nsIURI* aPage, const nsACString& aName)
 {
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsNavBookmarks::OnItemAnnotationRemoved(int64_t aItemId, const nsACString& aName)
+nsNavBookmarks::OnItemAnnotationRemoved(int64_t aItemId, const nsACString& aName,
+                                        uint16_t aSource)
 {
   // As of now this is doing the same as OnItemAnnotationSet, so just forward
   // the call.
-  nsresult rv = OnItemAnnotationSet(aItemId, aName);
+  nsresult rv = OnItemAnnotationSet(aItemId, aName, aSource);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -161,16 +161,17 @@ public:
    * @note If aFolder is -1, uses the autoincrement id for folder index.
    * @note aTitle will be truncated to TITLE_LENGTH_MAX
    */
   nsresult CreateContainerWithID(int64_t aId, int64_t aParent,
                                  const nsACString& aTitle,
                                  bool aIsBookmarkFolder,
                                  int32_t* aIndex,
                                  const nsACString& aGUID,
+                                 uint16_t aSource,
                                  int64_t* aNewFolder);
 
   /**
    * Fetches information about the specified id from the database.
    *
    * @param aItemId
    *        Id of the item to fetch information for.
    * @param aBookmark
@@ -374,25 +375,27 @@ private:
       nsresult rv = bookmarks->FetchItemInfo(mID, folder);
       // TODO (Bug 656935): store the BookmarkData struct instead.
       mParent = folder.parentId;
       mIndex = folder.position;
 
       rv = bookmarks->GetItemTitle(mID, mTitle);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      return bookmarks->RemoveItem(mID);
+      return bookmarks->RemoveItem(mID, nsINavBookmarksService::SOURCE_DEFAULT);
     }
 
     NS_IMETHOD UndoTransaction() override {
       nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
       NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
       int64_t newFolder;
       return bookmarks->CreateContainerWithID(mID, mParent, mTitle, true,
-                                              &mIndex, EmptyCString(), &newFolder);
+                                              &mIndex, EmptyCString(),
+                                              nsINavBookmarksService::SOURCE_DEFAULT,
+                                              &newFolder);
     }
 
     NS_IMETHOD RedoTransaction() override {
       return DoTransaction();
     }
 
     NS_IMETHOD GetIsTransient(bool* aResult) override {
       *aResult = false;
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -2841,17 +2841,18 @@ NS_IMETHODIMP
 nsNavHistoryQueryResultNode::OnItemAdded(int64_t aItemId,
                                          int64_t aParentId,
                                          int32_t aIndex,
                                          uint16_t aItemType,
                                          nsIURI* aURI,
                                          const nsACString& aTitle,
                                          PRTime aDateAdded,
                                          const nsACString& aGUID,
-                                         const nsACString& aParentGUID)
+                                         const nsACString& aParentGUID,
+                                         uint16_t aSource)
 {
   if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
       mLiveUpdate != QUERYUPDATE_SIMPLE &&  mLiveUpdate != QUERYUPDATE_TIME) {
     nsresult rv = Refresh();
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
@@ -2859,17 +2860,18 @@ nsNavHistoryQueryResultNode::OnItemAdded
 
 NS_IMETHODIMP
 nsNavHistoryQueryResultNode::OnItemRemoved(int64_t aItemId,
                                            int64_t aParentId,
                                            int32_t aIndex,
                                            uint16_t aItemType,
                                            nsIURI* aURI,
                                            const nsACString& aGUID,
-                                           const nsACString& aParentGUID)
+                                           const nsACString& aParentGUID,
+                                           uint16_t aSource)
 {
   if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
       mLiveUpdate != QUERYUPDATE_SIMPLE && mLiveUpdate != QUERYUPDATE_TIME) {
     nsresult rv = Refresh();
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
@@ -2880,17 +2882,18 @@ nsNavHistoryQueryResultNode::OnItemChang
                                            const nsACString& aProperty,
                                            bool aIsAnnotationProperty,
                                            const nsACString& aNewValue,
                                            PRTime aLastModified,
                                            uint16_t aItemType,
                                            int64_t aParentId,
                                            const nsACString& aGUID,
                                            const nsACString& aParentGUID,
-                                           const nsACString& aOldValue)
+                                           const nsACString& aOldValue,
+                                           uint16_t aSource)
 {
   // History observers should not get OnItemChanged
   // but should get the corresponding history notifications instead.
   // For bookmark queries, "all bookmark" observers should get OnItemChanged.
   // For example, when a title of a bookmark changes, we want that to refresh.
 
   if (mLiveUpdate == QUERYUPDATE_COMPLEX_WITH_BOOKMARKS) {
     switch (aItemType) {
@@ -2927,17 +2930,17 @@ nsNavHistoryQueryResultNode::OnItemChang
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
                                                aIsAnnotationProperty,
                                                aNewValue, aLastModified,
                                                aItemType, aParentId, aGUID,
-                                               aParentGUID, aOldValue);
+                                               aParentGUID, aOldValue, aSource);
 }
 
 NS_IMETHODIMP
 nsNavHistoryQueryResultNode::OnItemVisited(int64_t aItemId,
                                            int64_t aVisitId,
                                            PRTime aTime,
                                            uint32_t aTransitionType,
                                            nsIURI* aURI,
@@ -3528,17 +3531,18 @@ NS_IMETHODIMP
 nsNavHistoryFolderResultNode::OnItemAdded(int64_t aItemId,
                                           int64_t aParentFolder,
                                           int32_t aIndex,
                                           uint16_t aItemType,
                                           nsIURI* aURI,
                                           const nsACString& aTitle,
                                           PRTime aDateAdded,
                                           const nsACString& aGUID,
-                                          const nsACString& aParentGUID)
+                                          const nsACString& aParentGUID,
+                                          uint16_t aSource)
 {
   MOZ_ASSERT(aParentFolder == mTargetFolderItemId, "Got wrong bookmark update");
 
   RESTART_AND_RETURN_IF_ASYNC_PENDING();
 
   {
     uint32_t index;
     nsNavHistoryResultNode* node = FindChildById(aItemId, &index);
@@ -3633,17 +3637,18 @@ nsNavHistoryFolderResultNode::OnItemAdde
 
 NS_IMETHODIMP
 nsNavHistoryFolderResultNode::OnItemRemoved(int64_t aItemId,
                                             int64_t aParentFolder,
                                             int32_t aIndex,
                                             uint16_t aItemType,
                                             nsIURI* aURI,
                                             const nsACString& aGUID,
-                                            const nsACString& aParentGUID)
+                                            const nsACString& aParentGUID,
+                                            uint16_t aSource)
 {
   // Folder shortcuts should not be notified removal of the target folder.
   MOZ_ASSERT_IF(mItemId != mTargetFolderItemId, aItemId != mTargetFolderItemId);
   // Concrete folders should not be notified their own removal.
   // Note aItemId may equal mItemId for recursive folder shortcuts.
   MOZ_ASSERT_IF(mItemId == mTargetFolderItemId, aItemId != mItemId);
 
   // In any case though, here we only care about the children removal.
@@ -3693,17 +3698,18 @@ nsNavHistoryResultNode::OnItemChanged(in
                                       const nsACString& aProperty,
                                       bool aIsAnnotationProperty,
                                       const nsACString& aNewValue,
                                       PRTime aLastModified,
                                       uint16_t aItemType,
                                       int64_t aParentId,
                                       const nsACString& aGUID,
                                       const nsACString& aParentGUID,
-                                      const nsACString& aOldValue)
+                                      const nsACString& aOldValue,
+                                      uint16_t aSource)
 {
   if (aItemId != mItemId)
     return NS_OK;
 
   mLastModified = aLastModified;
 
   nsNavHistoryResult* result = GetResult();
   NS_ENSURE_STATE(result);
@@ -3784,25 +3790,26 @@ nsNavHistoryFolderResultNode::OnItemChan
                                             const nsACString& aProperty,
                                             bool aIsAnnotationProperty,
                                             const nsACString& aNewValue,
                                             PRTime aLastModified,
                                             uint16_t aItemType,
                                             int64_t aParentId,
                                             const nsACString& aGUID,
                                             const nsACString& aParentGUID,
-                                            const nsACString& aOldValue)
+                                            const nsACString& aOldValue,
+                                            uint16_t aSource)
 {
   RESTART_AND_RETURN_IF_ASYNC_PENDING();
 
   return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
                                                aIsAnnotationProperty,
                                                aNewValue, aLastModified,
                                                aItemType, aParentId, aGUID,
-                                               aParentGUID, aOldValue);
+                                               aParentGUID, aOldValue, aSource);
 }
 
 /**
  * Updates visit count and last visit time and refreshes.
  */
 NS_IMETHODIMP
 nsNavHistoryFolderResultNode::OnItemVisited(int64_t aItemId,
                                             int64_t aVisitId,
@@ -3945,22 +3952,22 @@ nsNavHistoryFolderResultNode::OnItemMove
       NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
       nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(itemURI));
       NS_ENSURE_SUCCESS(rv, rv);
       rv = bookmarks->GetItemTitle(aItemId, itemTitle);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     if (aOldParent == mTargetFolderItemId) {
       OnItemRemoved(aItemId, aOldParent, aOldIndex, aItemType, itemURI,
-                    aGUID, aOldParentGUID);
+                    aGUID, aOldParentGUID, nsINavBookmarksService::SOURCE_DEFAULT);
     }
     if (aNewParent == mTargetFolderItemId) {
       OnItemAdded(aItemId, aNewParent, aNewIndex, aItemType, itemURI, itemTitle,
                   RoundedPRNow(), // This is a dummy dateAdded, not the real value.
-                  aGUID, aNewParentGUID);
+                  aGUID, aNewParentGUID, nsINavBookmarksService::SOURCE_DEFAULT);
     }
   }
   return NS_OK;
 }
 
 
 /**
  * Separator nodes do not hold any data.
@@ -4429,78 +4436,81 @@ NS_IMETHODIMP
 nsNavHistoryResult::OnItemAdded(int64_t aItemId,
                                 int64_t aParentId,
                                 int32_t aIndex,
                                 uint16_t aItemType,
                                 nsIURI* aURI,
                                 const nsACString& aTitle,
                                 PRTime aDateAdded,
                                 const nsACString& aGUID,
-                                const nsACString& aParentGUID)
+                                const nsACString& aParentGUID,
+                                uint16_t aSource)
 {
   NS_ENSURE_ARG(aItemType != nsINavBookmarksService::TYPE_BOOKMARK ||
                 aURI);
 
   ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
     OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
-                aGUID, aParentGUID)
+                aGUID, aParentGUID, aSource)
   );
   ENUMERATE_HISTORY_OBSERVERS(
     OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
-                aGUID, aParentGUID)
+                aGUID, aParentGUID, aSource)
   );
   ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
     OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
-                aGUID, aParentGUID)
+                aGUID, aParentGUID, aSource)
   );
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavHistoryResult::OnItemRemoved(int64_t aItemId,
                                   int64_t aParentId,
                                   int32_t aIndex,
                                   uint16_t aItemType,
                                   nsIURI* aURI,
                                   const nsACString& aGUID,
-                                  const nsACString& aParentGUID)
+                                  const nsACString& aParentGUID,
+                                  uint16_t aSource)
 {
   NS_ENSURE_ARG(aItemType != nsINavBookmarksService::TYPE_BOOKMARK ||
                 aURI);
 
   ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
       OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
-                    aParentGUID));
+                    aParentGUID, aSource));
   ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
       OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
-                    aParentGUID));
+                    aParentGUID, aSource));
   ENUMERATE_HISTORY_OBSERVERS(
       OnItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGUID,
-                    aParentGUID));
+                    aParentGUID, aSource));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavHistoryResult::OnItemChanged(int64_t aItemId,
                                   const nsACString &aProperty,
                                   bool aIsAnnotationProperty,
                                   const nsACString &aNewValue,
                                   PRTime aLastModified,
                                   uint16_t aItemType,
                                   int64_t aParentId,
                                   const nsACString& aGUID,
                                   const nsACString& aParentGUID,
-                                  const nsACString& aOldValue)
+                                  const nsACString& aOldValue,
+                                  uint16_t aSource)
 {
   ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
     OnItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue,
                   aLastModified, aItemType, aParentId, aGUID, aParentGUID,
-                  aOldValue));
+                  aOldValue, aSource));
 
   // Note: folder-nodes set their own bookmark observer only once they're
   // opened, meaning we cannot optimize this code path for changes done to
   // folder-nodes.
 
   FolderObserverList* list = BookmarkFolderObserversForId(aParentId, false);
   if (!list)
     return NS_OK;
@@ -4514,17 +4524,17 @@ nsNavHistoryResult::OnItemChanged(int64_
       // if ExcludeItems is true we don't update non visible items
       bool excludeItems = (mRootNode->mOptions->ExcludeItems()) ||
                              folder->mOptions->ExcludeItems();
       if (node &&
           (!excludeItems || !(node->IsURI() || node->IsSeparator())) &&
           folder->StartIncrementalUpdate()) {
         node->OnItemChanged(aItemId, aProperty, aIsAnnotationProperty,
                             aNewValue, aLastModified, aItemType, aParentId,
-                            aGUID, aParentGUID, aOldValue);
+                            aGUID, aParentGUID, aOldValue, aSource);
       }
     }
   }
 
   // Note: we do NOT call history observers in this case.  This notification is
   // the same as other history notification, except that here we know the item
   // is a bookmark.  History observers will handle the history notification
   // instead.
--- a/toolkit/components/places/nsNavHistoryResult.h
+++ b/toolkit/components/places/nsNavHistoryResult.h
@@ -286,17 +286,18 @@ public:
                            const nsACString &aProperty,
                            bool aIsAnnotationProperty,
                            const nsACString &aValue,
                            PRTime aNewLastModified,
                            uint16_t aItemType,
                            int64_t aParentId,
                            const nsACString& aGUID,
                            const nsACString& aParentGUID,
-                           const nsACString &aOldValue);
+                           const nsACString &aOldValue,
+                           uint16_t aSource);
 
 protected:
   virtual ~nsNavHistoryResultNode() {}
 
 public:
 
   nsNavHistoryResult* GetResult();
   nsNavHistoryQueryOptions* GetGeneratingOptions();
--- a/toolkit/components/places/nsTaggingService.js
+++ b/toolkit/components/places/nsTaggingService.js
@@ -29,19 +29,20 @@ function TaggingService() {
 TaggingService.prototype = {
   /**
    * Creates a tag container under the tags-root with the given name.
    *
    * @param aTagName
    *        the name for the new tag.
    * @returns the id of the new tag container.
    */
-  _createTag: function TS__createTag(aTagName) {
+  _createTag: function TS__createTag(aTagName, aSource) {
     var newFolderId = PlacesUtils.bookmarks.createFolder(
-      PlacesUtils.tagsFolderId, aTagName, PlacesUtils.bookmarks.DEFAULT_INDEX
+      PlacesUtils.tagsFolderId, aTagName, PlacesUtils.bookmarks.DEFAULT_INDEX,
+      /* aGuid */ null, aSource
     );
     // Add the folder to our local cache, so we can avoid doing this in the
     // observer that would have to check itemType.
     this._tagFolders[newFolderId] = aTagName;
 
     return newFolderId;
   },
 
@@ -130,46 +131,47 @@ TaggingService.prototype = {
       else {
         throw Cr.NS_ERROR_INVALID_ARG;
       }
       return tag;
     });
   },
 
   // nsITaggingService
-  tagURI: function TS_tagURI(aURI, aTags)
+  tagURI: function TS_tagURI(aURI, aTags, aSource)
   {
     if (!aURI || !aTags || !Array.isArray(aTags)) {
       throw Cr.NS_ERROR_INVALID_ARG;
     }
 
     // This also does some input validation.
     let tags = this._convertInputMixedTagsArray(aTags, true);
 
     let taggingFunction = () => {
       for (let tag of tags) {
         if (tag.id == -1) {
           // Tag does not exist yet, create it.
-          this._createTag(tag.name);
+          this._createTag(tag.name, aSource);
         }
 
         if (this._getItemIdForTaggedURI(aURI, tag.name) == -1) {
           // The provided URI is not yet tagged, add a tag for it.
           // Note that bookmarks under tag containers must have null titles.
           PlacesUtils.bookmarks.insertBookmark(
-            tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX, null
+            tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX,
+            /* aTitle */ null, /* aGuid */ null, aSource
           );
         }
 
         // Try to preserve user's tag name casing.
         // Rename the tag container so the Places view matches the most-recent
         // user-typed value.
         if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) {
           // this._tagFolders is updated by the bookmarks observer.
-          PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name);
+          PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name, aSource);
         }
       }
     };
 
     // Use a batch only if creating more than 2 tags.
     if (tags.length < 3) {
       taggingFunction();
     } else {
@@ -178,17 +180,17 @@ TaggingService.prototype = {
   },
 
   /**
    * Removes the tag container from the tags root if the given tag is empty.
    *
    * @param aTagId
    *        the itemId of the tag element under the tags root
    */
-  _removeTagIfEmpty: function TS__removeTagIfEmpty(aTagId) {
+  _removeTagIfEmpty: function TS__removeTagIfEmpty(aTagId, aSource) {
     let count = 0;
     let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
                                 .DBConnection;
     let stmt = db.createStatement(
       `SELECT count(*) AS count FROM moz_bookmarks
        WHERE parent = :tag_id`
     );
     stmt.params.tag_id = aTagId;
@@ -197,22 +199,22 @@ TaggingService.prototype = {
         count = stmt.row.count;
       }
     }
     finally {
       stmt.finalize();
     }
 
     if (count == 0) {
-      PlacesUtils.bookmarks.removeItem(aTagId);
+      PlacesUtils.bookmarks.removeItem(aTagId, aSource);
     }
   },
 
   // nsITaggingService
-  untagURI: function TS_untagURI(aURI, aTags)
+  untagURI: function TS_untagURI(aURI, aTags, aSource)
   {
     if (!aURI || (aTags && !Array.isArray(aTags))) {
       throw Cr.NS_ERROR_INVALID_ARG;
     }
 
     if (!aTags) {
       // Passing null should clear all tags for aURI, see the IDL.
       // XXXmano: write a perf-sensitive version of this code path...
@@ -230,17 +232,17 @@ TaggingService.prototype = {
 
     let untaggingFunction = () => {
       for (let tag of tags) {
         if (tag.id != -1) {
           // A tag could exist.
           let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
           if (itemId != -1) {
             // There is a tagged item.
-            PlacesUtils.bookmarks.removeItem(itemId);
+            PlacesUtils.bookmarks.removeItem(itemId, aSource);
           }
         }
       }
     };
 
     // Use a batch only if creating more than 2 tags.
     if (tags.length < 3) {
       untaggingFunction();
@@ -409,36 +411,37 @@ TaggingService.prototype = {
     if (aFolderId != PlacesUtils.tagsFolderId ||
         aItemType != PlacesUtils.bookmarks.TYPE_FOLDER)
       return;
 
     this._tagFolders[aItemId] = aTitle;
   },
 
   onItemRemoved: function TS_onItemRemoved(aItemId, aFolderId, aIndex,
-                                           aItemType, aURI) {
+                                           aItemType, aURI, aGuid, aParentGuid,
+                                           aSource) {
     // Item is a tag folder.
     if (aFolderId == PlacesUtils.tagsFolderId && this._tagFolders[aItemId]) {
       delete this._tagFolders[aItemId];
     }
     // Item is a bookmark that was removed from a non-tag folder.
     else if (aURI && !this._tagFolders[aFolderId]) {
       // If the only bookmark items now associated with the bookmark's URI are
       // contained in tag folders, the URI is no longer properly bookmarked, so
       // untag it.
       let itemIds = this._getTaggedItemIdsIfUnbookmarkedURI(aURI);
       for (let i = 0; i < itemIds.length; i++) {
         try {
-          PlacesUtils.bookmarks.removeItem(itemIds[i]);
+          PlacesUtils.bookmarks.removeItem(itemIds[i], aSource);
         } catch (ex) {}
       }
     }
     // Item is a tag entry.  If this was the last entry for this tag, remove it.
     else if (aURI && this._tagFolders[aFolderId]) {
-      this._removeTagIfEmpty(aFolderId);
+      this._removeTagIfEmpty(aFolderId, aSource);
     }
   },
 
   onItemChanged: function TS_onItemChanged(aItemId, aProperty,
                                            aIsAnnotationProperty, aNewValue,
                                            aLastModified, aItemType) {
     if (aProperty == "title" && this._tagFolders[aItemId])
       this._tagFolders[aItemId] = aNewValue;