--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1443,16 +1443,17 @@ var BookmarkingUI = {
CustomizableUI.removeListener(this);
this.star.removeEventListener("mouseover", this);
this._uninitView();
if (this._hasBookmarksObserver) {
PlacesUtils.bookmarks.removeObserver(this);
+ PlacesUtils.observers.removeListener(["bookmark-added"], this.handlePlacesEvents);
}
if (this._pendingUpdate) {
delete this._pendingUpdate;
}
},
onLocationChange: function BUI_onLocationChange() {
@@ -1488,16 +1489,18 @@ var BookmarkingUI = {
}
this._updateStar();
// Start observing bookmarks if needed.
if (!this._hasBookmarksObserver) {
try {
PlacesUtils.bookmarks.addObserver(this);
+ this.handlePlacesEvents = this.handlePlacesEvents.bind(this);
+ PlacesUtils.observers.addListener(["bookmark-added"], this.handlePlacesEvents);
this._hasBookmarksObserver = true;
} catch (ex) {
Cu.reportError("BookmarkingUI failed adding a bookmarks observer: " + ex);
}
}
delete this._pendingUpdate;
});
@@ -1683,30 +1686,32 @@ var BookmarkingUI = {
} else {
// Move it back to the palette.
CustomizableUI.removeWidgetFromArea(this.BOOKMARK_BUTTON_ID);
}
triggerNode.setAttribute("checked", !placement);
updateToggleControlLabel(triggerNode);
},
- // nsINavBookmarkObserver
- onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded, aGuid) {
- if (aURI && aURI.equals(this._uri)) {
- // If a new bookmark has been added to the tracked uri, register it.
- if (!this._itemGuids.has(aGuid)) {
- this._itemGuids.add(aGuid);
- // Only need to update the UI if it wasn't marked as starred before:
- if (this._itemGuids.size == 1) {
- this._updateStar();
+ handlePlacesEvents(aEvents) {
+ for (let {url, guid} of aEvents) {
+ if (url && url == this._uri.spec) {
+ // If a new bookmark has been added to the tracked uri, register it.
+ if (!this._itemGuids.has(guid)) {
+ this._itemGuids.add(guid);
+ // Only need to update the UI if it wasn't marked as starred before:
+ if (this._itemGuids.size == 1) {
+ this._updateStar();
+ }
}
}
}
},
+ // nsINavBookmarkObserver
onItemRemoved(aItemId, aParentId, aIndex, aItemType, aURI, aGuid) {
// If one of the tracked bookmarks has been removed, unregister it.
if (this._itemGuids.has(aGuid)) {
this._itemGuids.delete(aGuid);
// Only need to update the UI if the page is no longer starred
if (this._itemGuids.size == 0) {
this._updateStar();
}
--- a/browser/components/extensions/parent/ext-bookmarks.js
+++ b/browser/components/extensions/parent/ext-bookmarks.js
@@ -102,37 +102,44 @@ const convertBookmarks = result => {
};
let observer = new class extends EventEmitter {
constructor() {
super();
this.skipTags = true;
this.skipDescendantsOnItemRemoval = true;
+
+ this.handlePlacesEvents = this.handlePlacesEvents.bind(this);
}
onBeginUpdateBatch() {}
onEndUpdateBatch() {}
- onItemAdded(id, parentId, index, itemType, uri, title, dateAdded, guid, parentGuid, source) {
- let bookmark = {
- id: guid,
- parentId: parentGuid,
- index,
- title,
- dateAdded: dateAdded / 1000,
- type: BOOKMARKS_TYPES_TO_API_TYPES_MAP.get(itemType),
- url: getUrl(itemType, uri && uri.spec),
- };
+ handlePlacesEvents(events) {
+ for (let event of events) {
+ if (event.isTagging) {
+ continue;
+ }
+ let bookmark = {
+ id: event.guid,
+ parentId: event.parentGuid,
+ index: event.index,
+ title: event.title,
+ dateAdded: event.dateAdded,
+ type: BOOKMARKS_TYPES_TO_API_TYPES_MAP.get(event.itemType),
+ url: getUrl(event.itemType, event.url),
+ };
- if (itemType == TYPE_FOLDER) {
- bookmark.dateGroupModified = bookmark.dateAdded;
+ if (event.itemType == TYPE_FOLDER) {
+ bookmark.dateGroupModified = bookmark.dateAdded;
+ }
+
+ this.emit("created", bookmark);
}
-
- this.emit("created", bookmark);
}
onItemVisited() {}
onItemMoved(id, oldParentId, oldIndex, newParentId, newIndex, itemType, guid, oldParentGuid, newParentGuid, source) {
let info = {
parentId: newParentGuid,
index: newIndex,
@@ -168,23 +175,25 @@ let observer = new class extends EventEm
this.emit("changed", {guid, info});
}
}();
const decrementListeners = () => {
listenerCount -= 1;
if (!listenerCount) {
PlacesUtils.bookmarks.removeObserver(observer);
+ PlacesUtils.observers.removeListener(["bookmark-added"], observer.handlePlacesEvents);
}
};
const incrementListeners = () => {
listenerCount++;
if (listenerCount == 1) {
PlacesUtils.bookmarks.addObserver(observer);
+ PlacesUtils.observers.addListener(["bookmark-added"], observer.handlePlacesEvents);
}
};
this.bookmarks = class extends ExtensionAPI {
getAPI(context) {
return {
bookmarks: {
async get(idOrIdList) {
--- a/browser/components/places/content/editBookmark.js
+++ b/browser/components/places/content/editBookmark.js
@@ -999,17 +999,16 @@ var gEditItemOverlay = {
// Just setting selectItem _does not_ trigger oncommand, so we don't
// recurse.
PlacesUtils.bookmarks.fetch(newParentGuid).then(bm => {
this._folderMenuList.selectedItem = this._getFolderMenuItem(newParentGuid,
bm.title);
});
},
- onItemAdded() {},
onItemRemoved() { },
onBeginUpdateBatch() { },
onEndUpdateBatch() { },
onItemVisited() { },
};
for (let elt of ["folderMenuList", "folderTree", "namePicker",
"locationField", "keywordField",
--- a/browser/extensions/activity-stream/lib/PlacesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/PlacesFeed.jsm
@@ -76,56 +76,16 @@ class HistoryObserver extends Observer {
*/
class BookmarksObserver extends Observer {
constructor(dispatch) {
super(dispatch, Ci.nsINavBookmarkObserver);
this.skipTags = true;
}
/**
- * onItemAdded - Called when a bookmark is added
- *
- * @param {str} id
- * @param {str} folderId
- * @param {int} index
- * @param {int} type Indicates if the bookmark is an actual bookmark,
- * a folder, or a separator.
- * @param {str} uri
- * @param {str} title
- * @param {int} dateAdded
- * @param {str} guid The unique id of the bookmark
- * @param {str} parent guid
- * @param {int} source Used to distinguish bookmarks made by different
- * actions: sync, bookmarks import, other.
- */
- onItemAdded(id, folderId, index, type, uri, bookmarkTitle, dateAdded, bookmarkGuid, parentGuid, source) { // eslint-disable-line max-params
- // Skips items that are not bookmarks (like folders), about:* pages or
- // default bookmarks, added when the profile is created.
- if (type !== PlacesUtils.bookmarks.TYPE_BOOKMARK ||
- source === PlacesUtils.bookmarks.SOURCES.IMPORT ||
- source === PlacesUtils.bookmarks.SOURCES.RESTORE ||
- source === PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP ||
- source === PlacesUtils.bookmarks.SOURCES.SYNC ||
- (uri.scheme !== "http" && uri.scheme !== "https")) {
- return;
- }
-
- this.dispatch({type: at.PLACES_LINKS_CHANGED});
- this.dispatch({
- type: at.PLACES_BOOKMARK_ADDED,
- data: {
- bookmarkGuid,
- bookmarkTitle,
- dateAdded,
- url: uri.spec
- }
- });
- }
-
- /**
* onItemRemoved - Called when a bookmark is removed
*
* @param {str} id
* @param {str} folderId
* @param {int} index
* @param {int} type Indicates if the bookmark is an actual bookmark,
* a folder, or a separator.
* @param {str} uri
@@ -154,32 +114,72 @@ class BookmarksObserver extends Observer
onItemMoved() {}
// Disabled due to performance cost, see Issue 3203 /
// https://bugzilla.mozilla.org/show_bug.cgi?id=1392267.
onItemChanged() {}
}
+/**
+ * PlacesObserver - observes events from PlacesUtils.observers
+ */
+class PlacesObserver extends Observer {
+ constructor(dispatch) {
+ super(dispatch, Ci.nsINavBookmarkObserver);
+ this.handlePlacesEvent = this.handlePlacesEvent.bind(this);
+ }
+
+ handlePlacesEvent(events) {
+ for (let {itemType, source, dateAdded, guid, title, url, isTagging} of events) {
+ // Skips items that are not bookmarks (like folders), about:* pages or
+ // default bookmarks, added when the profile is created.
+ if (isTagging ||
+ itemType !== PlacesUtils.bookmarks.TYPE_BOOKMARK ||
+ source === PlacesUtils.bookmarks.SOURCES.IMPORT ||
+ source === PlacesUtils.bookmarks.SOURCES.RESTORE ||
+ source === PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP ||
+ source === PlacesUtils.bookmarks.SOURCES.SYNC ||
+ (!url.startsWith("http://") && !url.startsWith("https://"))) {
+ return;
+ }
+
+ this.dispatch({type: at.PLACES_LINKS_CHANGED});
+ this.dispatch({
+ type: at.PLACES_BOOKMARK_ADDED,
+ data: {
+ bookmarkGuid: guid,
+ bookmarkTitle: title,
+ dateAdded: dateAdded * 1000,
+ url
+ }
+ });
+ }
+ }
+}
+
class PlacesFeed {
constructor() {
this.placesChangedTimer = null;
this.customDispatch = this.customDispatch.bind(this);
this.historyObserver = new HistoryObserver(this.customDispatch);
this.bookmarksObserver = new BookmarksObserver(this.customDispatch);
+ this.placesObserver = new PlacesObserver(this.customDispatch);
}
addObservers() {
// NB: Directly get services without importing the *BIG* PlacesUtils module
Cc["@mozilla.org/browser/nav-history-service;1"]
.getService(Ci.nsINavHistoryService)
.addObserver(this.historyObserver, true);
Cc["@mozilla.org/browser/nav-bookmarks-service;1"]
.getService(Ci.nsINavBookmarksService)
.addObserver(this.bookmarksObserver, true);
+ PlacesUtils.observers.addListener(["bookmark-added"],
+ this.placesObserver.handlePlacesEvent);
Services.obs.addObserver(this, LINK_BLOCKED_EVENT);
}
/**
* setTimeout - A custom function that creates an nsITimer that can be cancelled
*
* @param {func} callback A function to be executed after the timer expires
@@ -210,16 +210,18 @@ class PlacesFeed {
removeObservers() {
if (this.placesChangedTimer) {
this.placesChangedTimer.cancel();
this.placesChangedTimer = null;
}
PlacesUtils.history.removeObserver(this.historyObserver);
PlacesUtils.bookmarks.removeObserver(this.bookmarksObserver);
+ PlacesUtils.observers.removeListener(["bookmark-added"],
+ this.placesObserver.handlePlacesEvent);
Services.obs.removeObserver(this, LINK_BLOCKED_EVENT);
}
/**
* observe - An observer for the LINK_BLOCKED_EVENT.
* Called when a link is blocked.
*
* @param {null} subject
--- a/browser/modules/SavantShieldStudy.jsm
+++ b/browser/modules/SavantShieldStudy.jsm
@@ -271,28 +271,37 @@ class BookmarkObserver {
// there are two probes: bookmark and follow_bookmark
this.METHOD_1 = "bookmark";
this.EXTRA_SUBCATEGORY_1 = "feature";
this.METHOD_2 = "follow_bookmark";
this.EXTRA_SUBCATEGORY_2 = "navigation";
this.TYPE_BOOKMARK = Ci.nsINavBookmarksService.TYPE_BOOKMARK;
// Ignore "fake" bookmarks created for bookmark tags
this.skipTags = true;
+ this.handlePlacesEvents = this.handlePlacesEvents.bind(this);
}
init() {
this.addObservers();
}
addObservers() {
PlacesUtils.bookmarks.addObserver(this);
+ PlacesUtils.observers.addListener(["bookmark-added"], this.handlePlacesEvents);
}
- onItemAdded(itemID, parentID, index, itemType, uri, title, dateAdded, guid, parentGUID, source) {
- this.handleItemAddRemove(itemType, uri, source, "save");
+ handlePlacesEvents(events) {
+ for (let event of events) {
+ if (event.itemType == this.TYPE_BOOKMARK) {
+ this.handleItemAddRemove(this.TYPE_BOOKMARK,
+ Services.io.newURI(event.url),
+ event.source,
+ "save");
+ }
+ }
}
onItemRemoved(itemID, parentID, index, itemType, uri, guid, parentGUID, source) {
this.handleItemAddRemove(itemType, uri, source, "remove");
}
handleItemAddRemove(itemType, uri, source, event) {
/*
@@ -318,16 +327,17 @@ class BookmarkObserver {
Services.telemetry.recordEvent(this.STUDY_TELEMETRY_CATEGORY, method, event, null,
{
subcategory
});
}
removeObservers() {
PlacesUtils.bookmarks.removeObserver(this);
+ PlacesUtils.observers.removeListener(["bookmark-added"], this.handlePlacesEvents);
}
uninit() {
if (SavantShieldStudy.shouldRemoveListeners) {
this.removeObservers();
}
}
}
new file mode 100644
--- /dev/null
+++ b/dom/base/PlacesBookmark.h
@@ -0,0 +1,53 @@
+/* -*- 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_PlacesBookmark_h
+#define mozilla_dom_PlacesBookmark_h
+
+#include "mozilla/dom/PlacesEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesBookmark : public PlacesEvent
+{
+public:
+ explicit PlacesBookmark(PlacesEventType aEventType) : PlacesEvent(aEventType) {}
+
+ JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return PlacesBookmark_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmark* AsPlacesBookmark() const override { return this; }
+
+ unsigned short ItemType() { return mItemType; }
+ int64_t Id() { return mId; }
+ int64_t ParentId() { return mParentId; }
+ void GetUrl(nsString& aUrl) { aUrl = mUrl; }
+ void GetGuid(nsCString& aGuid) { aGuid = mGuid; }
+ void GetParentGuid(nsCString& aParentGuid) { aParentGuid = mParentGuid; }
+ uint16_t Source() { return mSource; }
+ bool IsTagging() { return mIsTagging; }
+
+ unsigned short mItemType;
+ int64_t mId;
+ int64_t mParentId;
+ nsString mUrl;
+ nsCString mGuid;
+ nsCString mParentGuid;
+ uint16_t mSource;
+ bool mIsTagging;
+
+protected:
+ virtual ~PlacesBookmark() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesBookmark_h
new file mode 100644
--- /dev/null
+++ b/dom/base/PlacesBookmarkAddition.h
@@ -0,0 +1,62 @@
+/* -*- 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_PlacesBookmarkAddition_h
+#define mozilla_dom_PlacesBookmarkAddition_h
+
+#include "mozilla/dom/PlacesBookmark.h"
+
+namespace mozilla {
+namespace dom {
+
+class PlacesBookmarkAddition final : public PlacesBookmark
+{
+public:
+ explicit PlacesBookmarkAddition() : PlacesBookmark(PlacesEventType::Bookmark_added) {}
+
+ static already_AddRefed<PlacesBookmarkAddition>
+ Constructor(const GlobalObject& aGlobal,
+ const PlacesBookmarkAdditionInit& aInitDict,
+ ErrorResult& aRv) {
+ RefPtr<PlacesBookmarkAddition> event = new PlacesBookmarkAddition();
+ event->mItemType = aInitDict.mItemType;
+ event->mId = aInitDict.mId;
+ event->mParentId = aInitDict.mParentId;
+ event->mIndex = aInitDict.mIndex;
+ event->mUrl = aInitDict.mUrl;
+ event->mTitle = aInitDict.mTitle;
+ event->mDateAdded = aInitDict.mDateAdded;
+ event->mGuid = aInitDict.mGuid;
+ event->mParentGuid = aInitDict.mParentGuid;
+ event->mSource = aInitDict.mSource;
+ event->mIsTagging = aInitDict.mIsTagging;
+ return event.forget();
+ }
+
+ JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
+ {
+ return PlacesBookmarkAddition_Binding::Wrap(aCx, this, aGivenProto);
+ }
+
+ const PlacesBookmarkAddition* AsPlacesBookmarkAddition() const override { return this; }
+
+ int32_t Index() { return mIndex; }
+ void GetTitle(nsString& aTitle) { aTitle = mTitle; }
+ uint64_t DateAdded() { return mDateAdded; }
+
+ int32_t mIndex;
+ nsString mTitle;
+ uint64_t mDateAdded;
+
+private:
+ ~PlacesBookmarkAddition() = default;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PlacesBookmarkAddition_h
--- a/dom/base/PlacesEvent.h
+++ b/dom/base/PlacesEvent.h
@@ -32,16 +32,18 @@ public:
nsISupports* GetParentObject() const;
JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
PlacesEventType Type() const { return mType; }
virtual const PlacesVisit* AsPlacesVisit() const { return nullptr; }
+ virtual const PlacesBookmark* AsPlacesBookmark() const { return nullptr; }
+ virtual const PlacesBookmarkAddition* AsPlacesBookmarkAddition() const { return nullptr; }
protected:
virtual ~PlacesEvent() = default;
PlacesEventType mType;
};
} // namespace dom
} // namespace mozilla
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -193,16 +193,18 @@ EXPORTS.mozilla.dom += [
'MessageSender.h',
'MozQueryInterface.h',
'NameSpaceConstants.h',
'Navigator.h',
'NodeInfo.h',
'NodeInfoInlines.h',
'NodeIterator.h',
'ParentProcessMessageManager.h',
+ 'PlacesBookmark.h',
+ 'PlacesBookmarkAddition.h',
'PlacesEvent.h',
'PlacesObservers.h',
'PlacesVisit.h',
'PlacesWeakCallbackWrapper.h',
'Pose.h',
'ProcessGlobal.h',
'ProcessMessageManager.h',
'ResponsiveImageSelector.h',
--- a/dom/chrome-webidl/PlacesEvent.webidl
+++ b/dom/chrome-webidl/PlacesEvent.webidl
@@ -1,15 +1,20 @@
enum PlacesEventType {
"none",
/**
* data: PlacesVisit. Fired whenever a page is visited.
*/
"page-visited",
+ /**
+ * data: PlacesBookmarkAddition. Fired whenever a bookmark
+ * (or a bookmark folder/separator) is created.
+ */
+ "bookmark-added",
};
[ChromeOnly, Exposed=(Window,System)]
interface PlacesEvent {
readonly attribute PlacesEventType type;
};
[ChromeOnly, Exposed=(Window,System)]
@@ -62,8 +67,87 @@ interface PlacesVisit : PlacesEvent {
readonly attribute unsigned long typedCount;
/**
* The last known title of the page. Might not be from the current visit,
* and might be null if it is not known.
*/
readonly attribute DOMString? lastKnownTitle;
};
+
+/**
+ * Base class for properties that are common to all bookmark events.
+ */
+[ChromeOnly, Exposed=(Window,System)]
+interface PlacesBookmark : PlacesEvent {
+ /**
+ * The id of the item.
+ */
+ readonly attribute long long id;
+
+ /**
+ * The id of the folder to which the item belongs.
+ */
+ readonly attribute long long parentId;
+
+ /**
+ * The type of the added item (see TYPE_* constants in nsINavBooksService.idl).
+ */
+ readonly attribute unsigned short itemType;
+
+ /**
+ * The URI of the added item if it was TYPE_BOOKMARK, "" otherwise.
+ */
+ readonly attribute DOMString url;
+
+ /**
+ * The unique ID associated with the item.
+ */
+ readonly attribute ByteString guid;
+
+ /**
+ * The unique ID associated with the item's parent.
+ */
+ readonly attribute ByteString parentGuid;
+
+ /**
+ * A change source constant from nsINavBookmarksService::SOURCE_*,
+ * passed to the method that notifies the observer.
+ */
+ readonly attribute unsigned short source;
+
+ /**
+ * True if the item is a tag or a tag folder
+ */
+ readonly attribute boolean isTagging;
+};
+
+dictionary PlacesBookmarkAdditionInit {
+ required long long id;
+ required long long parentId;
+ required unsigned short itemType;
+ required DOMString url;
+ required ByteString guid;
+ required ByteString parentGuid;
+ required unsigned short source;
+ required long index;
+ required DOMString title;
+ required unsigned long long dateAdded;
+ required boolean isTagging;
+};
+
+[ChromeOnly, Exposed=(Window,System), Constructor(PlacesBookmarkAdditionInit initDict)]
+interface PlacesBookmarkAddition : PlacesBookmark {
+ /**
+ * The item's index in the folder.
+ */
+ readonly attribute long index;
+
+ /**
+ * The title of the added item.
+ */
+ readonly attribute DOMString title;
+
+ /**
+ * The time that the item was added, in milliseconds from the epoch.
+ */
+ readonly attribute unsigned long long dateAdded;
+};
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -1277,23 +1277,26 @@ BookmarksTracker.prototype = {
set ignoreAll(value) {},
// We never want to persist changed IDs, as the changes are already stored
// in Places.
persistChangedIDs: false,
onStart() {
PlacesUtils.bookmarks.addObserver(this, true);
+ this._placesListener = new PlacesWeakCallbackWrapper(this.handlePlacesEvents.bind(this));
+ PlacesUtils.observers.addListener(["bookmark-added"], this._placesListener);
Svc.Obs.add("bookmarks-restore-begin", this);
Svc.Obs.add("bookmarks-restore-success", this);
Svc.Obs.add("bookmarks-restore-failed", this);
},
onStop() {
PlacesUtils.bookmarks.removeObserver(this);
+ PlacesUtils.observers.removeListener(["bookmark-added"], this._placesListener);
Svc.Obs.remove("bookmarks-restore-begin", this);
Svc.Obs.remove("bookmarks-restore-success", this);
Svc.Obs.remove("bookmarks-restore-failed", this);
},
// Ensure we aren't accidentally using the base persistence.
addChangedID(id, when) {
throw new Error("Don't add IDs to the bookmarks tracker");
@@ -1351,25 +1354,25 @@ BookmarksTracker.prototype = {
_upScore: function BMT__upScore() {
if (this._batchDepth == 0) {
this.score += SCORE_INCREMENT_XLARGE;
} else {
this._batchSawScoreIncrement = true;
}
},
- onItemAdded: function BMT_onItemAdded(itemId, folder, index,
- itemType, uri, title, dateAdded,
- guid, parentGuid, source) {
- if (IGNORED_SOURCES.includes(source)) {
- return;
+ handlePlacesEvents(events) {
+ for (let event of events) {
+ if (IGNORED_SOURCES.includes(event.source)) {
+ continue;
+ }
+
+ this._log.trace("'bookmark-added': " + event.id);
+ this._upScore();
}
-
- this._log.trace("onItemAdded: " + itemId);
- this._upScore();
},
onItemRemoved(itemId, parentId, index, type, uri,
guid, parentGuid, source) {
if (IGNORED_SOURCES.includes(source)) {
return;
}
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -268,34 +268,46 @@ var Bookmarks = Object.freeze({
// Set index in the appending case.
if (insertInfo.index == this.DEFAULT_INDEX ||
insertInfo.index > parent._childCount) {
insertInfo.index = parent._childCount;
}
let item = await insertBookmark(insertInfo, parent);
- // Notify onItemAdded to listeners.
- let observers = PlacesUtils.bookmarks.getObservers();
// We need the itemId to notify, though once the switch to guids is
// complete we may stop using it.
- let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
let itemId = await PlacesUtils.promiseItemId(item.guid);
// 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;
- notify(observers, "onItemAdded", [ itemId, parent._id, item.index,
- item.type, uri, item.title,
- PlacesUtils.toPRTime(item.dateAdded), item.guid,
- item.parentGuid, item.source ],
- { isTagging: isTagging || isTagsFolder });
+ 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]);
// 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, true))) {
notify(observers, "onItemChanged", [ entry._id, "tags", false, "",
PlacesUtils.toPRTime(entry.lastModified),
entry.type, entry._parentId,
entry.guid, entry.parentGuid,
"", item.source ]);
}
}
@@ -543,55 +555,71 @@ var Bookmarks = Object.freeze({
for (let insertInfo of insertInfos) {
if (insertInfo.parentGuid == tree.guid) {
insertInfo.index += rootIndex++;
}
}
// We need the itemIds to notify, though once the switch to guids is
// complete we may stop using them.
let itemIdMap = await PlacesUtils.promiseManyItemIds(insertInfos.map(info => info.guid));
- // Notify onItemAdded to listeners.
- let observers = PlacesUtils.bookmarks.getObservers();
+
+ let notifications = [];
for (let i = 0; i < insertInfos.length; i++) {
let item = insertInfos[i];
let itemId = itemIdMap.get(item.guid);
- let uri = item.hasOwnProperty("url") ? PlacesUtils.toURI(item.url) : null;
// For sub-folders, we need to make sure their children have the correct parent ids.
let parentId;
if (item.parentGuid === treeParent.guid) {
// This is a direct child of the tree parent, so we can use the
// existing parent's id.
parentId = treeParent._id;
} else {
// This is a parent folder that's been updated, so we need to
// use the new item id.
parentId = itemIdMap.get(item.parentGuid);
}
- notify(observers, "onItemAdded", [ itemId, parentId, item.index,
- item.type, uri, item.title,
- PlacesUtils.toPRTime(item.dateAdded), item.guid,
- item.parentGuid, item.source ],
- { isTagging: false });
+ let url = "";
+ if (item.type == Bookmarks.TYPE_BOOKMARK) {
+ url = (item.url instanceof URL) ? item.url.href : item.url;
+ }
+
+ notifications.push(new PlacesBookmarkAddition({
+ id: itemId,
+ url,
+ itemType: item.type,
+ parentId,
+ index: item.index,
+ title: item.title,
+ dateAdded: item.dateAdded,
+ guid: item.guid,
+ parentGuid: item.parentGuid,
+ source: item.source,
+ isTagging: false,
+ }));
+
// Note, annotations for livemark data are deleted from insertInfo
// within appendInsertionInfoForInfoArray, so we won't be duplicating
// the insertions here.
try {
await handleBookmarkItemSpecialData(itemId, item);
} catch (ex) {
// This is not critical, regardless the bookmark has been created
// and we should continue notifying the next ones.
Cu.reportError(`An error occured while handling special bookmark data: ${ex}`);
}
// Remove non-enumerable properties.
delete item.source;
insertInfos[i] = Object.assign({}, item);
}
+
+ PlacesObservers.notifyListeners(notifications);
+
return insertInfos;
})();
},
/**
* Updates a bookmark-item.
*
* Only set the properties which should be changed (undefined properties
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -2704,36 +2704,39 @@ var GuidHelper = {
if (!("observer" in this)) {
/**
* This observers serves two purposes:
* (1) Invalidate cached id<->GUID paris on when items are removed.
* (2) Cache GUIDs given us free of charge by onItemAdded/onItemRemoved.
* So, for exmaple, when the NewBookmark needs the new GUID, we already
* have it cached.
*/
+ let listener = events => {
+ for (let event of events) {
+ this.updateCache(event.id, event.guid);
+ this.updateCache(event.parentId, event.parentGuid);
+ }
+ };
this.observer = {
- onItemAdded: (aItemId, aParentId, aIndex, aItemType, aURI, aTitle,
- aDateAdded, aGuid, aParentGuid) => {
- this.updateCache(aItemId, aGuid);
- this.updateCache(aParentId, aParentGuid);
- },
onItemRemoved:
(aItemId, aParentId, aIndex, aItemTyep, aURI, aGuid, aParentGuid) => {
this.guidsForIds.delete(aItemId);
this.idsForGuids.delete(aGuid);
this.updateCache(aParentId, aParentGuid);
},
QueryInterface: ChromeUtils.generateQI([Ci.nsINavBookmarkObserver]),
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onItemChanged() {},
onItemVisited() {},
onItemMoved() {},
};
PlacesUtils.bookmarks.addObserver(this.observer);
+ PlacesUtils.observers.addListener(["bookmark-added"], listener);
PlacesUtils.registerShutdownFunction(() => {
PlacesUtils.bookmarks.removeObserver(this.observer);
+ PlacesUtils.observers.removeListener(["bookmark-added"], listener);
});
}
}
};
--- a/toolkit/components/places/SyncedBookmarksMirror.jsm
+++ b/toolkit/components/places/SyncedBookmarksMirror.jsm
@@ -4558,24 +4558,29 @@ class BookmarkObserverRecorder {
WHERE frecency < 0
ORDER BY frecency ASC
LIMIT :limit
)`,
{ limit: this.maxFrecenciesToRecalculate });
}
noteItemAdded(info) {
- let uri = info.urlHref ? Services.io.newURI(info.urlHref) : null;
- this.bookmarkObserverNotifications.push({
- name: "onItemAdded",
+ this.bookmarkObserverNotifications.push(new PlacesBookmarkAddition({
+ id: info.id,
+ parentId: info.parentId,
+ index: info.position,
+ url: info.urlHref || "",
+ title: info.title,
+ dateAdded: info.dateAdded,
+ guid: info.guid,
+ parentGuid: info.parentGuid,
+ source: PlacesUtils.bookmarks.SOURCES.SYNC,
+ itemType: info.type,
isTagging: info.isTagging,
- args: [info.id, info.parentId, info.position, info.type, uri, info.title,
- info.dateAdded, info.guid, info.parentGuid,
- PlacesUtils.bookmarks.SOURCES.SYNC],
- });
+ }));
}
noteGuidChanged(info) {
PlacesUtils.invalidateCachedGuidFor(info.id);
this.bookmarkObserverNotifications.push({
name: "onItemChanged",
isTagging: false,
args: [info.id, "guid", /* isAnnotationProperty */ false, info.newGuid,
@@ -4654,22 +4659,30 @@ class BookmarkObserverRecorder {
});
}
async notifyBookmarkObservers() {
MirrorLog.trace("Notifying bookmark observers");
let observers = PlacesUtils.bookmarks.getObservers();
for (let observer of observers) {
this.notifyObserver(observer, "onBeginUpdateBatch");
- for await (let info of yieldingIterator(this.bookmarkObserverNotifications)) {
- if (info.isTagging && observer.skipTags) {
- continue;
+ }
+ for await (let info of yieldingIterator(this.bookmarkObserverNotifications)) {
+ if (info instanceof PlacesEvent) {
+ PlacesObservers.notifyListeners([info]);
+ } else {
+ for (let observer of observers) {
+ if (info.isTagging && observer.skipTags) {
+ continue;
+ }
+ this.notifyObserver(observer, info.name, info.args);
}
- this.notifyObserver(observer, info.name, info.args);
}
+ }
+ for (let observer of observers) {
this.notifyObserver(observer, "onEndUpdateBatch");
}
}
async notifyAnnoObservers() {
MirrorLog.trace("Notifying anno observers");
let observers = PlacesUtils.annotations.getObservers();
for (let observer of observers) {
--- a/toolkit/components/places/nsINavBookmarksService.idl
+++ b/toolkit/components/places/nsINavBookmarksService.idl
@@ -38,56 +38,16 @@ interface nsINavBookmarkObserver : nsISu
void onBeginUpdateBatch();
/**
* Notifies that a batch transaction has ended.
*/
void onEndUpdateBatch();
/**
- * Notifies that an item (any type) was added. Called after the actual
- * addition took place.
- * When a new item is created, all the items following it in the same folder
- * will have their index shifted down, but no additional notifications will
- * be sent.
- *
- * @param aItemId
- * The id of the item that was added.
- * @param aParentId
- * The id of the folder to which the item was added.
- * @param aIndex
- * The item's index in the folder.
- * @param aItemType
- * The type of the added item (see TYPE_* constants below).
- * @param aURI
- * The URI of the added item if it was TYPE_BOOKMARK, null otherwise.
- * @param aTitle
- * The title of the added item.
- * @param aDateAdded
- * The stored date added value, in microseconds from the epoch.
- * @param aGuid
- * The unique ID associated with the item.
- * @param aParentGuid
- * The unique ID associated with the item's parent.
- * @param aSource
- * A change source constant from nsINavBookmarksService::SOURCE_*,
- * passed to the method that notifies the observer.
- */
- void onItemAdded(in long long aItemId,
- in long long aParentId,
- in long aIndex,
- in unsigned short aItemType,
- in nsIURI aURI,
- in AUTF8String aTitle,
- in PRTime aDateAdded,
- in ACString aGuid,
- in ACString aParentGuid,
- in unsigned short aSource);
-
- /**
* Notifies that an item was removed. Called after the actual remove took
* place.
* When an item is removed, all the items following it in the same folder
* will have their index shifted down, but no additional notifications will
* be sent.
*
* @param aItemId
* The id of the item that was removed.
--- a/toolkit/components/places/nsLivemarkService.js
+++ b/toolkit/components/places/nsLivemarkService.js
@@ -356,17 +356,16 @@ LivemarkService.prototype = {
});
},
// nsINavBookmarkObserver
onBeginUpdateBatch() {},
onEndUpdateBatch() {},
onItemVisited() {},
- onItemAdded() {},
onItemChanged(id, property, isAnno, value, lastModified, itemType, parentId,
guid, parentGuid) {
if (itemType != Ci.nsINavBookmarksService.TYPE_FOLDER)
return;
this._withLivemarksMap(livemarksMap => {
if (livemarksMap.has(guid)) {
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -12,16 +12,17 @@
#include "nsAppDirectoryServiceDefs.h"
#include "nsNetUtil.h"
#include "nsUnicharUtils.h"
#include "nsPrintfCString.h"
#include "nsQueryObject.h"
#include "mozilla/Preferences.h"
#include "mozilla/storage.h"
+#include "mozilla/dom/PlacesBookmarkAddition.h"
#include "mozilla/dom/PlacesObservers.h"
#include "mozilla/dom/PlacesVisit.h"
#include "GeckoProfiler.h"
using namespace mozilla;
// These columns sit to the right of the kGetInfoIndex_* columns.
@@ -210,17 +211,17 @@ nsNavBookmarks::Init()
// Allows us to notify on title changes. MUST BE LAST so it is impossible
// to fail after this call, or the history service will have a reference to
// us and we won't go away.
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ENSURE_STATE(history);
history->AddObserver(this, true);
AutoTArray<PlacesEventType, 1> events;
- events.AppendElement(PlacesEventType::Page_visited);
+ events.AppendElement(PlacesEventType::Page_visited, fallible);
PlacesObservers::AddListener(events, this);
// DO NOT PUT STUFF HERE that can fail. See observer comment above.
return NS_OK;
}
nsresult
@@ -566,21 +567,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);
- NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers,
- SKIP_TAGS(grandParentId == mDB->GetTagsFolderId()),
- OnItemAdded(*aNewBookmarkId, aFolder, index,
- TYPE_BOOKMARK, aURI, title, dateAdded,
- guid, folderGuid, aSource));
+ if (mCanNotify) {
+ Sequence<OwningNonNull<PlacesEvent>> events;
+ 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);
+ }
// 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);
@@ -780,21 +798,34 @@ nsNavBookmarks::CreateFolder(int64_t aPa
nullptr, aSource, aNewFolderId, guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = transaction.Commit();
NS_ENSURE_SUCCESS(rv, rv);
int64_t tagsRootId = TagsRootId();
- NOTIFY_BOOKMARKS_OBSERVERS(mCanNotify, mObservers,
- SKIP_TAGS(aParent == tagsRootId),
- OnItemAdded(*aNewFolderId, aParent, index, FOLDER,
- nullptr, title, dateAdded, guid,
- folderGuid, aSource));
+ if (mCanNotify) {
+ Sequence<OwningNonNull<PlacesEvent>> events;
+ RefPtr<PlacesBookmarkAddition> folder = new PlacesBookmarkAddition();
+ folder->mItemType = TYPE_FOLDER;
+ folder->mId = *aNewFolderId;
+ folder->mParentId = aParent;
+ folder->mIndex = index;
+ folder->mTitle.Assign(NS_ConvertUTF8toUTF16(title));
+ folder->mDateAdded = dateAdded / 1000;
+ folder->mGuid.Assign(guid);
+ folder->mParentGuid.Assign(folderGuid);
+ folder->mSource = aSource;
+ folder->mIsTagging = aParent == tagsRootId;
+ bool success = !!events.AppendElement(folder.forget(), fallible);
+ MOZ_RELEASE_ASSERT(success);
+
+ PlacesObservers::NotifyListeners(events);
+ }
return NS_OK;
}
bool nsNavBookmarks::IsLivemark(int64_t aFolderId)
{
nsAnnotationService* annosvc = nsAnnotationService::GetAnnotationService();
NS_ENSURE_TRUE(annosvc, false);
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -2699,23 +2699,22 @@ nsNavHistoryQueryResultNode::NotifyIfTag
return NS_OK;
}
/**
* These are the bookmark observer functions for query nodes. They listen
* for bookmark events and refresh the results if we have any dependence on
* the bookmark system.
*/
-NS_IMETHODIMP
+nsresult
nsNavHistoryQueryResultNode::OnItemAdded(int64_t aItemId,
int64_t aParentId,
int32_t aIndex,
uint16_t aItemType,
nsIURI* aURI,
- const nsACString& aTitle,
PRTime aDateAdded,
const nsACString& aGUID,
const nsACString& aParentGUID,
uint16_t aSource)
{
if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
mLiveUpdate != QUERYUPDATE_SIMPLE &&
mLiveUpdate != QUERYUPDATE_TIME &&
@@ -3412,23 +3411,22 @@ nsNavHistoryFolderResultNode::OnBeginUpd
NS_IMETHODIMP
nsNavHistoryFolderResultNode::OnEndUpdateBatch()
{
return NS_OK;
}
-NS_IMETHODIMP
+nsresult
nsNavHistoryFolderResultNode::OnItemAdded(int64_t aItemId,
int64_t aParentFolder,
int32_t aIndex,
uint16_t aItemType,
nsIURI* aURI,
- const nsACString& aTitle,
PRTime aDateAdded,
const nsACString& aGUID,
const nsACString& aParentGUID,
uint16_t aSource)
{
MOZ_ASSERT(aParentFolder == mTargetFolderItemId, "Got wrong bookmark update");
RESTART_AND_RETURN_IF_ASYNC_PENDING();
@@ -3862,31 +3860,29 @@ nsNavHistoryFolderResultNode::OnItemMove
node->mBookmarkIndex = aNewIndex;
// adjust position
EnsureItemPosition(index);
return NS_OK;
} else {
// moving between two different folders, just do a remove and an add
nsCOMPtr<nsIURI> itemURI;
- nsAutoCString itemTitle;
if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
NS_ENSURE_TRUE(bookmarks, NS_ERROR_OUT_OF_MEMORY);
nsresult rv = bookmarks->GetBookmarkURI(aItemId, getter_AddRefs(itemURI));
NS_ENSURE_SUCCESS(rv, rv);
- rv = bookmarks->GetItemTitle(aItemId, itemTitle);
NS_ENSURE_SUCCESS(rv, rv);
}
if (aOldParent == mTargetFolderItemId) {
OnItemRemoved(aItemId, aOldParent, aOldIndex, aItemType, itemURI,
aGUID, aOldParentGUID, aSource);
}
if (aNewParent == mTargetFolderItemId) {
- OnItemAdded(aItemId, aNewParent, aNewIndex, aItemType, itemURI, itemTitle,
+ OnItemAdded(aItemId, aNewParent, aNewIndex, aItemType, itemURI,
RoundedPRNow(), // This is a dummy dateAdded, not the real value.
aGUID, aNewParentGUID, aSource);
}
}
return NS_OK;
}
@@ -3974,40 +3970,42 @@ nsNavHistoryResult::~nsNavHistoryResult(
delete it.Data();
it.Remove();
}
}
void
nsNavHistoryResult::StopObserving()
{
+ AutoTArray<PlacesEventType, 2> events;
if (mIsBookmarkFolderObserver || mIsAllBookmarksObserver) {
nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
if (bookmarks) {
bookmarks->RemoveObserver(this);
mIsBookmarkFolderObserver = false;
mIsAllBookmarksObserver = false;
}
+ events.AppendElement(PlacesEventType::Bookmark_added);
}
if (mIsMobilePrefObserver) {
Preferences::UnregisterCallback(OnMobilePrefChangedCallback,
MOBILE_BOOKMARKS_PREF,
this);
mIsMobilePrefObserver = false;
}
if (mIsHistoryObserver) {
nsNavHistory* history = nsNavHistory::GetHistoryService();
if (history) {
history->RemoveObserver(this);
- AutoTArray<PlacesEventType, 1> events;
events.AppendElement(PlacesEventType::Page_visited);
- PlacesObservers::RemoveListener(events, this);
mIsHistoryObserver = false;
}
}
+
+ PlacesObservers::RemoveListener(events, this);
}
void
nsNavHistoryResult::AddHistoryObserver(nsNavHistoryQueryResultNode* aNode)
{
if (!mIsHistoryObserver) {
nsNavHistory* history = nsNavHistory::GetHistoryService();
NS_ASSERTION(history, "Can't create history service");
@@ -4031,16 +4029,19 @@ nsNavHistoryResult::AddAllBookmarksObser
{
if (!mIsAllBookmarksObserver && !mIsBookmarkFolderObserver) {
nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
if (!bookmarks) {
MOZ_ASSERT_UNREACHABLE("Can't create bookmark service");
return;
}
bookmarks->AddObserver(this, true);
+ AutoTArray<PlacesEventType, 1> events;
+ events.AppendElement(PlacesEventType::Bookmark_added);
+ PlacesObservers::AddListener(events, this);
mIsAllBookmarksObserver = true;
}
// Don't add duplicate observers. In some case we don't unregister when
// children are cleared (see ClearChildren) and the next FillChildren call
// will try to add the observer again.
if (mAllBookmarksObservers.IndexOf(aNode) == QueryObserverList::NoIndex) {
mAllBookmarksObservers.AppendElement(aNode);
}
@@ -4335,47 +4336,16 @@ nsNavHistoryResult::OnEndUpdateBatch()
NOTIFY_RESULT_OBSERVERS(this, Batching(false));
}
return NS_OK;
}
NS_IMETHODIMP
-nsNavHistoryResult::OnItemAdded(int64_t aItemId,
- int64_t aParentId,
- int32_t aIndex,
- uint16_t aItemType,
- nsIURI* aURI,
- const nsACString& aTitle,
- PRTime aDateAdded,
- const nsACString& aGUID,
- const nsACString& aParentGUID,
- uint16_t aSource)
-{
- NS_ENSURE_ARG(aItemType != nsINavBookmarksService::TYPE_BOOKMARK ||
- aURI);
-
- ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(aParentId,
- OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
- aGUID, aParentGUID, aSource)
- );
- ENUMERATE_HISTORY_OBSERVERS(
- OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
- aGUID, aParentGUID, aSource)
- );
- ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
- OnItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle, aDateAdded,
- aGUID, aParentGUID, aSource)
- );
- return NS_OK;
-}
-
-
-NS_IMETHODIMP
nsNavHistoryResult::OnItemRemoved(int64_t aItemId,
int64_t aParentId,
int32_t aIndex,
uint16_t aItemType,
nsIURI* aURI,
const nsACString& aGUID,
const nsACString& aParentGUID,
uint16_t aSource)
@@ -4596,33 +4566,68 @@ nsNavHistoryResult::OnVisit(nsIURI* aURI
return NS_OK;
}
void
nsNavHistoryResult::HandlePlacesEvent(const PlacesEventSequence& aEvents) {
for (const auto& event : aEvents) {
- if (NS_WARN_IF(event->Type() != PlacesEventType::Page_visited)) {
- continue;
- }
-
- const dom::PlacesVisit* visit = event->AsPlacesVisit();
- if (NS_WARN_IF(!visit)) {
- continue;
+ switch (event->Type()) {
+ case PlacesEventType::Page_visited: {
+ const dom::PlacesVisit* visit = event->AsPlacesVisit();
+ if (NS_WARN_IF(!visit)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), visit->mUrl));
+ if (!uri) {
+ continue;
+ }
+ OnVisit(uri, visit->mVisitId, visit->mVisitTime * 1000,
+ visit->mTransitionType, visit->mPageGuid,
+ visit->mHidden, visit->mVisitCount, visit->mLastKnownTitle);
+ break;
+ }
+ case PlacesEventType::Bookmark_added: {
+ const dom::PlacesBookmarkAddition* item = event->AsPlacesBookmarkAddition();
+ if (NS_WARN_IF(!item)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ if (item->mItemType == nsINavBookmarksService::TYPE_BOOKMARK) {
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), item->mUrl));
+ if (!uri) {
+ continue;
+ }
+ }
+
+ ENUMERATE_BOOKMARK_FOLDER_OBSERVERS(item->mParentId,
+ OnItemAdded(item->mId, item->mParentId, item->mIndex,
+ item->mItemType, uri, item->mDateAdded * 1000,
+ item->mGuid, item->mParentGuid, item->mSource)
+ );
+ ENUMERATE_HISTORY_OBSERVERS(
+ OnItemAdded(item->mId, item->mParentId,item->mIndex,
+ item->mItemType, uri, item->mDateAdded * 1000,
+ item->mGuid, item->mParentGuid, item->mSource)
+ );
+ ENUMERATE_ALL_BOOKMARKS_OBSERVERS(
+ OnItemAdded(item->mId, item->mParentId,item->mIndex,
+ item->mItemType, uri, item->mDateAdded * 1000,
+ item->mGuid, item->mParentGuid, item->mSource)
+ );
+ break;
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("Receive notification of a type not subscribed to.");
+ }
}
-
- nsCOMPtr<nsIURI> uri;
- MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), visit->mUrl));
- if (!uri) {
- return;
- }
- OnVisit(uri, visit->mVisitId, visit->mVisitTime * 1000,
- visit->mTransitionType, visit->mPageGuid,
- visit->mHidden, visit->mVisitCount, visit->mLastKnownTitle);
}
}
NS_IMETHODIMP
nsNavHistoryResult::OnTitleChanged(nsIURI* aURI,
const nsAString& aPageTitle,
const nsACString& aGUID)
--- a/toolkit/components/places/nsNavHistoryResult.h
+++ b/toolkit/components/places/nsNavHistoryResult.h
@@ -623,16 +623,25 @@ public:
bool CanExpand();
bool IsContainersQuery();
virtual nsresult OpenContainer() override;
NS_DECL_BOOKMARK_HISTORY_OBSERVER_INTERNAL
+ nsresult OnItemAdded(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ PRTime aDateAdded,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource);
// The internal version has an output aAdded parameter, it is incremented by
// query nodes when the visited uri belongs to them. If no such query exists,
// the history result creates a new query node dynamically.
nsresult OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime,
uint32_t aTransitionType, bool aHidden,
uint32_t* aAdded);
virtual void OnRemoving() override;
@@ -701,16 +710,26 @@ public:
virtual nsresult OpenContainerAsync() override;
NS_DECL_ASYNCSTATEMENTCALLBACK
// This object implements a bookmark observer interface. This is called from the
// result's actual observer and it knows all observers are FolderResultNodes
NS_DECL_NSINAVBOOKMARKOBSERVER
+ nsresult OnItemAdded(int64_t aItemId,
+ int64_t aParentId,
+ int32_t aIndex,
+ uint16_t aItemType,
+ nsIURI* aURI,
+ PRTime aDateAdded,
+ const nsACString& aGUID,
+ const nsACString& aParentGUID,
+ uint16_t aSource);
+
virtual void OnRemoving() override;
// this indicates whether the folder contents are valid, they don't go away
// after the container is closed until a notification comes in
bool mContentsValid;
// If the node is generated from a place:folder=X query, this is the target
// folder id and GUID. For regular folder nodes, they are set to the same
--- a/toolkit/components/places/nsTaggingService.js
+++ b/toolkit/components/places/nsTaggingService.js
@@ -8,18 +8,21 @@ ChromeUtils.import("resource://gre/modul
ChromeUtils.import("resource://gre/modules/PlacesUtils.jsm");
const TOPIC_SHUTDOWN = "places-shutdown";
/**
* The Places Tagging Service
*/
function TaggingService() {
+ this.handlePlacesEvents = this.handlePlacesEvents.bind(this);
+
// Observe bookmarks changes.
PlacesUtils.bookmarks.addObserver(this);
+ PlacesUtils.observers.addListener(["bookmark-added"], this.handlePlacesEvents);
// Cleanup on shutdown.
Services.obs.addObserver(this, TOPIC_SHUTDOWN);
}
TaggingService.prototype = {
/**
* Creates a tag container under the tags-root with the given name.
@@ -341,16 +344,17 @@ TaggingService.prototype = {
get hasTags() {
return this._tagFolders.length > 0;
},
// nsIObserver
observe: function TS_observe(aSubject, aTopic, aData) {
if (aTopic == TOPIC_SHUTDOWN) {
PlacesUtils.bookmarks.removeObserver(this);
+ PlacesUtils.observers.removeListener(["bookmark-added"], this.handlePlacesEvents);
Services.obs.removeObserver(this, TOPIC_SHUTDOWN);
}
},
/**
* If the only bookmark items associated with aURI are contained in tag
* folders, returns the IDs of those items. This can be the case if
* the URI was bookmarked and tagged at some point, but the bookmark was
@@ -387,27 +391,28 @@ TaggingService.prototype = {
}
} finally {
stmt.finalize();
}
return isBookmarked ? [] : itemIds;
},
- // nsINavBookmarkObserver
- onItemAdded: function TS_onItemAdded(aItemId, aFolderId, aIndex, aItemType,
- aURI, aTitle) {
- // Nothing to do if this is not a tag.
- if (aFolderId != PlacesUtils.tagsFolderId ||
- aItemType != PlacesUtils.bookmarks.TYPE_FOLDER)
- return;
+ handlePlacesEvents(events) {
+ for (let event of events) {
+ if (!event.isTagging ||
+ event.itemType != PlacesUtils.bookmarks.TYPE_FOLDER) {
+ continue;
+ }
- this._tagFolders[aItemId] = aTitle;
+ this._tagFolders[event.id] = event.title;
+ }
},
+ // nsINavBookmarkObserver
onItemRemoved: function TS_onItemRemoved(aItemId, aFolderId, aIndex,
aItemType, aURI, aGuid, aParentGuid,
aSource) {
// Item is a tag folder.
if (aFolderId == PlacesUtils.tagsFolderId && this._tagFolders[aItemId]) {
delete this._tagFolders[aItemId];
} else if (aURI && !this._tagFolders[aFolderId]) {
// Item is a bookmark that was removed from a non-tag folder.