Bug 1678607: Implement a mechanism to fire bookmark-tags-changed event. r=mak
authorDaisuke Akatsuka <daisuke@birchill.co.jp>
Mon, 18 Oct 2021 04:43:44 +0000
changeset 596154 7705a6b69701b3c852548c04ced4b4ca7f88f181
parent 596153 d5597b7c7a5baad1f5a525bcedeced17ca746e4a
child 596155 13868a361ad3c4004be8450d7119faaa820e05bc
push id151653
push userdakatsuka.birchill@mozilla.com
push dateMon, 18 Oct 2021 04:46:21 +0000
treeherderautoland@63d10a00d256 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1678607
milestone95.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 1678607: Implement a mechanism to fire bookmark-tags-changed event. r=mak Differential Revision: https://phabricator.services.mozilla.com/D128326
dom/base/PlacesBookmarkTags.h
dom/base/PlacesEvent.h
dom/base/moz.build
dom/chrome-webidl/PlacesEvent.webidl
toolkit/components/places/Bookmarks.jsm
toolkit/components/places/nsNavBookmarks.cpp
tools/lint/eslint/eslint-plugin-mozilla/lib/environments/privileged.js
new file mode 100644
--- /dev/null
+++ b/dom/base/PlacesBookmarkTags.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PlacesBookmarkTags_h
+#define mozilla_dom_PlacesBookmarkTags_h
+
+#include "mozilla/dom/PlacesBookmarkChanged.h"
+
+namespace mozilla {
+namespace dom {
+class PlacesBookmarkTags final : public PlacesBookmarkChanged {
+ public:
+  explicit PlacesBookmarkTags()
+      : PlacesBookmarkChanged(PlacesEventType::Bookmark_tags_changed) {}
+
+  static already_AddRefed<PlacesBookmarkTags> Constructor(
+      const GlobalObject& aGlobal, const PlacesBookmarkTagsInit& aInitDict) {
+    RefPtr<PlacesBookmarkTags> event = new PlacesBookmarkTags();
+    event->mId = aInitDict.mId;
+    event->mItemType = aInitDict.mItemType;
+    event->mUrl = aInitDict.mUrl;
+    event->mGuid = aInitDict.mGuid;
+    event->mParentGuid = aInitDict.mParentGuid;
+    event->mLastModified = aInitDict.mLastModified;
+    event->mSource = aInitDict.mSource;
+    event->mIsTagging = aInitDict.mIsTagging;
+    return event.forget();
+  }
+
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override {
+    return PlacesBookmarkTags_Binding::Wrap(aCx, this, aGivenProto);
+  }
+
+  const PlacesBookmarkTags* AsPlacesBookmarkTags() const override {
+    return this;
+  }
+
+ private:
+  ~PlacesBookmarkTags() = default;
+};
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif
--- a/dom/base/PlacesEvent.h
+++ b/dom/base/PlacesEvent.h
@@ -42,16 +42,19 @@ class PlacesEvent : public nsWrapperCach
     return nullptr;
   }
   virtual const PlacesBookmarkMoved* AsPlacesBookmarkMoved() const {
     return nullptr;
   }
   virtual const PlacesBookmarkGuid* AsPlacesBookmarkGuid() const {
     return nullptr;
   }
+  virtual const PlacesBookmarkTags* AsPlacesBookmarkTags() const {
+    return nullptr;
+  }
   virtual const PlacesBookmarkTime* AsPlacesBookmarkTime() const {
     return nullptr;
   }
   virtual const PlacesBookmarkTitle* AsPlacesBookmarkTitle() const {
     return nullptr;
   }
   virtual const PlacesBookmarkUrl* AsPlacesBookmarkUrl() const {
     return nullptr;
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -221,16 +221,17 @@ EXPORTS.mozilla.dom += [
     "NodeIterator.h",
     "ParentProcessMessageManager.h",
     "PlacesBookmark.h",
     "PlacesBookmarkAddition.h",
     "PlacesBookmarkChanged.h",
     "PlacesBookmarkGuid.h",
     "PlacesBookmarkMoved.h",
     "PlacesBookmarkRemoved.h",
+    "PlacesBookmarkTags.h",
     "PlacesBookmarkTime.h",
     "PlacesBookmarkTitle.h",
     "PlacesBookmarkUrl.h",
     "PlacesEvent.h",
     "PlacesFavicon.h",
     "PlacesHistoryCleared.h",
     "PlacesObservers.h",
     "PlacesPurgeCaches.h",
--- a/dom/chrome-webidl/PlacesEvent.webidl
+++ b/dom/chrome-webidl/PlacesEvent.webidl
@@ -20,16 +20,20 @@ enum PlacesEventType {
    * (or a bookmark folder/separator) is moved.
    */
   "bookmark-moved",
   /**
    * data: PlacesBookmarkGuid. Fired whenever a bookmark guid changes.
    */
   "bookmark-guid-changed",
   /**
+   * data: PlacesBookmarkTags. Fired whenever tags of bookmark changes.
+   */
+  "bookmark-tags-changed",
+  /**
    * data: PlacesBookmarkTime.
    * Fired whenever dateAdded or lastModified of a bookmark is explicitly changed
    * through the Bookmarks API. This notification doesn't fire when a bookmark is
    * created, or when a property of a bookmark (e.g. title) is changed, even if
    * lastModified will be updated as a consequence of that change.
    */
   "bookmark-time-changed",
   /**
@@ -297,16 +301,32 @@ dictionary PlacesBookmarkGuidInit {
   required boolean isTagging;
 };
 
 [ChromeOnly, Exposed=Window]
 interface PlacesBookmarkGuid : PlacesBookmarkChanged {
   constructor(PlacesBookmarkGuidInit initDict);
 };
 
+dictionary PlacesBookmarkTagsInit {
+  required long long id;
+  required unsigned short itemType;
+  DOMString? url = null;
+  required ByteString guid;
+  required ByteString parentGuid;
+  required long long lastModified;
+  required unsigned short source;
+  required boolean isTagging;
+};
+
+[ChromeOnly, Exposed=Window]
+interface PlacesBookmarkTags : PlacesBookmarkChanged {
+  constructor(PlacesBookmarkTagsInit initDict);
+};
+
 dictionary PlacesBookmarkTimeInit {
   required long long id;
   required unsigned short itemType;
   DOMString? url = null;
   required ByteString guid;
   required ByteString parentGuid;
   required long long dateAdded;
   required long long lastModified;
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -315,30 +315,31 @@ var Bookmarks = Object.freeze({
       // Pass tagging information for the observers to skip over these notifications when needed.
       let isTagging = parent._parentId == PlacesUtils.tagsFolderId;
       let isTagsFolder = parent._id == PlacesUtils.tagsFolderId;
       let url = "";
       if (item.type == Bookmarks.TYPE_BOOKMARK) {
         url = item.url.href;
       }
 
-      let notification = new PlacesBookmarkAddition({
-        id: itemId,
-        url,
-        itemType: item.type,
-        parentId: parent._id,
-        index: item.index,
-        title: item.title,
-        dateAdded: item.dateAdded,
-        guid: item.guid,
-        parentGuid: item.parentGuid,
-        source: item.source,
-        isTagging: isTagging || isTagsFolder,
-      });
-      PlacesObservers.notifyListeners([notification]);
+      const notifications = [
+        new PlacesBookmarkAddition({
+          id: itemId,
+          url,
+          itemType: item.type,
+          parentId: parent._id,
+          index: item.index,
+          title: item.title,
+          dateAdded: item.dateAdded,
+          guid: item.guid,
+          parentGuid: item.parentGuid,
+          source: item.source,
+          isTagging: isTagging || isTagsFolder,
+        }),
+      ];
 
       // If it's a tag, notify OnItemChanged to all bookmarks for this URL.
       if (isTagging) {
         let observers = PlacesUtils.bookmarks.getObservers();
         for (let entry of await fetchBookmarksByURL(item, {
           concurrent: true,
         })) {
           notify(observers, "onItemChanged", [
@@ -349,19 +350,34 @@ var Bookmarks = Object.freeze({
             PlacesUtils.toPRTime(entry.lastModified),
             entry.type,
             entry._parentId,
             entry.guid,
             entry.parentGuid,
             "",
             item.source,
           ]);
+
+          notifications.push(
+            new PlacesBookmarkTags({
+              id: entry._id,
+              itemType: entry.type,
+              url,
+              guid: entry.guid,
+              parentGuid: entry.parentGuid,
+              lastModified: entry.lastModified,
+              source: item.source,
+              isTagging: false,
+            })
+          );
         }
       }
 
+      PlacesObservers.notifyListeners(notifications);
+
       // Remove non-enumerable properties.
       delete item.source;
       return Object.assign({}, item);
     })();
   },
 
   /**
    * Inserts a bookmark-tree into the existing bookmarks tree.
@@ -926,16 +942,29 @@ var Bookmarks = Object.freeze({
                   PlacesUtils.toPRTime(entry.lastModified),
                   entry.type,
                   entry._parentId,
                   entry.guid,
                   entry.parentGuid,
                   "",
                   updatedItem.source,
                 ]);
+
+                notifications.push(
+                  new PlacesBookmarkTags({
+                    id: entry._id,
+                    itemType: entry.type,
+                    url: entry.url,
+                    guid: entry.guid,
+                    parentGuid: entry.parentGuid,
+                    lastModified: entry.lastModified,
+                    source: updatedItem.source,
+                    isTagging: false,
+                  })
+                );
               }
             }
           }
           if (updateInfo.hasOwnProperty("url")) {
             await PlacesUtils.keywords.reassign(
               item.url,
               updatedItem.url,
               updatedItem.source
@@ -1347,16 +1376,29 @@ var Bookmarks = Object.freeze({
               PlacesUtils.toPRTime(entry.lastModified),
               entry.type,
               entry._parentId,
               entry.guid,
               entry.parentGuid,
               "",
               options.source,
             ]);
+
+            notifications.push(
+              new PlacesBookmarkTags({
+                id: entry._id,
+                itemType: entry.type,
+                url,
+                guid: entry.guid,
+                parentGuid: entry.parentGuid,
+                lastModified: entry.lastModified,
+                source: options.source,
+                isTagging: false,
+              })
+            );
           }
         }
       }
 
       PlacesObservers.notifyListeners(notifications);
     })();
   },
 
@@ -3323,16 +3365,29 @@ var removeFoldersContents = async functi
           PlacesUtils.toPRTime(entry.lastModified),
           entry.type,
           entry._parentId,
           entry.guid,
           entry.parentGuid,
           "",
           source,
         ]);
+
+        notifications.push(
+          new PlacesBookmarkTags({
+            id: entry._id,
+            itemType: entry.type,
+            url,
+            guid: entry.guid,
+            parentGuid: entry.parentGuid,
+            lastModified: entry.lastModified,
+            source,
+            isTagging: false,
+          })
+        );
       }
     }
   }
 
   if (notifications.length) {
     PlacesObservers.notifyListeners(notifications);
   }
 
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -14,16 +14,17 @@
 #include "nsUnicharUtils.h"
 #include "nsPrintfCString.h"
 #include "nsQueryObject.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProfilerLabels.h"
 #include "mozilla/storage.h"
 #include "mozilla/dom/PlacesBookmarkAddition.h"
 #include "mozilla/dom/PlacesBookmarkRemoved.h"
+#include "mozilla/dom/PlacesBookmarkTags.h"
 #include "mozilla/dom/PlacesBookmarkTime.h"
 #include "mozilla/dom/PlacesBookmarkTitle.h"
 #include "mozilla/dom/PlacesObservers.h"
 #include "mozilla/dom/PlacesVisit.h"
 
 using namespace mozilla;
 
 // These columns sit to the right of the kGetInfoIndex_* columns.
@@ -416,38 +417,38 @@ nsNavBookmarks::InsertBookmark(int64_t a
   if (grandParentId != tagsRootId) {
     rv = history->UpdateFrecency(placeId);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (mCanNotify) {
-    Sequence<OwningNonNull<PlacesEvent>> events;
-    nsAutoCString utf8spec;
-    aURI->GetSpec(utf8spec);
+  if (!mCanNotify) {
+    return NS_OK;
+  }
+
+  Sequence<OwningNonNull<PlacesEvent>> notifications;
+  nsAutoCString utf8spec;
+  aURI->GetSpec(utf8spec);
 
-    RefPtr<PlacesBookmarkAddition> bookmark = new PlacesBookmarkAddition();
-    bookmark->mItemType = TYPE_BOOKMARK;
-    bookmark->mId = *aNewBookmarkId;
-    bookmark->mParentId = aFolder;
-    bookmark->mIndex = index;
-    bookmark->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
-    bookmark->mTitle.Assign(NS_ConvertUTF8toUTF16(title));
-    bookmark->mDateAdded = dateAdded / 1000;
-    bookmark->mGuid.Assign(guid);
-    bookmark->mParentGuid.Assign(folderGuid);
-    bookmark->mSource = aSource;
-    bookmark->mIsTagging = grandParentId == mDB->GetTagsFolderId();
-    bool success = !!events.AppendElement(bookmark.forget(), fallible);
-    MOZ_RELEASE_ASSERT(success);
-
-    PlacesObservers::NotifyListeners(events);
-  }
+  RefPtr<PlacesBookmarkAddition> bookmark = new PlacesBookmarkAddition();
+  bookmark->mItemType = TYPE_BOOKMARK;
+  bookmark->mId = *aNewBookmarkId;
+  bookmark->mParentId = aFolder;
+  bookmark->mIndex = index;
+  bookmark->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
+  bookmark->mTitle.Assign(NS_ConvertUTF8toUTF16(title));
+  bookmark->mDateAdded = dateAdded / 1000;
+  bookmark->mGuid.Assign(guid);
+  bookmark->mParentGuid.Assign(folderGuid);
+  bookmark->mSource = aSource;
+  bookmark->mIsTagging = grandParentId == mDB->GetTagsFolderId();
+  bool success = !!notifications.AppendElement(bookmark.forget(), fallible);
+  MOZ_RELEASE_ASSERT(success);
 
   // If the bookmark has been added to a tag container, notify all
   // bookmark-folder result nodes which contain a bookmark for the new
   // bookmark's url.
   if (grandParentId == tagsRootId) {
     // Notify a tags change to all bookmarks for this URI.
     nsTArray<BookmarkData> bookmarks;
     rv = GetBookmarksForURI(aURI, bookmarks);
@@ -458,19 +459,33 @@ nsNavBookmarks::InsertBookmark(int64_t a
       MOZ_ASSERT(bookmarks[i].id != *aNewBookmarkId);
 
       NOTIFY_BOOKMARKS_OBSERVERS(
           mCanNotify, mObservers,
           OnItemChanged(bookmarks[i].id, "tags"_ns, false, ""_ns,
                         bookmarks[i].lastModified, TYPE_BOOKMARK,
                         bookmarks[i].parentId, bookmarks[i].guid,
                         bookmarks[i].parentGuid, ""_ns, aSource));
+
+      RefPtr<PlacesBookmarkTags> tagsChanged = new PlacesBookmarkTags();
+      tagsChanged->mId = bookmarks[i].id;
+      tagsChanged->mItemType = TYPE_BOOKMARK;
+      tagsChanged->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
+      tagsChanged->mGuid = bookmarks[i].guid;
+      tagsChanged->mParentGuid = bookmarks[i].parentGuid;
+      tagsChanged->mLastModified = bookmarks[i].lastModified / 1000;
+      tagsChanged->mSource = aSource;
+      tagsChanged->mIsTagging = false;
+      success = !!notifications.AppendElement(tagsChanged.forget(), fallible);
+      MOZ_RELEASE_ASSERT(success);
     }
   }
 
+  PlacesObservers::NotifyListeners(notifications);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNavBookmarks::RemoveItem(int64_t aItemId, uint16_t aSource) {
   AUTO_PROFILER_LABEL("nsNavBookmarks::RemoveItem", OTHER);
 
   NS_ENSURE_ARG(!IsRoot(aItemId));
@@ -545,55 +560,73 @@ nsNavBookmarks::RemoveItem(int64_t aItem
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // A broken url should not interrupt the removal process.
     (void)NS_NewURI(getter_AddRefs(uri), bookmark.url);
     // We cannot assert since some automated tests are checking this path.
     NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveItem");
   }
 
-  if (mCanNotify) {
-    Sequence<OwningNonNull<PlacesEvent>> events;
-    RefPtr<PlacesBookmarkRemoved> bookmarkRef = new PlacesBookmarkRemoved();
-    bookmarkRef->mItemType = bookmark.type;
-    bookmarkRef->mId = bookmark.id;
-    bookmarkRef->mParentId = bookmark.parentId;
-    bookmarkRef->mIndex = bookmark.position;
-    if (bookmark.type == TYPE_BOOKMARK) {
-      bookmarkRef->mUrl.Assign(NS_ConvertUTF8toUTF16(bookmark.url));
-    }
-    bookmarkRef->mGuid.Assign(bookmark.guid);
-    bookmarkRef->mParentGuid.Assign(bookmark.parentGuid);
-    bookmarkRef->mSource = aSource;
-    bookmarkRef->mIsTagging =
-        bookmark.parentId == tagsRootId || bookmark.grandParentId == tagsRootId;
-    bookmarkRef->mIsDescendantRemoval = false;
-    bool success = !!events.AppendElement(bookmarkRef.forget(), fallible);
-    MOZ_RELEASE_ASSERT(success);
+  if (!mCanNotify) {
+    return NS_OK;
+  }
 
-    PlacesObservers::NotifyListeners(events);
+  Sequence<OwningNonNull<PlacesEvent>> notifications;
+  RefPtr<PlacesBookmarkRemoved> bookmarkRef = new PlacesBookmarkRemoved();
+  bookmarkRef->mItemType = bookmark.type;
+  bookmarkRef->mId = bookmark.id;
+  bookmarkRef->mParentId = bookmark.parentId;
+  bookmarkRef->mIndex = bookmark.position;
+  if (bookmark.type == TYPE_BOOKMARK) {
+    bookmarkRef->mUrl.Assign(NS_ConvertUTF8toUTF16(bookmark.url));
   }
+  bookmarkRef->mGuid.Assign(bookmark.guid);
+  bookmarkRef->mParentGuid.Assign(bookmark.parentGuid);
+  bookmarkRef->mSource = aSource;
+  bookmarkRef->mIsTagging =
+      bookmark.parentId == tagsRootId || bookmark.grandParentId == tagsRootId;
+  bookmarkRef->mIsDescendantRemoval = false;
+  bool success = !!notifications.AppendElement(bookmarkRef.forget(), fallible);
+  MOZ_RELEASE_ASSERT(success);
+
   if (bookmark.type == TYPE_BOOKMARK && bookmark.grandParentId == tagsRootId &&
       uri) {
     // If the removed bookmark was child of a tag container, notify a tags
     // change to all bookmarks for this URI.
     nsTArray<BookmarkData> bookmarks;
     rv = GetBookmarksForURI(uri, bookmarks);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    nsAutoCString utf8spec;
+    uri->GetSpec(utf8spec);
+
     for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
       NOTIFY_BOOKMARKS_OBSERVERS(
           mCanNotify, mObservers,
           OnItemChanged(bookmarks[i].id, "tags"_ns, false, ""_ns,
                         bookmarks[i].lastModified, TYPE_BOOKMARK,
                         bookmarks[i].parentId, bookmarks[i].guid,
                         bookmarks[i].parentGuid, ""_ns, aSource));
+
+      RefPtr<PlacesBookmarkTags> tagsChanged = new PlacesBookmarkTags();
+      tagsChanged->mId = bookmarks[i].id;
+      tagsChanged->mItemType = TYPE_BOOKMARK;
+      tagsChanged->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
+      tagsChanged->mGuid = bookmarks[i].guid;
+      tagsChanged->mParentGuid = bookmarks[i].parentGuid;
+      tagsChanged->mLastModified = bookmarks[i].lastModified / 1000;
+      tagsChanged->mSource = aSource;
+      tagsChanged->mIsTagging = false;
+      success = !!notifications.AppendElement(tagsChanged.forget(), fallible);
+      MOZ_RELEASE_ASSERT(success);
     }
   }
 
+  PlacesObservers::NotifyListeners(notifications);
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNavBookmarks::CreateFolder(int64_t aParent, const nsACString& aTitle,
                              int32_t aIndex, const nsACString& aGUID,
                              uint16_t aSource, int64_t* aNewFolderId) {
   // NOTE: aParent can be null for root creation, so not checked
@@ -851,52 +884,70 @@ nsresult nsNavBookmarks::RemoveFolderChi
         NS_ENSURE_SUCCESS(rv, rv);
       }
       // A broken url should not interrupt the removal process.
       (void)NS_NewURI(getter_AddRefs(uri), child.url);
       // We cannot assert since some automated tests are checking this path.
       NS_WARNING_ASSERTION(uri, "Invalid URI in RemoveFolderChildren");
     }
 
-    if (mCanNotify) {
-      RefPtr<PlacesBookmarkRemoved> bookmark = new PlacesBookmarkRemoved();
-      bookmark->mItemType = TYPE_BOOKMARK;
-      bookmark->mId = child.id;
-      bookmark->mParentId = child.parentId;
-      bookmark->mIndex = child.position;
-      bookmark->mUrl.Assign(NS_ConvertUTF8toUTF16(child.url));
-      bookmark->mGuid.Assign(child.guid);
-      bookmark->mParentGuid.Assign(child.parentGuid);
-      bookmark->mSource = aSource;
-      bookmark->mIsTagging = (child.grandParentId == tagsRootId);
-      bookmark->mIsDescendantRemoval = (child.grandParentId != tagsRootId);
-      bool success = !!notifications.AppendElement(bookmark.forget(), fallible);
-      MOZ_RELEASE_ASSERT(success);
+    if (!mCanNotify) {
+      return NS_OK;
     }
+
+    RefPtr<PlacesBookmarkRemoved> bookmark = new PlacesBookmarkRemoved();
+    bookmark->mItemType = TYPE_BOOKMARK;
+    bookmark->mId = child.id;
+    bookmark->mParentId = child.parentId;
+    bookmark->mIndex = child.position;
+    bookmark->mUrl.Assign(NS_ConvertUTF8toUTF16(child.url));
+    bookmark->mGuid.Assign(child.guid);
+    bookmark->mParentGuid.Assign(child.parentGuid);
+    bookmark->mSource = aSource;
+    bookmark->mIsTagging = (child.grandParentId == tagsRootId);
+    bookmark->mIsDescendantRemoval = (child.grandParentId != tagsRootId);
+    bool success = !!notifications.AppendElement(bookmark.forget(), fallible);
+    MOZ_RELEASE_ASSERT(success);
+
     if (child.type == TYPE_BOOKMARK && child.grandParentId == tagsRootId &&
         uri) {
       // If the removed bookmark was a child of a tag container, notify all
       // bookmark-folder result nodes which contain a bookmark for the removed
       // bookmark's url.
       nsTArray<BookmarkData> bookmarks;
       rv = GetBookmarksForURI(uri, bookmarks);
       NS_ENSURE_SUCCESS(rv, rv);
 
+      nsAutoCString utf8spec;
+      uri->GetSpec(utf8spec);
+
       for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
         NOTIFY_BOOKMARKS_OBSERVERS(
             mCanNotify, mObservers,
             OnItemChanged(bookmarks[i].id, "tags"_ns, false, ""_ns,
                           bookmarks[i].lastModified, TYPE_BOOKMARK,
                           bookmarks[i].parentId, bookmarks[i].guid,
                           bookmarks[i].parentGuid, ""_ns, aSource));
+
+        RefPtr<PlacesBookmarkTags> tagsChanged = new PlacesBookmarkTags();
+        tagsChanged->mId = bookmarks[i].id;
+        tagsChanged->mItemType = TYPE_BOOKMARK;
+        tagsChanged->mUrl.Assign(NS_ConvertUTF8toUTF16(utf8spec));
+        tagsChanged->mGuid = bookmarks[i].guid;
+        tagsChanged->mParentGuid = bookmarks[i].parentGuid;
+        tagsChanged->mLastModified = bookmarks[i].lastModified / 1000;
+        tagsChanged->mSource = aSource;
+        tagsChanged->mIsTagging = false;
+        success = !!notifications.AppendElement(tagsChanged.forget(), fallible);
+        MOZ_RELEASE_ASSERT(success);
       }
     }
   }
 
-  if (notifications.Length() > 0) {
+  if (notifications.Length()) {
     PlacesObservers::NotifyListeners(notifications);
   }
 
   return NS_OK;
 }
 
 nsresult nsNavBookmarks::FetchItemInfo(int64_t aItemId,
                                        BookmarkData& _bookmark) {
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/privileged.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/privileged.js
@@ -427,16 +427,17 @@ module.exports = {
     PeriodicWave: false,
     PermissionStatus: false,
     Permissions: false,
     PlacesBookmark: false,
     PlacesBookmarkAddition: false,
     PlacesBookmarkGuid: false,
     PlacesBookmarkMoved: false,
     PlacesBookmarkRemoved: false,
+    PlacesBookmarkTags: false,
     PlacesBookmarkTime: false,
     PlacesBookmarkTitle: false,
     PlacesBookmarkUrl: false,
     PlacesEvent: false,
     PlacesHistoryCleared: false,
     PlacesObservers: false,
     PlacesPurgeCaches: false,
     PlacesRanking: false,