Bug 1285408 - Add source tracking for bookmark import and restore. r?markh draft
authorKit Cambridge <kcambridge@mozilla.com>
Wed, 10 Aug 2016 12:41:11 -0700
changeset 399368 fba21b93db727111e67267ae705b0fa3175f47a8
parent 399367 a69fa1d2052ad109f016390322a7617cfb24735b
child 399369 abc9fdb3cf0186a20cfbd1306ee134c614b2f167
push id25819
push userkcambridge@mozilla.com
push dateWed, 10 Aug 2016 23:35:35 +0000
reviewersmarkh
bugs1285408
milestone51.0a1
Bug 1285408 - Add source tracking for bookmark import and restore. r?markh MozReview-Commit-ID: FgsA1rHBQv2
toolkit/components/places/BookmarkHTMLUtils.jsm
toolkit/components/places/BookmarkJSONUtils.jsm
toolkit/components/places/Bookmarks.jsm
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/nsINavBookmarksService.idl
toolkit/components/places/nsNavBookmarks.cpp
--- a/toolkit/components/places/BookmarkHTMLUtils.jsm
+++ b/toolkit/components/places/BookmarkHTMLUtils.jsm
@@ -309,16 +309,20 @@ function Frame(aFrameId) {
    * Used to override the values set by insertBookmark, createFolder, etc.
    */
   this.previousDateAdded = 0;
   this.previousLastModifiedDate = 0;
 }
 
 function BookmarkImporter(aInitialImport) {
   this._isImportDefaults = aInitialImport;
+  // The bookmark change source, used to determine the sync status and change
+  // counter.
+  this._source = aInitialImport ? PlacesUtils.bookmarks.SOURCE_IMPORT_REPLACE :
+                                  PlacesUtils.bookmarks.SOURCE_IMPORT;
   this._frames = new Array();
   this._frames.push(new Frame(PlacesUtils.bookmarksMenuFolderId));
 }
 
 BookmarkImporter.prototype = {
 
   _safeTrim: function safeTrim(aStr) {
     return aStr ? aStr.trim() : aStr;
@@ -344,17 +348,18 @@ BookmarkImporter.prototype = {
     let containerType = frame.lastContainerType;
 
     switch (containerType) {
       case Container_Normal:
         // append a new folder
         containerId =
           PlacesUtils.bookmarks.createFolder(frame.containerId,
                                              containerTitle,
-                                             PlacesUtils.bookmarks.DEFAULT_INDEX);
+                                             PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                             /* aGuid */ null, this._source);
         break;
       case Container_Places:
         containerId = PlacesUtils.placesRootId;
         break;
       case Container_Menu:
         containerId = PlacesUtils.bookmarksMenuFolderId;
         break;
       case Container_Unfiled:
@@ -365,24 +370,24 @@ BookmarkImporter.prototype = {
         break;
       default:
         // NOT REACHED
         throw new Error("Unreached");
     }
 
     if (frame.previousDateAdded > 0) {
       try {
-        PlacesUtils.bookmarks.setItemDateAdded(containerId, frame.previousDateAdded);
+        PlacesUtils.bookmarks.setItemDateAdded(containerId, frame.previousDateAdded, this._source);
       } catch (e) {
       }
       frame.previousDateAdded = 0;
     }
     if (frame.previousLastModifiedDate > 0) {
       try {
-        PlacesUtils.bookmarks.setItemLastModified(containerId, frame.previousLastModifiedDate);
+        PlacesUtils.bookmarks.setItemLastModified(containerId, frame.previousLastModifiedDate, this._source);
       } catch (e) {
       }
       // don't clear last-modified, in case there's a description
     }
 
     frame.previousId = containerId;
 
     this._frames.push(new Frame(containerId));
@@ -396,17 +401,19 @@ BookmarkImporter.prototype = {
    *       We also don't import ADD_DATE or LAST_MODIFIED for separators because
    *       pre-Places bookmarks did not support them.
    */
   _handleSeparator: function handleSeparator(aElt) {
     let frame = this._curFrame;
     try {
       frame.previousId =
         PlacesUtils.bookmarks.insertSeparator(frame.containerId,
-                                              PlacesUtils.bookmarks.DEFAULT_INDEX);
+                                              PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                              /* aGuid */ null,
+                                              this._source);
     } catch (e) {}
   },
 
   /**
    * Handles <H1>. We check for the attribute PLACES_ROOT and reset the
    * container id if it's found. Otherwise, the default bookmark menu
    * root is assumed and imported things will go into the bookmarks menu.
    */
@@ -559,35 +566,37 @@ BookmarkImporter.prototype = {
     }
 
     // Create the bookmark.  The title is unknown for now, we will set it later.
     try {
       frame.previousId =
         PlacesUtils.bookmarks.insertBookmark(frame.containerId,
                                              frame.previousLink,
                                              PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                             "");
+                                             /* aTitle */ "",
+                                             /* aGuid */ null,
+                                             this._source);
     } catch (e) {
       return;
     }
 
     // Set the date added value, if we have it.
     if (dateAdded) {
       try {
         PlacesUtils.bookmarks.setItemDateAdded(frame.previousId,
-          this._convertImportedDateToInternalDate(dateAdded));
+          this._convertImportedDateToInternalDate(dateAdded), this._source);
       } catch (e) {
       }
     }
 
     // Adds tags to the URI, if there are any.
     if (tags) {
       try {
         let tagsArray = tags.split(",");
-        PlacesUtils.tagging.tagURI(frame.previousLink, tagsArray);
+        PlacesUtils.tagging.tagURI(frame.previousLink, tagsArray, this._source);
       } catch (e) {
       }
     }
 
     // Save the favicon.
     if (icon || iconUri) {
       let iconUriObject;
       try {
@@ -601,35 +610,37 @@ BookmarkImporter.prototype = {
         }
       }
     }
 
     // Save the keyword.
     if (keyword) {
       let kwPromise = PlacesUtils.keywords.insert({ keyword,
                                                     url: frame.previousLink.spec,
-                                                    postData });
+                                                    postData,
+                                                    source: this._source });
       this._importPromises.push(kwPromise);
     }
 
     // Set load-in-sidebar annotation for the bookmark.
     if (webPanel && webPanel.toLowerCase() == "true") {
       try {
         PlacesUtils.annotations.setItemAnnotation(frame.previousId,
                                                   LOAD_IN_SIDEBAR_ANNO,
                                                   1,
                                                   0,
-                                                  PlacesUtils.annotations.EXPIRE_NEVER);
+                                                  PlacesUtils.annotations.EXPIRE_NEVER,
+                                                  this._source);
       } catch (e) {
       }
     }
 
     // Import last charset.
     if (lastCharset) {
-      let chPromise = PlacesUtils.setCharsetForURI(frame.previousLink, lastCharset);
+      let chPromise = PlacesUtils.setCharsetForURI(frame.previousLink, lastCharset, this._source);
       this._importPromises.push(chPromise);
     }
   },
 
   _handleContainerBegin: function handleContainerBegin() {
     this._curFrame.containerNesting++;
   },
 
@@ -643,17 +654,18 @@ BookmarkImporter.prototype = {
     if (frame.containerNesting > 0)
       frame.containerNesting --;
     if (this._frames.length > 1 && frame.containerNesting == 0) {
       // we also need to re-set the imported last-modified date here. Otherwise
       // the addition of items will override the imported field.
       let prevFrame = this._previousFrame;
       if (prevFrame.previousLastModifiedDate > 0) {
         PlacesUtils.bookmarks.setItemLastModified(frame.containerId,
-                                                  prevFrame.previousLastModifiedDate);
+                                                  prevFrame.previousLastModifiedDate,
+                                                  this._source);
       }
       this._frames.pop();
     }
   },
 
   /**
    * Creates the new frame for this heading now that we know the name of the
    * container (tokens since the heading open tag will have been placed in
@@ -675,32 +687,35 @@ BookmarkImporter.prototype = {
         // The is a live bookmark.  We create it here since in HandleLinkBegin we
         // don't know the title.
         let lmPromise = PlacesUtils.livemarks.addLivemark({
           "title": frame.previousText,
           "parentId": frame.containerId,
           "index": PlacesUtils.bookmarks.DEFAULT_INDEX,
           "feedURI": frame.previousFeed,
           "siteURI": frame.previousLink,
+          "source": this._source,
         });
         this._importPromises.push(lmPromise);
       } else if (frame.previousLink) {
         // This is a common bookmark.
         PlacesUtils.bookmarks.setItemTitle(frame.previousId,
-                                           frame.previousText);
+                                           frame.previousText,
+                                           this._source);
       }
     } catch (e) {
     }
 
 
     // Set last modified date as the last change.
     if (frame.previousId > 0 && frame.previousLastModifiedDate > 0) {
       try {
         PlacesUtils.bookmarks.setItemLastModified(frame.previousId,
-                                                  frame.previousLastModifiedDate);
+                                                  frame.previousLastModifiedDate,
+                                                  this._source);
       } catch (e) {
       }
       // Note: don't clear previousLastModifiedDate, because if this item has a
       // description, we'll need to set it again.
     }
 
     frame.previousText = "";
 
@@ -753,17 +768,18 @@ BookmarkImporter.prototype = {
                                          : frame.previousId;
 
         try {
           if (!PlacesUtils.annotations.itemHasAnnotation(itemId, DESCRIPTION_ANNO)) {
             PlacesUtils.annotations.setItemAnnotation(itemId,
                                                       DESCRIPTION_ANNO,
                                                       frame.previousText,
                                                       0,
-                                                      PlacesUtils.annotations.EXPIRE_NEVER);
+                                                      PlacesUtils.annotations.EXPIRE_NEVER,
+                                                      this._source);
           }
         } catch (e) {
         }
         frame.previousText = "";
 
         // Set last-modified a 2nd time for all items with descriptions
         // we need to set last-modified as the *last* step in processing
         // any item type in the bookmarks.html file, so that we do
@@ -778,17 +794,18 @@ BookmarkImporter.prototype = {
         let lastModified;
         if (!frame.previousLink) {
           lastModified = this._previousFrame.previousLastModifiedDate;
         } else {
           lastModified = frame.previousLastModifiedDate;
         }
 
         if (itemId > 0 && lastModified > 0) {
-          PlacesUtils.bookmarks.setItemLastModified(itemId, lastModified);
+          PlacesUtils.bookmarks.setItemLastModified(itemId, lastModified,
+                                                    this._source);
         }
       }
       frame.inDescription = false;
     }
 
     if (aElt.namespaceURI != "http://www.w3.org/1999/xhtml") {
       return;
     }
@@ -889,19 +906,19 @@ BookmarkImporter.prototype = {
   },
 
   runBatched: function runBatched(aDoc) {
     if (!aDoc) {
       return;
     }
 
     if (this._isImportDefaults) {
-      PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarksMenuFolderId);
-      PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId);
-      PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+      PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.bookmarksMenuFolderId, this._source);
+      PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.toolbarFolderId, this._source);
+      PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId, this._source);
     }
 
     let current = aDoc;
     let next;
     for (;;) {
       switch (current.nodeType) {
         case Ci.nsIDOMNode.ELEMENT_NODE:
           this._openContainer(current);
--- a/toolkit/components/places/BookmarkJSONUtils.jsm
+++ b/toolkit/components/places/BookmarkJSONUtils.jsm
@@ -171,16 +171,20 @@ this.BookmarkJSONUtils = Object.freeze({
       yield OS.File.writeAtomic(aFilePath, jsonString, writeOptions);
       return { count: count, hash: hash };
     });
   }
 });
 
 function BookmarkImporter(aReplace) {
   this._replace = aReplace;
+  // The bookmark change source, used to determine the sync status and change
+  // counter.
+  this._source = aReplace ? PlacesUtils.bookmarks.SOURCE_IMPORT_REPLACE :
+                            PlacesUtils.bookmarks.SOURCE_IMPORT;
 }
 BookmarkImporter.prototype = {
   /**
    * Import bookmarks from a url.
    *
    * @param aSpec
    *        url of the bookmark data.
    *
@@ -283,19 +287,20 @@ BookmarkImporter.prototype = {
                 childIds.push(childId);
               }
             }
             root.containerOpen = false;
 
             for (let i = 0; i < childIds.length; i++) {
               let rootItemId = childIds[i];
               if (PlacesUtils.isRootItem(rootItemId)) {
-                PlacesUtils.bookmarks.removeFolderChildren(rootItemId);
+                PlacesUtils.bookmarks.removeFolderChildren(rootItemId,
+                                                           this._source);
               } else {
-                PlacesUtils.bookmarks.removeItem(rootItemId);
+                PlacesUtils.bookmarks.removeItem(rootItemId, this._source);
               }
             }
           }
 
           let searchIds = [];
           let folderIdMap = [];
 
           for (let node of batch.nodes) {
@@ -336,17 +341,17 @@ BookmarkImporter.prototype = {
             }
           }
 
           // Fixup imported place: uris that contain folders
           searchIds.forEach(function(aId) {
             let oldURI = PlacesUtils.bookmarks.getBookmarkURI(aId);
             let uri = fixupQuery(oldURI, folderIdMap);
             if (!uri.equals(oldURI)) {
-              PlacesUtils.bookmarks.changeBookmarkURI(aId, uri);
+              PlacesUtils.bookmarks.changeBookmarkURI(aId, uri, this._source);
             }
           });
 
           deferred.resolve();
         }.bind(this)
       };
 
       PlacesUtils.bookmarks.runInBatchMode(batch, null);
@@ -382,17 +387,17 @@ BookmarkImporter.prototype = {
     switch (aData.type) {
       case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
         if (aContainer == PlacesUtils.tagsFolderId) {
           // Node is a tag
           if (aData.children) {
             aData.children.forEach(function(aChild) {
               try {
                 PlacesUtils.tagging.tagURI(
-                  NetUtil.newURI(aChild.uri), [aData.title]);
+                  NetUtil.newURI(aChild.uri), [aData.title], this._source);
               } catch (ex) {
                 // Invalid tag child, skip it
               }
             });
             return [folderIdMap, searchIds];
           }
         } else if (aData.annos &&
                    aData.annos.some(anno => anno.name == PlacesUtils.LMANNO_FEEDURI)) {
@@ -415,29 +420,32 @@ BookmarkImporter.prototype = {
           if (feedURI) {
             let lmPromise = PlacesUtils.livemarks.addLivemark({
               title: aData.title,
               feedURI: feedURI,
               parentId: aContainer,
               index: aIndex,
               lastModified: aData.lastModified,
               siteURI: siteURI,
-              guid: aData.guid
-            }).then(function (aLivemark) {
+              guid: aData.guid,
+              source: this._source
+            }).then(aLivemark => {
               let id = aLivemark.id;
               if (aData.dateAdded)
-                PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded);
+                PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded,
+                                                       this._source);
               if (aData.annos && aData.annos.length)
-                PlacesUtils.setAnnotationsForItem(id, aData.annos);
+                PlacesUtils.setAnnotationsForItem(id, aData.annos,
+                                                  this._source);
             });
             this._importPromises.push(lmPromise);
           }
         } else {
           id = PlacesUtils.bookmarks.createFolder(
-                 aContainer, aData.title, aIndex, aData.guid);
+                 aContainer, aData.title, aIndex, aData.guid, this._source);
           folderIdMap[aData.id] = id;
           // Process children
           if (aData.children) {
             for (let i = 0; i < aData.children.length; i++) {
               let child = aData.children[i];
               let [folders, searches] =
                 this.importJSONNode(child, id, i, aContainer);
               for (let j = 0; j < folders.length; j++) {
@@ -446,35 +454,36 @@ BookmarkImporter.prototype = {
               }
               searchIds = searchIds.concat(searches);
             }
           }
         }
         break;
       case PlacesUtils.TYPE_X_MOZ_PLACE:
         id = PlacesUtils.bookmarks.insertBookmark(
-               aContainer, NetUtil.newURI(aData.uri), aIndex, aData.title, aData.guid);
+               aContainer, NetUtil.newURI(aData.uri), aIndex, aData.title, aData.guid, this._source);
         if (aData.keyword) {
           // POST data could be set in 2 ways:
           // 1. new backups have a postData property
           // 2. old backups have an item annotation
           let postDataAnno = aData.annos &&
                              aData.annos.find(anno => anno.name == PlacesUtils.POST_DATA_ANNO);
           let postData = aData.postData || (postDataAnno && postDataAnno.value);
           let kwPromise = PlacesUtils.keywords.insert({ keyword: aData.keyword,
                                                         url: aData.uri,
-                                                        postData });
+                                                        postData,
+                                                        source: this._source });
           this._importPromises.push(kwPromise);
         }
         if (aData.tags) {
           let tags = aData.tags.split(",").filter(aTag =>
             aTag.length <= Ci.nsITaggingService.MAX_TAG_LENGTH);
           if (tags.length) {
             try {
-              PlacesUtils.tagging.tagURI(NetUtil.newURI(aData.uri), tags);
+              PlacesUtils.tagging.tagURI(NetUtil.newURI(aData.uri), tags, this._source);
             } catch (ex) {
               // Invalid tag child, skip it.
               Cu.reportError(`Unable to set tags "${tags.join(", ")}" for ${aData.uri}: ${ex}`);
             }
           }
         }
         if (aData.charset) {
           PlacesUtils.annotations.setPageAnnotation(
@@ -505,31 +514,33 @@ BookmarkImporter.prototype = {
               PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
               Services.scriptSecurityManager.getSystemPrincipal());
           } catch (ex) {
             Components.utils.reportError("Failed to import favicon URI:" + ex);
           }
         }
         break;
       case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
-        id = PlacesUtils.bookmarks.insertSeparator(aContainer, aIndex, aData.guid);
+        id = PlacesUtils.bookmarks.insertSeparator(aContainer, aIndex, aData.guid, this._source);
         break;
       default:
         // Unknown node type
     }
 
     // Set generic properties, valid for all nodes
     if (id != -1 && aContainer != PlacesUtils.tagsFolderId &&
         aGrandParentId != PlacesUtils.tagsFolderId) {
       if (aData.dateAdded)
-        PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded);
+        PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded,
+                                               this._source);
       if (aData.lastModified)
-        PlacesUtils.bookmarks.setItemLastModified(id, aData.lastModified);
+        PlacesUtils.bookmarks.setItemLastModified(id, aData.lastModified,
+                                                  this._source);
       if (aData.annos && aData.annos.length)
-        PlacesUtils.setAnnotationsForItem(id, aData.annos);
+        PlacesUtils.setAnnotationsForItem(id, aData.annos, this._source);
     }
 
     return [folderIdMap, searchIds];
   }
 }
 
 function notifyObservers(topic) {
   Services.obs.notifyObservers(null, topic, "json");
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -100,16 +100,18 @@ var Bookmarks = Object.freeze({
 
   /**
    * Bookmark change source constants, passed as optional properties and
    * forwarded to observers. See nsINavBookmarksService.idl for an explanation.
    */
   SOURCES: {
     DEFAULT: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
     SYNC: Ci.nsINavBookmarksService.SOURCE_SYNC,
+    IMPORT: Ci.nsINavBookmarksService.SOURCE_IMPORT,
+    IMPORT_REPLACE: Ci.nsINavBookmarksService.SOURCE_IMPORT_REPLACE,
   },
 
   /**
    * Special GUIDs associated with bookmark roots.
    * It's guaranteed that the roots will always have these guids.
    */
 
    rootGuid:    "root________",
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -1060,29 +1060,29 @@ this.PlacesUtils = {
    * Annotate an item with a batch of annotations.
    * @param aItemId
    *        The identifier of the item for which annotations are to be set
    * @param aAnnotations
    *        Array of objects, each containing the following properties:
    *        name, flags, expires.
    *        If the value for an annotation is not set it will be removed.
    */
-  setAnnotationsForItem: function PU_setAnnotationsForItem(aItemId, aAnnos) {
+  setAnnotationsForItem: function PU_setAnnotationsForItem(aItemId, aAnnos, aSource) {
     var annosvc = this.annotations;
 
     aAnnos.forEach(function(anno) {
       if (anno.value === undefined || anno.value === null) {
-        annosvc.removeItemAnnotation(aItemId, anno.name);
+        annosvc.removeItemAnnotation(aItemId, anno.name, aSource);
       }
       else {
         let flags = ("flags" in anno) ? anno.flags : 0;
         let expires = ("expires" in anno) ?
           anno.expires : Ci.nsIAnnotationService.EXPIRE_NEVER;
         annosvc.setItemAnnotation(aItemId, anno.name, anno.value, flags,
-                                  expires);
+                                  expires, aSource);
       }
     });
   },
 
   // Identifier getters for special folders.
   // You should use these everywhere PlacesUtils is available to avoid XPCOM
   // traversal just to get roots' ids.
   get placesRootId() {
@@ -2267,17 +2267,17 @@ var Keywords = {
    * @return {Promise}
    * @resolves when the removal is complete.
    */
   remove(keywordOrEntry) {
     if (typeof(keywordOrEntry) == "string")
       keywordOrEntry = { keyword: keywordOrEntry };
 
     if (keywordOrEntry === null || typeof(keywordOrEntry) != "object" ||
-        typeof keywordOrEntry.keyword != "string")
+        !keywordOrEntry.keyword || typeof keywordOrEntry.keyword != "string")
       throw new Error("Invalid keyword");
 
     let { keyword,
           source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } = keywordOrEntry;
     keyword = keywordOrEntry.keyword.trim().toLowerCase();
     return PlacesUtils.withConnectionWrapper("Keywords.remove",  Task.async(function*(db) {
       let cache = yield gKeywordsCachePromise;
       if (!cache.has(keyword))
--- a/toolkit/components/places/nsINavBookmarksService.idl
+++ b/toolkit/components/places/nsINavBookmarksService.idl
@@ -286,21 +286,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.
+  // Sync and bookmarks import 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;
+  const unsigned short SOURCE_IMPORT = 2;
+  const unsigned short SOURCE_IMPORT_REPLACE = 3;
 
   /**
    * Inserts a child bookmark into the given folder.
    *
    *  @param aParentId
    *         The id of the parent folder
    *  @param aURI
    *         The URI to insert
@@ -421,21 +423,25 @@ interface nsINavBookmarksService : nsISu
    *  @param aParentId
    *         The id of the parent folder
    *  @param aIndex
    *         The separator's index under folder, or DEFAULT_INDEX to append
    *  @param [optional] aGuid
    *         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.
+   *  @param [optional] aSource
+   *         The change source, forwarded to all bookmark observers. Defaults
+   *         to SOURCE_DEFAULT.
    *  @return The ID of the new separator.
    *  @throws if aGuid is malformed.
    */
   long long insertSeparator(in long long aParentId, in long aIndex,
-                            [optional] in ACString aGuid);
+                            [optional] in ACString aGuid,
+                            [optional] in unsigned short aSource);
 
   /**
    * Get the itemId given the containing folder and the index.
    *  @param aParentId
    *         The id of the diret parent folder of the item
    *  @param aIndex
    *         The index of the item within the parent folder.
    *         Pass DEFAULT_INDEX for the last item.
@@ -472,18 +478,23 @@ interface nsINavBookmarksService : nsISu
   /**
    * Set the date added time for an item.
    *
    * @param aItemId
    *        the id of the item whose date added time should be updated.
    * @param aDateAdded
    *        the new date added value in microseconds.  Note that it is rounded
    *        down to milliseconds precision.
+   *  @param [optional] aSource
+   *         The change source, forwarded to all bookmark observers. Defaults
+   *         to SOURCE_DEFAULT.
    */
-  void setItemDateAdded(in long long aItemId, in PRTime aDateAdded);
+  void setItemDateAdded(in long long aItemId,
+                        in PRTime aDateAdded,
+                        [optional] in unsigned short aSource);
 
   /**
    * Get the date added time for an item.
    *
    * @param aItemId
    *        the id of the item whose date added time should be retrieved.
    *
    * @return the date added value in microseconds.
@@ -493,23 +504,28 @@ interface nsINavBookmarksService : nsISu
   /**
    * Set the last modified time for an item.
    *
    * @param aItemId
    *        the id of the item whose last modified time should be updated.
    * @param aLastModified
    *        the new last modified value in microseconds.  Note that it is
    *        rounded down to milliseconds precision.
+   * @param [optional] aSource
+   *        The change source, forwarded to all bookmark observers. Defaults
+   *        to SOURCE_DEFAULT.
    *
    * @note This is the only method that will send an itemChanged notification
    *       for the property.  lastModified will still be updated in
    *       any other method that changes an item property, but we will send
    *       the corresponding itemChanged notification instead.
    */
-  void setItemLastModified(in long long aItemId, in PRTime aLastModified);
+  void setItemLastModified(in long long aItemId,
+                           in PRTime aLastModified,
+                           [optional] in unsigned short aSource);
 
   /**
    * Get the last modified time for an item.
    *
    * @param aItemId
    *        the id of the item whose last modified time should be retrieved.
    *
    * @return the date added value in microseconds.
@@ -577,17 +593,19 @@ interface nsINavBookmarksService : nsISu
    */
   nsIURI getBookmarkedURIFor(in nsIURI aURI);
 
   /**
    * Change the bookmarked URI for a bookmark.
    * This changes which "place" the bookmark points at,
    * which means all annotations, etc are carried along.
    */
-  void changeBookmarkURI(in long long aItemId, in nsIURI aNewURI);
+  void changeBookmarkURI(in long long aItemId,
+                         in nsIURI aNewURI,
+                         [optional] in unsigned short aSource);
 
   /**
    * Get the parent folder's id for an item.
    */
   long long getFolderIdForItem(in long long aItemId);
 
   /**
    * Returns the list of bookmark ids that contain the given URI.
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -763,16 +763,17 @@ nsNavBookmarks::CreateContainerWithID(in
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::InsertSeparator(int64_t aParent,
                                 int32_t aIndex,
                                 const nsACString& aGUID,
+                                uint16_t aSource,
                                 int64_t* aNewItemId)
 {
   NS_ENSURE_ARG_MIN(aParent, 1);
   NS_ENSURE_ARG_MIN(aIndex, nsINavBookmarksService::DEFAULT_INDEX);
   NS_ENSURE_ARG_POINTER(aNewItemId);
 
   if (!aGUID.IsEmpty() && !IsValidGUID(aGUID))
     return NS_ERROR_INVALID_ARG;
@@ -1405,17 +1406,18 @@ nsNavBookmarks::SetItemDateInternal(enum
   // note, we are not notifying the observers
   // that the item has changed.
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsNavBookmarks::SetItemDateAdded(int64_t aItemId, PRTime aDateAdded)
+nsNavBookmarks::SetItemDateAdded(int64_t aItemId, PRTime aDateAdded,
+                                 uint16_t aSource)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
 
   BookmarkData bookmark;
   nsresult rv = FetchItemInfo(aItemId, bookmark);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Round here so that we notify with the right value.
@@ -1432,17 +1434,17 @@ nsNavBookmarks::SetItemDateAdded(int64_t
                                  false,
                                  nsPrintfCString("%lld", bookmark.dateAdded),
                                  bookmark.dateAdded,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
                                  bookmark.parentGuid,
                                  EmptyCString(),
-                                 SOURCE_DEFAULT));
+                                 aSource));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetItemDateAdded(int64_t aItemId, PRTime* _dateAdded)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
@@ -1453,17 +1455,18 @@ nsNavBookmarks::GetItemDateAdded(int64_t
   NS_ENSURE_SUCCESS(rv, rv);
 
   *_dateAdded = bookmark.dateAdded;
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified)
+nsNavBookmarks::SetItemLastModified(int64_t aItemId, PRTime aLastModified,
+                                    uint16_t aSource)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
 
   BookmarkData bookmark;
   nsresult rv = FetchItemInfo(aItemId, bookmark);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Round here so that we notify with the right value.
@@ -1480,17 +1483,17 @@ nsNavBookmarks::SetItemLastModified(int6
                                  false,
                                  nsPrintfCString("%lld", bookmark.lastModified),
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
                                  bookmark.parentGuid,
                                  EmptyCString(),
-                                 SOURCE_DEFAULT));
+                                 aSource));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetItemLastModified(int64_t aItemId, PRTime* _lastModified)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
@@ -1970,17 +1973,18 @@ nsNavBookmarks::GetBookmarkedURIFor(nsIU
   }
 
   // If there is no bookmarked origin, we will just return null.
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsNavBookmarks::ChangeBookmarkURI(int64_t aBookmarkId, nsIURI* aNewURI)
+nsNavBookmarks::ChangeBookmarkURI(int64_t aBookmarkId, nsIURI* aNewURI,
+                                  uint16_t aSource)
 {
   NS_ENSURE_ARG_MIN(aBookmarkId, 1);
   NS_ENSURE_ARG(aNewURI);
 
   BookmarkData bookmark;
   nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_ARG(bookmark.type == TYPE_BOOKMARK);
@@ -2036,17 +2040,17 @@ nsNavBookmarks::ChangeBookmarkURI(int64_
                                  false,
                                  spec,
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
                                  bookmark.parentGuid,
                                  bookmark.url,
-                                 SOURCE_DEFAULT));
+                                 aSource));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetFolderIdForItem(int64_t aItemId, int64_t* _parentId)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);