Backed out 5 changesets (bug 1340498) for build bustages on SelectionChangeListener.h on a CLOSED TREE
authorarthur.iakab <aiakab@mozilla.com>
Tue, 26 Jun 2018 07:59:01 +0300
changeset 423628 83d286f5134fd374fc4526093afd1be183bd868f
parent 423627 c89b86622d3885cb731dcc52274a35a7d626159b
child 423629 ca98b6f47b4e33673291a4dc2a4ebca28ce6db00
push id65534
push useraiakab@mozilla.com
push dateTue, 26 Jun 2018 04:59:25 +0000
treeherderautoland@83d286f5134f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1340498
milestone63.0a1
backs outc89b86622d3885cb731dcc52274a35a7d626159b
b270d4a0198604fb748e51773eb35f5cbf5488b8
8ed32495b46f58741c6c154de3bc5a1b5a5c5a31
df9a67c58183a189b4175dbe063bed7407f12b37
a5977e0708ea2bd07e9029b3b80b3035c7243534
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
Backed out 5 changesets (bug 1340498) for build bustages on SelectionChangeListener.h on a CLOSED TREE Backed out changeset c89b86622d38 (bug 1340498) Backed out changeset b270d4a01986 (bug 1340498) Backed out changeset 8ed32495b46f (bug 1340498) Backed out changeset df9a67c58183 (bug 1340498) Backed out changeset a5977e0708ea (bug 1340498)
browser/components/extensions/parent/ext-history.js
browser/components/migration/tests/unit/test_automigration.js
browser/extensions/activity-stream/lib/PlacesFeed.jsm
browser/modules/WindowsPreviewPerTab.jsm
docshell/test/browser/browser_bug420605.js
docshell/test/browser/browser_bug503832.js
docshell/test/browser/browser_bug655270.js
dom/base/PlacesEvent.cpp
dom/base/PlacesEvent.h
dom/base/PlacesObservers.cpp
dom/base/PlacesObservers.h
dom/base/PlacesVisit.h
dom/base/PlacesWeakCallbackWrapper.cpp
dom/base/PlacesWeakCallbackWrapper.h
dom/base/SelectionChangeListener.h
dom/base/ShadowRoot.cpp
dom/base/ShadowRoot.h
dom/base/StyleSheetList.h
dom/base/WindowDestroyedEvent.cpp
dom/base/moz.build
dom/base/nsDOMMutationObserver.cpp
dom/base/nsIGlobalObject.cpp
dom/base/nsINode.cpp
dom/base/nsWindowMemoryReporter.cpp
dom/base/nsXHTMLContentSerializer.cpp
dom/chrome-webidl/PlacesEvent.webidl
dom/chrome-webidl/PlacesObservers.webidl
dom/chrome-webidl/moz.build
services/sync/modules/engines/history.js
services/sync/tests/unit/head_helpers.js
services/sync/tests/unit/test_history_store.js
toolkit/components/downloads/DownloadIntegration.jsm
toolkit/components/downloads/test/unit/common_test_Download.js
toolkit/components/downloads/test/unit/head.js
toolkit/components/places/History.cpp
toolkit/components/places/INativePlacesEventCallback.h
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/moz.build
toolkit/components/places/mozIAsyncLivemarks.idl
toolkit/components/places/nsINavHistoryService.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/places/tests/PlacesTestUtils.jsm
toolkit/components/places/tests/browser/browser_bug399606.js
toolkit/components/places/tests/browser/browser_bug646422.js
toolkit/components/places/tests/browser/browser_double_redirect.js
toolkit/components/places/tests/browser/browser_multi_redirect_frecency.js
toolkit/components/places/tests/browser/browser_notfound.js
toolkit/components/places/tests/browser/browser_onvisit_title_null_for_navigation.js
toolkit/components/places/tests/browser/browser_redirect.js
toolkit/components/places/tests/browser/browser_settitle.js
toolkit/components/places/tests/browser/browser_visited_notfound.js
toolkit/components/places/tests/browser/head.js
toolkit/components/places/tests/expiration/test_notifications_onDeleteURI.js
toolkit/components/places/tests/expiration/test_notifications_onDeleteVisits.js
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/history/test_async_history_api.js
toolkit/components/places/tests/history/test_remove.js
toolkit/components/places/tests/history/test_removeByFilter.js
toolkit/components/places/tests/history/test_removeVisitsByFilter.js
toolkit/components/places/tests/unit/test_454977.js
toolkit/components/places/tests/unit/test_download_history.js
toolkit/components/places/tests/unit/test_history_observer.js
toolkit/components/places/tests/unit/test_markpageas.js
toolkit/components/thumbnails/PageThumbs.jsm
toolkit/modules/NewTabUtils.jsm
tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
--- 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]});
       }
-      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,
+      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,
           };
-          this.emit("visited", visit);
+          this.emit("visited", data);
         }
       }
       onBeginUpdateBatch() {}
       onEndUpdateBatch() {}
       onTitleChanged(uri, title) {
         this.emit("titleChanged", {url: uri.spec, title: title});
       }
       onClearHistory() {
@@ -117,19 +117,16 @@ 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/components/migration/tests/unit/test_automigration.js
+++ b/browser/components/migration/tests/unit/test_automigration.js
@@ -626,16 +626,19 @@ add_task(async function checkUndoVisitsS
   ]);
   let uriDeletedExpected = new Map([
     ["http://www.mozilla.org/", PromiseUtils.defer()],
   ]);
   let wrongMethodDeferred = PromiseUtils.defer();
   let observer = {
     onBeginUpdateBatch() {},
     onEndUpdateBatch() {},
+    onVisits(visits) {
+      wrongMethodDeferred.reject(new Error("Unexpected call to onVisits " + visits.length));
+    },
     onTitleChanged(uri) {
       wrongMethodDeferred.reject(new Error("Unexpected call to onTitleChanged " + uri.spec));
     },
     onClearHistory() {
       wrongMethodDeferred.reject("Unexpected call to onClearHistory");
     },
     onPageChanged(uri) {
       wrongMethodDeferred.reject(new Error("Unexpected call to onPageChanged " + uri.spec));
--- a/browser/extensions/activity-stream/lib/PlacesFeed.jsm
+++ b/browser/extensions/activity-stream/lib/PlacesFeed.jsm
@@ -55,16 +55,17 @@ 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,16 +811,17 @@ 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/docshell/test/browser/browser_bug420605.js
+++ b/docshell/test/browser/browser_bug420605.js
@@ -50,16 +50,17 @@ function test() {
                                                  gBrowser.selectedBrowser);
       }
     }
 
     /* Global history observer that triggers for the two test URLs above. */
     var historyObserver = {
         onBeginUpdateBatch: function() {},
         onEndUpdateBatch: function() {},
+        onVisits: function() {},
         onTitleChanged: function(aURI, aPageTitle) {},
         onDeleteURI: function(aURI) {},
         onClearHistory: function() {},
         onPageChanged: function(aURI, aWhat, aValue) {
             if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
                 return;
             }
             aURI = aURI.spec;
--- a/docshell/test/browser/browser_bug503832.js
+++ b/docshell/test/browser/browser_bug503832.js
@@ -10,16 +10,17 @@ add_task(async function() {
     var historyService = Cc["@mozilla.org/browser/nav-history-service;1"]
                          .getService(Ci.nsINavHistoryService);
 
     let fragmentPromise = new Promise(resolve => {
         /* Global history observer that triggers for the two test URLs above. */
         var historyObserver = {
             onBeginUpdateBatch: function() {},
             onEndUpdateBatch: function() {},
+            onVisits: function() {},
             onTitleChanged: function(aURI, aPageTitle) {
                 aURI = aURI.spec;
                 switch (aURI) {
                 case pageurl:
                     is(aPageTitle, pagetitle, "Correct page title for " + aURI);
                     return;
                 case fragmenturl:
                     is(aPageTitle, pagetitle, "Correct page title for " + aURI);
--- a/docshell/test/browser/browser_bug655270.js
+++ b/docshell/test/browser/browser_bug655270.js
@@ -43,16 +43,17 @@ function test() {
         gBrowser.removeTab(tab);
         PlacesUtils.history.removeObserver(this);
         finish();
       }
     },
 
     onBeginUpdateBatch: function() { },
     onEndUpdateBatch: function() { },
+    onVisits: function() { },
     onTitleChanged: function() { },
     onDeleteURI: function() { },
     onClearHistory: function() { },
     onDeleteVisits: function() { },
     QueryInterface: ChromeUtils.generateQI([Ci.nsINavHistoryObserver])
   };
 
   PlacesUtils.history.addObserver(observer);
deleted file mode 100644
--- a/dom/base/PlacesEvent.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/* -*- 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/. */
-
-#include "mozilla/dom/PlacesEvent.h"
-
-#include "mozilla/HoldDropJSObjects.h"
-
-namespace mozilla {
-namespace dom {
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(PlacesEvent)
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PlacesEvent)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PlacesEvent)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(PlacesEvent)
-  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
-NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PlacesEvent, AddRef)
-NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PlacesEvent, Release)
-
-already_AddRefed<PlacesEvent>
-PlacesEvent::Constructor(const GlobalObject& aGlobal,
-                         PlacesEventType aType,
-                         ErrorResult& rv)
-{
-  RefPtr<PlacesEvent> event = new PlacesEvent(aType);
-  return event.forget();
-}
-
-nsISupports*
-PlacesEvent::GetParentObject() const
-{
-  return nullptr;
-}
-
-JSObject*
-PlacesEvent::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return PlacesEventBinding::Wrap(aCx, this, aGivenProto);
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/base/PlacesEvent.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- 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_PlacesEvent_h
-#define mozilla_dom_PlacesEvent_h
-
-#include "mozilla/dom/PlacesEventBinding.h"
-#include "mozilla/ErrorResult.h"
-#include "nsWrapperCache.h"
-
-namespace mozilla {
-namespace dom {
-
-class PlacesEvent : public nsWrapperCache
-{
-public:
-  explicit PlacesEvent(PlacesEventType aType)
-    : mType(aType)
-  {}
-
-  static already_AddRefed<PlacesEvent>
-  Constructor(const GlobalObject& aGlobal,
-              PlacesEventType aType,
-              ErrorResult& aRv);
-
-  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PlacesEvent)
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PlacesEvent)
-
-  nsISupports* GetParentObject() const;
-
-  JSObject*
-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
-
-  PlacesEventType Type() const { return mType; }
-
-  virtual const PlacesVisit* AsPlacesVisit() const { return nullptr; }
-protected:
-  virtual ~PlacesEvent() = default;
-  PlacesEventType mType;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_PlacesEvent_h
deleted file mode 100644
--- a/dom/base/PlacesObservers.cpp
+++ /dev/null
@@ -1,350 +0,0 @@
-/* -*- 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/. */
-
-#include "PlacesObservers.h"
-
-#include "PlacesWeakCallbackWrapper.h"
-#include "nsIWeakReferenceUtils.h"
-#include "mozilla/ClearOnShutdown.h"
-
-namespace mozilla {
-namespace dom {
-
-template <class T>
-struct Flagged
-{
-  Flagged(uint32_t aFlags, T&& aValue)
-    : flags(aFlags)
-    , value(std::forward<T>(aValue))
-  {}
-  Flagged(Flagged&& aOther)
-    : Flagged(std::move(aOther.flags), std::move(aOther.value))
-  {}
-  Flagged(const Flagged& aOther) = default;
-  ~Flagged() = default;
-
-  uint32_t flags;
-  T value;
-};
-
-template <class T>
-using FlaggedArray = nsTArray<Flagged<T>>;
-
-template <class T>
-struct ListenerCollection
-{
-  static StaticAutoPtr<FlaggedArray<T>> gListeners;
-  static StaticAutoPtr<FlaggedArray<T>> gListenersToRemove;
-
-  static FlaggedArray<T>* GetListeners() {
-    MOZ_ASSERT(NS_IsMainThread());
-    if (!gListeners) {
-      gListeners = new FlaggedArray<T>();
-      ClearOnShutdown(&gListeners);
-    }
-    return gListeners;
-  }
-
-  static FlaggedArray<T>* GetListenersToRemove() {
-    MOZ_ASSERT(NS_IsMainThread());
-    if (!gListenersToRemove) {
-      gListenersToRemove = new FlaggedArray<T>();
-      ClearOnShutdown(&gListenersToRemove);
-    }
-    return gListenersToRemove;
-  }
-};
-
-template <class T>
-StaticAutoPtr<FlaggedArray<T>> ListenerCollection<T>::gListeners;
-template <class T>
-StaticAutoPtr<FlaggedArray<T>> ListenerCollection<T>::gListenersToRemove;
-
-typedef ListenerCollection<RefPtr<PlacesEventCallback>> JSListeners;
-typedef ListenerCollection<WeakPtr<PlacesWeakCallbackWrapper>> WeakJSListeners;
-typedef ListenerCollection<WeakPtr<places::INativePlacesEventCallback>> WeakNativeListeners;
-
-static bool gCallingListeners = false;
-
-uint32_t
-GetEventTypeFlag(PlacesEventType aEventType)
-{
-  if (aEventType == PlacesEventType::None) {
-    return 0;
-  }
-  return 1 << ((uint32_t)aEventType - 1);
-}
-
-uint32_t
-GetFlagsForEventTypes(const nsTArray<PlacesEventType>& aEventTypes)
-{
-  uint32_t flags = 0;
-  for (PlacesEventType eventType : aEventTypes) {
-    flags |= GetEventTypeFlag(eventType);
-  }
-  return flags;
-}
-
-uint32_t
-GetFlagsForEvents(const nsTArray<OwningNonNull<PlacesEvent>>& aEvents)
-{
-  uint32_t flags = 0;
-  for (const PlacesEvent& event : aEvents) {
-    flags |= GetEventTypeFlag(event.Type());
-  }
-  return flags;
-}
-
-template <class TWrapped, class TUnwrapped>
-void CallListeners(uint32_t aEventFlags,
-                   FlaggedArray<TWrapped>& aListeners,
-                   const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
-                   const std::function<TUnwrapped(TWrapped&)>& aUnwrapListener,
-                   const std::function<void(TUnwrapped&, const Sequence<OwningNonNull<PlacesEvent>>&)>& aCallListener)
-{
-  for (uint32_t i = 0; i < aListeners.Length(); i++) {
-    Flagged<TWrapped>& l = aListeners[i];
-    TUnwrapped unwrapped = aUnwrapListener(l.value);
-    if (!unwrapped) {
-      aListeners.RemoveElementAt(i);
-      i--;
-      continue;
-    }
-
-    if ((l.flags & aEventFlags) == aEventFlags) {
-      aCallListener(unwrapped, aEvents);
-    } else if (l.flags & aEventFlags) {
-      Sequence<OwningNonNull<PlacesEvent>> filtered;
-      for (const OwningNonNull<PlacesEvent>& event : aEvents) {
-        if (l.flags & GetEventTypeFlag(event->Type())) {
-          bool success = !!filtered.AppendElement(event, fallible);
-          MOZ_RELEASE_ASSERT(success);
-        }
-      }
-      aCallListener(unwrapped, filtered);
-    }
-  }
-}
-
-void
-PlacesObservers::AddListener(GlobalObject& aGlobal,
-                             const nsTArray<PlacesEventType>& aEventTypes,
-                             PlacesEventCallback& aCallback,
-                             ErrorResult& rv)
-{
-  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
-
-  FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
-    JSListeners::GetListeners();
-  Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
-  listeners->AppendElement(pair);
-}
-
-void
-PlacesObservers::AddListener(GlobalObject& aGlobal,
-                             const nsTArray<PlacesEventType>& aEventTypes,
-                             PlacesWeakCallbackWrapper& aCallback,
-                             ErrorResult& rv)
-{
-  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
-
-  FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
-    WeakJSListeners::GetListeners();
-  WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
-  MOZ_ASSERT(weakCb.get());
-  Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags, std::move(weakCb));
-  listeners->AppendElement(flagged);
-}
-
-void
-PlacesObservers::AddListener(const nsTArray<PlacesEventType>& aEventTypes,
-                             places::INativePlacesEventCallback* aCallback)
-{
-  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
-
-  FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
-    WeakNativeListeners::GetListeners();
-  Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
-  listeners->AppendElement(pair);
-}
-
-void
-PlacesObservers::RemoveListener(GlobalObject& aGlobal,
-                                const nsTArray<PlacesEventType>& aEventTypes,
-                                PlacesEventCallback& aCallback,
-                                ErrorResult& rv)
-{
-  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
-  if (gCallingListeners) {
-    FlaggedArray<RefPtr<PlacesEventCallback>>* listeners =
-      JSListeners::GetListenersToRemove();
-    Flagged<RefPtr<PlacesEventCallback>> pair(flags, &aCallback);
-    listeners->AppendElement(pair);
-  } else {
-    RemoveListener(flags, aCallback);
-  }
-}
-
-void
-PlacesObservers::RemoveListener(GlobalObject& aGlobal,
-                                const nsTArray<PlacesEventType>& aEventTypes,
-                                PlacesWeakCallbackWrapper& aCallback,
-                                ErrorResult& rv)
-{
-  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
-  if (gCallingListeners) {
-    FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>* listeners =
-      WeakJSListeners::GetListenersToRemove();
-    WeakPtr<PlacesWeakCallbackWrapper> weakCb(&aCallback);
-    MOZ_ASSERT(weakCb.get());
-    Flagged<WeakPtr<PlacesWeakCallbackWrapper>> flagged(flags, std::move(weakCb));
-    listeners->AppendElement(flagged);
-  } else {
-    RemoveListener(flags, aCallback);
-  }
-}
-
-void
-PlacesObservers::RemoveListener(const nsTArray<PlacesEventType>& aEventTypes,
-                                places::INativePlacesEventCallback* aCallback)
-{
-  uint32_t flags = GetFlagsForEventTypes(aEventTypes);
-  if (gCallingListeners) {
-    FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>* listeners =
-      WeakNativeListeners::GetListenersToRemove();
-    Flagged<WeakPtr<places::INativePlacesEventCallback>> pair(flags, aCallback);
-    listeners->AppendElement(pair);
-  } else {
-    RemoveListener(flags, aCallback);
-  }
-}
-
-void
-PlacesObservers::RemoveListener(uint32_t aFlags,
-                                PlacesEventCallback& aCallback)
-{
-  FlaggedArray<RefPtr<PlacesEventCallback>>& listeners =
-    *JSListeners::GetListeners();
-  for (uint32_t i = 0; i < listeners.Length(); i++) {
-    Flagged<RefPtr<PlacesEventCallback>>& l = listeners[i];
-    if (!(*l.value == aCallback)) {
-      continue;
-    }
-    if (l.flags == aFlags) {
-      listeners.RemoveElementAt(i);
-      i--;
-    } else {
-      l.flags &= ~aFlags;
-    }
-  }
-}
-
-void
-PlacesObservers::RemoveListener(uint32_t aFlags,
-                                PlacesWeakCallbackWrapper& aCallback)
-{
-  FlaggedArray<WeakPtr<PlacesWeakCallbackWrapper>>& listeners =
-    *WeakJSListeners::GetListeners();
-  for (uint32_t i = 0; i < listeners.Length(); i++) {
-    Flagged<WeakPtr<PlacesWeakCallbackWrapper>>& l = listeners[i];
-    RefPtr<PlacesWeakCallbackWrapper> unwrapped = l.value.get();
-    if (unwrapped != &aCallback) {
-      continue;
-    }
-    if (l.flags == aFlags) {
-      listeners.RemoveElementAt(i);
-      i--;
-    } else {
-      l.flags &= ~aFlags;
-    }
-  }
-}
-
-void
-PlacesObservers::RemoveListener(uint32_t aFlags,
-                                places::INativePlacesEventCallback* aCallback)
-{
-  FlaggedArray<WeakPtr<places::INativePlacesEventCallback>>& listeners =
-    *WeakNativeListeners::GetListeners();
-  for (uint32_t i = 0; i < listeners.Length(); i++) {
-    Flagged<WeakPtr<places::INativePlacesEventCallback>>& l = listeners[i];
-    RefPtr<places::INativePlacesEventCallback> unwrapped = l.value.get();
-    if (unwrapped != aCallback) {
-      continue;
-    }
-    if (l.flags == aFlags) {
-      listeners.RemoveElementAt(i);
-      i--;
-    } else {
-      l.flags &= ~aFlags;
-    }
-  }
-}
-
-void
-PlacesObservers::NotifyListeners(GlobalObject& aGlobal,
-                                 const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
-                                 ErrorResult& rv)
-{
-  NotifyListeners(aEvents);
-}
-
-void
-PlacesObservers::NotifyListeners(const Sequence<OwningNonNull<PlacesEvent>>& aEvents)
-{
-  MOZ_RELEASE_ASSERT(!gCallingListeners);
-  gCallingListeners = true;
-  uint32_t flags = GetFlagsForEvents(aEvents);
-
-  CallListeners<RefPtr<PlacesEventCallback>, RefPtr<PlacesEventCallback>>(
-    flags, *JSListeners::GetListeners(), aEvents,
-    [](auto& cb) { return cb; },
-    [&](auto& cb, const auto& events) {
-      cb->Call(aEvents);
-    });
-
-  CallListeners<WeakPtr<places::INativePlacesEventCallback>,
-                RefPtr<places::INativePlacesEventCallback>>(
-    flags, *WeakNativeListeners::GetListeners(), aEvents,
-    [](auto& cb) { return cb.get(); },
-    [&](auto& cb, const Sequence<OwningNonNull<PlacesEvent>>& events) {
-      cb->HandlePlacesEvent(events);
-    });
-
-  CallListeners<WeakPtr<PlacesWeakCallbackWrapper>,
-                RefPtr<PlacesWeakCallbackWrapper>>(
-    flags, *WeakJSListeners::GetListeners(), aEvents,
-    [](auto& cb) { return cb.get(); },
-    [&](auto& cb, const auto& events) {
-      cb->mCallback->Call(aEvents);
-    });
-
-  auto& listenersToRemove = *JSListeners::GetListenersToRemove();
-  if (listenersToRemove.Length() > 0) {
-    for (auto& listener : listenersToRemove) {
-      RemoveListener(listener.flags, *listener.value);
-    }
-  }
-
-  auto& weakListenersToRemove = *WeakJSListeners::GetListenersToRemove();
-  if (weakListenersToRemove.Length() > 0) {
-    for (auto& listener : weakListenersToRemove) {
-      RemoveListener(listener.flags, *listener.value.get());
-    }
-  }
-
-  auto& nativeListenersToRemove = *WeakNativeListeners::GetListenersToRemove();
-  if (nativeListenersToRemove.Length() > 0) {
-    for (auto& listener : nativeListenersToRemove) {
-      RemoveListener(listener.flags, listener.value.get());
-    }
-  }
-
-  gCallingListeners = false;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/base/PlacesObservers.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/* -*- 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_PlacesObservers__
-#define mozilla_dom_PlacesObservers__
-
-#include "mozilla/AlreadyAddRefed.h"
-#include "mozilla/dom/BindingDeclarations.h"
-#include "mozilla/dom/PlacesObserversBinding.h"
-#include "mozilla/dom/PlacesEvent.h"
-#include "mozilla/ErrorResult.h"
-#include "mozilla/Pair.h"
-#include "mozilla/places/INativePlacesEventCallback.h"
-#include "nsIWeakReferenceUtils.h"
-
-namespace mozilla {
-
-namespace dom {
-
-class PlacesObservers
-{
-public:
-  static void AddListener(GlobalObject& aGlobal,
-                          const nsTArray<PlacesEventType>& aEventTypes,
-                          PlacesEventCallback& aCallback,
-                          ErrorResult& rv);
-  static void AddListener(GlobalObject& aGlobal,
-                          const nsTArray<PlacesEventType>& aEventTypes,
-                          PlacesWeakCallbackWrapper& aCallback,
-                          ErrorResult& rv);
-  static void AddListener(const nsTArray<PlacesEventType>& aEventTypes,
-                          places::INativePlacesEventCallback* aCallback);
-  static void RemoveListener(GlobalObject& aGlobal,
-                             const nsTArray<PlacesEventType>& aEventTypes,
-                             PlacesEventCallback& aCallback,
-                             ErrorResult& rv);
-  static void RemoveListener(GlobalObject& aGlobal,
-                             const nsTArray<PlacesEventType>& aEventTypes,
-                             PlacesWeakCallbackWrapper& aCallback,
-                             ErrorResult& rv);
-  static void RemoveListener(const nsTArray<PlacesEventType>& aEventTypes,
-                             places::INativePlacesEventCallback* aCallback);
-
-  static void NotifyListeners(GlobalObject& aGlobal,
-                              const Sequence<OwningNonNull<PlacesEvent>>& aEvents,
-                              ErrorResult& rv);
-
-  static void NotifyListeners(const Sequence<OwningNonNull<PlacesEvent>>& aEvents);
-
-private:
-  static void RemoveListener(uint32_t aFlags, PlacesEventCallback& aCallback);
-  static void RemoveListener(uint32_t aFlags, PlacesWeakCallbackWrapper& aCallback);
-  static void RemoveListener(uint32_t aFlags, places::INativePlacesEventCallback* aCallback);
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_PlacesObservers__
deleted file mode 100644
--- a/dom/base/PlacesVisit.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/* -*- 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_PlacesVisit_h
-#define mozilla_dom_PlacesVisit_h
-
-#include "mozilla/dom/PlacesEvent.h"
-
-namespace mozilla {
-namespace dom {
-
-class PlacesVisit final : public PlacesEvent
-{
-public:
-  explicit PlacesVisit() : PlacesEvent(PlacesEventType::Page_visited) {}
-
-  static already_AddRefed<PlacesVisit>
-  Constructor(const GlobalObject& aGlobal,
-              ErrorResult& aRv) {
-    RefPtr<PlacesVisit> event = new PlacesVisit();
-    return event.forget();
-  }
-
-  JSObject*
-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
-  {
-    return PlacesVisitBinding::Wrap(aCx, this, aGivenProto);
-  }
-
-  const PlacesVisit* AsPlacesVisit() const override { return this; }
-
-  void GetUrl(nsString& aUrl) { aUrl = mUrl; }
-  uint64_t VisitId() { return mVisitId; }
-  uint64_t VisitTime() { return mVisitTime; }
-  uint64_t ReferringVisitId() { return mReferringVisitId; }
-  uint64_t TransitionType() { return mTransitionType; }
-  void GetPageGuid(nsTString<char>& aPageGuid) { aPageGuid = mPageGuid; }
-  bool Hidden() { return mHidden; }
-  uint32_t VisitCount() { return mVisitCount; }
-  uint32_t TypedCount() { return mTypedCount; }
-  void GetLastKnownTitle(nsString& aLastKnownTitle) { aLastKnownTitle = mLastKnownTitle; }
-
-  // It's convenient for these to be directly available in C++, so just expose
-  // them. These are generally passed around with const qualifiers anyway, so
-  // it shouldn't be a problem.
-  nsString mUrl;
-  uint64_t mVisitId;
-  uint64_t mVisitTime;
-  uint64_t mReferringVisitId;
-  uint32_t mTransitionType;
-  nsCString mPageGuid;
-  bool mHidden;
-  uint32_t mVisitCount;
-  uint32_t mTypedCount;
-  nsString mLastKnownTitle;
-
-private:
-  ~PlacesVisit() = default;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_PlacesVisit_h
deleted file mode 100644
--- a/dom/base/PlacesWeakCallbackWrapper.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- 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/. */
-
-#include "mozilla/dom/PlacesWeakCallbackWrapper.h"
-
-#include "mozilla/HoldDropJSObjects.h"
-#include "mozilla/dom/ProcessGlobal.h"
-
-namespace mozilla {
-namespace dom {
-
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PlacesWeakCallbackWrapper, mParent, mCallback)
-NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(PlacesWeakCallbackWrapper, AddRef)
-NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PlacesWeakCallbackWrapper, Release)
-
-PlacesWeakCallbackWrapper::PlacesWeakCallbackWrapper(nsISupports* aParent,
-                                                     PlacesEventCallback& aCallback)
-  : mParent(do_GetWeakReference(aParent))
-  , mCallback(&aCallback)
-{
-}
-
-already_AddRefed<PlacesWeakCallbackWrapper>
-PlacesWeakCallbackWrapper::Constructor(const GlobalObject& aGlobal,
-                                       PlacesEventCallback& aCallback,
-                                       ErrorResult& rv)
-{
-  nsCOMPtr<nsISupports> parent = aGlobal.GetAsSupports();
-  RefPtr<PlacesWeakCallbackWrapper> wrapper =
-    new PlacesWeakCallbackWrapper(parent, aCallback);
-  return wrapper.forget();
-}
-
-PlacesWeakCallbackWrapper::~PlacesWeakCallbackWrapper()
-{
-}
-
-nsISupports*
-PlacesWeakCallbackWrapper::GetParentObject() const
-{
-  nsCOMPtr<nsISupports> parent = do_QueryReferent(mParent);
-  return parent;
-}
-
-JSObject*
-PlacesWeakCallbackWrapper::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return PlacesWeakCallbackWrapperBinding::Wrap(aCx, this, aGivenProto);
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/base/PlacesWeakCallbackWrapper.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- 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_PlacesWeakCallbackWrapper_h
-#define mozilla_dom_PlacesWeakCallbackWrapper_h
-
-#include "mozilla/dom/PlacesObserversBinding.h"
-#include "mozilla/ErrorResult.h"
-#include "mozilla/Pair.h"
-#include "nsWrapperCache.h"
-
-namespace mozilla {
-namespace dom {
-
-class PlacesWeakCallbackWrapper final : public nsWrapperCache
-                                      , public SupportsWeakPtr<PlacesWeakCallbackWrapper>
-{
-public:
-  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(PlacesWeakCallbackWrapper)
-  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PlacesWeakCallbackWrapper)
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PlacesWeakCallbackWrapper)
-
-  explicit PlacesWeakCallbackWrapper(nsISupports* aParent,
-                                     PlacesEventCallback& aCallback);
-
-  static already_AddRefed<PlacesWeakCallbackWrapper>
-  Constructor(const GlobalObject& aGlobal,
-              PlacesEventCallback& aCallback,
-              ErrorResult& rv);
-
-  nsISupports* GetParentObject() const;
-
-  JSObject*
-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
-
-protected:
-  friend class PlacesObservers;
-  ~PlacesWeakCallbackWrapper();
-  nsWeakPtr mParent;
-  RefPtr<PlacesEventCallback> mCallback;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_PlacesWeakCallbackWrapper_h
--- a/dom/base/SelectionChangeListener.h
+++ b/dom/base/SelectionChangeListener.h
@@ -4,21 +4,16 @@
  * 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_SelectionChangeListener_h_
 #define mozilla_SelectionChangeListener_h_
 
 #include "nsISelectionListener.h"
 #include "mozilla/Attributes.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsTArray.h"
-#include "nsCOMPtr.h"
-
-class nsRange;
 
 namespace mozilla {
 namespace dom {
 
 class SelectionChangeListener final : public nsISelectionListener
 {
 public:
   // SelectionChangeListener has to participate in cycle collection because
--- a/dom/base/ShadowRoot.cpp
+++ b/dom/base/ShadowRoot.cpp
@@ -1,16 +1,17 @@
 /* -*- 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/. */
 
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/ShadowRootBinding.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "ChildIterator.h"
 #include "nsContentUtils.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLSlotElement.h"
 #include "nsXBLPrototypeBinding.h"
 #include "mozilla/EventDispatcher.h"
--- a/dom/base/ShadowRoot.h
+++ b/dom/base/ShadowRoot.h
@@ -5,22 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_shadowroot_h__
 #define mozilla_dom_shadowroot_h__
 
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/DocumentOrShadowRoot.h"
 #include "mozilla/dom/NameSpaceConstants.h"
-#include "mozilla/dom/ShadowRootBinding.h"
-#include "mozilla/ServoBindings.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIdentifierMapEntry.h"
-#include "nsStubMutationObserver.h"
 #include "nsTHashtable.h"
 
 class nsAtom;
 class nsIContent;
 class nsXBLPrototypeBinding;
 
 namespace mozilla {
 
--- a/dom/base/StyleSheetList.h
+++ b/dom/base/StyleSheetList.h
@@ -2,17 +2,16 @@
 /* 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_StyleSheetList_h
 #define mozilla_dom_StyleSheetList_h
 
-#include "nsContentUtils.h"
 #include "mozilla/dom/DocumentOrShadowRoot.h"
 #include "nsStubMutationObserver.h"
 #include "nsWrapperCache.h"
 
 class nsINode;
 
 namespace mozilla {
 class StyleSheet;
--- a/dom/base/WindowDestroyedEvent.cpp
+++ b/dom/base/WindowDestroyedEvent.cpp
@@ -3,17 +3,16 @@
 #include "nsJSUtils.h"
 #include "jsapi.h"              // for JSAutoRequest
 #include "js/Wrapper.h"
 #include "nsIPrincipal.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIAppStartup.h"
 #include "nsToolkitCompsCID.h"
 #include "nsCOMPtr.h"
-#include "nsContentUtils.h"
 
 namespace mozilla {
 
 // Try to match compartments that are not web content by matching compartments
 // with principals that are either the system principal or an expanded principal.
 // This may not return true for all non-web-content compartments.
 struct BrowserCompartmentMatcher : public js::CompartmentFilter {
   bool match(JS::Compartment* aC) const override
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -196,20 +196,16 @@ EXPORTS.mozilla.dom += [
     'MessageSender.h',
     'MozQueryInterface.h',
     'NameSpaceConstants.h',
     'Navigator.h',
     'NodeInfo.h',
     'NodeInfoInlines.h',
     'NodeIterator.h',
     'ParentProcessMessageManager.h',
-    'PlacesEvent.h',
-    'PlacesObservers.h',
-    'PlacesVisit.h',
-    'PlacesWeakCallbackWrapper.h',
     'Pose.h',
     'ProcessGlobal.h',
     'ProcessMessageManager.h',
     'ResponsiveImageSelector.h',
     'SameProcessMessageQueue.h',
     'ScreenOrientation.h',
     'Selection.h',
     'ShadowRoot.h',
@@ -389,23 +385,16 @@ if CONFIG['MOZ_WEBRTC']:
         'nsDOMDataChannel.cpp',
     ]
 
 if CONFIG['FUZZING']:
     UNIFIED_SOURCES += [
         'FuzzingFunctions.cpp',
     ]
 
-if CONFIG['MOZ_PLACES']:
-    UNIFIED_SOURCES += [
-        'PlacesEvent.cpp',
-        'PlacesObservers.cpp',
-        'PlacesWeakCallbackWrapper.cpp',
-    ]
-
 # these files couldn't be in UNIFIED_SOURCES for now for reasons given below:
 SOURCES += [
     # Several conflicts with other bindings.
     'DOMIntersectionObserver.cpp',
     # Because of OS X headers.
     'nsContentUtils.cpp',
     # this file doesn't like windows.h
     'nsDOMWindowUtils.cpp',
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -19,18 +19,16 @@
 #include "nsError.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsServiceManagerUtils.h"
 #include "nsTextFragment.h"
 #include "nsThreadUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
-using mozilla::dom::DocGroup;
-using mozilla::dom::HTMLSlotElement;
 
 AutoTArray<RefPtr<nsDOMMutationObserver>, 4>*
   nsDOMMutationObserver::sScheduledMutationObservers = nullptr;
 
 uint32_t nsDOMMutationObserver::sMutationLevel = 0;
 uint64_t nsDOMMutationObserver::sCount = 0;
 
 AutoTArray<AutoTArray<RefPtr<nsDOMMutationObserver>, 4>, 4>*
--- a/dom/base/nsIGlobalObject.cpp
+++ b/dom/base/nsIGlobalObject.cpp
@@ -10,17 +10,16 @@
 #include "mozilla/dom/ServiceWorker.h"
 #include "mozilla/dom/ServiceWorkerRegistration.h"
 #include "nsContentUtils.h"
 #include "nsThreadUtils.h"
 
 using mozilla::MallocSizeOf;
 using mozilla::Maybe;
 using mozilla::DOMEventTargetHelper;
-using mozilla::dom::BlobURLProtocolHandler;
 using mozilla::dom::ClientInfo;
 using mozilla::dom::ServiceWorker;
 using mozilla::dom::ServiceWorkerDescriptor;
 using mozilla::dom::ServiceWorkerRegistration;
 using mozilla::dom::ServiceWorkerRegistrationDescriptor;
 
 nsIGlobalObject::~nsIGlobalObject()
 {
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -85,17 +85,16 @@
 #include "nsPresContext.h"
 #include "nsString.h"
 #include "nsStyleConsts.h"
 #include "nsSVGUtils.h"
 #include "nsTextNode.h"
 #include "nsUnicharUtils.h"
 #include "nsXBLBinding.h"
 #include "nsXBLPrototypeBinding.h"
-#include "nsWindowSizes.h"
 #include "mozilla/Preferences.h"
 #include "xpcpublic.h"
 #include "HTMLLegendElement.h"
 #include "nsWrapperCacheInlines.h"
 #include "WrapperFactory.h"
 #include <algorithm>
 #include "nsGlobalWindow.h"
 #include "nsDOMMutationObserver.h"
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -20,17 +20,16 @@
 #include "js/MemoryMetrics.h"
 #include "nsQueryObject.h"
 #include "nsServiceManagerUtils.h"
 #ifdef MOZ_XUL
 #include "nsXULPrototypeCache.h"
 #endif
 
 using namespace mozilla;
-using namespace mozilla::dom;
 
 StaticRefPtr<nsWindowMemoryReporter> sWindowReporter;
 
 /**
  * Don't trigger a ghost window check when a DOM window is detached if we've
  * run it this recently.
  */
 const int32_t kTimeBetweenChecks = 45; /* seconds */
--- a/dom/base/nsXHTMLContentSerializer.cpp
+++ b/dom/base/nsXHTMLContentSerializer.cpp
@@ -7,17 +7,16 @@
 /*
  * nsIContentSerializer implementation that can be used with an
  * nsIDocumentEncoder to convert an XHTML (not HTML!) DOM to an XHTML
  * string that could be parsed into more or less the original DOM.
  */
 
 #include "nsXHTMLContentSerializer.h"
 
-#include "mozilla/dom/Element.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsElementTable.h"
 #include "nsNameSpaceManager.h"
 #include "nsString.h"
 #include "nsUnicharUtils.h"
 #include "nsIServiceManager.h"
 #include "nsIDocumentEncoder.h"
@@ -26,16 +25,17 @@
 #include "nsNetUtil.h"
 #include "nsEscape.h"
 #include "nsCRT.h"
 #include "nsContentUtils.h"
 #include "nsIScriptElement.h"
 #include "nsStubMutationObserver.h"
 #include "nsAttrName.h"
 #include "nsComputedDOMStyle.h"
+#include "mozilla/dom/Element.h"
 
 using namespace mozilla;
 
 static const int32_t kLongLineLen = 128;
 
 #define kXMLNS "xmlns"
 
 nsresult
deleted file mode 100644
--- a/dom/chrome-webidl/PlacesEvent.webidl
+++ /dev/null
@@ -1,69 +0,0 @@
-enum PlacesEventType {
-  "none",
-
-  /**
-   * data: PlacesVisit. Fired whenever a page is visited.
-   */
-  "page-visited",
-};
-
-[ChromeOnly, Exposed=(Window,System)]
-interface PlacesEvent {
-  readonly attribute PlacesEventType type;
-};
-
-[ChromeOnly, Exposed=(Window,System)]
-interface PlacesVisit : PlacesEvent {
-  /**
-   * URL of the visit.
-   */
-  readonly attribute DOMString url;
-
-  /**
-   * Id of the visit.
-   */
-  readonly attribute unsigned long long visitId;
-
-  /**
-   * Time of the visit, in milliseconds since epoch.
-   */
-  readonly attribute unsigned long long visitTime;
-
-  /**
-   * The id of the visit the user came from, defaults to 0 for no referrer.
-   */
-  readonly attribute unsigned long long referringVisitId;
-
-  /**
-   * One of nsINavHistory.TRANSITION_*
-   */
-  readonly attribute unsigned long transitionType;
-
-  /**
-   * The unique id associated with the page.
-   */
-  readonly attribute ByteString pageGuid;
-
-  /**
-   * Whether the visited page is marked as hidden.
-   */
-  readonly attribute boolean hidden;
-
-  /**
-   * Number of visits (including this one) for this URL.
-   */
-  readonly attribute unsigned long visitCount;
-
-  /**
-   * Whether the URL has been typed or not.
-   * TODO (Bug 1271801): This will become a count, rather than a boolean.
-   * For future compatibility, always compare it with "> 0".
-   */
-  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;
-};
deleted file mode 100644
--- a/dom/chrome-webidl/PlacesObservers.webidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/.
- */
-
-callback PlacesEventCallback = void (sequence<PlacesEvent> events);
-
-[ChromeOnly, Exposed=(Window,System),
- Constructor(PlacesEventCallback callback)]
-interface PlacesWeakCallbackWrapper {
-};
-
-// Global singleton which should handle all events for places.
-[ChromeOnly, Exposed=(Window,System)]
-namespace PlacesObservers {
-  [Throws]
-  void addListener(sequence<PlacesEventType> eventTypes,
-                   PlacesEventCallback listener);
-  [Throws]
-  void addListener(sequence<PlacesEventType> eventTypes,
-                   PlacesWeakCallbackWrapper listener);
-  [Throws]
-  void removeListener(sequence<PlacesEventType> eventTypes,
-                      PlacesEventCallback listener);
-  [Throws]
-  void removeListener(sequence<PlacesEventType> eventTypes,
-                      PlacesWeakCallbackWrapper listener);
-  [Throws]
-  void notifyListeners(sequence<PlacesEvent> events);
-};
-
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -41,14 +41,8 @@ WEBIDL_FILES = [
     'MozStorageStatementParams.webidl',
     'MozStorageStatementRow.webidl',
     'PrecompiledScript.webidl',
     'PromiseDebugging.webidl',
     'StructuredCloneHolder.webidl',
     'WebExtensionContentScript.webidl',
     'WebExtensionPolicy.webidl',
 ]
-
-if CONFIG['MOZ_PLACES']:
-    WEBIDL_FILES += [
-        'PlacesEvent.webidl',
-        'PlacesObservers.webidl',
-    ]
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -478,27 +478,21 @@ 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) {
@@ -519,30 +513,29 @@ HistoryTracker.prototype = {
   },
 
   onDeleteURI(uri, guid, reason) {
     this.asyncObserver.enqueueCall(() =>
       this.onDeleteAffectsGUID(uri, guid, reason, "onDeleteURI", SCORE_INCREMENT_XLARGE)
     );
   },
 
-  handlePlacesEvents(aEvents) {
-    this.asyncObserver.enqueueCall(() => this._handlePlacesEvents(aEvents));
+  onVisits(aVisits) {
+    this.asyncObserver.enqueueCall(() => this._onVisits(aVisits));
   },
 
-  async _handlePlacesEvents(aEvents) {
+  async _onVisits(aVisits) {
     if (this.ignoreAll) {
       this._log.trace("ignoreAll: ignoring visits [" +
-                      aEvents.map(v => v.guid).join(",") + "]");
+                      aVisits.map(v => v.guid).join(",") + "]");
       return;
     }
-    for (let event of aEvents) {
-      this._log.trace("'page-visited': " + event.url);
-      if (this.engine.shouldSyncURL(event.url) &&
-          await this.addChangedID(event.pageGuid)) {
+    for (let {uri, guid} of aVisits) {
+      this._log.trace("onVisits: " + uri.spec);
+      if (this.engine.shouldSyncURL(uri.spec) && (await this.addChangedID(guid))) {
         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/services/sync/tests/unit/head_helpers.js
+++ b/services/sync/tests/unit/head_helpers.js
@@ -544,44 +544,39 @@ async function serverForFoo(engine, call
 }
 
 // Places notifies history observers asynchronously, so `addVisits` might return
 // before the tracker receives the notification. This helper registers an
 // observer that resolves once the expected notification fires.
 async function promiseVisit(expectedType, expectedURI) {
   return new Promise(resolve => {
     function done(type, uri) {
-      if (uri == expectedURI.spec && type == expectedType) {
+      if (uri.equals(expectedURI) && type == expectedType) {
         PlacesUtils.history.removeObserver(observer);
-        PlacesObservers.removeListener(["page-visited"],
-                                       observer.handlePlacesEvents);
         resolve();
       }
     }
     let observer = {
-      handlePlacesEvents(events) {
-        Assert.equal(events.length, 1);
-        Assert.equal(events[0].type, "page-visited");
-        done("added", events[0].url);
+      onVisits(visits) {
+        Assert.equal(visits.length, 1);
+        done("added", visits[0].uri);
       },
       onBeginUpdateBatch() {},
       onEndUpdateBatch() {},
       onTitleChanged() {},
       onFrecencyChanged() {},
       onManyFrecenciesChanged() {},
       onDeleteURI(uri) {
-        done("removed", uri.spec);
+        done("removed", uri);
       },
       onClearHistory() {},
       onPageChanged() {},
       onDeleteVisits() {},
     };
     PlacesUtils.history.addObserver(observer, false);
-    PlacesObservers.addListener(["page-visited"],
-                                observer.handlePlacesEvents);
   });
 }
 
 async function addVisit(suffix, referrer = null, transition = PlacesUtils.history.TRANSITION_LINK) {
   let uriString = "http://getfirefox.com/" + suffix;
   let uri = CommonUtils.makeURI(uriString);
   _("Adding visit for URI " + uriString);
 
--- a/services/sync/tests/unit/test_history_store.js
+++ b/services/sync/tests/unit/test_history_store.js
@@ -8,21 +8,36 @@ ChromeUtils.import("resource://services-
 ChromeUtils.import("resource://services-sync/util.js");
 
 const TIMESTAMP1 = (Date.now() - 103406528) * 1000;
 const TIMESTAMP2 = (Date.now() - 6592903) * 1000;
 const TIMESTAMP3 = (Date.now() - 123894) * 1000;
 
 function promiseOnVisitObserved() {
   return new Promise(res => {
-    let listener = new PlacesWeakCallbackWrapper((events) => {
-      PlacesObservers.removeListener(["page-visited"], listener);
-      res();
-    });
-    PlacesObservers.addListener(["page-visited"], listener);
+    PlacesUtils.history.addObserver({
+      onBeginUpdateBatch: function onBeginUpdateBatch() {},
+      onEndUpdateBatch: function onEndUpdateBatch() {},
+      onPageChanged: function onPageChanged() {},
+      onTitleChanged: function onTitleChanged() {
+      },
+      onVisits: function onVisits() {
+        PlacesUtils.history.removeObserver(this);
+        res();
+      },
+      onDeleteVisits: function onDeleteVisits() {},
+      onPageExpired: function onPageExpired() {},
+      onDeleteURI: function onDeleteURI() {},
+      onClearHistory: function onClearHistory() {},
+      QueryInterface: ChromeUtils.generateQI([
+        Ci.nsINavHistoryObserver,
+        Ci.nsINavHistoryObserver_MOZILLA_1_9_1_ADDITIONS,
+        Ci.nsISupportsWeakReference
+      ])
+    }, true);
   });
 }
 
 function isDateApproximately(actual, expected, skewMillis = 1000) {
   let lowerBound = expected - skewMillis;
   let upperBound = expected + skewMillis;
   return actual >= lowerBound && actual <= upperBound;
 }
--- a/toolkit/components/downloads/DownloadIntegration.jsm
+++ b/toolkit/components/downloads/DownloadIntegration.jsm
@@ -1017,16 +1017,17 @@ 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/downloads/test/unit/common_test_Download.js
+++ b/toolkit/components/downloads/test/unit/common_test_Download.js
@@ -2395,17 +2395,17 @@ add_task(async function test_history() {
   await PlacesUtils.history.clear();
   let promiseVisit = promiseWaitForVisit(httpUrl("interruptible.txt"));
 
   // Start a download that is not allowed to finish yet.
   let download = await promiseStartDownload(httpUrl("interruptible.txt"));
 
   // The history notifications should be received before the download completes.
   let [time, transitionType] = await promiseVisit;
-  Assert.equal(time, download.startTime.getTime());
+  Assert.equal(time, download.startTime.getTime() * 1000);
   Assert.equal(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
 
   // Restart and complete the download after clearing history.
   await PlacesUtils.history.clear();
   download.cancel();
   continueResponses();
   await download.start();
 
@@ -2429,17 +2429,17 @@ add_task(async function test_history_try
 
   // The history notifications should be received before the download completes.
   let [time, transitionType] = await promiseVisit;
   Assert.equal(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
 
   // The time set by nsIHelperAppService may be different than the start time in
   // the download object, thus we only check that it is a meaningful time.  Note
   // that we subtract one second from the earliest time to account for rounding.
-  Assert.ok(time >= beforeStartTimeMs - 1000);
+  Assert.ok(time >= beforeStartTimeMs * 1000 - 1000000);
 
   // Complete the download before finishing the test.
   continueResponses();
   await promiseDownloadStopped(download);
 });
 
 /**
  * Tests that the temp download files are removed on exit and exiting private
--- a/toolkit/components/downloads/test/unit/head.js
+++ b/toolkit/components/downloads/test/unit/head.js
@@ -141,26 +141,42 @@ function promiseTimeout(aTime) {
  *        String containing the URI that will be visited.
  *
  * @return {Promise}
  * @resolves Array [aTime, aTransitionType] from nsINavHistoryObserver.onVisit.
  * @rejects Never.
  */
 function promiseWaitForVisit(aUrl) {
   return new Promise(resolve => {
-    function listener(aEvents) {
-      Assert.equal(aEvents.length, 1);
-      let event = aEvents[0];
-      Assert.equal(event.type, "page-visited");
-      if (event.url == aUrl) {
-        PlacesObservers.removeListener(["page-visited"], listener);
-        resolve([event.visitTime, event.transitionType]);
-      }
-    }
-    PlacesObservers.addListener(["page-visited"], listener);
+
+    let uri = NetUtil.newURI(aUrl);
+
+    PlacesUtils.history.addObserver({
+      QueryInterface: ChromeUtils.generateQI([Ci.nsINavHistoryObserver]),
+      onBeginUpdateBatch() {},
+      onEndUpdateBatch() {},
+      onVisits(aVisits) {
+        Assert.equal(aVisits.length, 1);
+        let {
+          uri: visitUri,
+          time,
+          transitionType,
+        } = aVisits[0];
+        if (visitUri.equals(uri)) {
+          PlacesUtils.history.removeObserver(this);
+          resolve([time, transitionType]);
+        }
+      },
+      onTitleChanged() {},
+      onDeleteURI() {},
+      onClearHistory() {},
+      onPageChanged() {},
+      onDeleteVisits() {},
+    });
+
   });
 }
 
 /**
  * Creates a new Download object, setting a temporary file as the target.
  *
  * @param aSourceUrl
  *        String containing the URI for the download source, or null to use
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -33,20 +33,16 @@
 #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;
@@ -169,16 +165,131 @@ struct VisitData {
   bool shouldUpdateFrecency;
 
   // Whether to override the visit type bonus with a redirect bonus when
   // calculating frecency on the most recent visit.
   bool useFrecencyRedirectBonus;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
+//// nsVisitData
+
+class nsVisitData : public nsIVisitData
+{
+public:
+  explicit nsVisitData(nsIURI* aURI,
+                       int64_t aVisitId,
+                       PRTime aTime,
+                       int64_t aReferrerVisitId,
+                       int32_t aTransitionType,
+                       const nsACString& aGuid,
+                       bool aHidden,
+                       uint32_t aVisitCount,
+                       uint32_t aTyped,
+                       const nsAString& aLastKnownTitle)
+    : mURI(aURI)
+    , mVisitId(aVisitId)
+    , mTime(aTime)
+    , mReferrerVisitId(aReferrerVisitId)
+    , mTransitionType(aTransitionType)
+    , mGuid(aGuid)
+    , mHidden(aHidden)
+    , mVisitCount(aVisitCount)
+    , mTyped(aTyped)
+    , mLastKnownTitle(aLastKnownTitle)
+  {
+    MOZ_ASSERT(NS_IsMainThread(),
+               "nsVisitData should only be constructed on the main thread.");
+  }
+
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD GetUri(nsIURI** aUri) override
+  {
+    NS_ENSURE_ARG_POINTER(aUri);
+    *aUri = mURI;
+    NS_IF_ADDREF(*aUri);
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetVisitId(int64_t* aVisitId) override
+  {
+    *aVisitId = mVisitId;
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetTime(PRTime* aTime) override
+  {
+    *aTime = mTime;
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetReferrerId(int64_t* aReferrerVisitId) override
+  {
+    *aReferrerVisitId = mReferrerVisitId;
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetTransitionType(uint32_t* aTransitionType) override
+  {
+    *aTransitionType = mTransitionType;
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetGuid(nsACString& aGuid) override
+  {
+    aGuid.Assign(mGuid);
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetHidden(bool* aHidden) override
+  {
+    *aHidden = mHidden;
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetVisitCount(uint32_t* aVisitCount) override
+  {
+    *aVisitCount = mVisitCount;
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetTyped(uint32_t* aTyped) override
+  {
+    *aTyped = mTyped;
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetLastKnownTitle(nsAString& aLastKnownTitle) override
+  {
+    aLastKnownTitle.Assign(mLastKnownTitle);
+    return NS_OK;
+  }
+
+private:
+  virtual ~nsVisitData() {
+    MOZ_ASSERT(NS_IsMainThread(),
+               "nsVisitData should only be destructed on the main thread.");
+  };
+
+  nsCOMPtr<nsIURI> mURI;
+  int64_t mVisitId;
+  PRTime mTime;
+  int64_t mReferrerVisitId;
+  uint32_t mTransitionType;
+  nsCString mGuid;
+  bool mHidden;
+  uint32_t mVisitCount;
+  uint32_t mTyped;
+  nsString mLastKnownTitle;
+};
+
+NS_IMPL_ISUPPORTS(nsVisitData, nsIVisitData)
+
+////////////////////////////////////////////////////////////////////////////////
 //// RemoveVisitsFilter
 
 /**
  * Used to store visit filters for RemoveVisits.
  */
 struct RemoveVisitsFilter {
   RemoveVisitsFilter()
   : transitionType(UINT32_MAX)
@@ -665,38 +776,31 @@ 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,
-                         Sequence<OwningNonNull<PlacesEvent>>& aEvents) {
+                         nsCOMArray<nsIVisitData>& aPlaces) {
     if (aPlace.transitionType != nsINavHistoryService::TRANSITION_EMBED) {
-      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);
+      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());
     }
   }
 
   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.
@@ -709,40 +813,39 @@ 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();
 
-    Sequence<OwningNonNull<PlacesEvent>> events;
+    nsCOMArray<nsIVisitData> places;
     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, events);
+        AddPlaceForNotify(mPlaces[i], uri, places);
         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, events);
+      AddPlaceForNotify(mPlace, uri, places);
       uris.AppendElement(uri.forget());
     }
-
-    if (events.Length() > 0) {
-      PlacesObservers::NotifyListeners(events);
+    if (places.Length() > 0) {
+      navHistory->NotifyOnVisits(places.Elements(), places.Length());
     }
 
     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);
deleted file mode 100644
--- a/toolkit/components/places/INativePlacesEventCallback.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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_image_INativePlacesEventCallback_h
-#define mozilla_image_INativePlacesEventCallback_h
-
-#include "mozilla/dom/PlacesObserversBinding.h"
-#include "mozilla/WeakPtr.h"
-#include "nsISupports.h"
-#include "nsTArray.h"
-
-namespace mozilla {
-namespace places {
-
-class INativePlacesEventCallback : public SupportsWeakPtr<INativePlacesEventCallback>
-{
-public:
-  typedef dom::Sequence<OwningNonNull<dom::PlacesEvent>> PlacesEventSequence;
-
-  MOZ_DECLARE_WEAKREFERENCE_TYPENAME(INativePlacesEventCallback)
-  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
-
-  virtual void HandlePlacesEvent(const PlacesEventSequence& aEvents) = 0;
-
-protected:
-  virtual ~INativePlacesEventCallback() { }
-};
-
-} // namespace places
-} // namespace mozilla
-
-#endif // mozilla_image_INativePlacesEventCallback_h
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -334,17 +334,16 @@ 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/moz.build
+++ b/toolkit/components/places/moz.build
@@ -24,17 +24,16 @@ if CONFIG['MOZ_PLACES']:
         'nsIFaviconService.idl',
         'nsINavBookmarksService.idl',
         'nsITaggingService.idl',
     ]
 
     EXPORTS.mozilla.places = [
         'Database.h',
         'History.h',
-        'INativePlacesEventCallback.h',
         'Shutdown.h',
     ]
 
     UNIFIED_SOURCES += [
         'Database.cpp',
         'FaviconHelpers.cpp',
         'Helpers.cpp',
         'History.cpp',
--- a/toolkit/components/places/mozIAsyncLivemarks.idl
+++ b/toolkit/components/places/mozIAsyncLivemarks.idl
@@ -58,18 +58,16 @@ 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/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -24,16 +24,77 @@ interface nsIAsyncShutdownClient;
 
 interface nsINavHistoryContainerResultNode;
 interface nsINavHistoryQueryResultNode;
 interface nsINavHistoryQuery;
 interface nsINavHistoryQueryOptions;
 interface nsINavHistoryResult;
 interface nsINavHistoryBatchCallback;
 
+/**
+ * This interface exists specifically for passing visit information
+ * in bulk to onVisits below.
+ */
+[scriptable, uuid(9d8df1ff-142f-4ca7-9f45-3c62a508c7e2)]
+interface nsIVisitData : nsISupports
+{
+  /**
+   * URI of the visit that was just created.
+   */
+  readonly attribute nsIURI uri;
+
+  /**
+   * Id of the visit that was just created.
+   */
+  readonly attribute long long visitId;
+
+  /**
+   * Time of the visit.
+   */
+  readonly attribute PRTime time;
+
+  /**
+   * The id of the visit the user came from, defaults to 0 for no referrer.
+   */
+  readonly attribute long long referrerId;
+
+  /**
+   * One of nsINavHistory.TRANSITION_*
+   */
+  readonly attribute unsigned long transitionType;
+
+  /**
+   * The unique id associated with the page.
+   */
+  readonly attribute ACString guid;
+
+  /**
+   * Whether the visited page is marked as hidden.
+   */
+  readonly attribute boolean hidden;
+
+  /**
+   * Number of visits (included this one) for this URI.
+   */
+  readonly attribute unsigned long visitCount;
+
+  /**
+   * Whether the URI has been typed or not.
+   * TODO (Bug 1271801): This will become a count, rather than a boolean.
+   * For future compatibility, always compare it with "> 0".
+   */
+  readonly attribute unsigned long typed;
+
+  /**
+   * 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 AString lastKnownTitle;
+};
+
 [scriptable, uuid(91d104bb-17ef-404b-9f9a-d9ed8de6824c)]
 interface nsINavHistoryResultNode : nsISupports
 {
   /**
    * Indentifies the parent result node in the result set. This is null for
    * top level nodes.
    */
   readonly attribute nsINavHistoryContainerResultNode parent;
@@ -577,16 +638,28 @@ interface nsINavHistoryObserver : nsISup
 
   /**
    * Notifies you that we are done doing a bunch of things and you should go
    * ahead and update UI, etc.
    */
   void onEndUpdateBatch();
 
   /**
+   * Called everytime a URI is visited, or once for a batch of visits if visits were
+   * added in bulk.
+   *
+   * @note TRANSITION_EMBED visits (corresponding to images in a page, for
+   *       example) are not displayed in history results. Most observers can
+   *       ignore TRANSITION_EMBED visit notifications (which will comprise the
+   *       majority of visit notifications) to save work.
+   */
+  void onVisits([array, size_is(aVisitsCount)] in nsIVisitData aVisits,
+                in unsigned long aVisitsCount);
+
+  /**
    * Called whenever either the "real" title or the custom title of the page
    * changed. BOTH TITLES ARE ALWAYS INCLUDED in this notification, even though
    * only one will change at a time. Often, consumers will want to display the
    * user title if it is available, and fall back to the page title (the one
    * specified in the <title> tag of the page).
    *
    * Note that there is a difference between an empty title and a NULL title.
    * An empty string means that somebody specifically set the title to be
--- a/toolkit/components/places/nsLivemarkService.js
+++ b/toolkit/components/places/nsLivemarkService.js
@@ -7,22 +7,18 @@
 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(livemarks, true);
-  let listener = new PlacesWeakCallbackWrapper(
-    livemarks.handlePlacesEvents.bind(livemarks));
-  PlacesObservers.addListener(["page-visited"], listener);
+  PlacesUtils.history.addObserver(PlacesUtils.livemarks, true);
   return PlacesUtils.history;
 });
 
 // Constants
 
 // Delay between reloads of consecute livemarks.
 const RELOAD_DELAY_MS = 500;
 // Expire livemarks after this time.
@@ -335,31 +331,16 @@ 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,
@@ -426,16 +407,26 @@ 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,
@@ -696,36 +687,36 @@ Livemark.prototype = {
     for (let [ container, observer ] of this._resultObservers) {
       observer.invalidateContainer(container);
     }
   },
 
   /**
    * Updates the visited status of nodes observing this livemark.
    *
-   * @param href
+   * @param aURI
    *        If provided will update nodes having the given uri,
    *        otherwise any node.
-   * @param visitedStatus
+   * @param aVisitedStatus
    *        Whether the nodes should be set as visited.
    */
-  updateURIVisitedStatus(href, visitedStatus) {
+  updateURIVisitedStatus(aURI, aVisitedStatus) {
     let wasVisited = false;
     for (let child of this.children) {
-      if (!href || child.uri.spec == href) {
+      if (!aURI || child.uri.equals(aURI)) {
         wasVisited = child.visited;
-        child.visited = visitedStatus;
+        child.visited = aVisitedStatus;
       }
     }
 
     for (let [ container, observer ] of this._resultObservers) {
       if (this._nodes.has(container)) {
         let nodes = this._nodes.get(container);
         for (let node of nodes) {
-          if (!href || node.uri == href) {
+          if (!aURI || node.uri == aURI.spec) {
             Services.tm.dispatchToMainThread(() => {
               observer.nodeHistoryDetailsChanged(node, node.time, wasVisited);
             });
           }
         }
       }
     }
   },
@@ -822,17 +813,16 @@ 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,18 +12,16 @@
 
 #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;
@@ -209,19 +207,16 @@ 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,
@@ -2053,38 +2048,40 @@ NS_IMETHODIMP
 nsNavBookmarks::OnEndUpdateBatch()
 {
   NOTIFY_OBSERVERS(mCanNotify, mObservers,
                    nsINavBookmarkObserver, OnEndUpdateBatch());
   return NS_OK;
 }
 
 
-void
-nsNavBookmarks::HandlePlacesEvent(const PlacesEventSequence& aEvents)
+NS_IMETHODIMP
+nsNavBookmarks::OnVisits(nsIVisitData** aVisits, uint32_t aVisitsCount)
 {
-  for (const auto& event : aEvents) {
-    if (NS_WARN_IF(event->Type() != PlacesEventType::Page_visited)) {
-      continue;
-    }
+  NS_ENSURE_ARG(aVisits);
+  NS_ENSURE_ARG(aVisitsCount);
 
-    const dom::PlacesVisit* visit = event->AsPlacesVisit();
-    if (NS_WARN_IF(!visit)) {
-      continue;
-    }
+  for (uint32_t i = 0; i < aVisitsCount; ++i) {
+    nsIVisitData* place = aVisits[i];
+    nsCOMPtr<nsIURI> uri;
+    MOZ_ALWAYS_SUCCEEDS(place->GetUri(getter_AddRefs(uri)));
 
+    // If the page is bookmarked, notify observers for each associated bookmark.
     ItemVisitData visitData;
-    visitData.visitId = visit->mVisitId;
-    visitData.bookmark.url = NS_ConvertUTF16toUTF8(visit->mUrl);
-    visitData.time = visit->mVisitTime * 1000;
-    visitData.transitionType = visit->mTransitionType;
+    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));
+
     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,17 +73,16 @@ 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();
@@ -203,26 +202,16 @@ 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,25 +516,35 @@ 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::UpdateDaysOfHistory(PRTime visitTime)
+nsNavHistory::NotifyOnVisits(nsIVisitData** aVisits, uint32_t aVisitsCount)
 {
+  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;
   }
 
-  if (visitTime > mLastCachedEndOfDay || visitTime < mLastCachedStartOfDay) {
-    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;
+    }
   }
+
+  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,20 +430,19 @@ public:
   }
 
   int32_t GetNumVisitsForFrecency() const
   {
     return mNumVisitsForFrecency;
   }
 
   /**
-   * Updates and invalidates the mDaysOfHistory cache. Should be
-   * called whenever a visit is added.
+   * Fires onVisits event to nsINavHistoryService observers
    */
-  void UpdateDaysOfHistory(PRTime visitTime);
+  void NotifyOnVisits(nsIVisitData** aVisits, uint32_t aVisitsCount);
 
   /**
    * 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,18 +14,16 @@
 #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)
@@ -3992,34 +3990,28 @@ 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);
   }
@@ -4593,37 +4585,42 @@ nsNavHistoryResult::OnVisit(nsIURI* aURI
     // cause changes to the array.
     ENUMERATE_QUERY_OBSERVERS(Refresh(), mHistoryObservers, IsContainersQuery());
   }
 
   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;
-    }
-
+NS_IMETHODIMP
+nsNavHistoryResult::OnVisits(nsIVisitData** aVisits,
+                             uint32_t aVisitsCount) {
+  for (uint32_t i = 0; i < aVisitsCount; ++i) {
+    nsIVisitData* place = aVisits[i];
     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);
+    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);
   }
+  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,17 +7,16 @@
  * 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;
@@ -94,26 +93,27 @@ 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 mozilla::places::INativePlacesEventCallback
+                                 public nsINavHistoryObserver
 {
 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,18 +169,16 @@ 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,16 +531,17 @@ 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/places/tests/PlacesTestUtils.jsm
+++ b/toolkit/components/places/tests/PlacesTestUtils.jsm
@@ -313,28 +313,16 @@ var PlacesTestUtils = Object.freeze({
       ORDER BY guid`);
     return rows.map(row => ({
       guid: row.getResultByName("guid"),
       dateRemoved: PlacesUtils.toDate(row.getResultByName("dateRemoved")),
     }));
   },
 
   waitForNotification(notification, conditionFn = () => true, type = "bookmarks") {
-    if (type == "places") {
-      return new Promise(resolve => {
-        function listener(events) {
-          if (conditionFn(events)) {
-            PlacesObservers.removeListener([notification], listener);
-            resolve();
-          }
-        }
-        PlacesObservers.addListener([notification], listener);
-      });
-    }
-
     let iface = type == "bookmarks" ? Ci.nsINavBookmarkObserver
                                     : Ci.nsINavHistoryObserver;
     return new Promise(resolve => {
       let proxifiedObserver = new Proxy({}, {
         get: (target, name) => {
           if (name == "QueryInterface")
             return ChromeUtils.generateQI([iface]);
           if (name == notification)
--- a/toolkit/components/places/tests/browser/browser_bug399606.js
+++ b/toolkit/components/places/tests/browser/browser_bug399606.js
@@ -10,38 +10,48 @@ add_task(async function() {
     "http://example.com/tests/toolkit/components/places/tests/browser/399606-history.go-0.html",
     "http://example.com/tests/toolkit/components/places/tests/browser/399606-location.replace.html",
     "http://example.com/tests/toolkit/components/places/tests/browser/399606-location.reload.html",
     "http://example.com/tests/toolkit/components/places/tests/browser/399606-httprefresh.html",
     "http://example.com/tests/toolkit/components/places/tests/browser/399606-window.location.html",
   ];
 
   // Create and add history observer.
-  let count = 0;
-  let expectedURI = null;
-  function onVisitsListener(aEvents) {
-    for (let event of aEvents) {
-      info("Received onVisits: " + event.url);
-      if (event.url == expectedURI) {
-        count++;
+  let historyObserver = {
+    count: 0,
+    expectedURI: null,
+    onVisits(aVisits) {
+      for (let {uri} of aVisits) {
+        info("Received onVisits: " + uri.spec);
+        if (uri.equals(this.expectedURI)) {
+          this.count++;
+        }
       }
-    }
-  }
+    },
+    onBeginUpdateBatch() {},
+    onEndUpdateBatch() {},
+    onTitleChanged() {},
+    onDeleteURI() {},
+    onClearHistory() {},
+    onPageChanged() {},
+    onDeleteVisits() {},
+    QueryInterface: ChromeUtils.generateQI([Ci.nsINavHistoryObserver])
+  };
 
   async function promiseLoadedThreeTimes(uri) {
-    count = 0;
-    expectedURI = uri;
+    historyObserver.count = 0;
+    historyObserver.expectedURI = Services.io.newURI(uri);
     let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
-    PlacesObservers.addListener(["page-visited"], onVisitsListener);
+    PlacesUtils.history.addObserver(historyObserver);
     gBrowser.loadURI(uri);
     await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
     await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
     await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri);
-    PlacesObservers.removeListener(["page-visited"], onVisitsListener);
+    PlacesUtils.history.removeObserver(historyObserver);
     BrowserTestUtils.removeTab(tab);
   }
 
   for (let uri of URIS) {
     await promiseLoadedThreeTimes(uri);
-    is(count, 1,
-      "'page-visited' has been received right number of times for " + uri);
+    is(historyObserver.count, 1,
+      "onVisit has been received right number of times for " + uri);
   }
 });
--- a/toolkit/components/places/tests/browser/browser_bug646422.js
+++ b/toolkit/components/places/tests/browser/browser_bug646422.js
@@ -18,16 +18,17 @@ add_task(async function() {
         if (/new_page$/.test(uri.spec)) {
           resolve(title);
           PlacesUtils.history.removeObserver(observer);
         }
       },
 
       onBeginUpdateBatch() { },
       onEndUpdateBatch() { },
+      onVisits() { },
       onDeleteURI() { },
       onClearHistory() { },
       onPageChanged() { },
       onDeleteVisits() { },
       QueryInterface: ChromeUtils.generateQI([Ci.nsINavHistoryObserver])
     };
 
     PlacesUtils.history.addObserver(observer);
--- a/toolkit/components/places/tests/browser/browser_double_redirect.js
+++ b/toolkit/components/places/tests/browser/browser_double_redirect.js
@@ -6,28 +6,29 @@ add_task(async function() {
   await PlacesUtils.history.clear();
 
   const BASE_URL = "http://example.com/tests/toolkit/components/places/tests/browser/";
   const TEST_URI = NetUtil.newURI(BASE_URL + "begin.html");
   const FIRST_REDIRECTING_URI = NetUtil.newURI(BASE_URL + "redirect_twice.sjs");
   const FINAL_URI = NetUtil.newURI(BASE_URL + "final.html");
 
   let promiseVisits = new Promise(resolve => {
-    let observer = {
+    PlacesUtils.history.addObserver({
+      __proto__: NavHistoryObserver.prototype,
       _notified: [],
       onVisit(uri, id, time, referrerId, transition) {
-        info("Received onVisit: " + uri);
+        info("Received onVisit: " + uri.spec);
         this._notified.push(uri);
 
-        if (uri != FINAL_URI.spec) {
+        if (!uri.equals(FINAL_URI)) {
           return;
         }
 
         is(this._notified.length, 4);
-        PlacesObservers.removeListener(["page-visited"], this.handleEvents);
+        PlacesUtils.history.removeObserver(this);
 
         (async function() {
           // Get all pages visited from the original typed one
           let db = await PlacesUtils.promiseDBConnection();
           let rows = await db.execute(
             `SELECT url FROM moz_historyvisits
              JOIN moz_places h ON h.id = place_id
              WHERE from_visit IN
@@ -39,31 +40,28 @@ add_task(async function() {
           is(rows.length, 1, "Found right number of visits");
           let visitedUrl = rows[0].getResultByName("url");
           // Check that redirect from_visit is not from the original typed one
           is(visitedUrl, FIRST_REDIRECTING_URI.spec, "Check referrer for " + visitedUrl);
 
           resolve();
         })();
       },
-      handleEvents(events) {
-        is(events.length, 1, "Right number of visits notified");
-        is(events[0].type, "page-visited");
+      onVisits(visits) {
+        is(visits.length, 1, "Right number of visits notified");
         let {
-          url,
+          uri,
           visitId,
-          visitTime,
-          referringVisitId,
+          time,
+          referrerId,
           transitionType,
-        } = events[0];
-        this.onVisit(url, visitId, visitTime, referringVisitId, transitionType);
+        } = visits[0];
+        this.onVisit(uri, visitId, time, referrerId, transitionType);
       }
-    };
-    observer.handleEvents = observer.handleEvents.bind(observer);
-    PlacesObservers.addListener(["page-visited"], observer.handleEvents);
+    });
   });
 
   PlacesUtils.history.markPageAsTyped(TEST_URI);
   await BrowserTestUtils.withNewTab({
     gBrowser,
     url: TEST_URI.spec,
   }, async function(browser) {
     // Load begin page, click link on page to record visits.
--- a/toolkit/components/places/tests/browser/browser_multi_redirect_frecency.js
+++ b/toolkit/components/places/tests/browser/browser_multi_redirect_frecency.js
@@ -27,25 +27,25 @@ async function check_uri(uri, frecency, 
   is(await PlacesTestUtils.fieldInDB(uri, "frecency"), frecency,
     "Frecency of the page is the expected one");
   is(await PlacesTestUtils.fieldInDB(uri, "hidden"), hidden,
     "Hidden value of the page is the expected one");
 }
 
 async function waitVisitedNotifications() {
   let redirectNotified = false;
-  await PlacesTestUtils.waitForNotification("page-visited", visits => {
+  await PlacesTestUtils.waitForNotification("onVisits", visits => {
     is(visits.length, 1, "Was notified for the right number of visits.");
-    let {url} = visits[0];
-    info("Received 'page-visited': " + url);
-    if (url == REDIRECT_URI.spec) {
+    let {uri} = visits[0];
+    info("Received onVisits: " + uri.spec);
+    if (uri.equals(REDIRECT_URI)) {
       redirectNotified = true;
     }
-    return url == TARGET_URI.spec;
-  }, "places");
+    return uri.equals(TARGET_URI);
+  }, "history");
   return redirectNotified;
 }
 
 let firstRedirectBonus = 0;
 let nextRedirectBonus = 0;
 let targetBonus = 0;
 
 add_task(async function test_multiple_redirect() {
--- a/toolkit/components/places/tests/browser/browser_notfound.js
+++ b/toolkit/components/places/tests/browser/browser_notfound.js
@@ -5,34 +5,52 @@
 add_task(async function() {
   const TEST_URL = "http://mochi.test:8888/notFoundPage.html";
 
   // Used to verify errors are not marked as typed.
   PlacesUtils.history.markPageAsTyped(NetUtil.newURI(TEST_URL));
 
   // Create and add history observer.
   let visitedPromise = new Promise(resolve => {
-    function listener(aEvents) {
-      is(aEvents.length, 1, "Right number of visits notified");
-      is(aEvents[0].type, "page-visited");
-      let uri = NetUtil.newURI(aEvents[0].url);
-      PlacesObservers.removeListener(["page-visited"], listener);
-      info("Received 'page-visited': " + uri.spec);
-      fieldForUrl(uri, "frecency", function(aFrecency) {
-        is(aFrecency, 0, "Frecency should be 0");
-        fieldForUrl(uri, "hidden", function(aHidden) {
-          is(aHidden, 0, "Page should not be hidden");
-          fieldForUrl(uri, "typed", function(aTyped) {
-            is(aTyped, 0, "page should not be marked as typed");
-            resolve();
+    let historyObserver = {
+      onVisit(aURI, aVisitID, aTime, aReferringID, aTransitionType) {
+        PlacesUtils.history.removeObserver(historyObserver);
+        info("Received onVisit: " + aURI.spec);
+        fieldForUrl(aURI, "frecency", function(aFrecency) {
+          is(aFrecency, 0, "Frecency should be 0");
+          fieldForUrl(aURI, "hidden", function(aHidden) {
+            is(aHidden, 0, "Page should not be hidden");
+            fieldForUrl(aURI, "typed", function(aTyped) {
+              is(aTyped, 0, "page should not be marked as typed");
+              resolve();
+            });
           });
         });
-      });
-    }
-    PlacesObservers.addListener(["page-visited"], listener);
+      },
+      onVisits(aVisits) {
+        is(aVisits.length, 1, "Right number of visits notified");
+        let {
+          uri,
+          visitId,
+          time,
+          referrerId,
+          transitionType,
+        } = aVisits[0];
+        this.onVisit(uri, visitId, time, referrerId, transitionType);
+      },
+      onBeginUpdateBatch() {},
+      onEndUpdateBatch() {},
+      onTitleChanged() {},
+      onDeleteURI() {},
+      onClearHistory() {},
+      onPageChanged() {},
+      onDeleteVisits() {},
+      QueryInterface: ChromeUtils.generateQI([Ci.nsINavHistoryObserver])
+    };
+    PlacesUtils.history.addObserver(historyObserver);
   });
 
   let newTabPromise = BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
   await Promise.all([visitedPromise, newTabPromise]);
 
   await PlacesUtils.history.clear();
   gBrowser.removeCurrentTab();
 });
--- a/toolkit/components/places/tests/browser/browser_onvisit_title_null_for_navigation.js
+++ b/toolkit/components/places/tests/browser/browser_onvisit_title_null_for_navigation.js
@@ -1,34 +1,32 @@
 const TEST_PATH = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "http://example.com");
 
 add_task(async function checkTitleNotificationForNavigation() {
   const EXPECTED_URL = Services.io.newURI(TEST_PATH + "empty_page.html");
   let promiseTitleChanged = new Promise(resolve => {
-    function onVisits(aEvents) {
-      Assert.equal(aEvents.length, 1, "Right number of visits notified");
-      Assert.equal(aEvents[0].type, "page-visited");
-      let {
-        url,
-        lastKnownTitle,
-      } = aEvents[0];
-      info("'page-visited': " + url);
-      if (url == EXPECTED_URL.spec) {
-        Assert.equal(lastKnownTitle, null, "Should not have a title");
-      }
-      PlacesObservers.removeListener(["page-visited"], onVisits);
-    }
     let obs = {
+      onVisits(aVisits) {
+        Assert.equal(aVisits.length, 1, "Right number of visits notified");
+        let {
+          uri,
+          lastKnownTitle,
+        } = aVisits[0];
+        info("onVisits: " + uri.spec);
+        if (uri.equals(EXPECTED_URL)) {
+          Assert.equal(lastKnownTitle, null, "Should not have a title");
+        }
+      },
+
       onTitleChanged(aURI, aTitle, aGuid) {
         if (aURI.equals(EXPECTED_URL)) {
           is(aTitle, "I am an empty page", "Should have correct title in titlechanged notification");
           PlacesUtils.history.removeObserver(obs);
           resolve();
         }
       },
     };
     PlacesUtils.history.addObserver(obs);
-    PlacesObservers.addListener(["page-visited"], onVisits);
   });
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, EXPECTED_URL.spec);
   await promiseTitleChanged;
   BrowserTestUtils.removeTab(tab);
 });
--- a/toolkit/components/places/tests/browser/browser_redirect.js
+++ b/toolkit/components/places/tests/browser/browser_redirect.js
@@ -34,25 +34,25 @@ async function check_uri(uri, frecency, 
 add_task(async function redirect_check_new_typed_visit() {
   // Used to verify the redirect bonus overrides the typed bonus.
   PlacesUtils.history.markPageAsTyped(REDIRECT_URI);
 
   redirectSourceFrecency += REDIRECT_SOURCE_VISIT_BONUS;
   redirectTargetFrecency += TYPED_VISIT_BONUS;
   let redirectNotified = false;
 
-  let visitedPromise = PlacesTestUtils.waitForNotification("page-visited", visits => {
+  let visitedPromise = PlacesTestUtils.waitForNotification("onVisits", visits => {
     is(visits.length, 1, "Was notified for the right number of visits.");
-    let {url} = visits[0];
-    info("Received 'page-visited': " + url);
-    if (url == REDIRECT_URI.spec) {
+    let {uri} = visits[0];
+    info("Received onVisits for: " + uri.spec);
+    if (uri.equals(REDIRECT_URI)) {
       redirectNotified = true;
     }
-    return url == TARGET_URI.spec;
-  }, "places");
+    return uri.equals(TARGET_URI);
+  }, "history");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, REDIRECT_URI.spec);
   info("Waiting for onVisits");
   await visitedPromise;
   ok(redirectNotified, "The redirect should have been notified");
 
   await check_uri(REDIRECT_URI, redirectSourceFrecency, 1);
   await check_uri(TARGET_URI, redirectTargetFrecency, 0);
@@ -63,25 +63,25 @@ add_task(async function redirect_check_n
 add_task(async function redirect_check_second_typed_visit() {
   // A second visit with a typed url.
   PlacesUtils.history.markPageAsTyped(REDIRECT_URI);
 
   redirectSourceFrecency += REDIRECT_SOURCE_VISIT_BONUS;
   redirectTargetFrecency += TYPED_VISIT_BONUS;
   let redirectNotified = false;
 
-  let visitedPromise = PlacesTestUtils.waitForNotification("page-visited", visits => {
+  let visitedPromise = PlacesTestUtils.waitForNotification("onVisits", visits => {
     is(visits.length, 1, "Was notified for the right number of visits.");
-    let {url} = visits[0];
-    info("Received 'page-visited': " + url);
-    if (url == REDIRECT_URI.spec) {
+    let {uri} = visits[0];
+    info("Received onVisits: " + uri.spec);
+    if (uri.equals(REDIRECT_URI)) {
       redirectNotified = true;
     }
-    return url == TARGET_URI.spec;
-  }, "places");
+    return uri.equals(TARGET_URI);
+  }, "history");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, REDIRECT_URI.spec);
   info("Waiting for onVisits");
   await visitedPromise;
   ok(redirectNotified, "The redirect should have been notified");
 
   await check_uri(REDIRECT_URI, redirectSourceFrecency, 1);
   await check_uri(TARGET_URI, redirectTargetFrecency, 0);
@@ -90,25 +90,25 @@ add_task(async function redirect_check_s
 });
 
 add_task(async function redirect_check_subsequent_link_visit() {
   // Another visit, but this time as a visited url.
   redirectSourceFrecency += REDIRECT_SOURCE_VISIT_BONUS;
   redirectTargetFrecency += LINK_VISIT_BONUS;
   let redirectNotified = false;
 
-  let visitedPromise = PlacesTestUtils.waitForNotification("page-visited", visits => {
+  let visitedPromise = PlacesTestUtils.waitForNotification("onVisits", visits => {
     is(visits.length, 1, "Was notified for the right number of visits.");
-    let {url} = visits[0];
-    info("Received 'page-visited': " + url);
-    if (url == REDIRECT_URI.spec) {
+    let {uri} = visits[0];
+    info("Received onVisits: " + uri.spec);
+    if (uri.equals(REDIRECT_URI)) {
       redirectNotified = true;
     }
-    return url == TARGET_URI.spec;
-  }, "places");
+    return uri.equals(TARGET_URI);
+  }, "history");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, REDIRECT_URI.spec);
   info("Waiting for onVisits");
   await visitedPromise;
   ok(redirectNotified, "The redirect should have been notified");
 
   await check_uri(REDIRECT_URI, redirectSourceFrecency, 1);
   await check_uri(TARGET_URI, redirectTargetFrecency, 0);
--- a/toolkit/components/places/tests/browser/browser_settitle.js
+++ b/toolkit/components/places/tests/browser/browser_settitle.js
@@ -20,16 +20,17 @@ add_task(async function() {
   // notifications.
 
   // Create and add history observer.
   let titleChangedPromise = new Promise(resolve => {
     var historyObserver = {
       data: [],
       onBeginUpdateBatch() {},
       onEndUpdateBatch() {},
+      onVisits() {},
       onTitleChanged(aURI, aPageTitle, aGUID) {
         this.data.push({ uri: aURI, title: aPageTitle, guid: aGUID });
 
         // We only expect one title change.
         //
         // Although we are loading two different pages, the first page does not
         // have a title.  Since the title starts out as empty and then is set
         // to empty, there is no title change notification.
--- a/toolkit/components/places/tests/browser/browser_visited_notfound.js
+++ b/toolkit/components/places/tests/browser/browser_visited_notfound.js
@@ -15,24 +15,26 @@ add_task(async function test() {
   await PlacesTestUtils.addVisits({ uri: TEST_URL });
   let frecency = await PlacesTestUtils.fieldInDB(TEST_URL, "frecency");
   is(frecency, 100, "Check initial frecency");
 
   // Used to verify errors are not marked as typed.
   PlacesUtils.history.markPageAsTyped(NetUtil.newURI(TEST_URL));
 
   let promiseVisit = new Promise(resolve => {
-    function onVisits(events) {
-      PlacesObservers.removeListener(["page-visited"], onVisits);
-      is(events.length, 1, "Right number of visits");
-      is(events[0].type, "page-visited");
-      is(events[0].url, TEST_URL, "Check visited url");
-      resolve();
-    }
-    PlacesObservers.addListener(["page-visited"], onVisits);
+    let historyObserver = {
+      __proto__: NavHistoryObserver.prototype,
+      onVisits(visits) {
+        PlacesUtils.history.removeObserver(historyObserver);
+        is(visits.length, 1, "Right number of visits");
+        is(visits[0].uri.spec, TEST_URL, "Check visited url");
+        resolve();
+      }
+    };
+    PlacesUtils.history.addObserver(historyObserver);
   });
   gBrowser.selectedBrowser.loadURI(TEST_URL);
   await promiseVisit;
 
   is(await PlacesTestUtils.fieldInDB(TEST_URL, "frecency"), frecency, "Frecency should be unchanged");
   is(await PlacesTestUtils.fieldInDB(TEST_URL, "hidden"), 0, "Page should not be hidden");
   is(await PlacesTestUtils.fieldInDB(TEST_URL, "typed"), 0, "page should not be marked as typed");
 });
--- a/toolkit/components/places/tests/browser/head.js
+++ b/toolkit/components/places/tests/browser/head.js
@@ -72,16 +72,17 @@ function promiseFieldForUrl(aURI, aField
  * Generic nsINavHistoryObserver that doesn't implement anything, but provides
  * dummy methods to prevent errors about an object not having a certain method.
  */
 function NavHistoryObserver() {}
 
 NavHistoryObserver.prototype = {
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
+  onVisits() {},
   onTitleChanged() {},
   onDeleteURI() {},
   onClearHistory() {},
   onPageChanged() {},
   onDeleteVisits() {},
   QueryInterface: ChromeUtils.generateQI([
     Ci.nsINavHistoryObserver,
   ])
--- a/toolkit/components/places/tests/expiration/test_notifications_onDeleteURI.js
+++ b/toolkit/components/places/tests/expiration/test_notifications_onDeleteURI.js
@@ -72,16 +72,17 @@ add_task(async function test_notificatio
       currentTest.bookmarks.push(page);
     }
 
     // Observe history.
     let historyObserver = {
       onBeginUpdateBatch: function PEX_onBeginUpdateBatch() {},
       onEndUpdateBatch: function PEX_onEndUpdateBatch() {},
       onClearHistory() {},
+      onVisits() {},
       onTitleChanged() {},
       onDeleteURI(aURI, aGUID, aReason) {
         currentTest.receivedNotifications++;
         // Check this uri was not bookmarked.
         Assert.equal(currentTest.bookmarks.indexOf(aURI.spec), -1);
         do_check_valid_places_guid(aGUID);
         Assert.equal(aReason, Ci.nsINavHistoryObserver.REASON_EXPIRED);
       },
--- a/toolkit/components/places/tests/expiration/test_notifications_onDeleteVisits.js
+++ b/toolkit/components/places/tests/expiration/test_notifications_onDeleteVisits.js
@@ -97,16 +97,17 @@ add_task(async function test_notificatio
       currentTest.bookmarks.push(page);
     }
 
     // Observe history.
     let historyObserver = {
       onBeginUpdateBatch: function PEX_onBeginUpdateBatch() {},
       onEndUpdateBatch: function PEX_onEndUpdateBatch() {},
       onClearHistory() {},
+      onVisits() {},
       onTitleChanged() {},
       onDeleteURI(aURI, aGUID, aReason) {
         // Check this uri was not bookmarked.
         Assert.equal(currentTest.bookmarks.indexOf(aURI.spec), -1);
         do_check_valid_places_guid(aGUID);
         Assert.equal(aReason, Ci.nsINavHistoryObserver.REASON_EXPIRED);
       },
       onPageChanged() {},
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -729,16 +729,17 @@ NavBookmarkObserver.prototype = {
  * Generic nsINavHistoryObserver that doesn't implement anything, but provides
  * dummy methods to prevent errors about an object not having a certain method.
  */
 function NavHistoryObserver() {}
 
 NavHistoryObserver.prototype = {
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
+  onVisits() {},
   onTitleChanged() {},
   onDeleteURI() {},
   onClearHistory() {},
   onPageChanged() {},
   onDeleteVisits() {},
   QueryInterface: ChromeUtils.generateQI([
     Ci.nsINavHistoryObserver,
   ])
--- a/toolkit/components/places/tests/history/test_async_history_api.js
+++ b/toolkit/components/places/tests/history/test_async_history_api.js
@@ -75,57 +75,58 @@ TitleChangedObserver.prototype = {
     Assert.equal(aTitle, this.expectedTitle);
     do_check_guid_for_uri(aURI, aGUID);
     this.callback();
   },
 };
 
 /**
  * Listens for a visit notification, and calls aCallback when it gets it.
+ *
+ * @param aURI
+ *        The URI of the page we expect a notification for.
+ * @param aCallback
+ *        The method to call when we have gotten the proper notification about
+ *        being visited.
  */
-class VisitObserver {
-  constructor(aURI,
-              aGUID,
-              aCallback) {
-    this.uri = aURI;
-    this.guid = aGUID;
-    this.callback = aCallback;
-    this.handlePlacesEvent = this.handlePlacesEvent.bind(this);
-    PlacesObservers.addListener(["page-visited"], this.handlePlacesEvent);
-  }
-
-  handlePlacesEvent(aEvents) {
-    info("'page-visited'!!!");
-    Assert.equal(aEvents.length, 1, "Right number of visits notified");
-    Assert.equal(aEvents[0].type, "page-visited");
+function VisitObserver(aURI,
+                       aGUID,
+                       aCallback) {
+  this.uri = aURI;
+  this.guid = aGUID;
+  this.callback = aCallback;
+}
+VisitObserver.prototype = {
+  __proto__: NavHistoryObserver.prototype,
+  onVisits(aVisits) {
+    info("onVisits()!!!");
+    Assert.equal(aVisits.length, 1, "Right number of visits notified");
     let {
-      url,
+      uri,
       visitId,
-      visitTime,
-      referringVisitId,
+      time,
+      referrerId,
       transitionType,
-      pageGuid,
+      guid,
       hidden,
       visitCount,
-      typedCount,
+      typed,
       lastKnownTitle,
-    } = aEvents[0];
+    } = aVisits[0];
     let args = [
-      visitId, visitTime, referringVisitId, transitionType, pageGuid,
-      hidden, visitCount, typedCount, lastKnownTitle,
+      visitId, time, referrerId, transitionType, guid,
+      hidden, visitCount, typed, lastKnownTitle,
     ];
-    info("'page-visited' (" + url + args.join(", ") + ")");
-    if (this.uri.spec != url || this.guid != pageGuid) {
+    info("onVisit(" + uri.spec + args.join(", ") + ")");
+    if (!this.uri.equals(uri) || this.guid != guid) {
       return;
     }
-    this.callback(visitTime * 1000, transitionType, lastKnownTitle);
-
-    PlacesObservers.removeListener(["page-visited"], this.handlePlacesEvent);
-  }
-}
+    this.callback(time, transitionType, lastKnownTitle);
+  },
+};
 
 /**
  * Tests that a title was set properly in the database.
  *
  * @param aURI
  *        The uri to check.
  * @param aTitle
  *        The expected title in the database.
@@ -978,25 +979,25 @@ add_task(async function test_title_chang
         PlacesUtils.history.removeObserver(titleChangeObserver);
         resolve();
       }
     });
     PlacesUtils.history.addObserver(titleChangeObserver);
   });
 
   let visitPromise = new Promise(resolve => {
-    function onVisits(events) {
-      Assert.equal(events.length, 1, "Should only get notified for one visit.");
-      Assert.equal(events[0].type, "page-visited");
-      let {url} = events[0];
-      Assert.equal(url, place.uri.spec, "Should get notified for visiting the new URI.");
-      PlacesObservers.removeListener(["page-visited"], onVisits);
-      resolve();
-    }
-    PlacesObservers.addListener(["page-visited"], onVisits);
+    PlacesUtils.history.addObserver({
+      onVisits(visits) {
+        Assert.equal(visits.length, 1, "Should only get notified for one visit.");
+        let {uri} = visits[0];
+        Assert.equal(uri.spec, place.uri.spec, "Should get notified for visiting the new URI.");
+        PlacesUtils.history.removeObserver(this);
+        resolve();
+      }
+    });
   });
   asyncHistory.updatePlaces(place);
   await visitPromise;
 
   // The third case to test is to make sure we get a notification when
   // we change an existing place.
   expectedNotification = true;
   titleChangeObserver.expectedTitle = place.title = "title 2";
@@ -1022,25 +1023,27 @@ add_task(async function test_visit_notif
   function promiseVisitObserver(aPlace) {
     return new Promise((resolve, reject) => {
       let callbackCount = 0;
       let finisher = function() {
         if (++callbackCount == 2) {
           resolve();
         }
       };
-      new VisitObserver(place.uri, place.guid,
+      let visitObserver = new VisitObserver(place.uri, place.guid,
                                             function(aVisitDate,
                                                      aTransitionType) {
         let visit = place.visits[0];
         Assert.equal(visit.visitDate, aVisitDate);
         Assert.equal(visit.transitionType, aTransitionType);
 
+        PlacesUtils.history.removeObserver(visitObserver);
         finisher();
       });
+      PlacesUtils.history.addObserver(visitObserver);
       let observer = function(aSubject, aTopic, aData) {
         info("observe(" + aSubject + ", " + aTopic + ", " + aData + ")");
         Assert.ok(aSubject instanceof Ci.nsIURI);
         Assert.ok(aSubject.equals(place.uri));
 
         Services.obs.removeObserver(observer, URI_VISIT_SAVED);
         finisher();
       };
@@ -1261,63 +1264,69 @@ add_task(async function test_title_on_in
     uri: NetUtil.newURI(TEST_DOMAIN + "test_visit_title"),
     title: "My title",
     visits: [
       new VisitInfo(),
     ],
     guid: "mnopqrstuvwx",
   };
   let visitPromise = new Promise(resolve => {
-    new VisitObserver(place.uri, place.guid,
-                      function(aVisitDate,
-                               aTransitionType,
-                               aLastKnownTitle) {
+    let visitObserver = new VisitObserver(place.uri, place.guid,
+                                          function(aVisitDate,
+                                                   aTransitionType,
+                                                   aLastKnownTitle) {
       Assert.equal(place.title, aLastKnownTitle);
 
+      PlacesUtils.history.removeObserver(visitObserver);
       resolve();
     });
+    PlacesUtils.history.addObserver(visitObserver);
   });
   await promiseUpdatePlaces(place);
   await visitPromise;
 
   // Now check an empty title doesn't get reported as null
   place = {
     uri: NetUtil.newURI(TEST_DOMAIN + "test_visit_title"),
     title: "",
     visits: [
       new VisitInfo(),
     ],
     guid: "fghijklmnopq",
   };
   visitPromise = new Promise(resolve => {
-    new VisitObserver(place.uri, place.guid,
-                      function(aVisitDate,
-                               aTransitionType,
-                               aLastKnownTitle) {
+    let visitObserver = new VisitObserver(place.uri, place.guid,
+                                          function(aVisitDate,
+                                                   aTransitionType,
+                                                   aLastKnownTitle) {
       Assert.equal(place.title, aLastKnownTitle);
 
+      PlacesUtils.history.removeObserver(visitObserver);
       resolve();
     });
+    PlacesUtils.history.addObserver(visitObserver);
   });
   await promiseUpdatePlaces(place);
   await visitPromise;
 
   // and that a missing title correctly gets reported as null.
   place = {
     uri: NetUtil.newURI(TEST_DOMAIN + "test_visit_title"),
     visits: [
       new VisitInfo(),
     ],
     guid: "fghijklmnopq",
   };
   visitPromise = new Promise(resolve => {
-    new VisitObserver(place.uri, place.guid,
-                      function(aVisitDate,
-                               aTransitionType,
-                               aLastKnownTitle) {
+    let visitObserver = new VisitObserver(place.uri, place.guid,
+                                          function(aVisitDate,
+                                                   aTransitionType,
+                                                   aLastKnownTitle) {
       Assert.equal(null, aLastKnownTitle);
 
+      PlacesUtils.history.removeObserver(visitObserver);
       resolve();
     });
+    PlacesUtils.history.addObserver(visitObserver);
   });
   await promiseUpdatePlaces(place);
   await visitPromise;
 });
--- a/toolkit/components/places/tests/history/test_remove.js
+++ b/toolkit/components/places/tests/history/test_remove.js
@@ -39,16 +39,19 @@ add_task(async function test_remove_sing
     }
 
     let shouldRemove = !options.addBookmark;
     let observer;
     let promiseObserved = new Promise((resolve, reject) => {
       observer = {
         onBeginUpdateBatch() {},
         onEndUpdateBatch() {},
+        onVisits(aVisits) {
+          reject(new Error("Unexpected call to onVisits " + aVisits.length));
+        },
         onTitleChanged(aUri) {
           reject(new Error("Unexpected call to onTitleChanged " + aUri.spec));
         },
         onClearHistory() {
           reject("Unexpected call to onClearHistory");
         },
         onPageChanged(aUri) {
           reject(new Error("Unexpected call to onPageChanged " + aUri.spec));
--- a/toolkit/components/places/tests/history/test_removeByFilter.js
+++ b/toolkit/components/places/tests/history/test_removeByFilter.js
@@ -287,16 +287,19 @@ function getObserverPromise(bookmarkedUr
   if (!bookmarkedUri) {
     return { observer: null, promiseObserved: Promise.resolve() };
   }
   let observer;
   let promiseObserved = new Promise((resolve, reject) => {
     observer = {
       onBeginUpdateBatch() {},
       onEndUpdateBatch() {},
+      onVisits() {
+        reject(new Error("Unexpected call to onVisits"));
+      },
       onTitleChanged(aUri) {
         reject(new Error("Unexpected call to onTitleChanged"));
       },
       onClearHistory() {
         reject(new Error("Unexpected call to onClearHistory"));
       },
       onPageChanged(aUri) {
         reject(new Error("Unexpected call to onPageChanged"));
--- a/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
+++ b/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
@@ -129,16 +129,19 @@ add_task(async function test_removeVisit
         uriDeletePromises.set(removedItems[i].uri.spec, PromiseUtils.defer());
       }
     }
 
     let observer = {
       deferred: PromiseUtils.defer(),
       onBeginUpdateBatch() {},
       onEndUpdateBatch() {},
+      onVisits(aVisits) {
+        this.deferred.reject(new Error("Unexpected call to onVisits " + aVisits.length));
+      },
       onTitleChanged(uri) {
         this.deferred.reject(new Error("Unexpected call to onTitleChanged " + uri.spec));
       },
       onClearHistory() {
         this.deferred.reject("Unexpected call to onClearHistory");
       },
       onPageChanged(uri) {
         this.deferred.reject(new Error("Unexpected call to onPageChanged " + uri.spec));
--- a/toolkit/components/places/tests/unit/test_454977.js
+++ b/toolkit/components/places/tests/unit/test_454977.js
@@ -6,21 +6,21 @@
 
 // Cache actual visit_count value, filled by add_visit, used by check_results
 var visit_count = 0;
 
 // Returns the Place ID corresponding to an added visit.
 async function task_add_visit(aURI, aVisitType) {
   // Wait for a visits notification and get the visitId.
   let visitId;
-  let visitsPromise = PlacesTestUtils.waitForNotification("page-visited", visits => {
+  let visitsPromise = PlacesTestUtils.waitForNotification("onVisits", visits => {
     visitId = visits[0].visitId;
-    let {url} = visits[0];
-    return url == aURI.spec;
-  }, "places");
+    let {uri} = visits[0];
+    return uri.equals(aURI);
+  }, "history");
 
   // Add visits.
   await PlacesTestUtils.addVisits([{
     uri: aURI,
     transition: aVisitType
   }]);
 
   if (aVisitType != TRANSITION_EMBED) {
--- a/toolkit/components/places/tests/unit/test_download_history.js
+++ b/toolkit/components/places/tests/unit/test_download_history.js
@@ -15,38 +15,39 @@ const PRIVATE_URI = NetUtil.newURI("http
 
 /**
  * Waits for the first visit notification to be received.
  *
  * @param aCallback
  *        Callback function to be called with the same arguments of onVisit.
  */
 function waitForOnVisit(aCallback) {
-  function listener(aEvents) {
-    Assert.equal(aEvents.length, 1, "Right number of visits notified");
-    Assert.equal(aEvents[0].type, "page-visited");
-    let {
-      url,
-      visitId,
-      visitTime,
-      referringVisitId,
-      transitionType,
-      pageGuid,
-      hidden,
-      visitCount,
-      typedCount,
-      lastKnownTitle,
-    } = aEvents[0];
-    PlacesObservers.removeListener(["page-visited"], listener);
-    let uriArg = NetUtil.newURI(url);
-    aCallback(uriArg, visitId, visitTime, 0, referringVisitId,
-              transitionType, pageGuid, hidden, visitCount,
-              typedCount, lastKnownTitle);
-  }
-  PlacesObservers.addListener(["page-visited"], listener);
+  let historyObserver = {
+    __proto__: NavHistoryObserver.prototype,
+    onVisits: function HO_onVisit(aVisits) {
+      Assert.equal(aVisits.length, 1, "Right number of visits notified");
+      let {
+        uri,
+        visitId,
+        time,
+        referrerId,
+        transitionType,
+        guid,
+        hidden,
+        visitCount,
+        typed,
+        lastKnownTitle,
+      } = aVisits[0];
+      PlacesUtils.history.removeObserver(this);
+      aCallback(uri, visitId, time, 0, referrerId,
+                transitionType, guid, hidden, visitCount,
+                typed, lastKnownTitle);
+    }
+  };
+  PlacesUtils.history.addObserver(historyObserver);
 }
 
 /**
  * Waits for the first onDeleteURI notification to be received.
  *
  * @param aCallback
  *        Callback function to be called with the same arguments of onDeleteURI.
  */
@@ -151,45 +152,44 @@ add_task(async function test_dh_addBookm
 
     gDownloadHistory.addDownload(DOWNLOAD_URI, null, Date.now() * 1000);
   });
 });
 
 add_task(async function test_dh_addDownload_referrer() {
   // Wait for visits notification and get the visit id.
   let visitId;
-  let referrerPromise = PlacesTestUtils.waitForNotification("page-visited", visits => {
+  let referrerPromise = PlacesTestUtils.waitForNotification("onVisits", visits => {
     visitId = visits[0].visitId;
-    let {url} = visits[0];
-    return url == REFERRER_URI.spec;
-  }, "places");
+    let {uri} = visits[0];
+    return uri.equals(REFERRER_URI);
+  }, "history");
 
   await PlacesTestUtils.addVisits([{
     uri: REFERRER_URI,
     transition: Ci.nsINavHistoryService.TRANSITION_TYPED
   }]);
   await referrerPromise;
 
   // Verify results for referrer uri.
   Assert.ok(!!PlacesTestUtils.isPageInDB(REFERRER_URI));
   Assert.equal(visitId, 1);
 
   // Wait for visits notification and get the referrer Id.
   let referrerId;
-  let downloadPromise = PlacesTestUtils.waitForNotification("page-visited", visits => {
-    referrerId = visits[0].referringVisitId;
-    let {url} = visits[0];
-    return url == DOWNLOAD_URI.spec;
-  }, "places");
+  let downloadPromise = PlacesTestUtils.waitForNotification("onVisits", visits => {
+    referrerId = visits[0].referrerId;
+    let {uri} = visits[0];
+    return uri.equals(DOWNLOAD_URI);
+  }, "history");
 
   gDownloadHistory.addDownload(DOWNLOAD_URI, REFERRER_URI, Date.now() * 1000);
   await downloadPromise;
 
   // Verify results for download uri.
-  // ensure that we receive the 'page-visited' notification before we call addDownload.
   Assert.ok(!!PlacesTestUtils.isPageInDB(DOWNLOAD_URI));
   Assert.equal(visitId, referrerId);
 
   await PlacesUtils.history.clear();
 });
 
 add_test(function test_dh_addDownload_disabledHistory() {
   waitForOnVisit(function DHAD_onVisit(aURI) {
@@ -255,16 +255,17 @@ add_test(function test_dh_details() {
     onItemAnnotationSet() {},
     onPageAnnotationRemoved() {},
     onItemAnnotationRemoved() {}
   };
 
   let historyObserver = {
     onBeginUpdateBatch() {},
     onEndUpdateBatch() {},
+    onVisits() {},
     onTitleChanged: function HO_onTitleChanged(aURI, aPageTitle) {
       if (aURI.equals(SOURCE_URI)) {
         titleSet = true;
         Assert.equal(aPageTitle, DEST_FILE_NAME);
         checkFinished();
       }
     },
     onDeleteURI() {},
--- a/toolkit/components/places/tests/unit/test_history_observer.js
+++ b/toolkit/components/places/tests/unit/test_history_observer.js
@@ -5,16 +5,17 @@
  * Generic nsINavHistoryObserver that doesn't implement anything, but provides
  * dummy methods to prevent errors about an object not having a certain method.
  */
 function NavHistoryObserver() {
 }
 NavHistoryObserver.prototype = {
   onBeginUpdateBatch() { },
   onEndUpdateBatch() { },
+  onVisits() { },
   onTitleChanged() { },
   onDeleteURI() { },
   onClearHistory() { },
   onPageChanged() { },
   onDeleteVisits() { },
   QueryInterface: ChromeUtils.generateQI([Ci.nsINavHistoryObserver])
 };
 
@@ -31,124 +32,110 @@ function onNotify(callback) {
       callback.apply(this, arguments);
       resolve();
     };
     PlacesUtils.history.addObserver(obs);
   });
 }
 
 /**
- * Registers a one-time places observer for 'page-visited',
- * which resolves a promise on being called.
- */
-function promiseVisitAdded(callback) {
-  return new Promise(resolve => {
-    function listener(events) {
-      PlacesObservers.removeListener(["page-visited"], listener);
-      Assert.equal(events.length, 1, "Right number of visits notified");
-      Assert.equal(events[0].type, "page-visited");
-      callback(events[0]);
-      resolve();
-    }
-    PlacesObservers.addListener(["page-visited"], listener);
-  });
-}
-
-/**
  * Asynchronous task that adds a visit to the history database.
  */
 async function task_add_visit(uri, timestamp, transition) {
   uri = uri || NetUtil.newURI("http://firefox.com/");
   timestamp = timestamp || Date.now() * 1000;
   await PlacesTestUtils.addVisits({
     uri,
     transition: transition || TRANSITION_TYPED,
     visitDate: timestamp
   });
   return [uri, timestamp];
 }
 
-add_task(async function test_visitAdded() {
-  let promiseNotify = promiseVisitAdded(function(visit) {
+add_task(async function test_onVisits() {
+  let promiseNotify = onNotify(function onVisits(aVisits) {
+    Assert.equal(aVisits.length, 1, "Right number of visits notified");
+    let visit = aVisits[0];
+    Assert.ok(visit.uri.equals(testuri));
     Assert.ok(visit.visitId > 0);
-    Assert.equal(visit.url, testuri.spec);
-    Assert.equal(visit.visitTime, testtime / 1000);
-    Assert.equal(visit.referringVisitId, 0);
+    Assert.equal(visit.time, testtime);
+    Assert.equal(visit.referrerId, 0);
     Assert.equal(visit.transitionType, TRANSITION_TYPED);
-    let uri = NetUtil.newURI(visit.url);
-    do_check_guid_for_uri(uri, visit.pageGuid);
+    do_check_guid_for_uri(visit.uri, visit.guid);
     Assert.ok(!visit.hidden);
     Assert.equal(visit.visitCount, 1);
-    Assert.equal(visit.typedCount, 1);
+    Assert.equal(visit.typed, 1);
   });
   let testuri = NetUtil.newURI("http://firefox.com/");
   let testtime = Date.now() * 1000;
   await task_add_visit(testuri, testtime);
   await promiseNotify;
 });
 
-add_task(async function test_visitAdded() {
-  let promiseNotify = promiseVisitAdded(function(visit) {
+add_task(async function test_onVisits() {
+  let promiseNotify = onNotify(function onVisits(aVisits) {
+    Assert.equal(aVisits.length, 1, "Right number of visits notified");
+    let visit = aVisits[0];
+    Assert.ok(visit.uri.equals(testuri));
     Assert.ok(visit.visitId > 0);
-    Assert.equal(visit.url, testuri.spec);
-    Assert.equal(visit.visitTime, testtime / 1000);
-    Assert.equal(visit.referringVisitId, 0);
+    Assert.equal(visit.time, testtime);
+    Assert.equal(visit.referrerId, 0);
     Assert.equal(visit.transitionType, TRANSITION_FRAMED_LINK);
-    let uri = NetUtil.newURI(visit.url);
-    do_check_guid_for_uri(uri, visit.pageGuid);
+    do_check_guid_for_uri(visit.uri, visit.guid);
     Assert.ok(visit.hidden);
     Assert.equal(visit.visitCount, 1);
-    Assert.equal(visit.typedCount, 0);
+    Assert.equal(visit.typed, 0);
   });
   let testuri = NetUtil.newURI("http://hidden.firefox.com/");
   let testtime = Date.now() * 1000;
   await task_add_visit(testuri, testtime, TRANSITION_FRAMED_LINK);
   await promiseNotify;
 });
 
 add_task(async function test_multiple_onVisit() {
   let testuri = NetUtil.newURI("http://self.firefox.com/");
   let promiseNotifications = new Promise(resolve => {
-    function listener(aEvents) {
-      Assert.equal(aEvents.length, 3, "Right number of visits notified");
-      for (let i = 0; i < aEvents.length; i++) {
-        Assert.equal(aEvents[i].type, "page-visited");
-        let visit = aEvents[i];
-        Assert.equal(testuri.spec, visit.url);
-        Assert.ok(visit.visitId > 0);
-        Assert.ok(visit.visitTime > 0);
-        Assert.ok(!visit.hidden);
-        let uri = NetUtil.newURI(visit.url);
-        do_check_guid_for_uri(uri, visit.pageGuid);
-        switch (i) {
-          case 0:
-            Assert.equal(visit.referringVisitId, 0);
-            Assert.equal(visit.transitionType, TRANSITION_LINK);
-            Assert.equal(visit.visitCount, 1);
-            Assert.equal(visit.typedCount, 0);
-            break;
-          case 1:
-            Assert.ok(visit.referringVisitId > 0);
-            Assert.equal(visit.transitionType, TRANSITION_LINK);
-            Assert.equal(visit.visitCount, 2);
-            Assert.equal(visit.typedCount, 0);
-            break;
-          case 2:
-            Assert.equal(visit.referringVisitId, 0);
-            Assert.equal(visit.transitionType, TRANSITION_TYPED);
-            Assert.equal(visit.visitCount, 3);
-            Assert.equal(visit.typedCount, 1);
+    let observer = {
+      __proto__: NavHistoryObserver.prototype,
+      onVisits(aVisits) {
+        Assert.equal(aVisits.length, 3, "Right number of visits notified");
+        for (let i = 0; i < aVisits.length; i++) {
+          let visit = aVisits[i];
+          Assert.ok(testuri.equals(visit.uri));
+          Assert.ok(visit.visitId > 0);
+          Assert.ok(visit.time > 0);
+          Assert.ok(!visit.hidden);
+          do_check_guid_for_uri(visit.uri, visit.guid);
+          switch (i) {
+            case 0:
+              Assert.equal(visit.referrerId, 0);
+              Assert.equal(visit.transitionType, TRANSITION_LINK);
+              Assert.equal(visit.visitCount, 1);
+              Assert.equal(visit.typed, 0);
+              break;
+            case 1:
+              Assert.ok(visit.referrerId > 0);
+              Assert.equal(visit.transitionType, TRANSITION_LINK);
+              Assert.equal(visit.visitCount, 2);
+              Assert.equal(visit.typed, 0);
+              break;
+            case 2:
+              Assert.equal(visit.referrerId, 0);
+              Assert.equal(visit.transitionType, TRANSITION_TYPED);
+              Assert.equal(visit.visitCount, 3);
+              Assert.equal(visit.typed, 1);
 
-            PlacesObservers.removeListener(["page-visited"], listener);
-            resolve();
-            break;
+              PlacesUtils.history.removeObserver(observer, false);
+              resolve();
+              break;
+          }
         }
-      }
-    }
-    PlacesObservers.addListener(["page-visited"], listener);
+      },
+    };
+    PlacesUtils.history.addObserver(observer);
   });
   await PlacesTestUtils.addVisits([
     { uri: testuri, transition: TRANSITION_LINK },
     { uri: testuri, referrer: testuri, transition: TRANSITION_LINK },
     { uri: testuri, transition: TRANSITION_TYPED },
   ]);
   await promiseNotifications;
 });
--- a/toolkit/components/places/tests/unit/test_markpageas.js
+++ b/toolkit/components/places/tests/unit/test_markpageas.js
@@ -7,34 +7,53 @@
 var gVisits = [{url: "http://www.mozilla.com/",
                 transition: TRANSITION_TYPED},
                {url: "http://www.google.com/",
                 transition: TRANSITION_BOOKMARK},
                {url: "http://www.espn.com/",
                 transition: TRANSITION_LINK}];
 
 add_task(async function test_execute() {
+  let observer;
   let completionPromise = new Promise(resolveCompletionPromise => {
-    let visitCount = 0;
-    function listener(aEvents) {
-      Assert.equal(aEvents.length, 1, "Right number of visits notified");
-      Assert.equal(aEvents[0].type, "page-visited");
-      let event = aEvents[0];
-      Assert.equal(event.url, gVisits[visitCount].url);
-      Assert.equal(event.transitionType, gVisits[visitCount].transition);
-      visitCount++;
+    observer = {
+      __proto__: NavHistoryObserver.prototype,
+      _visitCount: 0,
+      onVisit(aURI, aVisitID, aTime, aSessionID, aReferringID,
+                        aTransitionType, aAdded) {
+        Assert.equal(aURI.spec, gVisits[this._visitCount].url);
+        Assert.equal(aTransitionType, gVisits[this._visitCount].transition);
+        this._visitCount++;
 
-      if (visitCount == gVisits.length) {
-        resolveCompletionPromise();
-        PlacesObservers.removeListener(["page-visited"], listener);
-      }
-    }
-    PlacesObservers.addListener(["page-visited"], listener);
+        if (this._visitCount == gVisits.length) {
+          resolveCompletionPromise();
+        }
+      },
+      onVisits(aVisits) {
+        Assert.equal(aVisits.length, 1, "Right number of visits notified");
+        let {
+          uri,
+          visitId,
+          time,
+          referrerId,
+          transitionType,
+          guid,
+          hidden,
+          visitCount,
+          typed,
+          lastKnownTitle,
+        } = aVisits[0];
+        this.onVisit(uri, visitId, time, 0, referrerId,
+                     transitionType, guid, hidden, visitCount,
+                     typed, lastKnownTitle);
+      },
+    };
   });
 
+  PlacesUtils.history.addObserver(observer);
 
   for (var visit of gVisits) {
     if (visit.transition == TRANSITION_TYPED)
       PlacesUtils.history.markPageAsTyped(uri(visit.url));
     else if (visit.transition == TRANSITION_BOOKMARK)
       PlacesUtils.history.markPageAsFollowedBookmark(uri(visit.url));
     else {
      // because it is a top level visit with no referrer,
@@ -43,9 +62,10 @@ add_task(async function test_execute() {
     await PlacesTestUtils.addVisits({
       uri: uri(visit.url),
       transition: visit.transition
     });
   }
 
   await completionPromise;
 
+  PlacesUtils.history.removeObserver(observer);
 });
--- a/toolkit/components/thumbnails/PageThumbs.jsm
+++ b/toolkit/components/thumbnails/PageThumbs.jsm
@@ -822,14 +822,15 @@ 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,19 +544,16 @@ 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();
@@ -654,21 +651,21 @@ var PlacesProvider = {
   onEndUpdateBatch() {
     this._batchProcessingDepth -= 1;
     if (this._batchProcessingDepth == 0 && this._batchCalledFrecencyChanged) {
       this.onManyFrecenciesChanged();
       this._batchCalledFrecencyChanged = false;
     }
   },
 
-  handlePlacesEvents(aEvents) {
+  onVisits(aVisits) {
     if (!this._batchProcessingDepth) {
-      for (let event of aEvents) {
-        if (event.visitCount == 1 && event.lastKnownTitle) {
-          this.onTitleChanged(event.url, event.lastKnownTitle, event.pageGuid);
+      for (let visit of aVisits) {
+        if (visit.visitCount == 1 && visit.lastKnownTitle) {
+          this.onTitleChanged(visit.uri, visit.lastKnownTitle, visit.guid);
         }
       }
     }
   },
 
   onDeleteURI: function PlacesProvider_onDeleteURI(aURI, aGUID, aReason) {
     // let observers remove sensetive data associated with deleted visit
     this._callObservers("onDeleteURI", {
@@ -709,21 +706,18 @@ 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,
+      url: aURI.spec,
       title: aNewTitle
     });
   },
 
   _callObservers: function PlacesProvider__callObservers(aMethodName, aArg) {
     for (let obs of this._observers) {
       if (obs[aMethodName]) {
         try {
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
@@ -36,19 +36,16 @@ module.exports = {
     // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/InternalError
     "InternalError": true,
     "KeyEvent": false,
     "MatchGlob": false,
     "MatchPattern": false,
     "MatchPatternSet": false,
     "MenuBoxObject": false,
     // Specific to Firefox (Chrome code only).
-    "PlacesObservers": false,
-    "PlacesWeakCallbackWrapper": false,
-    // Specific to Firefox (Chrome code only).
     "SharedArrayBuffer": false,
     "SimpleGestureEvent": false,
     // Note: StopIteration will likely be removed as part of removing legacy
     // generators, see bug 968038.
     "StopIteration": false,
     "StructuredCloneHolder": false,
     "WebAssembly": false,
     "WebExtensionContentScript": false,