Bug 1340498 - Update onVisits uses to 'page-visited' r?mak draft
authorDoug Thayer <dothayer@mozilla.com>
Wed, 14 Feb 2018 09:11:49 -0800
changeset 811528 6554d385aaa2aef87f6c7475a07daa79703f0d93
parent 811527 400e56826e64fd063edffbbaeaafbbced7bc9d66
child 811529 1274b21b579cdd08ce37b92ea296eec336022544
push id114328
push userbmo:dothayer@mozilla.com
push dateWed, 27 Jun 2018 18:14:18 +0000
reviewersmak
bugs1340498
milestone63.0a1
Bug 1340498 - Update onVisits uses to 'page-visited' r?mak Consuming the new 'page-visited' notification was fairly trivial, since it was already brought over to onVisits. There's not much to say about this other than that I'm a little bit uncertain about all the hoops we have to jump through to get a JSContext and GlobalObject from History.cpp (which is discussed in the earlier commit in the series). MozReview-Commit-ID: LHaBWSylyLI
browser/components/extensions/parent/ext-history.js
browser/extensions/activity-stream/lib/PlacesFeed.jsm
browser/modules/WindowsPreviewPerTab.jsm
services/sync/modules/engines/history.js
toolkit/components/downloads/DownloadIntegration.jsm
toolkit/components/places/History.cpp
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/mozIAsyncLivemarks.idl
toolkit/components/places/nsLivemarkService.js
toolkit/components/places/nsNavBookmarks.cpp
toolkit/components/places/nsNavBookmarks.h
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/nsNavHistory.h
toolkit/components/places/nsNavHistoryResult.cpp
toolkit/components/places/nsNavHistoryResult.h
toolkit/components/places/nsPlacesExpiration.js
toolkit/components/thumbnails/PageThumbs.jsm
toolkit/modules/NewTabUtils.jsm
--- a/browser/components/extensions/parent/ext-history.js
+++ b/browser/components/extensions/parent/ext-history.js
@@ -89,27 +89,27 @@ const convertNavHistoryContainerResultNo
 var _observer;
 
 const getHistoryObserver = () => {
   if (!_observer) {
     _observer = new class extends EventEmitter {
       onDeleteURI(uri, guid, reason) {
         this.emit("visitRemoved", {allHistory: false, urls: [uri.spec]});
       }
-      onVisits(visits) {
-        for (let visit of visits) {
-          let data = {
-            id: visit.guid,
-            url: visit.uri.spec,
-            title: visit.lastKnownTitle || "",
-            lastVisitTime: visit.time / 1000,  // time from Places is microseconds,
-            visitCount: visit.visitCount,
-            typedCount: visit.typed,
+      handlePlacesEvents(events) {
+        for (let event of events) {
+          let visit = {
+            id: event.pageGuid,
+            url: event.url,
+            title: event.lastKnownTitle || "",
+            lastVisitTime: event.visitTime,
+            visitCount: event.visitCount,
+            typedCount: event.typedCount,
           };
-          this.emit("visited", data);
+          this.emit("visited", visit);
         }
       }
       onBeginUpdateBatch() {}
       onEndUpdateBatch() {}
       onTitleChanged(uri, title) {
         this.emit("titleChanged", {url: uri.spec, title: title});
       }
       onClearHistory() {
@@ -117,16 +117,19 @@ const getHistoryObserver = () => {
       }
       onPageChanged() {}
       onFrecencyChanged() {}
       onManyFrecenciesChanged() {}
       onDeleteVisits(uri, time, guid, reason) {
         this.emit("visitRemoved", {allHistory: false, urls: [uri.spec]});
       }
     }();
+    PlacesUtils.observers.addListener(
+      ["page-visited"],
+      _observer.handlePlacesEvents.bind(_observer));
     PlacesUtils.history.addObserver(_observer);
   }
   return _observer;
 };
 
 this.history = class extends ExtensionAPI {
   getAPI(context) {
     return {
--- a/browser/extensions/activity-stream/lib/PlacesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/PlacesFeed.jsm
@@ -55,17 +55,16 @@ class HistoryObserver extends Observer {
     this.dispatch({type: at.PLACES_HISTORY_CLEARED});
   }
 
   // Empty functions to make xpconnect happy
   onBeginUpdateBatch() {}
 
   onEndUpdateBatch() {}
 
-  onVisits() {}
 
   onTitleChanged() {}
 
   onFrecencyChanged() {}
 
   onManyFrecenciesChanged() {}
 
   onPageChanged() {}
--- a/browser/modules/WindowsPreviewPerTab.jsm
+++ b/browser/modules/WindowsPreviewPerTab.jsm
@@ -811,17 +811,16 @@ var AeroPeek = {
         });
         break;
     }
   },
 
   /* nsINavHistoryObserver implementation */
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
-  onVisits() {},
   onTitleChanged() {},
   onFrecencyChanged() {},
   onManyFrecenciesChanged() {},
   onDeleteURI() {},
   onClearHistory() {},
   onDeleteVisits() {},
   onPageChanged(uri, changedConst, newValue) {
     if (this.enabled && changedConst == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -478,21 +478,27 @@ function HistoryTracker(name, engine) {
   Tracker.call(this, name, engine);
 }
 HistoryTracker.prototype = {
   __proto__: Tracker.prototype,
 
   onStart() {
     this._log.info("Adding Places observer.");
     PlacesUtils.history.addObserver(this, true);
+    this._placesObserver =
+      new PlacesWeakCallbackWrapper(this.handlePlacesEvents.bind(this));
+    PlacesObservers.addListener(["page-visited"], this._placesObserver);
   },
 
   onStop() {
     this._log.info("Removing Places observer.");
     PlacesUtils.history.removeObserver(this);
+    if (this._placesObserver) {
+      PlacesObservers.removeListener(["page-visited"], this._placesObserver);
+    }
   },
 
   QueryInterface: ChromeUtils.generateQI([
     Ci.nsINavHistoryObserver,
     Ci.nsISupportsWeakReference
   ]),
 
   async onDeleteAffectsGUID(uri, guid, reason, source, increment) {
@@ -513,29 +519,30 @@ HistoryTracker.prototype = {
   },
 
   onDeleteURI(uri, guid, reason) {
     this.asyncObserver.enqueueCall(() =>
       this.onDeleteAffectsGUID(uri, guid, reason, "onDeleteURI", SCORE_INCREMENT_XLARGE)
     );
   },
 
-  onVisits(aVisits) {
-    this.asyncObserver.enqueueCall(() => this._onVisits(aVisits));
+  handlePlacesEvents(aEvents) {
+    this.asyncObserver.enqueueCall(() => this._handlePlacesEvents(aEvents));
   },
 
-  async _onVisits(aVisits) {
+  async _handlePlacesEvents(aEvents) {
     if (this.ignoreAll) {
       this._log.trace("ignoreAll: ignoring visits [" +
-                      aVisits.map(v => v.guid).join(",") + "]");
+                      aEvents.map(v => v.guid).join(",") + "]");
       return;
     }
-    for (let {uri, guid} of aVisits) {
-      this._log.trace("onVisits: " + uri.spec);
-      if (this.engine.shouldSyncURL(uri.spec) && (await this.addChangedID(guid))) {
+    for (let event of aEvents) {
+      this._log.trace("'page-visited': " + event.url);
+      if (this.engine.shouldSyncURL(event.url) &&
+          await this.addChangedID(event.pageGuid)) {
         this.score += SCORE_INCREMENT_SMALL;
       }
     }
   },
 
   onClearHistory() {
     this._log.trace("onClearHistory");
     // Note that we're going to trigger a sync, but none of the cleared
--- a/toolkit/components/downloads/DownloadIntegration.jsm
+++ b/toolkit/components/downloads/DownloadIntegration.jsm
@@ -1017,17 +1017,16 @@ this.DownloadHistoryObserver.prototype =
   // nsINavHistoryObserver
   onClearHistory: function DL_onClearHistory() {
     this._list.removeFinished();
   },
 
   onTitleChanged() {},
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
-  onVisits() {},
   onPageChanged() {},
   onDeleteVisits() {},
 };
 
 /**
  * This view can be added to a DownloadList object to trigger a save operation
  * in the given DownloadStore object when a relevant change occurs.  You should
  * call the "initialize" method in order to register the view and load the
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -33,16 +33,20 @@
 #include "mozilla/Unused.h"
 #include "nsContentUtils.h" // for nsAutoScriptBlocker
 #include "nsJSUtils.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "nsPrintfCString.h"
 #include "nsTHashtable.h"
 #include "jsapi.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/PlacesObservers.h"
+#include "mozilla/dom/PlacesVisit.h"
+#include "mozilla/dom/ProcessGlobal.h"
+#include "mozilla/dom/ScriptSettings.h"
 
 // Initial size for the cache holding visited status observers.
 #define VISIT_OBSERVERS_INITIAL_CACHE_LENGTH 64
 
 // Initial length for the visits removal hash.
 #define VISITS_REMOVAL_INITIAL_HASH_LENGTH 64
 
 using namespace mozilla::dom;
@@ -661,31 +665,38 @@ public:
       mHistory->AppendToRecentlyVisitedURIs(aURI);
     }
     mHistory->NotifyVisited(aURI);
 
     if (aPlace.titleChanged) {
       aNavHistory->NotifyTitleChange(aURI, aPlace.title, aPlace.guid);
     }
 
+    aNavHistory->UpdateDaysOfHistory(aPlace.visitTime);
+
     return NS_OK;
   }
 
   void AddPlaceForNotify(const VisitData& aPlace,
                          nsIURI* aURI,
-                         nsCOMArray<nsIVisitData>& aPlaces) {
+                         Sequence<OwningNonNull<PlacesEvent>>& aEvents) {
     if (aPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
-      nsCOMPtr<nsIVisitData> notifyPlace = new nsVisitData(
-        aURI, aPlace.visitId, aPlace.visitTime,
-        aPlace.referrerVisitId, aPlace.transitionType,
-        aPlace.guid, aPlace.hidden,
-        aPlace.visitCount + 1, // Add current visit.
-        static_cast<uint32_t>(aPlace.typed),
-        aPlace.title);
-      aPlaces.AppendElement(notifyPlace.forget());
+      RefPtr<PlacesVisit> vd = new PlacesVisit();
+      vd->mVisitId = aPlace.visitId;
+      vd->mUrl.Assign(NS_ConvertUTF8toUTF16(aPlace.spec));
+      vd->mVisitTime = aPlace.visitTime / 1000;
+      vd->mReferringVisitId = aPlace.referrerVisitId;
+      vd->mTransitionType = aPlace.transitionType;
+      vd->mPageGuid.Assign(aPlace.guid);
+      vd->mHidden = aPlace.hidden;
+      vd->mVisitCount = aPlace.visitCount + 1; // Add current visit
+      vd->mTypedCount = static_cast<uint32_t>(aPlace.typed);
+      vd->mLastKnownTitle.Assign(aPlace.title);
+      bool success = !!aEvents.AppendElement(vd.forget(), fallible);
+      MOZ_RELEASE_ASSERT(success);
     }
   }
 
   NS_IMETHOD Run() override
   {
     MOZ_ASSERT(NS_IsMainThread(), "This should be called on the main thread");
 
     // We are in the main thread, no need to lock.
@@ -698,39 +709,40 @@ public:
     if (!navHistory) {
       NS_WARNING("Trying to notify visits observers but cannot get the history service!");
       return NS_OK;
     }
 
     nsCOMPtr<nsIObserverService> obsService =
       mozilla::services::GetObserverService();
 
-    nsCOMArray<nsIVisitData> places;
+    Sequence<OwningNonNull<PlacesEvent>> events;
     nsCOMArray<nsIURI> uris;
     if (mPlaces.Length() > 0) {
       for (uint32_t i = 0; i < mPlaces.Length(); ++i) {
         nsCOMPtr<nsIURI> uri;
         MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlaces[i].spec));
         if (!uri) {
           return NS_ERROR_UNEXPECTED;
         }
-        AddPlaceForNotify(mPlaces[i], uri, places);
+        AddPlaceForNotify(mPlaces[i], uri, events);
         uris.AppendElement(uri.forget());
       }
     } else {
       nsCOMPtr<nsIURI> uri;
       MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), mPlace.spec));
       if (!uri) {
         return NS_ERROR_UNEXPECTED;
       }
-      AddPlaceForNotify(mPlace, uri, places);
+      AddPlaceForNotify(mPlace, uri, events);
       uris.AppendElement(uri.forget());
     }
-    if (places.Length() > 0) {
-      navHistory->NotifyOnVisits(places.Elements(), places.Length());
+
+    if (events.Length() > 0) {
+      PlacesObservers::NotifyListeners(events);
     }
 
     PRTime now = PR_Now();
     if (mPlaces.Length() > 0) {
       InfallibleTArray<URIParams> serializableUris(mPlaces.Length());
       for (uint32_t i = 0; i < mPlaces.Length(); ++i) {
         nsresult rv = NotifyVisit(navHistory, obsService, now, uris[i], mPlaces[i]);
         NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -333,16 +333,17 @@ var PlacesUtils = {
   TOPIC_FEEDBACK_UPDATED: "places-autocomplete-feedback-updated",
   TOPIC_FAVICONS_EXPIRED: "places-favicons-expired",
   TOPIC_VACUUM_STARTING: "places-vacuum-starting",
   TOPIC_BOOKMARKS_RESTORE_BEGIN: "bookmarks-restore-begin",
   TOPIC_BOOKMARKS_RESTORE_SUCCESS: "bookmarks-restore-success",
   TOPIC_BOOKMARKS_RESTORE_FAILED: "bookmarks-restore-failed",
 
   ACTION_SCHEME: "moz-action:",
+  observers: PlacesObservers,
 
   /**
     * GUIDs associated with virtual queries that are used for displaying the
     * top-level folders in the left pane.
     */
   virtualAllBookmarksGuid: "allbms_____v",
   virtualHistoryGuid: "history____v",
   virtualDownloadsGuid: "downloads__v",
--- a/toolkit/components/places/mozIAsyncLivemarks.idl
+++ b/toolkit/components/places/mozIAsyncLivemarks.idl
@@ -58,16 +58,18 @@ interface mozIAsyncLivemarks : nsISuppor
    * @param [optional]aForceUpdate
    *        If set to true forces a reload even if contents are still valid.
    *
    * @note The update process is asynchronous, observers registered through
    *       registerForUpdates will be notified of updated contents.
    */
   void reloadLivemarks([optional]in boolean aForceUpdate);
 
+  void handlePlacesEvents(in jsval aEvents);
+
   jsval invalidateCachedLivemarks();
 };
 
 [scriptable, uuid(3a3c5e8f-ec4a-4086-ae0a-d16420d30c9f)]
 interface mozILivemarkInfo : nsISupports
 {
   /**
    * Id of the bookmarks folder representing this livemark.
--- a/toolkit/components/places/nsLivemarkService.js
+++ b/toolkit/components/places/nsLivemarkService.js
@@ -7,18 +7,22 @@
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "PlacesUtils",
                                "resource://gre/modules/PlacesUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "NetUtil",
                                "resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "history", function() {
+  let livemarks = PlacesUtils.livemarks;
   // Lazily add an history observer when it's actually needed.
-  PlacesUtils.history.addObserver(PlacesUtils.livemarks, true);
+  PlacesUtils.history.addObserver(livemarks, true);
+  let listener = new PlacesWeakCallbackWrapper(
+    livemarks.handlePlacesEvents.bind(livemarks));
+  PlacesObservers.addListener(["page-visited"], listener);
   return PlacesUtils.history;
 });
 
 // Constants
 
 // Delay between reloads of consecute livemarks.
 const RELOAD_DELAY_MS = 500;
 // Expire livemarks after this time.
@@ -331,16 +335,31 @@ LivemarkService.prototype = {
 
     return promise;
   },
 
   invalidateCachedLivemarks() {
     return this._invalidateCachedLivemarks();
   },
 
+  handlePlacesEvents(aEvents) {
+    if (!aEvents) {
+      throw new Components.Exception("Invalid arguments",
+                                     Cr.NS_ERROR_INVALID_ARG);
+    }
+
+    this._withLivemarksMap(livemarksMap => {
+      for (let event of aEvents) {
+        for (let livemark of livemarksMap.values()) {
+          livemark.updateURIVisitedStatus(event.url, true);
+        }
+      }
+    });
+  },
+
   // nsINavBookmarkObserver
 
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
   onItemVisited() {},
   onItemAdded() {},
 
   onItemChanged(id, property, isAnno, value, lastModified, itemType, parentId,
@@ -407,26 +426,16 @@ LivemarkService.prototype = {
   onDeleteURI(aURI) {
     this._withLivemarksMap(livemarksMap => {
       for (let livemark of livemarksMap.values()) {
         livemark.updateURIVisitedStatus(aURI, false);
       }
     });
   },
 
-  onVisits(aVisits) {
-    this._withLivemarksMap(livemarksMap => {
-      for (let {uri} of aVisits) {
-        for (let livemark of livemarksMap.values()) {
-          livemark.updateURIVisitedStatus(uri, true);
-        }
-      }
-    });
-  },
-
   // nsISupports
 
   classID: Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}"),
 
   _xpcom_factory: XPCOMUtils.generateSingletonFactory(LivemarkService),
 
   QueryInterface: ChromeUtils.generateQI([
     Ci.mozIAsyncLivemarks,
@@ -687,36 +696,36 @@ Livemark.prototype = {
     for (let [ container, observer ] of this._resultObservers) {
       observer.invalidateContainer(container);
     }
   },
 
   /**
    * Updates the visited status of nodes observing this livemark.
    *
-   * @param aURI
+   * @param href
    *        If provided will update nodes having the given uri,
    *        otherwise any node.
-   * @param aVisitedStatus
+   * @param visitedStatus
    *        Whether the nodes should be set as visited.
    */
-  updateURIVisitedStatus(aURI, aVisitedStatus) {
+  updateURIVisitedStatus(href, visitedStatus) {
     let wasVisited = false;
     for (let child of this.children) {
-      if (!aURI || child.uri.equals(aURI)) {
+      if (!href || child.uri.spec == href) {
         wasVisited = child.visited;
-        child.visited = aVisitedStatus;
+        child.visited = visitedStatus;
       }
     }
 
     for (let [ container, observer ] of this._resultObservers) {
       if (this._nodes.has(container)) {
         let nodes = this._nodes.get(container);
         for (let node of nodes) {
-          if (!aURI || node.uri == aURI.spec) {
+          if (!href || node.uri == href) {
             Services.tm.dispatchToMainThread(() => {
               observer.nodeHistoryDetailsChanged(node, node.time, wasVisited);
             });
           }
         }
       }
     }
   },
@@ -813,16 +822,17 @@ LivemarkLoadListener.prototype = {
         }
 
         let title = entry.title ? entry.title.plainText() : "";
         livemarkChildren.push({ uri, title, visited: false });
       }
 
       this._livemark.children = livemarkChildren;
     } catch (ex) {
+      Cu.reportError(ex);
       this.abort(ex);
     } finally {
       this._processor.listener = null;
       this._processor = null;
     }
   },
 
   onDataAvailable(aRequest, aContext, aInputStream, aSourceOffset, aCount) {
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -12,16 +12,18 @@
 
 #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/PlacesObservers.h"
+#include "mozilla/dom/PlacesVisit.h"
 
 #include "GeckoProfiler.h"
 
 using namespace mozilla;
 
 // These columns sit to the right of the kGetInfoIndex_* columns.
 const int32_t nsNavBookmarks::kGetChildrenIndex_Guid = 18;
 const int32_t nsNavBookmarks::kGetChildrenIndex_Position = 19;
@@ -207,16 +209,19 @@ nsNavBookmarks::Init()
   mCanNotify = true;
 
   // 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);
+  PlacesObservers::AddListener(events, this);
 
   // DO NOT PUT STUFF HERE that can fail. See observer comment above.
 
   return NS_OK;
 }
 
 nsresult
 nsNavBookmarks::AdjustIndices(int64_t aFolderId,
@@ -2048,40 +2053,38 @@ NS_IMETHODIMP
 nsNavBookmarks::OnEndUpdateBatch()
 {
   NOTIFY_OBSERVERS(mCanNotify, mObservers,
                    nsINavBookmarkObserver, OnEndUpdateBatch());
   return NS_OK;
 }
 
 
-NS_IMETHODIMP
-nsNavBookmarks::OnVisits(nsIVisitData** aVisits, uint32_t aVisitsCount)
+void
+nsNavBookmarks::HandlePlacesEvent(const PlacesEventSequence& aEvents)
 {
-  NS_ENSURE_ARG(aVisits);
-  NS_ENSURE_ARG(aVisitsCount);
+  for (const auto& event : aEvents) {
+    if (NS_WARN_IF(event->Type() != PlacesEventType::Page_visited)) {
+      continue;
+    }
 
-  for (uint32_t i = 0; i < aVisitsCount; ++i) {
-    nsIVisitData* place = aVisits[i];
-    nsCOMPtr<nsIURI> uri;
-    MOZ_ALWAYS_SUCCEEDS(place->GetUri(getter_AddRefs(uri)));
+    const dom::PlacesVisit* visit = event->AsPlacesVisit();
+    if (NS_WARN_IF(!visit)) {
+      continue;
+    }
 
-    // If the page is bookmarked, notify observers for each associated bookmark.
     ItemVisitData visitData;
-    nsresult rv = uri->GetSpec(visitData.bookmark.url);
-    NS_ENSURE_SUCCESS(rv, rv);
-    MOZ_ALWAYS_SUCCEEDS(place->GetVisitId(&visitData.visitId));
-    MOZ_ALWAYS_SUCCEEDS(place->GetTime(&visitData.time));
-    MOZ_ALWAYS_SUCCEEDS(place->GetTransitionType(&visitData.transitionType));
-
+    visitData.visitId = visit->mVisitId;
+    visitData.bookmark.url = NS_ConvertUTF16toUTF8(visit->mUrl);
+    visitData.time = visit->mVisitTime * 1000;
+    visitData.transitionType = visit->mTransitionType;
     RefPtr< AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData> > notifier =
       new AsyncGetBookmarksForURI<ItemVisitMethod, ItemVisitData>(this, &nsNavBookmarks::NotifyItemVisited, visitData);
     notifier->Init();
   }
-  return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavBookmarks::OnDeleteURI(nsIURI* aURI,
                             const nsACString& aGUID,
                             uint16_t aReason)
 {
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -73,16 +73,17 @@ namespace places {
 
 } // namespace places
 } // namespace mozilla
 
 class nsNavBookmarks final : public nsINavBookmarksService
                            , public nsINavHistoryObserver
                            , public nsIObserver
                            , public nsSupportsWeakReference
+                           , public mozilla::places::INativePlacesEventCallback
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSINAVBOOKMARKSSERVICE
   NS_DECL_NSINAVHISTORYOBSERVER
   NS_DECL_NSIOBSERVER
 
   nsNavBookmarks();
@@ -202,16 +203,26 @@ public:
    *
    * @param aItemId
    *        The changed item id.
    * @param aData
    *        Details about the change.
    */
   void NotifyItemChanged(const ItemChangeData& aData);
 
+
+  /**
+   * Part of INativePlacesEventCallback - handles events from the places
+   * observer system.
+   * @param aCx
+   *        A JSContext for extracting the values from aEvents.
+   * @param aEvents
+   *        An array of weakly typed events detailing what changed.
+   */
+  void HandlePlacesEvent(const PlacesEventSequence& aEvents) override;
   static const int32_t kGetChildrenIndex_Guid;
   static const int32_t kGetChildrenIndex_Position;
   static const int32_t kGetChildrenIndex_Type;
   static const int32_t kGetChildrenIndex_PlaceID;
   static const int32_t kGetChildrenIndex_SyncStatus;
 
   static mozilla::Atomic<int64_t> sLastInsertedItemId;
   static void StoreLastInsertedId(const nsACString& aTable,
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -516,35 +516,25 @@ nsNavHistory::LoadPrefs()
   FRECENCY_PREF(mThirdBucketWeight,        PREF_FREC_THIRD_BUCKET_WEIGHT);
   FRECENCY_PREF(mFourthBucketWeight,       PREF_FREC_FOURTH_BUCKET_WEIGHT);
   FRECENCY_PREF(mDefaultWeight,            PREF_FREC_DEFAULT_BUCKET_WEIGHT);
 
 #undef FRECENCY_PREF
 }
 
 void
-nsNavHistory::NotifyOnVisits(nsIVisitData** aVisits, uint32_t aVisitsCount)
+nsNavHistory::UpdateDaysOfHistory(PRTime visitTime)
 {
-  MOZ_ASSERT(aVisits, "Can't call NotifyOnVisits with a NULL aVisits");
-  MOZ_ASSERT(aVisitsCount, "Should have at least 1 visit when notifying");
-
   if (mDaysOfHistory == 0) {
     mDaysOfHistory = 1;
   }
 
-  for (uint32_t i = 0; i < aVisitsCount; ++i) {
-    PRTime time;
-    MOZ_ALWAYS_SUCCEEDS(aVisits[i]->GetTime(&time));
-    if (time > mLastCachedEndOfDay || time < mLastCachedStartOfDay) {
-      mDaysOfHistory = -1;
-    }
+  if (visitTime > mLastCachedEndOfDay || visitTime < mLastCachedStartOfDay) {
+    mDaysOfHistory = -1;
   }
-
-  NOTIFY_OBSERVERS(mCanNotify, mObservers, nsINavHistoryObserver,
-                   OnVisits(aVisits, aVisitsCount));
 }
 
 void
 nsNavHistory::NotifyTitleChange(nsIURI* aURI,
                                 const nsString& aTitle,
                                 const nsACString& aGUID)
 {
   MOZ_ASSERT(!aGUID.IsEmpty());
--- a/toolkit/components/places/nsNavHistory.h
+++ b/toolkit/components/places/nsNavHistory.h
@@ -430,19 +430,20 @@ public:
   }
 
   int32_t GetNumVisitsForFrecency() const
   {
     return mNumVisitsForFrecency;
   }
 
   /**
-   * Fires onVisits event to nsINavHistoryService observers
+   * Updates and invalidates the mDaysOfHistory cache. Should be
+   * called whenever a visit is added.
    */
-  void NotifyOnVisits(nsIVisitData** aVisits, uint32_t aVisitsCount);
+  void UpdateDaysOfHistory(PRTime visitTime);
 
   /**
    * Fires onTitleChanged event to nsINavHistoryService observers
    */
   void NotifyTitleChange(nsIURI* aURI,
                          const nsString& title,
                          const nsACString& aGUID);
 
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -14,16 +14,18 @@
 #include "mozilla/DebugOnly.h"
 #include "nsDebug.h"
 #include "nsNetUtil.h"
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsUnicharUtils.h"
 #include "prtime.h"
 #include "nsQueryObject.h"
+#include "mozilla/dom/PlacesObservers.h"
+#include "mozilla/dom/PlacesVisit.h"
 
 #include "nsCycleCollectionParticipant.h"
 
 // Thanks, Windows.h :(
 #undef CompareString
 
 #define TO_ICONTAINER(_node)                                                  \
     static_cast<nsINavHistoryContainerResultNode*>(_node)
@@ -3991,28 +3993,34 @@ nsNavHistoryResult::StopObserving()
                                     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;
     }
   }
 }
 
 void
 nsNavHistoryResult::AddHistoryObserver(nsNavHistoryQueryResultNode* aNode)
 {
   if (!mIsHistoryObserver) {
       nsNavHistory* history = nsNavHistory::GetHistoryService();
       NS_ASSERTION(history, "Can't create history service");
       history->AddObserver(this, true);
+      AutoTArray<PlacesEventType, 1> events;
+      events.AppendElement(PlacesEventType::Page_visited);
+      PlacesObservers::AddListener(events, this);
       mIsHistoryObserver = 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 (mHistoryObservers.IndexOf(aNode) == QueryObserverList::NoIndex) {
     mHistoryObservers.AppendElement(aNode);
   }
@@ -4586,42 +4594,37 @@ nsNavHistoryResult::OnVisit(nsIURI* aURI
     // cause changes to the array.
     ENUMERATE_QUERY_OBSERVERS(Refresh(), mHistoryObservers, IsContainersQuery());
   }
 
   return NS_OK;
 }
 
 
-NS_IMETHODIMP
-nsNavHistoryResult::OnVisits(nsIVisitData** aVisits,
-                             uint32_t aVisitsCount) {
-  for (uint32_t i = 0; i < aVisitsCount; ++i) {
-    nsIVisitData* place = aVisits[i];
+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;
+    }
+
     nsCOMPtr<nsIURI> uri;
-    MOZ_ALWAYS_SUCCEEDS(place->GetUri(getter_AddRefs(uri)));
-    int64_t visitId;
-    MOZ_ALWAYS_SUCCEEDS(place->GetVisitId(&visitId));
-    PRTime time;
-    MOZ_ALWAYS_SUCCEEDS(place->GetTime(&time));
-    uint32_t transitionType;
-    MOZ_ALWAYS_SUCCEEDS(place->GetTransitionType(&transitionType));
-    nsCString guid;
-    MOZ_ALWAYS_SUCCEEDS(place->GetGuid(guid));
-    bool hidden;
-    MOZ_ALWAYS_SUCCEEDS(place->GetHidden(&hidden));
-    uint32_t visitCount;
-    MOZ_ALWAYS_SUCCEEDS(place->GetVisitCount(&visitCount));
-    nsString lastKnownTitle;
-    MOZ_ALWAYS_SUCCEEDS(place->GetLastKnownTitle(lastKnownTitle));
-    nsresult rv = OnVisit(uri, visitId, time, transitionType, guid, hidden,
-                          visitCount, lastKnownTitle);
-    NS_ENSURE_SUCCESS(rv, rv);
+    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);
   }
-  return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsNavHistoryResult::OnTitleChanged(nsIURI* aURI,
                                    const nsAString& aPageTitle,
                                    const nsACString& aGUID)
 {
--- a/toolkit/components/places/nsNavHistoryResult.h
+++ b/toolkit/components/places/nsNavHistoryResult.h
@@ -7,16 +7,17 @@
  * The definitions of objects that make up a history query result set. This file
  * should only be included by nsNavHistory.h, include that if you want these
  * classes.
  */
 
 #ifndef nsNavHistoryResult_h_
 #define nsNavHistoryResult_h_
 
+#include "INativePlacesEventCallback.h"
 #include "nsTArray.h"
 #include "nsInterfaceHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsCycleCollectionParticipant.h"
 #include "mozilla/storage.h"
 #include "Helpers.h"
 
 class nsNavHistory;
@@ -93,27 +94,26 @@ private:
 //    object initialization.
 
 #define NS_NAVHISTORYRESULT_IID \
   { 0x455d1d40, 0x1b9b, 0x40e6, { 0xa6, 0x41, 0x8b, 0xb7, 0xe8, 0x82, 0x23, 0x87 } }
 
 class nsNavHistoryResult final : public nsSupportsWeakReference,
                                  public nsINavHistoryResult,
                                  public nsINavBookmarkObserver,
-                                 public nsINavHistoryObserver
+                                 public nsINavHistoryObserver,
+                                 public mozilla::places::INativePlacesEventCallback
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_NAVHISTORYRESULT_IID)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSINAVHISTORYRESULT
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsNavHistoryResult, nsINavHistoryResult)
   NS_DECL_BOOKMARK_HISTORY_OBSERVER_EXTERNAL(override)
-  NS_IMETHOD OnVisits(nsIVisitData** aVisits,
-                      uint32_t aVisitsCount) override;
 
   void AddHistoryObserver(nsNavHistoryQueryResultNode* aNode);
   void AddBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode, int64_t aFolder);
   void AddAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode);
   void AddMobilePrefsObserver(nsNavHistoryQueryResultNode* aNode);
   void RemoveHistoryObserver(nsNavHistoryQueryResultNode* aNode);
   void RemoveBookmarkFolderObserver(nsNavHistoryFolderResultNode* aNode, int64_t aFolder);
   void RemoveAllBookmarksObserver(nsNavHistoryQueryResultNode* aNode);
@@ -169,16 +169,18 @@ public:
   bool mBatchInProgress;
 
   nsMaybeWeakPtrArray<nsINavHistoryResultObserver> mObservers;
   bool mSuppressNotifications;
 
   ContainerObserverList mRefreshParticipants;
   void requestRefresh(nsNavHistoryContainerResultNode* aContainer);
 
+  void HandlePlacesEvent(const PlacesEventSequence& aEvents) override;
+
   void OnMobilePrefChanged();
 
   static void OnMobilePrefChangedCallback(const char* prefName, void* closure);
 
 protected:
   virtual ~nsNavHistoryResult();
 };
 
--- a/toolkit/components/places/nsPlacesExpiration.js
+++ b/toolkit/components/places/nsPlacesExpiration.js
@@ -531,17 +531,16 @@ nsPlacesExpiration.prototype = {
       this._newTimer();
   },
 
   onClearHistory: function PEX_onClearHistory() {
     // History status is clean after a clear history.
     this.status = STATUS.CLEAN;
   },
 
-  onVisits() {},
   onTitleChanged() {},
   onDeleteURI() {},
   onPageChanged() {},
   onDeleteVisits() {},
 
   // nsITimerCallback
 
   notify: function PEX_timerCallback() {
--- a/toolkit/components/thumbnails/PageThumbs.jsm
+++ b/toolkit/components/thumbnails/PageThumbs.jsm
@@ -822,15 +822,14 @@ var PageThumbsHistoryObserver = {
 
   onClearHistory() {
     PageThumbsStorage.wipe();
   },
 
   onTitleChanged() {},
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
-  onVisits() {},
   onPageChanged() {},
   onDeleteVisits() {},
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsINavHistoryObserver,
                                           Ci.nsISupportsWeakReference])
 };
--- a/toolkit/modules/NewTabUtils.jsm
+++ b/toolkit/modules/NewTabUtils.jsm
@@ -544,16 +544,19 @@ var PlacesProvider = {
    */
   maxNumLinks: HISTORY_RESULTS_LIMIT,
 
   /**
    * Must be called before the provider is used.
    */
   init: function PlacesProvider_init() {
     PlacesUtils.history.addObserver(this, true);
+    this._placesObserver =
+      new PlacesWeakCallbackWrapper(this.handlePlacesEvents.bind(this));
+    PlacesObservers.addListener(["page-visited"], this._placesObserver);
   },
 
   /**
    * Gets the current set of links delivered by this provider.
    * @param aCallback The function that the array of links is passed to.
    */
   getLinks: function PlacesProvider_getLinks(aCallback) {
     let options = PlacesUtils.history.getNewQueryOptions();
@@ -651,21 +654,21 @@ var PlacesProvider = {
   onEndUpdateBatch() {
     this._batchProcessingDepth -= 1;
     if (this._batchProcessingDepth == 0 && this._batchCalledFrecencyChanged) {
       this.onManyFrecenciesChanged();
       this._batchCalledFrecencyChanged = false;
     }
   },
 
-  onVisits(aVisits) {
+  handlePlacesEvents(aEvents) {
     if (!this._batchProcessingDepth) {
-      for (let visit of aVisits) {
-        if (visit.visitCount == 1 && visit.lastKnownTitle) {
-          this.onTitleChanged(visit.uri, visit.lastKnownTitle, visit.guid);
+      for (let event of aEvents) {
+        if (event.visitCount == 1 && event.lastKnownTitle) {
+          this.onTitleChanged(event.url, event.lastKnownTitle, event.pageGuid);
         }
       }
     }
   },
 
   onDeleteURI: function PlacesProvider_onDeleteURI(aURI, aGUID, aReason) {
     // let observers remove sensetive data associated with deleted visit
     this._callObservers("onDeleteURI", {
@@ -706,18 +709,21 @@ var PlacesProvider = {
   onManyFrecenciesChanged: function PlacesProvider_onManyFrecenciesChanged() {
     this._callObservers("onManyLinksChanged");
   },
 
   /**
    * Called by the history service.
    */
   onTitleChanged: function PlacesProvider_onTitleChanged(aURI, aNewTitle, aGUID) {
+    if (aURI instanceof Ci.nsIURI) {
+      aURI = aURI.spec;
+    }
     this._callObservers("onLinkChanged", {
-      url: aURI.spec,
+      url: aURI,
       title: aNewTitle
     });
   },
 
   _callObservers: function PlacesProvider__callObservers(aMethodName, aArg) {
     for (let obs of this._observers) {
       if (obs[aMethodName]) {
         try {