Bug 1150678 - Part 1: notify the old value in onItemChanged (only URI changes for now). r=ttaubert
☠☠ backed out by 6498bb256c33 ☠ ☠
authorMarco Bonardo <mbonardo@mozilla.com>
Wed, 05 Aug 2015 23:10:11 +0200
changeset 288066 9f1f1e11ca3774feaea4b29ea9fe37fa65de7cd6
parent 288065 53560cc91161fdd60bc0ca8e140b5b44917c0913
child 288067 1c24d237080c55ed699fbb8ff2532293b30a4d35
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert
bugs1150678
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1150678 - Part 1: notify the old value in onItemChanged (only URI changes for now). r=ttaubert
toolkit/components/places/Bookmarks.jsm
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/nsINavBookmarksService.idl
toolkit/components/places/nsNavBookmarks.cpp
toolkit/components/places/nsNavBookmarks.h
toolkit/components/places/nsNavHistoryResult.cpp
toolkit/components/places/nsNavHistoryResult.h
toolkit/components/places/tests/bookmarks/test_bookmarks.js
toolkit/components/places/tests/bookmarks/test_bookmarks_notifications.js
toolkit/components/places/tests/bookmarks/test_keywords.js
toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js
toolkit/components/places/tests/unit/test_keywords.js
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -182,17 +182,18 @@ let Bookmarks = Object.freeze({
 
       // If it's a tag, notify OnItemChanged to all bookmarks for this URL.
       let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
       if (isTagging) {
         for (let entry of (yield fetchBookmarksByURL(item))) {
           notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                                toPRTime(entry.lastModified),
                                                entry.type, entry._parentId,
-                                               entry.guid, entry.parentGuid ]);
+                                               entry.guid, entry.parentGuid,
+                                               "" ]);
         }
       }
 
       // Remove non-enumerable properties.
       return Object.assign({}, item);
     }.bind(this));
   },
 
@@ -320,35 +321,36 @@ let Bookmarks = Object.freeze({
             item.lastModified != updatedItem.lastModified) {
           notify(observers, "onItemChanged", [ updatedItem._id, "lastModified",
                                                false,
                                                `${toPRTime(updatedItem.lastModified)}`,
                                                toPRTime(updatedItem.lastModified),
                                                updatedItem.type,
                                                updatedItem._parentId,
                                                updatedItem.guid,
-                                               updatedItem.parentGuid ]);
+                                               updatedItem.parentGuid, "" ]);
         }
         if (updateInfo.hasOwnProperty("title")) {
           notify(observers, "onItemChanged", [ updatedItem._id, "title",
                                                false, updatedItem.title,
                                                toPRTime(updatedItem.lastModified),
                                                updatedItem.type,
                                                updatedItem._parentId,
                                                updatedItem.guid,
-                                               updatedItem.parentGuid ]);
+                                               updatedItem.parentGuid, "" ]);
         }
         if (updateInfo.hasOwnProperty("url")) {
           notify(observers, "onItemChanged", [ updatedItem._id, "uri",
                                                false, updatedItem.url.href,
                                                toPRTime(updatedItem.lastModified),
                                                updatedItem.type,
                                                updatedItem._parentId,
                                                updatedItem.guid,
-                                               updatedItem.parentGuid ]);
+                                               updatedItem.parentGuid,
+                                               item.url.href ]);
         }
         // If the item was moved, notify onItemMoved.
         if (item.parentGuid != updatedItem.parentGuid ||
             item.index != updatedItem.index) {
           notify(observers, "onItemMoved", [ updatedItem._id, item._parentId,
                                              item.index, updatedItem._parentId,
                                              updatedItem.index, updatedItem.type,
                                              updatedItem.guid, item.parentGuid,
@@ -405,17 +407,18 @@ let Bookmarks = Object.freeze({
                                            item.parentGuid ]);
 
       let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
       if (isUntagging) {
         for (let entry of (yield fetchBookmarksByURL(item))) {
           notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                                toPRTime(entry.lastModified),
                                                entry.type, entry._parentId,
-                                               entry.guid, entry.parentGuid ]);
+                                               entry.guid, entry.parentGuid,
+                                               "" ]);
         }
       }
 
       // Remove non-enumerable properties.
       return Object.assign({}, item);
     });
   },
 
@@ -867,17 +870,18 @@ function fetchBookmarkByPosition(info) {
   }));
 }
 
 function fetchBookmarksByURL(info) {
   return PlacesUtils.withConnectionWrapper("Bookmarks.jsm: fetchBookmarksByURL",
     Task.async(function*(db) {
 
     let rows = yield db.executeCached(
-      `SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
+      `/* do not warn (bug no): not worth to add an index */
+       SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
               b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
               b.id AS _id, b.parent AS _parentId,
               (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
               p.parent AS _grandParentId
        FROM moz_bookmarks b
        LEFT JOIN moz_bookmarks p ON p.id = b.parent
        LEFT JOIN moz_places h ON h.id = b.fk
        WHERE h.url = :url
@@ -1423,13 +1427,14 @@ Task.async(function* (db, folderGuids) {
                                          item.guid, item.parentGuid ]);
 
     let isUntagging = item._grandParentId == PlacesUtils.tagsFolderId;
     if (isUntagging) {
       for (let entry of (yield fetchBookmarksByURL(item))) {
         notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
                                              toPRTime(entry.lastModified),
                                              entry.type, entry._parentId,
-                                             entry.guid, entry.parentGuid ]);
+                                             entry.guid, entry.parentGuid,
+                                             "" ]);
       }
     }
   }
 });
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -112,17 +112,18 @@ function* notifyKeywordChange(url, keywo
   let observers = PlacesUtils.bookmarks.getObservers();
   gIgnoreKeywordNotifications = true;
   for (let bookmark of bookmarks) {
     notify(observers, "onItemChanged", [ bookmark.id, "keyword", false,
                                          keyword,
                                          bookmark.lastModified * 1000,
                                          bookmark.type,
                                          bookmark.parentId,
-                                         bookmark.guid, bookmark.parentGuid
+                                         bookmark.guid, bookmark.parentGuid,
+                                         ""
                                        ]);
   }
   gIgnoreKeywordNotifications = false;
 }
 
 /**
  * Serializes the given node in JSON format.
  *
--- a/toolkit/components/places/nsINavBookmarksService.idl
+++ b/toolkit/components/places/nsINavBookmarksService.idl
@@ -8,17 +8,17 @@
 interface nsIFile;
 interface nsIURI;
 interface nsITransaction;
 interface nsINavHistoryBatchCallback;
 
 /**
  * Observer for bookmarks changes.
  */
-[scriptable, uuid(8ab925f8-af9b-4837-afa0-ffed507212ce)]
+[scriptable, uuid(cff3efcc-e144-490d-9f23-8b6f6dd09e7f)]
 interface nsINavBookmarkObserver : nsISupports
 {
   /**
    * Notifies that a batch transaction has started.
    * Other notifications will be sent during the batch, but the observer is
    * guaranteed that onEndUpdateBatch() will be called at its completion.
    * During a batch the observer should do its best to reduce the work done to
    * handle notifications, since multiple changes are going to happen in a short
@@ -117,37 +117,51 @@ interface nsINavBookmarkObserver : nsISu
    * @param aItemType
    *        The type of the item to be removed (see TYPE_* constants below).
    * @param aParentId
    *        The id of the folder containing the item.
    * @param aGuid
    *        The unique ID associated with the item.
    * @param aParentGuid
    *        The unique ID associated with the item's parent.
+   * @param aOldValue
+   *        For certain properties, this is set to the new value of the
+   *        property (see the list below).
    *
    * @note List of values that may be associated with properties:
    *       aProperty     | aNewValue
    *       =====================================================================
-   *       cleartime      | Empty string (all visits to this item were removed).
+   *       cleartime     | Empty string (all visits to this item were removed).
    *       title         | The new title.
    *       favicon       | The "moz-anno" URL of the new favicon.
    *       uri           | new URL.
    *       tags          | Empty string (tags for this item changed)
    *       dateAdded     | PRTime (as string) when the item was first added.
    *       lastModified  | PRTime (as string) when the item was last modified.
+   *
+   *       aProperty     | aOldValue
+   *       =====================================================================
+   *       cleartime     | Empty string (currently unused).
+   *       title         | Empty string (currently unused).
+   *       favicon       | Empty string (currently unused).
+   *       uri           | old URL.
+   *       tags          | Empty string (currently unused).
+   *       dateAdded     | Empty string (currently unused).
+   *       lastModified  | Empty string (currently unused).
    */
   void onItemChanged(in long long aItemId,
                      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 ACString aParentGuid,
+                     in AUTF8String aOldValue);
 
   /**
    * 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
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -546,17 +546,18 @@ nsNavBookmarks::InsertBookmark(int64_t a
                        OnItemChanged(bookmarks[i].id,
                                      NS_LITERAL_CSTRING("tags"),
                                      false,
                                      EmptyCString(),
                                      bookmarks[i].lastModified,
                                      TYPE_BOOKMARK,
                                      bookmarks[i].parentId,
                                      bookmarks[i].guid,
-                                     bookmarks[i].parentGuid));
+                                     bookmarks[i].parentGuid,
+                                     EmptyCString()));
     }
   }
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
@@ -651,17 +652,18 @@ nsNavBookmarks::RemoveItem(int64_t aItem
                        OnItemChanged(bookmarks[i].id,
                                      NS_LITERAL_CSTRING("tags"),
                                      false,
                                      EmptyCString(),
                                      bookmarks[i].lastModified,
                                      TYPE_BOOKMARK,
                                      bookmarks[i].parentId,
                                      bookmarks[i].guid,
-                                     bookmarks[i].parentGuid));
+                                     bookmarks[i].parentGuid,
+                                     EmptyCString()));
     }
 
   }
 
   return NS_OK;
 }
 
 
@@ -1127,17 +1129,18 @@ nsNavBookmarks::RemoveFolderChildren(int
                          OnItemChanged(bookmarks[i].id,
                                        NS_LITERAL_CSTRING("tags"),
                                        false,
                                        EmptyCString(),
                                        bookmarks[i].lastModified,
                                        TYPE_BOOKMARK,
                                        bookmarks[i].parentId,
                                        bookmarks[i].guid,
-                                       bookmarks[i].parentGuid));
+                                       bookmarks[i].parentGuid,
+                                       EmptyCString()));
       }
     }
   }
 
   return NS_OK;
 }
 
 
@@ -1408,17 +1411,18 @@ nsNavBookmarks::SetItemDateAdded(int64_t
                    OnItemChanged(bookmark.id,
                                  NS_LITERAL_CSTRING("dateAdded"),
                                  false,
                                  nsPrintfCString("%lld", bookmark.dateAdded),
                                  bookmark.dateAdded,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
-                                 bookmark.parentGuid));
+                                 bookmark.parentGuid,
+                                 EmptyCString()));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetItemDateAdded(int64_t aItemId, PRTime* _dateAdded)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
@@ -1454,17 +1458,18 @@ nsNavBookmarks::SetItemLastModified(int6
                    OnItemChanged(bookmark.id,
                                  NS_LITERAL_CSTRING("lastModified"),
                                  false,
                                  nsPrintfCString("%lld", bookmark.lastModified),
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
-                                 bookmark.parentGuid));
+                                 bookmark.parentGuid,
+                                 EmptyCString()));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetItemLastModified(int64_t aItemId, PRTime* _lastModified)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
@@ -1522,17 +1527,18 @@ nsNavBookmarks::SetItemTitle(int64_t aIt
                    OnItemChanged(bookmark.id,
                                  NS_LITERAL_CSTRING("title"),
                                  false,
                                  title,
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
-                                 bookmark.parentGuid));
+                                 bookmark.parentGuid,
+                                 EmptyCString()));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetItemTitle(int64_t aItemId,
                              nsACString& _title)
 {
@@ -2005,17 +2011,18 @@ nsNavBookmarks::ChangeBookmarkURI(int64_
                    OnItemChanged(bookmark.id,
                                  NS_LITERAL_CSTRING("uri"),
                                  false,
                                  spec,
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
-                                 bookmark.parentGuid));
+                                 bookmark.parentGuid,
+                                 bookmark.url));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetFolderIdForItem(int64_t aItemId, int64_t* _parentId)
 {
   NS_ENSURE_ARG_MIN(aItemId, 1);
@@ -2039,16 +2046,17 @@ nsNavBookmarks::GetBookmarkIdsForURITArr
                                            bool aSkipTags)
 {
   NS_ENSURE_ARG(aURI);
 
   // Double ordering covers possible lastModified ties, that could happen when
   // importing, syncing or due to extensions.
   // Note: not using a JOIN is cheaper in this case.
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+    "/* do not warn (bug 1175249) */ "
     "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
     "FROM moz_bookmarks b "
     "JOIN moz_bookmarks t on t.id = b.parent "
     "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
     "ORDER BY b.lastModified DESC, b.id DESC "
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
@@ -2082,16 +2090,17 @@ nsNavBookmarks::GetBookmarksForURI(nsIUR
                                    nsTArray<BookmarkData>& aBookmarks)
 {
   NS_ENSURE_ARG(aURI);
 
   // Double ordering covers possible lastModified ties, that could happen when
   // importing, syncing or due to extensions.
   // Note: not using a JOIN is cheaper in this case.
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
+    "/* do not warn (bug 1175249) */ "
     "SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
     "FROM moz_bookmarks b "
     "JOIN moz_bookmarks t on t.id = b.parent "
     "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
     "ORDER BY b.lastModified DESC, b.id DESC "
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
@@ -2297,17 +2306,18 @@ nsNavBookmarks::SetKeywordForBookmark(in
                        OnItemChanged(bookmarks[i].id,
                                      NS_LITERAL_CSTRING("keyword"),
                                      false,
                                      EmptyCString(),
                                      bookmarks[i].lastModified,
                                      TYPE_BOOKMARK,
                                      bookmarks[i].parentId,
                                      bookmarks[i].guid,
-                                     bookmarks[i].parentGuid));
+                                     bookmarks[i].parentGuid,
+                                     EmptyCString()));
     }
 
     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.
@@ -2349,17 +2359,18 @@ nsNavBookmarks::SetKeywordForBookmark(in
                        OnItemChanged(bookmarks[i].id,
                                      NS_LITERAL_CSTRING("keyword"),
                                      false,
                                      EmptyCString(),
                                      bookmarks[i].lastModified,
                                      TYPE_BOOKMARK,
                                      bookmarks[i].parentId,
                                      bookmarks[i].guid,
-                                     bookmarks[i].parentGuid));
+                                     bookmarks[i].parentGuid,
+                                     EmptyCString()));
     }
 
     stmt = mDB->GetStatement(
       "UPDATE moz_keywords SET place_id = :place_id WHERE keyword = :keyword"
     );
     NS_ENSURE_STATE(stmt);
   }
   else {
@@ -2388,17 +2399,18 @@ nsNavBookmarks::SetKeywordForBookmark(in
                      OnItemChanged(bookmarks[i].id,
                                    NS_LITERAL_CSTRING("keyword"),
                                    false,
                                    NS_ConvertUTF16toUTF8(keyword),
                                    bookmarks[i].lastModified,
                                    TYPE_BOOKMARK,
                                    bookmarks[i].parentId,
                                    bookmarks[i].guid,
-                                   bookmarks[i].parentGuid));
+                                   bookmarks[i].parentGuid,
+                                   EmptyCString()));
   }
 
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::GetKeywordForBookmark(int64_t aBookmarkId, nsAString& aKeyword)
@@ -2584,17 +2596,18 @@ nsNavBookmarks::NotifyItemChanged(const 
                    OnItemChanged(aData.bookmark.id,
                                  aData.property,
                                  aData.isAnnotation,
                                  aData.newValue,
                                  aData.bookmark.lastModified,
                                  aData.bookmark.type,
                                  aData.bookmark.parentId,
                                  aData.bookmark.guid,
-                                 aData.bookmark.parentGuid));
+                                 aData.bookmark.parentGuid,
+                                 aData.oldValue));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIObserver
 
 NS_IMETHODIMP
 nsNavBookmarks::Observe(nsISupports *aSubject, const char *aTopic,
                         const char16_t *aData)
@@ -2815,17 +2828,18 @@ nsNavBookmarks::OnItemAnnotationSet(int6
                    OnItemChanged(bookmark.id,
                                  aName,
                                  true,
                                  EmptyCString(),
                                  bookmark.lastModified,
                                  bookmark.type,
                                  bookmark.parentId,
                                  bookmark.guid,
-                                 bookmark.parentGuid));
+                                 bookmark.parentGuid,
+                                 EmptyCString()));
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::OnPageAnnotationRemoved(nsIURI* aPage, const nsACString& aName)
 {
   return NS_OK;
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -50,16 +50,17 @@ namespace places {
     PRTime time;
   };
 
   struct ItemChangeData {
     BookmarkData bookmark;
     nsCString property;
     bool isAnnotation;
     nsCString newValue;
+    nsCString oldValue;
   };
 
   typedef void (nsNavBookmarks::*ItemVisitMethod)(const ItemVisitData&);
   typedef void (nsNavBookmarks::*ItemChangeMethod)(const ItemChangeData&);
 
   enum BookmarkDate {
     DATE_ADDED = 0
   , LAST_MODIFIED
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -2851,17 +2851,18 @@ NS_IMETHODIMP
 nsNavHistoryQueryResultNode::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& aParentGUID,
+                                           const nsACString& aOldValue)
 {
   // 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) {
@@ -2897,17 +2898,17 @@ nsNavHistoryQueryResultNode::OnItemChang
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
                                                aIsAnnotationProperty,
                                                aNewValue, aLastModified,
                                                aItemType, aParentId, aGUID,
-                                               aParentGUID);
+                                               aParentGUID, aOldValue);
 }
 
 NS_IMETHODIMP
 nsNavHistoryQueryResultNode::OnItemVisited(int64_t aItemId,
                                            int64_t aVisitId,
                                            PRTime aTime,
                                            uint32_t aTransitionType,
                                            nsIURI* aURI,
@@ -3662,17 +3663,18 @@ NS_IMETHODIMP
 nsNavHistoryResultNode::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& aParentGUID,
+                                      const nsACString& aOldValue)
 {
   if (aItemId != mItemId)
     return NS_OK;
 
   mLastModified = aLastModified;
 
   nsNavHistoryResult* result = GetResult();
   NS_ENSURE_STATE(result);
@@ -3752,25 +3754,26 @@ NS_IMETHODIMP
 nsNavHistoryFolderResultNode::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& aParentGUID,
+                                            const nsACString& aOldValue)
 {
   RESTART_AND_RETURN_IF_ASYNC_PENDING();
 
   return nsNavHistoryResultNode::OnItemChanged(aItemId, aProperty,
                                                aIsAnnotationProperty,
                                                aNewValue, aLastModified,
                                                aItemType, aParentId, aGUID,
-                                               aParentGUID);
+                                               aParentGUID, aOldValue);
 }
 
 /**
  * Updates visit count and last visit time and refreshes.
  */
 NS_IMETHODIMP
 nsNavHistoryFolderResultNode::OnItemVisited(int64_t aItemId,
                                             int64_t aVisitId,
@@ -4457,21 +4460,23 @@ 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& aParentGUID,
+                                  const nsACString& aOldValue)
 {
   ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
     OnItemChanged(aItemId, aProperty, aIsAnnotationProperty, aNewValue,
-                  aLastModified, aItemType, aParentId, aGUID, aParentGUID));
+                  aLastModified, aItemType, aParentId, aGUID, aParentGUID,
+                  aOldValue));
 
   // 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;
@@ -4485,17 +4490,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);
+                            aGUID, aParentGUID, aOldValue);
       }
     }
   }
 
   // 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
@@ -276,17 +276,18 @@ public:
   NS_IMETHOD OnItemChanged(int64_t aItemId,
                            const nsACString &aProperty,
                            bool aIsAnnotationProperty,
                            const nsACString &aValue,
                            PRTime aNewLastModified,
                            uint16_t aItemType,
                            int64_t aParentId,
                            const nsACString& aGUID,
-                           const nsACString& aParentGUID);
+                           const nsACString& aParentGUID,
+                           const nsACString &aOldValue);
 
 protected:
   virtual ~nsNavHistoryResultNode() {}
 
 public:
 
   nsNavHistoryResult* GetResult();
   nsNavHistoryQueryOptions* GetGeneratingOptions();
--- a/toolkit/components/places/tests/bookmarks/test_bookmarks.js
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks.js
@@ -38,21 +38,23 @@ let bookmarksObserver = {
     stmt.finalize();
   },
   onItemRemoved: function(id, folder, index, itemType) {
     this._itemRemovedId = id;
     this._itemRemovedFolder = folder;
     this._itemRemovedIndex = index;
   },
   onItemChanged: function(id, property, isAnnotationProperty, value,
-                          lastModified, itemType) {
+                          lastModified, itemType, parentId, guid, parentGuid,
+                          oldValue) {
     this._itemChangedId = id;
     this._itemChangedProperty = property;
     this._itemChanged_isAnnotationProperty = isAnnotationProperty;
     this._itemChangedValue = value;
+    this._itemChangedOldValue = oldValue;
   },
   onItemVisited: function(id, visitID, time) {
     this._itemVisitedId = id;
     this._itemVisitedVistId = visitID;
     this._itemVisitedTime = time;
   },
   onItemMoved: function(id, oldParent, oldIndex, newParent, newIndex,
                         itemType) {
@@ -447,16 +449,17 @@ add_task(function test_bookmarks() {
   do_print("lastModified = " + lastModified);
   do_print("lastModified2 = " + lastModified2);
   do_check_true(is_time_ordered(lastModified, lastModified2));
   do_check_true(is_time_ordered(dateAdded, lastModified2));
 
   do_check_eq(bookmarksObserver._itemChangedId, newId10);
   do_check_eq(bookmarksObserver._itemChangedProperty, "uri");
   do_check_eq(bookmarksObserver._itemChangedValue, "http://foo11.com/");
+  do_check_eq(bookmarksObserver._itemChangedOldValue, "http://foo10.com/");
 
   // test getBookmarkURI
   let newId11 = bs.insertBookmark(testRoot, uri("http://foo11.com/"),
                                   bs.DEFAULT_INDEX, "");
   let bmURI = bs.getBookmarkURI(newId11);
   do_check_eq("http://foo11.com/", bmURI.spec);
 
   // test getBookmarkURI with non-bookmark items
--- a/toolkit/components/places/tests/bookmarks/test_bookmarks_notifications.js
+++ b/toolkit/components/places/tests/bookmarks/test_bookmarks_notifications.js
@@ -89,68 +89,68 @@ add_task(function* insert_bookmark_tag_n
 
   observer.check([ { name: "onItemAdded",
                      arguments: [ tagId, tagParentId, tag.index, tag.type,
                                   tag.url, null, tag.dateAdded,
                                   tag.guid, tag.parentGuid ] },
                    { name: "onItemChanged",
                      arguments: [ itemId, "tags", false, "",
                                   bm.lastModified, bm.type, parentId,
-                                  bm.guid, bm.parentGuid ] }
+                                  bm.guid, bm.parentGuid, "" ] }
                  ]);
 });
 
 add_task(function* update_bookmark_lastModified() {
   let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
                                                 parentGuid: PlacesUtils.bookmarks.unfiledGuid,
                                                 url: new URL("http://lastmod.example.com/") });
   let observer = expectNotifications();
   bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
                                             lastModified: new Date() });
   let itemId = yield PlacesUtils.promiseItemId(bm.guid);
   let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
 
   observer.check([ { name: "onItemChanged",
                      arguments: [ itemId, "lastModified", false,
                                   `${bm.lastModified * 1000}`, bm.lastModified,
-                                  bm.type, parentId, bm.guid, bm.parentGuid ] }
+                                  bm.type, parentId, bm.guid, bm.parentGuid, "" ] }
                  ]);
 });
 
 add_task(function* update_bookmark_title() {
   let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
                                                 parentGuid: PlacesUtils.bookmarks.unfiledGuid,
                                                 url: new URL("http://title.example.com/") });
   let observer = expectNotifications();
   bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
                                             title: "new title" });
   let itemId = yield PlacesUtils.promiseItemId(bm.guid);
   let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
 
   observer.check([ { name: "onItemChanged",
                      arguments: [ itemId, "title", false, bm.title,
                                   bm.lastModified, bm.type, parentId, bm.guid,
-                                  bm.parentGuid ] }
+                                  bm.parentGuid, "" ] }
                  ]);
 });
 
 add_task(function* update_bookmark_uri() {
   let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
                                                 parentGuid: PlacesUtils.bookmarks.unfiledGuid,
                                                 url: new URL("http://url.example.com/") });
   let observer = expectNotifications();
   bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
                                             url: "http://mozilla.org/" });
   let itemId = yield PlacesUtils.promiseItemId(bm.guid);
   let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
 
   observer.check([ { name: "onItemChanged",
                      arguments: [ itemId, "uri", false, bm.url.href,
                                   bm.lastModified, bm.type, parentId, bm.guid,
-                                  bm.parentGuid ] }
+                                  bm.parentGuid, "http://url.example.com/" ] }
                  ]);
 });
 
 add_task(function* update_move_same_folder() {
   // Ensure there are at least two items in place (others test do so for us,
   // but we don't have to depend on that).
   let sep = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
                                                  parentGuid: PlacesUtils.bookmarks.unfiledGuid });
@@ -259,17 +259,17 @@ add_task(function* remove_bookmark_tag_n
   let removed = yield PlacesUtils.bookmarks.remove(tag.guid);
 
   observer.check([ { name: "onItemRemoved",
                      arguments: [ tagId, tagParentId, tag.index, tag.type,
                                   tag.url, tag.guid, tag.parentGuid ] },
                    { name: "onItemChanged",
                      arguments: [ itemId, "tags", false, "",
                                   bm.lastModified, bm.type, parentId,
-                                  bm.guid, bm.parentGuid ] }
+                                  bm.guid, bm.parentGuid, "" ] }
                  ]);
 });
 
 add_task(function* remove_folder_notification() {
   let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
                                                      parentGuid: PlacesUtils.bookmarks.unfiledGuid });
   let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
   let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
--- a/toolkit/components/places/tests/bookmarks/test_keywords.js
+++ b/toolkit/components/places/tests/bookmarks/test_keywords.js
@@ -39,17 +39,17 @@ function expectNotifications() {
     get(target, name) {
       if (name == "check") {
         PlacesUtils.bookmarks.removeObserver(observer);
         return expectedNotifications =>
           Assert.deepEqual(notifications, expectedNotifications);
       }
 
       if (name.startsWith("onItemChanged")) {
-        return (id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid) => {
+        return (id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid, oldVal) => {
           if (prop != "keyword")
             return;
           let args = Array.from(arguments, arg => {
             if (arg && arg instanceof Ci.nsIURI)
               return new URL(arg.spec);
             if (arg && typeof(arg) == "number" && arg >= Date.now() * 1000)
               return new Date(parseInt(arg/1000));
             return arg;
@@ -92,17 +92,17 @@ add_task(function test_addBookmarkAndKey
                                          "test");
 
   PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
   let bookmark = yield PlacesUtils.bookmarks.fetch({ url: URI1 });
   observer.check([ { name: "onItemChanged",
                      arguments: [ itemId, "keyword", false, "keyword",
                                   bookmark.lastModified, bookmark.type,
                                   (yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
-                                  bookmark.guid, bookmark.parentGuid ] }
+                                  bookmark.guid, bookmark.parentGuid, "" ] }
                  ]);
   yield PlacesTestUtils.promiseAsyncUpdates();
 
   check_keyword(URI1, "keyword");
   Assert.equal((yield foreign_count(URI1)), fc + 2); // + 1 bookmark + 1 keyword
 
   yield PlacesTestUtils.promiseAsyncUpdates();
   yield check_orphans();
@@ -143,22 +143,22 @@ add_task(function test_sameKeywordDiffer
 
   let bookmark1 = yield PlacesUtils.bookmarks.fetch({ url: URI1 });
   let bookmark2 = yield PlacesUtils.bookmarks.fetch({ url: URI2 });
   observer.check([ { name: "onItemChanged",
                      arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
                                   "keyword", false, "",
                                   bookmark1.lastModified, bookmark1.type,
                                   (yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
-                                  bookmark1.guid, bookmark1.parentGuid ] },
+                                  bookmark1.guid, bookmark1.parentGuid, "" ] },
                     { name: "onItemChanged",
                      arguments: [ itemId, "keyword", false, "keyword",
                                   bookmark2.lastModified, bookmark2.type,
                                   (yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
-                                  bookmark2.guid, bookmark2.parentGuid ] }
+                                  bookmark2.guid, bookmark2.parentGuid, "" ] }
                  ]);
   yield PlacesTestUtils.promiseAsyncUpdates();
 
   // The keyword should have been "moved" to the new URI.
   check_keyword(URI1, null);
   Assert.equal((yield foreign_count(URI1)), fc1 - 1); // - 1 keyword
   check_keyword(URI2, "keyword");
   Assert.equal((yield foreign_count(URI2)), fc2 + 2); // + 1 bookmark + 1 keyword
@@ -182,23 +182,23 @@ add_task(function test_sameURIDifferentK
 
   let bookmarks = [];
   yield PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
   observer.check([ { name: "onItemChanged",
                      arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[0].guid)),
                                   "keyword", false, "keyword2",
                                   bookmarks[0].lastModified, bookmarks[0].type,
                                   (yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
-                                  bookmarks[0].guid, bookmarks[0].parentGuid ] },
+                                  bookmarks[0].guid, bookmarks[0].parentGuid, "" ] },
                     { name: "onItemChanged",
                      arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
                                   "keyword", false, "keyword2",
                                   bookmarks[1].lastModified, bookmarks[1].type,
                                   (yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
-                                  bookmarks[1].guid, bookmarks[1].parentGuid ] }
+                                  bookmarks[1].guid, bookmarks[1].parentGuid, "" ] }
                  ]);
   yield PlacesTestUtils.promiseAsyncUpdates();
 
   check_keyword(URI2, "keyword2");
   Assert.equal((yield foreign_count(URI2)), fc + 2); // + 1 bookmark + 1 keyword
 
   yield PlacesTestUtils.promiseAsyncUpdates();
   check_orphans();
@@ -238,29 +238,29 @@ add_task(function test_unsetKeyword() {
   let bookmarks = [];
   yield PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
   do_print(bookmarks.length);
   observer.check([ { name: "onItemChanged",
                      arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[0].guid)),
                                   "keyword", false, "",
                                   bookmarks[0].lastModified, bookmarks[0].type,
                                   (yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
-                                  bookmarks[0].guid, bookmarks[0].parentGuid ] },
+                                  bookmarks[0].guid, bookmarks[0].parentGuid, "" ] },
                     { name: "onItemChanged",
                      arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
                                   "keyword", false, "",
                                   bookmarks[1].lastModified, bookmarks[1].type,
                                   (yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
-                                  bookmarks[1].guid, bookmarks[1].parentGuid ] },
+                                  bookmarks[1].guid, bookmarks[1].parentGuid, "" ] },
                     { name: "onItemChanged",
                      arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[2].guid)),
                                   "keyword", false, "",
                                   bookmarks[2].lastModified, bookmarks[2].type,
                                   (yield PlacesUtils.promiseItemId(bookmarks[2].parentGuid)),
-                                  bookmarks[2].guid, bookmarks[2].parentGuid ] }
+                                  bookmarks[2].guid, bookmarks[2].parentGuid, "" ] }
                  ]);
 
   check_keyword(URI1, null);
   check_keyword(URI2, null);
   Assert.equal((yield foreign_count(URI2)), fc - 1); // + 1 bookmark - 2 keyword
 
   yield PlacesTestUtils.promiseAsyncUpdates();
   check_orphans();
@@ -281,17 +281,17 @@ add_task(function test_addRemoveBookmark
   let parentId = yield PlacesUtils.promiseItemId(bookmark.parentGuid);
   PlacesUtils.bookmarks.removeItem(itemId);
 
   observer.check([ { name: "onItemChanged",
                      arguments: [ itemId,
                                   "keyword", false, "keyword",
                                   bookmark.lastModified, bookmark.type,
                                   parentId,
-                                  bookmark.guid, bookmark.parentGuid ] }
+                                  bookmark.guid, bookmark.parentGuid, "" ] }
                  ]);
 
   check_keyword(URI3, null);
   // Don't check the foreign count since the process is async.
   // The new test_keywords.js in unit is checking this though.
 
   yield PlacesTestUtils.promiseAsyncUpdates();
   check_orphans();
--- a/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js
+++ b/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js
@@ -16,30 +16,37 @@ let gBookmarksObserver = {
     }
 
     if (this.expected.length == 0) {
       run_next_test();
     }
   },
 
   // nsINavBookmarkObserver
-  onBeginUpdateBatch: function onBeginUpdateBatch()
-    this.validate(arguments.callee.name, arguments),
-  onEndUpdateBatch: function onEndUpdateBatch()
-    this.validate(arguments.callee.name, arguments),
-  onItemAdded: function onItemAdded()
-    this.validate(arguments.callee.name, arguments),
-  onItemRemoved: function onItemRemoved()
-    this.validate(arguments.callee.name, arguments),
-  onItemChanged: function onItemChanged()
-    this.validate(arguments.callee.name, arguments),
-  onItemVisited: function onItemVisited()
-    this.validate(arguments.callee.name, arguments),
-  onItemMoved: function onItemMoved()
-    this.validate(arguments.callee.name, arguments),
+  onBeginUpdateBatch() {
+    return this.validate("onBeginUpdateBatch", arguments);
+  },
+  onEndUpdateBatch() {
+    return this.validate("onEndUpdateBatch", arguments);
+  },
+  onItemAdded() {
+    return this.validate("onItemAdded", arguments);
+  },
+  onItemRemoved() {
+    return this.validate("onItemRemoved", arguments);
+  },
+  onItemChanged() {
+    return this.validate("onItemChanged", arguments);
+  },
+  onItemVisited() {
+    return this.validate("onItemVisited", arguments);
+  },
+  onItemMoved() {
+    return this.validate("onItemMoved", arguments);
+  },
 
   // nsISupports
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarkObserver]),
 }
 
 add_test(function batch() {
   gBookmarksObserver.expected = [
     { name: "onBeginUpdateBatch",
@@ -127,16 +134,17 @@ add_test(function onItemChanged_title_bo
         { name: "property", check: function (v) v === "title" },
         { name: "isAnno", check: function (v) v === false },
         { name: "newValue", check: function (v) v === TITLE },
         { name: "lastModified", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
         { name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
         { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
         { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
+        { name: "oldValue", check: function (v) typeof(v) == "string" },
       ] },
   ];
   PlacesUtils.bookmarks.setItemTitle(id, TITLE);
 });
 
 add_test(function onItemChanged_tags_bookmark() {
   let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
   let uri = PlacesUtils.bookmarks.getBookmarkURI(id);
@@ -173,16 +181,17 @@ add_test(function onItemChanged_tags_boo
         { name: "property", check: function (v) v === "tags" },
         { name: "isAnno", check: function (v) v === false },
         { name: "newValue", check: function (v) v === "" },
         { name: "lastModified", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
         { name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
         { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
         { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
+        { name: "oldValue", check: function (v) typeof(v) == "string" },
       ] },
     { name: "onItemRemoved", // This is the tag.
       args: [
         { name: "itemId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "parentId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "index", check: function (v) v === 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
         { name: "uri", check: function (v) v instanceof Ci.nsIURI && v.equals(uri) },
@@ -205,16 +214,17 @@ add_test(function onItemChanged_tags_boo
         { name: "property", check: function (v) v === "tags" },
         { name: "isAnno", check: function (v) v === false },
         { name: "newValue", check: function (v) v === "" },
         { name: "lastModified", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
         { name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
         { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
         { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
+        { name: "oldValue", check: function (v) typeof(v) == "string" },
       ] },
   ];
   PlacesUtils.tagging.tagURI(uri, [TAG]);
   PlacesUtils.tagging.untagURI(uri, [TAG]);
 });
 
 add_test(function onItemMoved_bookmark() {
   let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId, 0);
@@ -278,16 +288,17 @@ add_test(function onItemRemoved_bookmark
         { name: "property", check: function (v) v === "" },
         { name: "isAnno", check: function (v) v === true },
         { name: "newValue", check: function (v) v === "" },
         { name: "lastModified", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
         { name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
         { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
         { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
+        { name: "oldValue", check: function (v) typeof(v) == "string" },
       ] },
     { name: "onItemRemoved",
       args: [
         { name: "itemId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "parentId", check: function (v) v === PlacesUtils.unfiledBookmarksFolderId },
         { name: "index", check: function (v) v === 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_BOOKMARK },
         { name: "uri", check: function (v) v instanceof Ci.nsIURI && v.equals(uri) },
@@ -307,16 +318,17 @@ add_test(function onItemRemoved_separato
         { name: "property", check: function (v) v === "" },
         { name: "isAnno", check: function (v) v === true },
         { name: "newValue", check: function (v) v === "" },
         { name: "lastModified", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_SEPARATOR },
         { name: "parentId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
         { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
+        { name: "oldValue", check: function (v) typeof(v) == "string" },
       ] },
     { name: "onItemRemoved",
       args: [
         { name: "itemId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "parentId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "index", check: function (v) v === 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_SEPARATOR },
         { name: "uri", check: function (v) v === null },
@@ -337,16 +349,17 @@ add_test(function onItemRemoved_folder()
         { name: "property", check: function (v) v === "" },
         { name: "isAnno", check: function (v) v === true },
         { name: "newValue", check: function (v) v === "" },
         { name: "lastModified", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_FOLDER },
         { name: "parentId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
         { name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
+        { name: "oldValue", check: function (v) typeof(v) == "string" },
       ] },
     { name: "onItemRemoved",
       args: [
         { name: "itemId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "parentId", check: function (v) typeof(v) == "number" && v > 0 },
         { name: "index", check: function (v) v === 0 },
         { name: "itemType", check: function (v) v === PlacesUtils.bookmarks.TYPE_FOLDER },
         { name: "uri", check: function (v) v === null },
--- a/toolkit/components/places/tests/unit/test_keywords.js
+++ b/toolkit/components/places/tests/unit/test_keywords.js
@@ -180,31 +180,31 @@ add_task(function* test_addBookmarkAndKe
   let observer = expectBookmarkNotifications();
   yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
 
   observer.check([{ name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
                                  "keyword", false, "keyword",
                                  bookmark.lastModified, bookmark.type,
                                  (yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
-                                 bookmark.guid, bookmark.parentGuid ] } ]);
+                                 bookmark.guid, bookmark.parentGuid, "" ] } ]);
 
   yield check_keyword(true, "http://example.com/", "keyword");
   Assert.equal((yield foreign_count("http://example.com/")), fc + 2); // +1 bookmark +1 keyword
 
   // Now remove the keyword.
   observer = expectBookmarkNotifications();
   yield PlacesUtils.keywords.remove("keyword");
 
   observer.check([{ name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
                                  "keyword", false, "",
                                  bookmark.lastModified, bookmark.type,
                                  (yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
-                                 bookmark.guid, bookmark.parentGuid ] } ]);
+                                 bookmark.guid, bookmark.parentGuid, "" ] } ]);
 
   yield check_keyword(false, "http://example.com/", "keyword");
   Assert.equal((yield foreign_count("http://example.com/")), fc + 1); // -1 keyword
 
   // Add again the keyword, then remove the bookmark.
   yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
 
   observer = expectBookmarkNotifications();
@@ -307,38 +307,38 @@ add_task(function* test_sameKeywordDiffe
   let observer = expectBookmarkNotifications();
   yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example2.com/" });
 
   observer.check([{ name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
                                  "keyword", false, "",
                                  bookmark1.lastModified, bookmark1.type,
                                  (yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
-                                 bookmark1.guid, bookmark1.parentGuid ] },
+                                 bookmark1.guid, bookmark1.parentGuid, "" ] },
                   { name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark2.guid)),
                                  "keyword", false, "keyword",
                                  bookmark2.lastModified, bookmark2.type,
                                  (yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
-                                 bookmark2.guid, bookmark2.parentGuid ] } ]);
+                                 bookmark2.guid, bookmark2.parentGuid, "" ] } ]);
 
   yield check_keyword(false, "http://example1.com/", "keyword");
   Assert.equal((yield foreign_count("http://example1.com/")), fc1 + 1); // -1 keyword
   yield check_keyword(true, "http://example2.com/", "keyword");
   Assert.equal((yield foreign_count("http://example2.com/")), fc2 + 2); // +1 keyword
 
   // Now remove the keyword.
   observer = expectBookmarkNotifications();
   yield PlacesUtils.keywords.remove("keyword");
   observer.check([{ name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark2.guid)),
                                  "keyword", false, "",
                                  bookmark2.lastModified, bookmark2.type,
                                  (yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
-                                 bookmark2.guid, bookmark2.parentGuid ] } ]);
+                                 bookmark2.guid, bookmark2.parentGuid, "" ] } ]);
 
   yield check_keyword(false, "http://example1.com/", "keyword");
   yield check_keyword(false, "http://example2.com/", "keyword");
   Assert.equal((yield foreign_count("http://example1.com/")), fc1 + 1);
   Assert.equal((yield foreign_count("http://example2.com/")), fc2 + 1); // -1 keyword
 
   yield PlacesUtils.bookmarks.remove(bookmark1);
   yield PlacesUtils.bookmarks.remove(bookmark2);
@@ -360,29 +360,29 @@ add_task(function* test_sameURIDifferent
   yield check_keyword(true, "http://example.com/", "keyword");
   Assert.equal((yield foreign_count("http://example.com/")), fc + 2); // +1 bookmark +1 keyword
 
   observer.check([{ name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
                                  "keyword", false, "keyword",
                                  bookmark.lastModified, bookmark.type,
                                  (yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
-                                 bookmark.guid, bookmark.parentGuid ] } ]);
+                                 bookmark.guid, bookmark.parentGuid, "" ] } ]);
 
   observer = expectBookmarkNotifications();
   yield PlacesUtils.keywords.insert({ keyword: "keyword2", url: "http://example.com/" });
   yield check_keyword(true, "http://example.com/", "keyword");
   yield check_keyword(true, "http://example.com/", "keyword2");
   Assert.equal((yield foreign_count("http://example.com/")), fc + 3); // +1 keyword
   observer.check([{ name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
                                  "keyword", false, "keyword2",
                                  bookmark.lastModified, bookmark.type,
                                  (yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
-                                 bookmark.guid, bookmark.parentGuid ] } ]);
+                                 bookmark.guid, bookmark.parentGuid, "" ] } ]);
 
   // Add a third keyword.
   yield PlacesUtils.keywords.insert({ keyword: "keyword3", url: "http://example.com/" });
   yield check_keyword(true, "http://example.com/", "keyword");
   yield check_keyword(true, "http://example.com/", "keyword2");
   yield check_keyword(true, "http://example.com/", "keyword3");
   Assert.equal((yield foreign_count("http://example.com/")), fc + 4); // +1 keyword
 
@@ -392,17 +392,17 @@ add_task(function* test_sameURIDifferent
   yield check_keyword(false, "http://example.com/", "keyword");
   yield check_keyword(true, "http://example.com/", "keyword2");
   yield check_keyword(true, "http://example.com/", "keyword3");
   observer.check([{ name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
                                  "keyword", false, "",
                                  bookmark.lastModified, bookmark.type,
                                  (yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
-                                 bookmark.guid, bookmark.parentGuid ] } ]);
+                                 bookmark.guid, bookmark.parentGuid, "" ] } ]);
   Assert.equal((yield foreign_count("http://example.com/")), fc + 3); // -1 keyword
 
   // Now remove the bookmark.
   yield PlacesUtils.bookmarks.remove(bookmark);
   while ((yield foreign_count("http://example.com/")));
   yield check_keyword(false, "http://example.com/", "keyword");
   yield check_keyword(false, "http://example.com/", "keyword2");
   yield check_keyword(false, "http://example.com/", "keyword3");
@@ -424,40 +424,40 @@ add_task(function* test_deleteKeywordMul
 
   yield check_keyword(true, "http://example.com/", "keyword");
   Assert.equal((yield foreign_count("http://example.com/")), fc + 3); // +2 bookmark +1 keyword
   observer.check([{ name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark2.guid)),
                                  "keyword", false, "keyword",
                                  bookmark2.lastModified, bookmark2.type,
                                  (yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
-                                 bookmark2.guid, bookmark2.parentGuid ] },
+                                 bookmark2.guid, bookmark2.parentGuid, "" ] },
                   { name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
                                  "keyword", false, "keyword",
                                  bookmark1.lastModified, bookmark1.type,
                                  (yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
-                                 bookmark1.guid, bookmark1.parentGuid ] } ]);
+                                 bookmark1.guid, bookmark1.parentGuid, "" ] } ]);
 
   observer = expectBookmarkNotifications();
   yield PlacesUtils.keywords.remove("keyword");
   yield check_keyword(false, "http://example.com/", "keyword");
   Assert.equal((yield foreign_count("http://example.com/")), fc + 2); // -1 keyword
   observer.check([{ name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark2.guid)),
                                  "keyword", false, "",
                                  bookmark2.lastModified, bookmark2.type,
                                  (yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
-                                 bookmark2.guid, bookmark2.parentGuid ] },
+                                 bookmark2.guid, bookmark2.parentGuid, "" ] },
                   { name: "onItemChanged",
                     arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
                                  "keyword", false, "",
                                  bookmark1.lastModified, bookmark1.type,
                                  (yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
-                                 bookmark1.guid, bookmark1.parentGuid ] } ]);
+                                 bookmark1.guid, bookmark1.parentGuid, "" ] } ]);
 
   // Now remove the bookmarks.
   yield PlacesUtils.bookmarks.remove(bookmark1);
   yield PlacesUtils.bookmarks.remove(bookmark2);
   Assert.equal((yield foreign_count("http://example.com/")), fc); // -2 bookmarks
 
   check_no_orphans();
 });