Bug 1677408 - Part 2 - Update consumers of onPageChanged. r=Standard8
authorMarco Bonardo <mbonardo@mozilla.com>
Mon, 23 Nov 2020 16:26:17 +0000
changeset 558313 d739ef17da5f38112b3b7da8cf7472208d9a436f
parent 558312 49f8c976469eeaf1f989a41c0764fafd025feff6
child 558314 8684e7486a10d5dbae287a3a66586665624b0f48
push id37978
push usernerli@mozilla.com
push dateTue, 24 Nov 2020 09:22:28 +0000
treeherdermozilla-central@0e1c1a720ca7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1677408
milestone85.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1677408 - Part 2 - Update consumers of onPageChanged. r=Standard8 Differential Revision: https://phabricator.services.mozilla.com/D97275
browser/base/content/test/favicons/browser_favicon_load.js
browser/components/extensions/parent/ext-history.js
browser/components/newtab/lib/PlacesFeed.jsm
browser/components/newtab/test/unit/lib/PlacesFeed.test.js
browser/components/originattributes/test/browser/browser_favicon_firstParty.js
browser/components/originattributes/test/browser/browser_favicon_userContextId.js
browser/components/places/tests/browser/browser_views_iconsupdate.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
browser/modules/WindowsJumpLists.jsm
browser/modules/WindowsPreviewPerTab.jsm
docshell/test/browser/browser_bug420605.js
docshell/test/browser/browser_bug503832.js
docshell/test/browser/browser_bug655270.js
services/sync/modules/engines/bookmarks.js
services/sync/modules/engines/history.js
services/sync/tests/unit/head_helpers.js
toolkit/components/downloads/DownloadHistory.jsm
toolkit/components/downloads/DownloadIntegration.jsm
toolkit/components/places/PlacesExpiration.jsm
toolkit/components/places/nsINavBookmarksService.idl
toolkit/components/places/nsINavHistoryService.idl
toolkit/components/places/nsNavHistoryResult.cpp
toolkit/components/places/nsNavHistoryResult.h
toolkit/components/places/tests/bookmarks/test_async_observers.js
toolkit/components/places/tests/browser/browser_bug646422.js
toolkit/components/places/tests/browser/browser_settitle.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/favicons/head_favicons.js
toolkit/components/places/tests/favicons/test_copyFavicons.js
toolkit/components/places/tests/favicons/test_query_result_favicon_changed_on_child.js
toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js
toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_redirects.js
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/history/test_remove.js
toolkit/components/places/tests/history/test_removeByFilter.js
toolkit/components/places/tests/history/test_removeMany.js
toolkit/components/places/tests/history/test_removeVisitsByFilter.js
toolkit/components/places/tests/unit/test_history_observer.js
toolkit/components/thumbnails/PageThumbs.jsm
--- a/browser/base/content/test/favicons/browser_favicon_load.js
+++ b/browser/base/content/test/favicons/browser_favicon_load.js
@@ -95,21 +95,19 @@ FaviconObserver.prototype = {
 
   get promise() {
     return this._faviconLoaded.promise;
   },
 };
 
 function waitOnFaviconLoaded(aFaviconURL) {
   return PlacesTestUtils.waitForNotification(
-    "onPageChanged",
-    (uri, attr, value, id) =>
-      attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
-      value === aFaviconURL,
-    "history"
+    "favicon-changed",
+    events => events.some(e => e.faviconUrl == aFaviconURL),
+    "places"
   );
 }
 
 async function doTest(aTestPage, aFaviconURL, aTailingEnabled) {
   let pageURI = Services.io.newURI(aTestPage);
 
   // Create the observer object for observing favion channels.
   let observer = new FaviconObserver(pageURI, aFaviconURL, aTailingEnabled);
--- a/browser/components/extensions/parent/ext-history.js
+++ b/browser/components/extensions/parent/ext-history.js
@@ -118,17 +118,16 @@ const getHistoryObserver = () => {
       onBeginUpdateBatch() {}
       onEndUpdateBatch() {}
       onTitleChanged(uri, title) {
         this.emit("titleChanged", { url: uri.spec, title: title });
       }
       onClearHistory() {
         this.emit("visitRemoved", { allHistory: true, urls: [] });
       }
-      onPageChanged() {}
       onFrecencyChanged() {}
       onManyFrecenciesChanged() {}
       onDeleteVisits(uri, partialRemoval, guid, reason) {
         if (!partialRemoval) {
           this.emit("visitRemoved", { allHistory: false, urls: [uri.spec] });
         }
       }
     })();
--- a/browser/components/newtab/lib/PlacesFeed.jsm
+++ b/browser/components/newtab/lib/PlacesFeed.jsm
@@ -81,18 +81,16 @@ class HistoryObserver extends Observer {
   onEndUpdateBatch() {}
 
   onTitleChanged() {}
 
   onFrecencyChanged() {}
 
   onManyFrecenciesChanged() {}
 
-  onPageChanged() {}
-
   onDeleteVisits() {}
 }
 
 /**
  * BookmarksObserver - observes events from PlacesUtils.bookmarks
  */
 class BookmarksObserver extends Observer {
   constructor(dispatch) {
--- a/browser/components/newtab/test/unit/lib/PlacesFeed.test.js
+++ b/browser/components/newtab/test/unit/lib/PlacesFeed.test.js
@@ -746,17 +746,16 @@ describe("PlacesFeed", () => {
     });
     describe("Other empty methods (to keep code coverage happy)", () => {
       it("should have a various empty functions for xpconnect happiness", () => {
         observer.onBeginUpdateBatch();
         observer.onEndUpdateBatch();
         observer.onTitleChanged();
         observer.onFrecencyChanged();
         observer.onManyFrecenciesChanged();
-        observer.onPageChanged();
         observer.onDeleteVisits();
       });
     });
   });
 
   describe("Custom dispatch", () => {
     it("should only dispatch 1 PLACES_LINKS_CHANGED action if many bookmark-added notifications happened at once", async () => {
       // Yes, onItemAdded has at least 8 arguments. See function definition for docs.
--- a/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
+++ b/browser/components/originattributes/test/browser/browser_favicon_firstParty.js
@@ -6,16 +6,19 @@ if (SpecialPowers.useRemoteSubframes) {
   requestLongerTimeout(2);
 }
 
 const CC = Components.Constructor;
 
 const { PlacesUtils } = ChromeUtils.import(
   "resource://gre/modules/PlacesUtils.jsm"
 );
+const { PlacesTestUtils } = ChromeUtils.import(
+  "resource://testing-common/PlacesTestUtils.jsm"
+);
 
 let EventUtils = {};
 Services.scriptloader.loadSubScript(
   "chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
   EventUtils
 );
 
 const FIRST_PARTY_ONE = "example.com";
@@ -166,31 +169,21 @@ function waitOnFaviconResponse(aFaviconU
     };
 
     Services.obs.addObserver(observer, "http-on-examine-response");
     Services.obs.addObserver(observer, "http-on-examine-cached-response");
   });
 }
 
 function waitOnFaviconLoaded(aFaviconURL) {
-  return new Promise(resolve => {
-    let observer = {
-      onPageChanged(uri, attr, value, id) {
-        if (
-          attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
-          value === aFaviconURL
-        ) {
-          resolve();
-          PlacesUtils.history.removeObserver(observer, false);
-        }
-      },
-    };
-
-    PlacesUtils.history.addObserver(observer);
-  });
+  return PlacesTestUtils.waitForNotification(
+    "favicon-changed",
+    events => events.some(e => e.faviconUrl == aFaviconURL),
+    "places"
+  );
 }
 
 async function openTab(aURL) {
   let tab = BrowserTestUtils.addTab(gBrowser, aURL);
 
   // Select tab and make sure its browser is focused.
   gBrowser.selectedTab = tab;
   tab.ownerGlobal.focus();
--- a/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
+++ b/browser/components/originattributes/test/browser/browser_favicon_userContextId.js
@@ -153,21 +153,19 @@ FaviconObserver.prototype = {
 
   get promise() {
     return this._faviconLoaded.promise;
   },
 };
 
 function waitOnFaviconLoaded(aFaviconURL) {
   return PlacesTestUtils.waitForNotification(
-    "onPageChanged",
-    (uri, attr, value, id) =>
-      attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
-      value === aFaviconURL,
-    "history"
+    "favicon-changed",
+    events => events.some(e => e.faviconUrl == aFaviconURL),
+    "places"
   );
 }
 
 async function generateCookies(aHost) {
   // we generate two different cookies for two userContextIds.
   let cookies = [];
   cookies.push(Math.random().toString());
   cookies.push(Math.random().toString());
--- a/browser/components/places/tests/browser/browser_views_iconsupdate.js
+++ b/browser/components/places/tests/browser/browser_views_iconsupdate.js
@@ -50,48 +50,49 @@ add_task(async function() {
     setTimeout(resolve, 3000);
   });
 
   let toolbarElt = getNodeForToolbarItem(bm.guid);
   let toolbarShot1 = TestUtils.screenshotArea(toolbarElt, window);
   let sidebarRect = await getRectForSidebarItem(bm.guid);
   let sidebarShot1 = TestUtils.screenshotArea(sidebarRect, window);
 
-  await new Promise(resolve => {
+  let iconURI = await new Promise(resolve => {
     PlacesUtils.favicons.setAndFetchFaviconForPage(
       PAGE_URI,
       ICON_URI,
       true,
       PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
       resolve,
       Services.scriptSecurityManager.getSystemPrincipal()
     );
   });
+  Assert.ok(iconURI.equals(ICON_URI), "Succesfully set the icon");
 
   // The icon is read asynchronously from the network, we don't have an easy way
-  // to wait for that.
-  await new Promise(resolve => {
-    setTimeout(resolve, 3000);
-  });
+  // to wait for that, thus we must poll.
+  await TestUtils.waitForCondition(() => {
+    // Assert.notEqual truncates the strings, so it is unusable here for failure
+    // debugging purposes.
+    let toolbarShot2 = TestUtils.screenshotArea(toolbarElt, window);
+    if (toolbarShot1 != toolbarShot2) {
+      info("Before toolbar: " + toolbarShot1);
+      info("After toolbar: " + toolbarShot2);
+    }
+    return toolbarShot1 != toolbarShot2;
+  }, "Waiting for the toolbar icon to update");
 
-  // Assert.notEqual truncates the strings, so it is unusable here for failure
-  // debugging purposes.
-  let toolbarShot2 = TestUtils.screenshotArea(toolbarElt, window);
-  if (toolbarShot1 != toolbarShot2) {
-    info("Before toolbar: " + toolbarShot1);
-    info("After toolbar: " + toolbarShot2);
-  }
-  Assert.notEqual(toolbarShot1, toolbarShot2, "The UI should have updated");
-
-  let sidebarShot2 = TestUtils.screenshotArea(sidebarRect, window);
-  if (sidebarShot1 != sidebarShot2) {
-    info("Before sidebar: " + sidebarShot1);
-    info("After sidebar: " + sidebarShot2);
-  }
-  Assert.notEqual(sidebarShot1, sidebarShot2, "The UI should have updated");
+  await TestUtils.waitForCondition(() => {
+    let sidebarShot2 = TestUtils.screenshotArea(sidebarRect, window);
+    if (sidebarShot1 != sidebarShot2) {
+      info("Before sidebar: " + sidebarShot1);
+      info("After sidebar: " + sidebarShot2);
+    }
+    return sidebarShot1 != sidebarShot2;
+  }, "Waiting for the sidebar icon to update");
 });
 
 /**
  * Get Element for a bookmark in the bookmarks toolbar.
  *
  * @param guid
  *        GUID of the item to search.
  * @returns DOM Node of the element.
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
@@ -151,31 +151,21 @@ function waitOnFaviconResponse(aFaviconU
     };
 
     Services.obs.addObserver(observer, "http-on-examine-response");
     Services.obs.addObserver(observer, "http-on-examine-cached-response");
   });
 }
 
 function waitOnFaviconLoaded(aFaviconURL) {
-  return new Promise(resolve => {
-    let observer = {
-      onPageChanged(uri, attr, value, id) {
-        if (
-          attr === Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
-          value === aFaviconURL
-        ) {
-          resolve();
-          PlacesUtils.history.removeObserver(observer, false);
-        }
-      },
-    };
-
-    PlacesUtils.history.addObserver(observer);
-  });
+  return PlacesTestUtils.waitForNotification(
+    "favicon-changed",
+    events => events.some(e => e.faviconUrl == aFaviconURL),
+    "places"
+  );
 }
 
 async function assignCookies(aBrowser, aURL, aCookieValue) {
   let tabInfo = await openTab(aBrowser, aURL);
 
   await SpecialPowers.spawn(tabInfo.browser, [aCookieValue], async function(
     value
   ) {
--- a/browser/modules/WindowsJumpLists.jsm
+++ b/browser/modules/WindowsJumpLists.jsm
@@ -77,17 +77,16 @@ XPCOMUtils.defineLazyGetter(this, "gHist
     },
     onBeginUpdateBatch() {},
     onEndUpdateBatch() {},
     onVisits() {},
     onTitleChanged() {},
     onFrecencyChanged() {},
     onManyFrecenciesChanged() {},
     onDeleteURI() {},
-    onPageChanged() {},
     onDeleteVisits() {},
     QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
   });
 });
 
 /**
  * Global functions
  */
--- a/browser/modules/WindowsPreviewPerTab.jsm
+++ b/browser/modules/WindowsPreviewPerTab.jsm
@@ -765,17 +765,21 @@ var AeroPeek = {
   },
 
   _observersAdded: false,
 
   enable() {
     if (!this._observersAdded) {
       this.prefs.addObserver(DISABLE_THRESHOLD_PREF_NAME, this, true);
       this.prefs.addObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this, true);
-      PlacesUtils.history.addObserver(this, true);
+      this._placesListener = this.handlePlacesEvents.bind(this);
+      PlacesUtils.observers.addListener(
+        ["favicon-changed"],
+        this._placesListener
+      );
       this._observersAdded = true;
     }
 
     this.cacheLifespan = this.prefs.getIntPref(CACHE_EXPIRATION_TIME_PREF_NAME);
 
     this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
 
     // If the user toggled us on/off while the browser was already up
@@ -793,16 +797,20 @@ var AeroPeek = {
   disable() {
     while (this.windows.length) {
       // We can't call onCloseWindow here because it'll bail if we're not
       // enabled.
       let tabWinObject = this.windows[0];
       tabWinObject.destroy(); // This will remove us from the array.
       delete tabWinObject.win.gTaskbarTabGroup; // Tidy up the window.
     }
+    PlacesUtils.observers.removeListener(
+      ["favicon-changed"],
+      this._placesListener
+    );
   },
 
   addPreview(preview) {
     this.previews.push(preview);
     this.checkPreviewCount();
   },
 
   removePreview(preview) {
@@ -874,43 +882,34 @@ var AeroPeek = {
         this.previews.forEach(function(preview) {
           let controller = preview.controller.wrappedJSObject;
           controller.resetCanvasPreview();
         });
         break;
     }
   },
 
-  /* nsINavHistoryObserver implementation */
-  onBeginUpdateBatch() {},
-  onEndUpdateBatch() {},
-  onTitleChanged() {},
-  onFrecencyChanged() {},
-  onManyFrecenciesChanged() {},
-  onDeleteURI() {},
-  onClearHistory() {},
-  onDeleteVisits() {},
-  onPageChanged(uri, changedConst, newValue) {
-    if (
-      this.enabled &&
-      changedConst == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON
-    ) {
-      for (let win of this.windows) {
-        for (let [tab] of win.previews) {
-          if (tab.getAttribute("image") == newValue) {
-            win.updateFavicon(tab, newValue);
+  handlePlacesEvents(events) {
+    for (let event of events) {
+      switch (event.type) {
+        case "favicon-changed": {
+          for (let win of this.windows) {
+            for (let [tab] of win.previews) {
+              if (tab.getAttribute("image") == event.faviconUrl) {
+                win.updateFavicon(tab, event.faviconUrl);
+              }
+            }
           }
         }
       }
     }
   },
 
   QueryInterface: ChromeUtils.generateQI([
     "nsISupportsWeakReference",
-    "nsINavHistoryObserver",
     "nsIObserver",
   ]),
 };
 
 XPCOMUtils.defineLazyGetter(AeroPeek, "cacheTimer", () =>
   Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer)
 );
 
--- a/docshell/test/browser/browser_bug420605.js
+++ b/docshell/test/browser/browser_bug420605.js
@@ -1,125 +1,133 @@
 /* Test for Bug 420605
  * https://bugzilla.mozilla.org/show_bug.cgi?id=420605
  */
 
-function test() {
-  waitForExplicitFinish();
+const { PlacesTestUtils } = ChromeUtils.import(
+  "resource://testing-common/PlacesTestUtils.jsm"
+);
 
+add_task(async function test() {
   var pageurl =
     "http://mochi.test:8888/browser/docshell/test/browser/file_bug420605.html";
   var fragmenturl =
     "http://mochi.test:8888/browser/docshell/test/browser/file_bug420605.html#firefox";
 
-  var historyService = Cc[
-    "@mozilla.org/browser/nav-history-service;1"
-  ].getService(Ci.nsINavHistoryService);
-
   /* Queries nsINavHistoryService and returns a single history entry
    * for a given URI */
   function getNavHistoryEntry(aURI) {
-    var options = historyService.getNewQueryOptions();
+    var options = PlacesUtils.history.getNewQueryOptions();
     options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
     options.maxResults = 1;
 
-    var query = historyService.getNewQuery();
+    var query = PlacesUtils.history.getNewQuery();
     query.uri = aURI;
-
-    var result = historyService.executeQuery(query, options);
+    var result = PlacesUtils.history.executeQuery(query, options);
     result.root.containerOpen = true;
 
     if (!result.root.childCount) {
       return null;
     }
     return result.root.getChild(0);
   }
 
   // We'll save the favicon URL of the orignal page here and check that the
   // page with a hash has the same favicon.
   var originalFavicon;
 
   // Control flow in this test is a bit complicated.
   //
   // When the page loads, onPageLoad (the DOMContentLoaded handler) and
-  // historyObserver::onPageChanged are both called, in some order.  Once
+  // favicon-changed are both called, in some order.  Once
   // they've both run, we click a fragment link in the content page
-  // (clickLinkIfReady), which should trigger another onPageChanged event,
+  // (clickLinkIfReady), which should trigger another favicon-changed event,
   // this time for the fragment's URL.
 
   var _clickLinkTimes = 0;
   function clickLinkIfReady() {
     _clickLinkTimes++;
     if (_clickLinkTimes == 2) {
       BrowserTestUtils.synthesizeMouseAtCenter(
         "#firefox-link",
         {},
         gBrowser.selectedBrowser
       );
     }
   }
 
-  /* Global history observer that triggers for the two test URLs above. */
-  var historyObserver = {
-    onBeginUpdateBatch() {},
-    onEndUpdateBatch() {},
-    onTitleChanged(aURI, aPageTitle) {},
-    onDeleteURI(aURI) {},
-    onClearHistory() {},
-    onPageChanged(aURI, aWhat, aValue) {
-      if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
-        return;
-      }
-      aURI = aURI.spec;
-      switch (aURI) {
-        case pageurl:
-          ok(aValue, "Favicon value is not null for page without fragment.");
-          originalFavicon = aValue;
-
-          // Now that the favicon has loaded, click on fragment link.
-          // This should trigger the |case fragmenturl| below.
-          clickLinkIfReady();
-
-          return;
-        case fragmenturl:
-          // If the fragment URL's favicon isn't set, this branch won't
-          // be called and the test will time out.
-
-          is(
-            aValue,
-            originalFavicon,
-            "New favicon should be same as original favicon."
-          );
-
-          // Let's explicitly check that we can get the favicon
-          // from nsINavHistoryService now.
-          let info = getNavHistoryEntry(makeURI(aURI));
-          ok(info, "There must be a history entry for the fragment.");
-          ok(info.icon, "The history entry must have an associated favicon.");
-          historyService.removeObserver(historyObserver, false);
-          gBrowser.removeCurrentTab();
-          finish();
-      }
-    },
-    onPageExpired(aURI, aVisitTime, aWholeEntry) {},
-    QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
-  };
-  historyService.addObserver(historyObserver);
-
   function onPageLoad() {
     clickLinkIfReady();
   }
 
   // Make sure neither of the test pages haven't been loaded before.
   var info = getNavHistoryEntry(makeURI(pageurl));
   ok(!info, "The test page must not have been visited already.");
   info = getNavHistoryEntry(makeURI(fragmenturl));
   ok(!info, "The fragment test page must not have been visited already.");
 
+  let promiseIcon1 = PlacesTestUtils.waitForNotification(
+    "favicon-changed",
+    events =>
+      events.some(e => {
+        if (e.url == pageurl) {
+          ok(
+            e.faviconUrl,
+            "Favicon value is not null for page without fragment."
+          );
+          originalFavicon = e.faviconUrl;
+
+          // Now that the favicon has loaded, click on fragment link.
+          // This should trigger the |case fragmenturl| below.
+          clickLinkIfReady();
+          return true;
+        }
+        return false;
+      }),
+    "places"
+  );
+  let promiseIcon2 = PlacesTestUtils.waitForNotification(
+    "favicon-changed",
+    events =>
+      events.some(e => {
+        if (e.url == fragmenturl) {
+          // If the fragment URL's favicon isn't set, this branch won't
+          // be called and the test will time out.
+          is(
+            e.faviconUrl,
+            originalFavicon,
+            "New favicon should be same as original favicon."
+          );
+          ok(
+            e.faviconUrl,
+            "Favicon value is not null for page without fragment."
+          );
+          originalFavicon = e.faviconUrl;
+
+          // Now that the favicon has loaded, click on fragment link.
+          // This should trigger the |case fragmenturl| below.
+          clickLinkIfReady();
+          return true;
+        }
+        return false;
+      }),
+    "places"
+  );
+
   // Now open the test page in a new tab.
   gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
   BrowserTestUtils.waitForContentEvent(
     gBrowser.selectedBrowser,
     "DOMContentLoaded",
     true
   ).then(onPageLoad);
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, pageurl);
-}
+
+  await promiseIcon1;
+  await promiseIcon2;
+
+  // Let's explicitly check that we can get the favicon
+  // from nsINavHistoryService now.
+  info = getNavHistoryEntry(makeURI(fragmenturl));
+  ok(info, "There must be a history entry for the fragment.");
+  ok(info.icon, "The history entry must have an associated favicon.");
+  gBrowser.removeCurrentTab();
+});
--- a/docshell/test/browser/browser_bug503832.js
+++ b/docshell/test/browser/browser_bug503832.js
@@ -30,17 +30,16 @@ add_task(async function() {
             // branch won't be called and the test will timeout,
             // resulting in a failure
             historyService.removeObserver(historyObserver, false);
             resolve();
         }
       },
       onDeleteURI(aURI) {},
       onClearHistory() {},
-      onPageChanged(aURI, aWhat, aValue) {},
       onDeleteVisits() {},
       QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
     };
 
     historyService.addObserver(historyObserver);
   });
 
   /* Queries nsINavHistoryService and returns a single history entry
--- a/docshell/test/browser/browser_bug655270.js
+++ b/docshell/test/browser/browser_bug655270.js
@@ -3,58 +3,62 @@
 
 /**
  * Test for Bug 655273
  *
  * Call pushState and then make sure that the favicon service associates our
  * old favicon with the new URI.
  */
 
-function test() {
+const { PlacesTestUtils } = ChromeUtils.import(
+  "resource://testing-common/PlacesTestUtils.jsm"
+);
+
+add_task(async function test() {
   const testDir = "http://mochi.test:8888/browser/docshell/test/browser/";
   const origURL = testDir + "file_bug655270.html";
   const newURL = origURL + "?new_page";
 
   const faviconURL = testDir + "favicon_bug655270.ico";
 
-  waitForExplicitFinish();
-
-  let tab = BrowserTestUtils.addTab(gBrowser, origURL);
+  let icon1;
+  let promiseIcon1 = PlacesTestUtils.waitForNotification(
+    "favicon-changed",
+    events =>
+      events.some(e => {
+        if (e.url == origURL) {
+          icon1 = e.faviconUrl;
+          return true;
+        }
+        return false;
+      }),
+    "places"
+  );
+  let icon2;
+  let promiseIcon2 = PlacesTestUtils.waitForNotification(
+    "favicon-changed",
+    events =>
+      events.some(e => {
+        if (e.url == newURL) {
+          icon2 = e.faviconUrl;
+          return true;
+        }
+        return false;
+      }),
+    "places"
+  );
 
   // The page at origURL has a <link rel='icon'>, so we should get a call into
   // our observer below when it loads.  Once we verify that we have the right
-  // favicon URI, we call pushState, which should trigger another onPageChange
+  // favicon URI, we call pushState, which should trigger another favicon change
   // event, this time for the URI after pushState.
-
-  let observer = {
-    onPageChanged(aURI, aWhat, aValue) {
-      if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
-        return;
-      }
-
-      if (aURI.spec == origURL) {
-        is(aValue, faviconURL, "FaviconURL for original URI");
-        // Ignore the promise returned here and wait for the next
-        // onPageChanged notification.
-        SpecialPowers.spawn(tab.linkedBrowser, [], function() {
-          content.history.pushState("", "", "?new_page");
-        });
-      }
-
-      if (aURI.spec == newURL) {
-        is(aValue, faviconURL, "FaviconURL for new URI");
-        gBrowser.removeTab(tab);
-        PlacesUtils.history.removeObserver(this);
-        finish();
-      }
-    },
-
-    onBeginUpdateBatch() {},
-    onEndUpdateBatch() {},
-    onTitleChanged() {},
-    onDeleteURI() {},
-    onClearHistory() {},
-    onDeleteVisits() {},
-    QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
-  };
-
-  PlacesUtils.history.addObserver(observer);
-}
+  let tab = BrowserTestUtils.addTab(gBrowser, origURL);
+  await promiseIcon1;
+  is(icon1, faviconURL, "FaviconURL for original URI");
+  // Ignore the promise returned here and wait for the next
+  // onPageChanged notification.
+  SpecialPowers.spawn(tab.linkedBrowser, [], function() {
+    content.history.pushState("", "", "?new_page");
+  });
+  await promiseIcon2;
+  is(icon2, faviconURL, "FaviconURL for new URI");
+  gBrowser.removeTab(tab);
+});
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -915,21 +915,16 @@ BookmarksTracker.prototype = {
       return;
     }
 
     if (isAnno && !ANNOS_TO_TRACK.includes(property)) {
       // Ignore annotations except for the ones that we sync.
       return;
     }
 
-    // Ignore favicon changes to avoid unnecessary churn.
-    if (property == "favicon") {
-      return;
-    }
-
     this._log.trace(
       "onItemChanged: " +
         itemId +
         (", " + property + (isAnno ? " (anno)" : "")) +
         (value ? ' = "' + value + '"' : "")
     );
     this._upScore();
   },
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -605,12 +605,11 @@ HistoryTracker.prototype = {
     // Note that we're going to trigger a sync, but none of the cleared
     // pages are tracked, so the deletions will not be propagated.
     // See Bug 578694.
     this.score += SCORE_INCREMENT_XLARGE;
   },
 
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
-  onPageChanged() {},
   onTitleChanged() {},
   onBeforeDeleteURI() {},
 };
--- a/services/sync/tests/unit/head_helpers.js
+++ b/services/sync/tests/unit/head_helpers.js
@@ -635,17 +635,16 @@ async function promiseVisit(expectedType
       onEndUpdateBatch() {},
       onTitleChanged() {},
       onFrecencyChanged() {},
       onManyFrecenciesChanged() {},
       onDeleteURI(uri) {
         done("removed", uri.spec);
       },
       onClearHistory() {},
-      onPageChanged() {},
       onDeleteVisits() {},
     };
     PlacesUtils.history.addObserver(observer, false);
     PlacesObservers.addListener(["page-visited"], observer.handlePlacesEvents);
   });
 }
 
 async function addVisit(
--- a/toolkit/components/downloads/DownloadHistory.jsm
+++ b/toolkit/components/downloads/DownloadHistory.jsm
@@ -330,17 +330,16 @@ var DownloadCache = {
   onClearHistory() {
     this._data.clear();
   },
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
   onTitleChanged() {},
   onFrecencyChanged() {},
   onManyFrecenciesChanged() {},
-  onPageChanged() {},
   onDeleteVisits() {},
 };
 
 /**
  * Represents a download from the browser history. This object implements part
  * of the interface of the Download object.
  *
  * While Download objects are shared between the public DownloadList and all the
--- a/toolkit/components/downloads/DownloadIntegration.jsm
+++ b/toolkit/components/downloads/DownloadIntegration.jsm
@@ -1265,17 +1265,16 @@ DownloadHistoryObserver.prototype = {
   // nsINavHistoryObserver
   onClearHistory: function DL_onClearHistory() {
     this._list.removeFinished();
   },
 
   onTitleChanged() {},
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
-  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
  * current state from disk.
--- a/toolkit/components/places/PlacesExpiration.jsm
+++ b/toolkit/components/places/PlacesExpiration.jsm
@@ -585,17 +585,16 @@ nsPlacesExpiration.prototype = {
 
   onClearHistory: function PEX_onClearHistory() {
     // History status is clean after a clear history.
     this.status = STATUS.CLEAN;
   },
 
   onTitleChanged() {},
   onDeleteURI() {},
-  onPageChanged() {},
   onDeleteVisits() {},
 
   // nsITimerCallback
 
   notify() {
     // Run at the first idle, or after 5 minutes, whatever comes first.
     Services.tm.idleDispatchToMainThread(async () => {
       let db = await PlacesUtils.promiseDBConnection();
--- a/toolkit/components/places/nsINavBookmarksService.idl
+++ b/toolkit/components/places/nsINavBookmarksService.idl
@@ -67,28 +67,26 @@ interface nsINavBookmarkObserver : nsISu
    *        passed to the method that notifies the observer.
    *
    * @note List of values that may be associated with properties:
    *       aProperty     | aNewValue
    *       =====================================================================
    *       guid          | The new bookmark guid.
    *       cleartime     | Empty string (all visits to this item were removed).
    *       title         | The new title.
-   *       favicon       | The "moz-anno" URL of the new favicon.
    *       uri           | new URL.
    *       tags          | Empty string (tags for this item changed)
    *       dateAdded     | PRTime (as string) when the item was first added.
    *       lastModified  | PRTime (as string) when the item was last modified.
    *
    *       aProperty     | aOldValue
    *       =====================================================================
    *       guid          | The old bookmark guid.
    *       cleartime     | Empty string (currently unused).
    *       title         | Empty string (currently unused).
-   *       favicon       | Empty string (currently unused).
    *       uri           | old URL.
    *       tags          | Empty string (currently unused).
    *       dateAdded     | Empty string (currently unused).
    *       lastModified  | Empty string (currently unused).
    */
   void onItemChanged(in long long aItemId,
                      in ACString aProperty,
                      in boolean aIsAnnotationProperty,
--- a/toolkit/components/places/nsINavHistoryService.idl
+++ b/toolkit/components/places/nsINavHistoryService.idl
@@ -655,39 +655,16 @@ interface nsINavHistoryObserver : nsISup
                    in unsigned short aReason);
 
   /**
    * Notification that all of history is being deleted.
    */
   void onClearHistory();
 
   /**
-   * onPageChanged attribute indicating that favicon has been updated.
-   * aNewValue parameter will be set to the new favicon URI string.
-   */
-  const unsigned long ATTRIBUTE_FAVICON = 3;
-
-  /**
-   * An attribute of this page changed.
-   *
-   * @param aURI
-   *        The URI of the page on which an attribute changed.
-   * @param aChangedAttribute
-   *        The attribute whose value changed.  See ATTRIBUTE_* constants.
-   * @param aNewValue
-   *        The attribute's new value.
-   * @param aGUID
-   *        The unique ID associated with the page.
-   */
-  void onPageChanged(in nsIURI aURI,
-                     in unsigned long aChangedAttribute,
-                     in AString aNewValue,
-                     in ACString aGUID);
-
-  /**
    * Called when some visits of an history entry are expired.
    *
    * @param aURI
    *        The page whose visits have been expired.
    * @param aPartialRemoval
    *        Set to true if only some of the visits for the page have been removed.
    * @param aGUID
    *        The unique ID associated with the page.
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -1330,33 +1330,33 @@ nsresult nsNavHistoryContainerResultNode
   return NS_OK;
 }
 
 /**
  * Searches for matches for the given URI.  If aOnlyOne is set, it will
  * terminate as soon as it finds a single match.  This would be used when there
  * are URI results so there will only ever be one copy of any URI.
  *
- * When aOnlyOne is false, it will check all elements.  This is for visit
- * style results that may have multiple copies of any given URI.
+ * When aOnlyOne is false, it will check all elements.  This is for non-history
+ * or visit style results that may have multiple copies of any given URI.
  */
 void nsNavHistoryContainerResultNode::RecursiveFindURIs(
     bool aOnlyOne, nsNavHistoryContainerResultNode* aContainer,
     const nsCString& aSpec, nsCOMArray<nsNavHistoryResultNode>* aMatches) {
-  for (int32_t child = 0; child < aContainer->mChildren.Count(); ++child) {
-    uint32_t type;
-    aContainer->mChildren[child]->GetType(&type);
-    if (nsNavHistoryResultNode::IsTypeURI(type)) {
-      // compare URIs
-      nsNavHistoryResultNode* uriNode = aContainer->mChildren[child];
-      if (uriNode->mURI.Equals(aSpec)) {
-        // found
-        aMatches->AppendObject(uriNode);
-        if (aOnlyOne) return;
+  for (int32_t i = 0; i < aContainer->mChildren.Count(); ++i) {
+    auto* node = aContainer->mChildren[i];
+    if (node->IsURI()) {
+      if (node->mURI.Equals(aSpec)) {
+        aMatches->AppendObject(node);
+        if (aOnlyOne) {
+          return;
+        }
       }
+    } else if (node->IsContainer() && node->GetAsContainer()->mExpanded) {
+      RecursiveFindURIs(aOnlyOne, node->GetAsContainer(), aSpec, aMatches);
     }
   }
 }
 
 /**
  * If aUpdateSort is true, we will also update the sorting of this item.
  * Normally you want this to be true, but it can be false if the thing you are
  * changing can not affect sorting (like favicons).
@@ -2319,38 +2319,16 @@ static nsresult setFaviconCallback(nsNav
                                    const nsNavHistoryResult* aResult) {
   if (aResult && (!aNode->mParent || aNode->mParent->AreChildrenVisible()))
     NOTIFY_RESULT_OBSERVERS(aResult, NodeIconChanged(aNode));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsNavHistoryQueryResultNode::OnPageChanged(nsIURI* aURI,
-                                           uint32_t aChangedAttribute,
-                                           const nsAString& aNewValue,
-                                           const nsACString& aGUID) {
-  nsAutoCString spec;
-  nsresult rv = aURI->GetSpec(spec);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  switch (aChangedAttribute) {
-    case nsINavHistoryObserver::ATTRIBUTE_FAVICON: {
-      bool onlyOneEntry =
-          mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_URI;
-      UpdateURIs(true, onlyOneEntry, false, spec, setFaviconCallback, nullptr);
-      break;
-    }
-    default:
-      NS_WARNING("Unknown page changed notification");
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsNavHistoryQueryResultNode::OnDeleteVisits(nsIURI* aURI, bool aPartialRemoval,
                                             const nsACString& aGUID,
                                             uint16_t aReason,
                                             uint32_t aTransitionType) {
   MOZ_ASSERT(
       mOptions->QueryType() == nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY,
       "Bookmarks queries should not get a OnDeleteVisits notification");
 
@@ -3202,24 +3180,22 @@ nsresult nsNavHistoryFolderResultNode::O
 }
 
 NS_IMETHODIMP
 nsNavHistoryResultNode::OnItemChanged(
     int64_t aItemId, const nsACString& aProperty, bool aIsAnnotationProperty,
     const nsACString& aNewValue, PRTime aLastModified, uint16_t aItemType,
     int64_t aParentId, const nsACString& aGUID, const nsACString& aParentGUID,
     const nsACString& aOldValue, uint16_t aSource) {
-  if (aItemId != mItemId) return NS_OK;
-
-  // Last modified isn't changed for favicon updates and it is notified as `0`,
-  // so don't reset it here.
-  if (!aProperty.EqualsLiteral("favicon")) {
-    mLastModified = aLastModified;
+  if (aItemId != mItemId) {
+    return NS_OK;
   }
 
+  mLastModified = aLastModified;
+
   nsNavHistoryResult* result = GetResult();
   NS_ENSURE_STATE(result);
 
   bool shouldNotify = !mParent || mParent->AreChildrenVisible();
 
   if (aProperty.EqualsLiteral("title")) {
     // XXX: what should we do if the new title is void?
     mTitle = aNewValue;
@@ -3227,18 +3203,16 @@ nsNavHistoryResultNode::OnItemChanged(
       NOTIFY_RESULT_OBSERVERS(result, NodeTitleChanged(this, mTitle));
   } else if (aProperty.EqualsLiteral("uri")) {
     // clear the tags string as well
     mTags.SetIsVoid(true);
     nsCString oldURI(mURI);
     mURI = aNewValue;
     if (shouldNotify)
       NOTIFY_RESULT_OBSERVERS(result, NodeURIChanged(this, oldURI));
-  } else if (aProperty.EqualsLiteral("favicon")) {
-    if (shouldNotify) NOTIFY_RESULT_OBSERVERS(result, NodeIconChanged(this));
   } else if (aProperty.EqualsLiteral("cleartime")) {
     PRTime oldTime = mTime;
     mTime = 0;
     if (shouldNotify) {
       NOTIFY_RESULT_OBSERVERS(
           result, NodeHistoryDetailsChanged(this, oldTime, mAccessCount));
     }
   } else if (aProperty.EqualsLiteral("tags")) {
@@ -3520,17 +3494,18 @@ nsNavHistoryResult::~nsNavHistoryResult(
   // Delete all heap-allocated bookmark folder observer arrays.
   for (auto it = mBookmarkFolderObservers.Iter(); !it.Done(); it.Next()) {
     delete it.Data();
     it.Remove();
   }
 }
 
 void nsNavHistoryResult::StopObserving() {
-  AutoTArray<PlacesEventType, 3> events;
+  AutoTArray<PlacesEventType, 4> events;
+  events.AppendElement(PlacesEventType::Favicon_changed);
   if (mIsBookmarkFolderObserver || mIsAllBookmarksObserver) {
     nsNavBookmarks* bookmarks = nsNavBookmarks::GetBookmarksService();
     if (bookmarks) {
       bookmarks->RemoveObserver(this);
       mIsBookmarkFolderObserver = false;
       mIsAllBookmarksObserver = false;
     }
     events.AppendElement(PlacesEventType::Bookmark_added);
@@ -3718,16 +3693,24 @@ nsNavHistoryResult::AddObserver(nsINavHi
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If we are batching, notify a fake batch start to the observers.
   // Not doing so would then notify a not coupled batch end.
   if (mBatchInProgress) {
     NOTIFY_RESULT_OBSERVERS(this, Batching(true));
   }
 
+  if (!mRootNode->IsQuery() ||
+      mRootNode->GetAsQuery()->mLiveUpdate != QUERYUPDATE_NONE) {
+    // Pretty much all the views show favicons, thus observe changes to them.
+    AutoTArray<PlacesEventType, 1> events;
+    events.AppendElement(PlacesEventType::Favicon_changed);
+    PlacesObservers::AddListener(events, this);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNavHistoryResult::RemoveObserver(nsINavHistoryResultObserver* aObserver) {
   NS_ENSURE_ARG(aObserver);
   return mObservers.RemoveWeakElement(aObserver);
 }
@@ -4021,19 +4004,54 @@ nsresult nsNavHistoryResult::OnVisit(nsI
     // cause changes to the array.
     ENUMERATE_QUERY_OBSERVERS(Refresh(), mHistoryObservers,
                               IsContainersQuery());
   }
 
   return NS_OK;
 }
 
+void nsNavHistoryResult::OnIconChanged(nsIURI* aURI, nsIURI* aFaviconURI,
+                                       const nsACString& aGUID) {
+  if (!mRootNode->mExpanded) {
+    return;
+  }
+  // Find all nodes for the given URI and update them.
+  nsAutoCString spec;
+  if (NS_SUCCEEDED(aURI->GetSpec(spec))) {
+    bool onlyOneEntry =
+        mOptions->QueryType() ==
+            nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY &&
+        mOptions->ResultType() == nsINavHistoryQueryOptions::RESULTS_AS_URI;
+    mRootNode->UpdateURIs(true, onlyOneEntry, false, spec, setFaviconCallback,
+                          nullptr);
+  }
+}
+
 void nsNavHistoryResult::HandlePlacesEvent(const PlacesEventSequence& aEvents) {
   for (const auto& event : aEvents) {
     switch (event->Type()) {
+      case PlacesEventType::Favicon_changed: {
+        const dom::PlacesFavicon* faviconEvent = event->AsPlacesFavicon();
+        if (NS_WARN_IF(!faviconEvent)) {
+          continue;
+        }
+        nsCOMPtr<nsIURI> uri, faviconUri;
+        MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), faviconEvent->mUrl));
+        if (!uri) {
+          continue;
+        }
+        MOZ_ALWAYS_SUCCEEDS(
+            NS_NewURI(getter_AddRefs(faviconUri), faviconEvent->mFaviconUrl));
+        if (!faviconUri) {
+          continue;
+        }
+        OnIconChanged(uri, faviconUri, faviconEvent->mPageGuid);
+        break;
+      }
       case PlacesEventType::Page_visited: {
         const dom::PlacesVisit* visit = event->AsPlacesVisit();
         if (NS_WARN_IF(!visit)) {
           continue;
         }
 
         nsCOMPtr<nsIURI> uri;
         MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), visit->mUrl));
@@ -4137,27 +4155,16 @@ nsNavHistoryResult::OnDeleteURI(nsIURI* 
 }
 
 NS_IMETHODIMP
 nsNavHistoryResult::OnClearHistory() {
   ENUMERATE_HISTORY_OBSERVERS(OnClearHistory());
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsNavHistoryResult::OnPageChanged(nsIURI* aURI, uint32_t aChangedAttribute,
-                                  const nsAString& aValue,
-                                  const nsACString& aGUID) {
-  NS_ENSURE_ARG(aURI);
-
-  ENUMERATE_HISTORY_OBSERVERS(
-      OnPageChanged(aURI, aChangedAttribute, aValue, aGUID));
-  return NS_OK;
-}
-
 /**
  * Don't do anything when visits expire.
  */
 NS_IMETHODIMP
 nsNavHistoryResult::OnDeleteVisits(nsIURI* aURI, bool aPartialRemoval,
                                    const nsACString& aGUID, uint16_t aReason,
                                    uint32_t aTransitionType) {
   NS_ENSURE_ARG(aURI);
--- a/toolkit/components/places/nsNavHistoryResult.h
+++ b/toolkit/components/places/nsNavHistoryResult.h
@@ -68,19 +68,16 @@ class nsTrimInt64HashKey : public PLDHas
                             const nsACString& aGUID) __VA_ARGS__;      \
   NS_IMETHOD OnFrecencyChanged(nsIURI* aURI, int32_t aNewFrecency,     \
                                const nsACString& aGUID, bool aHidden,  \
                                PRTime aLastVisitDate) __VA_ARGS__;     \
   NS_IMETHOD OnManyFrecenciesChanged() __VA_ARGS__;                    \
   NS_IMETHOD OnDeleteURI(nsIURI* aURI, const nsACString& aGUID,        \
                          uint16_t aReason) __VA_ARGS__;                \
   NS_IMETHOD OnClearHistory() __VA_ARGS__;                             \
-  NS_IMETHOD OnPageChanged(nsIURI* aURI, uint32_t aChangedAttribute,   \
-                           const nsAString& aNewValue,                 \
-                           const nsACString& aGUID) __VA_ARGS__;       \
   NS_IMETHOD OnDeleteVisits(nsIURI* aURI, bool aPartialRemoval,        \
                             const nsACString& aGUID, uint16_t aReason, \
                             uint32_t aTransitionType) __VA_ARGS__;
 
 // The internal version is used by query nodes.
 #define NS_DECL_BOOKMARK_HISTORY_OBSERVER_INTERNAL \
   NS_DECL_BOOKMARK_HISTORY_OBSERVER_BASE()
 
@@ -128,17 +125,19 @@ class nsNavHistoryResult final
   void RemoveMobilePrefsObserver(nsNavHistoryQueryResultNode* aNode);
   void StopObserving();
 
   nsresult OnVisit(nsIURI* aURI, int64_t aVisitId, PRTime aTime,
                    uint32_t aTransitionType, const nsACString& aGUID,
                    bool aHidden, uint32_t aVisitCount,
                    const nsAString& aLastKnownTitle);
 
- public:
+  void OnIconChanged(nsIURI* aURI, nsIURI* aFaviconURI,
+                     const nsACString& aGUID);
+
   explicit nsNavHistoryResult(nsNavHistoryContainerResultNode* mRoot,
                               const RefPtr<nsNavHistoryQuery>& aQuery,
                               const RefPtr<nsNavHistoryQueryOptions>& aOptions);
 
   RefPtr<nsNavHistoryContainerResultNode> mRootNode;
 
   RefPtr<nsNavHistoryQuery> mQuery;
   RefPtr<nsNavHistoryQueryOptions> mOptions;
--- a/toolkit/components/places/tests/bookmarks/test_async_observers.js
+++ b/toolkit/components/places/tests/bookmarks/test_async_observers.js
@@ -50,36 +50,24 @@ add_task(async function test_add_visit()
   await promiseNotifications;
 });
 
 add_task(async function test_add_icon() {
   // Add a visit to the bookmark and wait for the observer.
   let guids = new Set(gBookmarkGuids);
   Assert.equal(guids.size, 2);
   let promiseNotifications = PlacesTestUtils.waitForNotification(
-    "onItemChanged",
-    (
-      id,
-      property,
-      isAnno,
-      newValue,
-      lastModified,
-      itemType,
-      parentId,
-      guid
-    ) => {
-      info(`Got a changed notification for ${guid}.`);
-      Assert.equal(property, "favicon");
-      Assert.ok(!isAnno);
-      Assert.equal(newValue, SMALLPNG_DATA_URI.spec);
-      Assert.equal(lastModified, 0);
-      Assert.equal(itemType, PlacesUtils.bookmarks.TYPE_BOOKMARK);
-      guids.delete(guid);
-      return guids.size == 0;
-    }
+    "favicon-changed",
+    events =>
+      events.some(
+        event =>
+          event.url == "http://book.ma.rk/" &&
+          event.faviconUrl.startsWith("data:image/png;base64")
+      ),
+    "places"
   );
 
   PlacesUtils.favicons.setAndFetchFaviconForPage(
     NetUtil.newURI("http://book.ma.rk/"),
     SMALLPNG_DATA_URI,
     true,
     PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
     null,
@@ -113,36 +101,8 @@ add_task(async function test_remove_page
       guids.delete(guid);
       return guids.size == 0;
     }
   );
 
   await PlacesUtils.history.remove("http://book.ma.rk/");
   await promiseNotifications;
 });
-
-add_task(async function shutdown() {
-  // Check that async observers don't try to create async statements after
-  // shutdown.  That would cause assertions, since the async thread is gone
-  // already.  Note that in such a case the notifications are not fired, so we
-  // cannot test for them.
-  // Put an history notification that triggers AsyncGetBookmarksForURI between
-  // asyncClose() and the actual connection closing.  Enqueuing a main-thread
-  // event as a shutdown blocker should ensure it runs before
-  // places-connection-closed.
-  // Notice this code is not using helpers cause it depends on a very specific
-  // order, a change in the helpers code could make this test useless.
-
-  let shutdownClient = PlacesUtils.history.shutdownClient.jsclient;
-  shutdownClient.addBlocker("Places Expiration: shutdown", function() {
-    Services.tm.mainThread.dispatch(() => {
-      // WARNING: this is very bad, never use out of testing code.
-      PlacesUtils.bookmarks
-        .QueryInterface(Ci.nsINavHistoryObserver)
-        .onPageChanged(
-          NetUtil.newURI("http://book.ma.rk/"),
-          Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON,
-          "test",
-          "test"
-        );
-    }, Ci.nsIThread.DISPATCH_NORMAL);
-  });
-});
--- a/toolkit/components/places/tests/browser/browser_bug646422.js
+++ b/toolkit/components/places/tests/browser/browser_bug646422.js
@@ -23,17 +23,16 @@ add_task(async function() {
           PlacesUtils.history.removeObserver(observer);
         }
       },
 
       onBeginUpdateBatch() {},
       onEndUpdateBatch() {},
       onDeleteURI() {},
       onClearHistory() {},
-      onPageChanged() {},
       onDeleteVisits() {},
       QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
     };
 
     PlacesUtils.history.addObserver(observer);
   });
 
   await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
--- a/toolkit/components/places/tests/browser/browser_settitle.js
+++ b/toolkit/components/places/tests/browser/browser_settitle.js
@@ -35,17 +35,16 @@ add_task(async function() {
         // have a title.  Since the title starts out as empty and then is set
         // to empty, there is no title change notification.
 
         PlacesUtils.history.removeObserver(this);
         resolve(this.data);
       },
       onDeleteURI() {},
       onClearHistory() {},
-      onPageChanged() {},
       onDeleteVisits() {},
       QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
     };
     PlacesUtils.history.addObserver(historyObserver);
   });
 
   const url1 =
     "http://example.com/tests/toolkit/components/places/tests/browser/title1.html";
--- a/toolkit/components/places/tests/browser/head.js
+++ b/toolkit/components/places/tests/browser/head.js
@@ -87,17 +87,16 @@ function promiseFieldForUrl(aURI, aField
 function NavHistoryObserver() {}
 
 NavHistoryObserver.prototype = {
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
   onTitleChanged() {},
   onDeleteURI() {},
   onClearHistory() {},
-  onPageChanged() {},
   onDeleteVisits() {},
   QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
 };
 
 function whenNewWindowLoaded(aOptions, aCallback) {
   BrowserTestUtils.waitForNewWindow().then(aCallback);
   OpenBrowserWindow(aOptions);
 }
--- a/toolkit/components/places/tests/expiration/test_notifications_onDeleteURI.js
+++ b/toolkit/components/places/tests/expiration/test_notifications_onDeleteURI.js
@@ -83,17 +83,16 @@ add_task(async function test_notificatio
       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);
       },
-      onPageChanged() {},
       onDeleteVisits() {},
     };
     hs.addObserver(historyObserver);
 
     // Expire now.
     await promiseForceExpirationStep(-1);
 
     hs.removeObserver(historyObserver, false);
--- a/toolkit/components/places/tests/expiration/test_notifications_onDeleteVisits.js
+++ b/toolkit/components/places/tests/expiration/test_notifications_onDeleteVisits.js
@@ -117,17 +117,16 @@ add_task(async function test_notificatio
       onClearHistory() {},
       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() {},
       onDeleteVisits(aURI, aPartialRemoval, aGUID, aReason) {
         currentTest.receivedNotifications++;
         do_check_guid_for_uri(aURI, aGUID);
         Assert.equal(
           aPartialRemoval,
           currentTest.expectedIsPartialRemoval,
           "Should have the correct flag setting for partial removal"
         );
--- a/toolkit/components/places/tests/favicons/head_favicons.js
+++ b/toolkit/components/places/tests/favicons/head_favicons.js
@@ -19,51 +19,16 @@ const systemPrincipal = Services.scriptS
 
 // This error icon must stay in sync with FAVICON_ERRORPAGE_URL in
 // nsIFaviconService.idl, aboutCertError.xhtml and netError.xhtml.
 const FAVICON_ERRORPAGE_URI = Services.io.newURI(
   "chrome://global/skin/icons/warning.svg"
 );
 
 /**
- * Waits for the first OnPageChanged notification for ATTRIBUTE_FAVICON, and
- * verifies that it matches the expected page URI and associated favicon URI.
- *
- * This function also double-checks the GUID parameter of the notification.
- *
- * @param aExpectedPageURI
- *        nsIURI object of the page whose favicon should change.
- * @param aExpectedFaviconURI
- *        nsIURI object of the newly associated favicon.
- * @param aCallback
- *        This function is called after the check finished.
- */
-function waitForFaviconChanged(
-  aExpectedPageURI,
-  aExpectedFaviconURI,
-  aCallback
-) {
-  let historyObserver = {
-    __proto__: NavHistoryObserver.prototype,
-    onPageChanged: function WFFC_onPageChanged(aURI, aWhat, aValue, aGUID) {
-      if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
-        return;
-      }
-      PlacesUtils.history.removeObserver(this);
-
-      Assert.ok(aURI.equals(aExpectedPageURI));
-      Assert.equal(aValue, aExpectedFaviconURI.spec);
-      do_check_guid_for_uri(aURI, aGUID);
-      aCallback();
-    },
-  };
-  PlacesUtils.history.addObserver(historyObserver);
-}
-
-/**
  * Checks that the favicon for the given page matches the provided data.
  *
  * @param aPageURI
  *        nsIURI object for the page to check.
  * @param aExpectedMimeType
  *        Expected MIME type of the icon, for example "image/png".
  * @param aExpectedData
  *        Expected icon data, expressed as an array of byte values.
@@ -109,12 +74,22 @@ function checkFaviconMissingForPage(aPag
   });
 }
 
 function promiseFaviconMissingForPage(aPageURI) {
   return new Promise(resolve => checkFaviconMissingForPage(aPageURI, resolve));
 }
 
 function promiseFaviconChanged(aExpectedPageURI, aExpectedFaviconURI) {
-  return new Promise(resolve =>
-    waitForFaviconChanged(aExpectedPageURI, aExpectedFaviconURI, resolve)
+  return PlacesTestUtils.waitForNotification(
+    "favicon-changed",
+    events =>
+      events.some(e => {
+        if (e.url == aExpectedPageURI.spec) {
+          Assert.equal(e.faviconUrl, aExpectedFaviconURI.spec);
+          do_check_guid_for_uri(aExpectedPageURI, e.pageGuid);
+          return true;
+        }
+        return false;
+      }),
+    "places"
   );
 }
--- a/toolkit/components/places/tests/favicons/test_copyFavicons.js
+++ b/toolkit/components/places/tests/favicons/test_copyFavicons.js
@@ -10,30 +10,22 @@ function copyFavicons(source, dest, inPr
       source,
       dest,
       inPrivate ? LOAD_PRIVATE : LOAD_NON_PRIVATE,
       resolve
     );
   });
 }
 
-function promisePageChanged() {
-  return new Promise(resolve => {
-    let observer = new NavHistoryObserver();
-    observer.onPageChanged = (uri, attribute, newValue, guid) => {
-      info("onPageChanged for attribute " + attribute + " and uri " + uri.spec);
-      if (attribute == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
-        PlacesUtils.history.removeObserver(observer);
-        Assert.ok(newValue, "newValue should be a valid value");
-        Assert.ok(guid, "Guid should be a valid value");
-        resolve(uri);
-      }
-    };
-    PlacesUtils.history.addObserver(observer, false);
-  });
+function promisePageChanged(url) {
+  return PlacesTestUtils.waitForNotification(
+    "favicon-changed",
+    events => events.some(e => e.url == url),
+    "places"
+  );
 }
 
 add_task(async function test_copyFavicons_inputcheck() {
   Assert.throws(
     () => PlacesUtils.favicons.copyFavicons(null, TEST_URI2, LOAD_PRIVATE),
     /NS_ERROR_ILLEGAL_VALUE/
   );
   Assert.throws(
@@ -99,54 +91,46 @@ add_task(async function test_copyFavicon
 });
 
 add_task(async function test_copyFavicons() {
   info("Normal copy across 2 pages");
   await PlacesTestUtils.addVisits(TEST_URI1);
   await setFaviconForPage(TEST_URI1, SMALLPNG_DATA_URI);
   await setFaviconForPage(TEST_URI1, SMALLSVG_DATA_URI);
   await PlacesTestUtils.addVisits(TEST_URI2);
-  let promiseChange = promisePageChanged();
+  let promiseChange = promisePageChanged(TEST_URI2.spec);
   Assert.equal(
     (await copyFavicons(TEST_URI1, TEST_URI2, false)).spec,
     SMALLSVG_DATA_URI.spec,
     "Icon should have been copied"
   );
-  Assert.equal(
-    (await promiseChange).spec,
-    TEST_URI2.spec,
-    "Notification should have fired"
-  );
+  await promiseChange;
   Assert.equal(
     await getFaviconUrlForPage(TEST_URI2, 1),
     SMALLPNG_DATA_URI.spec,
     "Small icon found"
   );
   Assert.equal(
     await getFaviconUrlForPage(TEST_URI2),
     SMALLSVG_DATA_URI.spec,
     "Large icon found"
   );
 
   info("Private copy to a bookmarked page");
   await PlacesUtils.bookmarks.insert({
     url: TEST_URI3,
     parentGuid: PlacesUtils.bookmarks.unfiledGuid,
   });
-  promiseChange = promisePageChanged();
+  promiseChange = promisePageChanged(TEST_URI3.spec);
   Assert.equal(
     (await copyFavicons(TEST_URI1, TEST_URI3, true)).spec,
     SMALLSVG_DATA_URI.spec,
     "Icon should have been copied"
   );
-  Assert.equal(
-    (await promiseChange).spec,
-    TEST_URI3.spec,
-    "Notification should have fired"
-  );
+  await promiseChange;
   Assert.equal(
     await getFaviconUrlForPage(TEST_URI3, 1),
     SMALLPNG_DATA_URI.spec,
     "Small icon found"
   );
   Assert.equal(
     await getFaviconUrlForPage(TEST_URI3),
     SMALLSVG_DATA_URI.spec,
@@ -159,27 +143,23 @@ add_task(async function test_copyFavicon
 
 add_task(async function test_copyFavicons_overlap() {
   info("Copy to a page that has one of the favicons already");
   await PlacesTestUtils.addVisits(TEST_URI1);
   await setFaviconForPage(TEST_URI1, SMALLPNG_DATA_URI);
   await setFaviconForPage(TEST_URI1, SMALLSVG_DATA_URI);
   await PlacesTestUtils.addVisits(TEST_URI2);
   await setFaviconForPage(TEST_URI2, SMALLPNG_DATA_URI);
-  let promiseChange = promisePageChanged();
+  let promiseChange = promisePageChanged(TEST_URI2.spec);
   Assert.equal(
     (await copyFavicons(TEST_URI1, TEST_URI2, false)).spec,
     SMALLSVG_DATA_URI.spec,
     "Icon should have been copied"
   );
-  Assert.equal(
-    (await promiseChange).spec,
-    TEST_URI2.spec,
-    "Notification should have fired"
-  );
+  await promiseChange;
   Assert.equal(
     await getFaviconUrlForPage(TEST_URI2, 1),
     SMALLPNG_DATA_URI.spec,
     "Small icon found"
   );
   Assert.equal(
     await getFaviconUrlForPage(TEST_URI2),
     SMALLSVG_DATA_URI.spec,
--- a/toolkit/components/places/tests/favicons/test_query_result_favicon_changed_on_child.js
+++ b/toolkit/components/places/tests/favicons/test_query_result_favicon_changed_on_child.js
@@ -42,20 +42,22 @@ add_task(async function test_query_resul
           false,
           PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
           null,
           Services.scriptSecurityManager.getSystemPrincipal()
         );
       }
     },
     nodeIconChanged(aNode) {
-      do_throw(
-        "The icon should be set only for the page," +
-          " not for the containing query."
-      );
+      if (PlacesUtils.nodeIsContainer(aNode)) {
+        do_throw(
+          "The icon should be set only for the page," +
+            " not for the containing query."
+        );
+      }
     },
   };
   result.addObserver(resultObserver);
 
   // Open the container and wait for containerStateChanged. We should start
   // observing before setting |containerOpen| as that's caused by the
   // setAndFetchFaviconForPage() call caused by the containerStateChanged
   // observer above.
--- a/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
@@ -67,26 +67,26 @@ add_task(async function() {
   for (let test of gTests) {
     info(test.desc);
     let pageURI = Services.io.newURI(test.href);
 
     await test.setup();
 
     let pageGuid;
     let promise = PlacesTestUtils.waitForNotification(
-      "onPageChanged",
-      (uri, prop, value, guid) => {
-        pageGuid = guid;
-        return (
-          prop == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
-          uri.equals(pageURI) &&
-          value == faviconURI.spec
-        );
-      },
-      "history"
+      "favicon-changed",
+      events =>
+        events.some(e => {
+          if (e.url == pageURI.spec && e.faviconUrl == faviconURI.spec) {
+            pageGuid = e.pageGuid;
+            return true;
+          }
+          return false;
+        }),
+      "places"
     );
 
     PlacesUtils.favicons.setAndFetchFaviconForPage(
       pageURI,
       faviconURI,
       true,
       test.private,
       null,
--- a/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js
@@ -16,25 +16,22 @@ add_task(async function() {
     await PlacesUtils.history.clear();
   });
 
   // We'll listen for favicon changes for the whole test, to ensure only the
   // last one will send a notification. Due to thread serialization, at that
   // point we can be sure previous calls didn't send a notification.
   let lastPageURI = Services.io.newURI("http://example.com/verification");
   let promiseIconChanged = PlacesTestUtils.waitForNotification(
-    "onPageChanged",
-    (uri, prop, value) => {
-      return (
-        prop == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
-        uri.equals(lastPageURI) &&
-        value == SMALLPNG_DATA_URI.spec
-      );
-    },
-    "history"
+    "favicon-changed",
+    events =>
+      events.some(
+        e => e.url == lastPageURI.spec && e.faviconUrl == SMALLPNG_DATA_URI.spec
+      ),
+    "places"
   );
 
   info("Test null page uri");
   Assert.throws(
     () => {
       PlacesUtils.favicons.setAndFetchFaviconForPage(
         null,
         faviconURI,
--- a/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_redirects.js
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_redirects.js
@@ -23,25 +23,22 @@ add_task(async function same_host_redire
   });
 
   registerCleanupFunction(async function() {
     await PlacesUtils.bookmarks.eraseEverything();
     await PlacesUtils.history.clear();
   });
 
   let promise = PlacesTestUtils.waitForNotification(
-    "onPageChanged",
-    (uri, prop, value, guid) => {
-      return (
-        prop == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
-        uri.spec == srcUrl &&
-        value == SMALLPNG_DATA_URI.spec
-      );
-    },
-    "history"
+    "favicon-changed",
+    events =>
+      events.some(
+        e => e.url == srcUrl && e.faviconUrl == SMALLPNG_DATA_URI.spec
+      ),
+    "places"
   );
 
   PlacesUtils.favicons.setAndFetchFaviconForPage(
     Services.io.newURI(destUrl),
     SMALLPNG_DATA_URI,
     true,
     PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
     null,
@@ -70,25 +67,22 @@ add_task(async function other_host_redir
   ]);
   await PlacesUtils.bookmarks.insert({
     parentGuid: PlacesUtils.bookmarks.unfiledGuid,
     url: srcUrl,
   });
 
   let promise = Promise.race([
     PlacesTestUtils.waitForNotification(
-      "onPageChanged",
-      (uri, prop, value, guid) => {
-        return (
-          prop == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON &&
-          uri.spec == srcUrl &&
-          value == SMALLPNG_DATA_URI.spec
-        );
-      },
-      "history"
+      "favicon-changed",
+      events =>
+        events.some(
+          e => e.url == srcUrl && e.faviconUrl == SMALLPNG_DATA_URI.spec
+        ),
+      "places"
     ),
     new Promise((resolve, reject) =>
       do_timeout(300, () => reject(new Error("timeout")))
     ),
   ]);
 
   PlacesUtils.favicons.setAndFetchFaviconForPage(
     Services.io.newURI(destUrl),
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -736,17 +736,16 @@ NavBookmarkObserver.prototype = {
 function NavHistoryObserver() {}
 
 NavHistoryObserver.prototype = {
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
   onTitleChanged() {},
   onDeleteURI() {},
   onClearHistory() {},
-  onPageChanged() {},
   onDeleteVisits() {},
   QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
 };
 
 /**
  * Generic nsINavHistoryResultObserver that doesn't implement anything, but
  * provides dummy methods to prevent errors about an object not having a certain
  * method.
--- a/toolkit/components/places/tests/history/test_remove.js
+++ b/toolkit/components/places/tests/history/test_remove.js
@@ -45,19 +45,16 @@ add_task(async function test_remove_sing
         onBeginUpdateBatch() {},
         onEndUpdateBatch() {},
         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));
-        },
         onFrecencyChanged(aURI) {
           try {
             Assert.ok(!shouldRemove, "Observing onFrecencyChanged");
             Assert.equal(
               aURI.spec,
               uri.spec,
               "Observing effect on the right uri"
             );
--- a/toolkit/components/places/tests/history/test_removeByFilter.js
+++ b/toolkit/components/places/tests/history/test_removeByFilter.js
@@ -450,19 +450,16 @@ function getObserverPromise(bookmarkedUr
       onBeginUpdateBatch() {},
       onEndUpdateBatch() {},
       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"));
-      },
       onFrecencyChanged(aURI) {},
       onManyFrecenciesChanged() {},
       onDeleteURI(aURI) {
         try {
           Assert.notEqual(
             aURI.spec,
             bookmarkedUri,
             "Bookmarked URI should not be deleted"
--- a/toolkit/components/places/tests/history/test_removeMany.js
+++ b/toolkit/components/places/tests/history/test_removeMany.js
@@ -85,19 +85,16 @@ add_task(async function test_remove_many
       Assert.ok(false, "Unexpected call to onVisits " + aVisits.length);
     },
     onTitleChanged(aURI) {
       Assert.ok(false, "Unexpected call to onTitleChanged " + aURI.spec);
     },
     onClearHistory() {
       Assert.ok(false, "Unexpected call to onClearHistory");
     },
-    onPageChanged(aURI) {
-      Assert.ok(false, "Unexpected call to onPageChanged " + aURI.spec);
-    },
     onFrecencyChanged(aURI) {
       let origin = pages.find(x => x.uri.spec == aURI.spec);
       Assert.ok(origin);
       Assert.ok(
         origin.hasBookmark,
         "Observing onFrecencyChanged on a page with a bookmark"
       );
       origin.onFrecencyChangedCalled = true;
--- a/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
+++ b/toolkit/components/places/tests/history/test_removeVisitsByFilter.js
@@ -151,21 +151,16 @@ add_task(async function test_removeVisit
       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)
-        );
-      },
       onFrecencyChanged(aURI) {
         info("onFrecencyChanged " + aURI.spec);
         let deferred = frecencyChangePromises.get(aURI.spec);
         Assert.ok(!!deferred, "Observing onFrecencyChanged");
         deferred.resolve();
       },
       onManyFrecenciesChanged() {
         info("Many frecencies changed");
--- a/toolkit/components/places/tests/unit/test_history_observer.js
+++ b/toolkit/components/places/tests/unit/test_history_observer.js
@@ -7,17 +7,16 @@
  */
 function NavHistoryObserver() {}
 NavHistoryObserver.prototype = {
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
   onTitleChanged() {},
   onDeleteURI() {},
   onClearHistory() {},
-  onPageChanged() {},
   onDeleteVisits() {},
   QueryInterface: ChromeUtils.generateQI(["nsINavHistoryObserver"]),
 };
 
 /**
  * Registers a one-time history observer for and calls the callback
  * when the specified nsINavHistoryObserver method is called.
  * Returns a promise that is resolved when the callback returns.
@@ -201,37 +200,8 @@ add_task(async function test_onTitleChan
   let [testuri] = await task_add_visit();
   let title = "test-title";
   await PlacesTestUtils.addVisits({
     uri: testuri,
     title,
   });
   await promiseNotify;
 });
-
-add_task(async function test_onPageChanged() {
-  let promiseNotify = onNotify(function onPageChanged(
-    aURI,
-    aChangedAttribute,
-    aNewValue,
-    aGUID
-  ) {
-    Assert.equal(aChangedAttribute, Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON);
-    Assert.ok(aURI.equals(testuri));
-    Assert.equal(aNewValue, SMALLPNG_DATA_URI.spec);
-    do_check_guid_for_uri(aURI, aGUID);
-  });
-
-  let [testuri] = await task_add_visit();
-
-  // The new favicon for the page must have data associated with it in order to
-  // receive the onPageChanged notification.  To keep this test self-contained,
-  // we use an URI representing the smallest possible PNG file.
-  PlacesUtils.favicons.setAndFetchFaviconForPage(
-    testuri,
-    SMALLPNG_DATA_URI,
-    false,
-    PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
-    null,
-    Services.scriptSecurityManager.getSystemPrincipal()
-  );
-  await promiseNotify;
-});
--- a/toolkit/components/thumbnails/PageThumbs.jsm
+++ b/toolkit/components/thumbnails/PageThumbs.jsm
@@ -869,16 +869,15 @@ var PageThumbsHistoryObserver = {
 
   onClearHistory() {
     PageThumbsStorage.wipe();
   },
 
   onTitleChanged() {},
   onBeginUpdateBatch() {},
   onEndUpdateBatch() {},
-  onPageChanged() {},
   onDeleteVisits() {},
 
   QueryInterface: ChromeUtils.generateQI([
     "nsINavHistoryObserver",
     "nsISupportsWeakReference",
   ]),
 };