Bug 1340498 - Update onVisits uses to 'page-visited' r=mak
authorDoug Thayer <dothayer@mozilla.com>
Wed, 14 Feb 2018 09:11:49 -0800
changeset 423972 b6f047709e8e11202f5e81dee8cc26620595f4d1
parent 423971 43edbd9319292926a5aae28603118a87c3e18996
child 423973 07d953128a21c4048d4bce3efab6cd031e68bdee
push id34197
push usercsabou@mozilla.com
push dateThu, 28 Jun 2018 09:44:02 +0000
treeherdermozilla-central@db455160668d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1340498
milestone63.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 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 {