Bug 1448057 - Asincify some Places tests and add a test for favicons on bookmark redirects. r=standard8
authorMarco Bonardo <mbonardo@mozilla.com>
Thu, 05 Apr 2018 11:03:19 +0200
changeset 412608 ad061f4fec730a878a175f214408780fe2ea8ace
parent 412607 704d18830c6d0776423b25a5d1ad49b75d0c94f7
child 412609 9ae7f58530880e2048ddf309ad69e49890c572f6
push id33813
push userccoroiu@mozilla.com
push dateTue, 10 Apr 2018 21:54:55 +0000
treeherdermozilla-central@d42671c2e69d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersstandard8
bugs1448057
milestone61.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 1448057 - Asincify some Places tests and add a test for favicons on bookmark redirects. r=standard8 Async-ify some tests. Moves some tests from browser-chrome to xpcshell. Due to the move, I found out that we are fetching icons from network even when we are not supposed to, so fix this bug (automatically tested by xpcshell through disallowing remote network access). Add a missing test for bookmark redirects, to cover the moved around code. MozReview-Commit-ID: EB2Z0huovJh
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
toolkit/components/places/FaviconHelpers.cpp
toolkit/components/places/FaviconHelpers.h
toolkit/components/places/tests/browser/browser.ini
toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage.js
toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage_failures.js
toolkit/components/places/tests/browser/head.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/favicons/xpcshell.ini
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/history/test_remove.js
toolkit/components/places/tests/history/test_removeMany.js
toolkit/components/places/tests/history/test_removeVisits.js
toolkit/components/places/tests/queries/test_history_queries_tags_liveUpdate.js
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_favicon.js
@@ -1,16 +1,14 @@
 /* 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/. */
 
 // This test make sure that the favicon of the private browsing is isolated.
 
-const CC = Components.Constructor;
-
 const TEST_SITE = "http://mochi.test:8888";
 const TEST_CACHE_SITE = "http://www.example.com";
 const TEST_DIRECTORY = "/browser/browser/components/privatebrowsing/test/browser/";
 
 const TEST_PAGE = TEST_SITE + TEST_DIRECTORY + "file_favicon.html";
 const TEST_CACHE_PAGE = TEST_CACHE_SITE + TEST_DIRECTORY + "file_favicon.html";
 const FAVICON_URI = TEST_SITE + TEST_DIRECTORY + "file_favicon.png";
 const FAVICON_CACHE_URI = TEST_CACHE_SITE + TEST_DIRECTORY + "file_favicon.png";
@@ -179,31 +177,27 @@ async function openTab(aBrowser, aURL) {
   aBrowser.selectedTab = tab;
   tab.ownerGlobal.focus();
 
   let browser = aBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
   return {tab, browser};
 }
 
-// A clean up function to prevent affecting other tests.
-registerCleanupFunction(() => {
-  // Clear all cookies.
+registerCleanupFunction(async () => {
   Services.cookies.removeAll();
-
-  // Clear all image caches and network caches.
   clearAllImageCaches();
-
   Services.cache2.clear();
+  await PlacesUtils.history.clear();
+  await PlacesUtils.bookmarks.eraseEverything();
 });
 
 add_task(async function test_favicon_privateBrowsing() {
   // Clear all image caches before running the test.
   clearAllImageCaches();
-
   // Clear all favicons in Places.
   await clearAllPlacesFavicons();
 
   // Create a private browsing window.
   let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ private: true });
   let pageURI = makeURI(TEST_PAGE);
 
   // Generate two random cookies for non-private window and private window
@@ -216,44 +210,50 @@ add_task(async function test_favicon_pri
   await assignCookies(privateWindow.gBrowser, TEST_SITE, cookies[0]);
 
   // Open a tab in non-private window and add a cookie into it.
   await assignCookies(gBrowser, TEST_SITE, cookies[1]);
 
   // Add the observer earlier in case we don't capture events in time.
   let promiseObserveFavicon = observeFavicon(true, cookies[0], pageURI);
 
+  // The page must be bookmarked for favicon requests to go through in PB mode.
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: TEST_PAGE
+  });
+
   // Open a tab for the private window.
   let tabInfo = await openTab(privateWindow.gBrowser, TEST_PAGE);
 
-  // Waiting until favicon requests are all made.
+  info("Waiting until favicon requests are all made in private window.");
   await promiseObserveFavicon;
 
   // Close the tab.
   BrowserTestUtils.removeTab(tabInfo.tab);
   // FIXME: We need to wait for the next event tick here to avoid observing
   //        the previous tab info in the next step (bug 1446725).
   await new Promise(executeSoon);
 
   // Add the observer earlier in case we don't capture events in time.
   promiseObserveFavicon = observeFavicon(false, cookies[1], pageURI);
 
   // Open a tab for the non-private window.
   tabInfo = await openTab(gBrowser, TEST_PAGE);
 
-  // Waiting until favicon requests are all made.
+  info("Waiting until favicon requests are all made in non-private window.");
   await promiseObserveFavicon;
 
   // Close the tab.
   BrowserTestUtils.removeTab(tabInfo.tab);
   await BrowserTestUtils.closeWindow(privateWindow);
 });
 
 add_task(async function test_favicon_cache_privateBrowsing() {
-  // Clear all image cahces and network cache before running the test.
+  // Clear all image caches and network cache before running the test.
   clearAllImageCaches();
 
   Services.cache2.clear();
 
   // Clear all favicons in Places.
   await clearAllPlacesFavicons();
 
   // Add an observer for making sure the favicon has been loaded and cached.
@@ -269,16 +269,22 @@ add_task(async function test_favicon_cac
   // Check that the favicon response has come from the network and it has the
   // correct privateBrowsingId.
   is(response.topic, "http-on-examine-response", "The favicon image should be loaded through network.");
   is(response.privateBrowsingId, 0, "We should observe the network response for the non-private tab.");
 
   // Create a private browsing window.
   let privateWindow = await BrowserTestUtils.openNewBrowserWindow({ private: true });
 
+  // The page must be bookmarked for favicon requests to go through in PB mode.
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: TEST_CACHE_PAGE
+  });
+
   // Open a tab for the private window.
   let tabInfoPrivate = await openTab(privateWindow.gBrowser, TEST_CACHE_PAGE);
 
   // Wait for the favicon response of the private tab.
   response = await waitOnFaviconResponse(FAVICON_CACHE_URI);
 
   // Make sure the favicon is loaded through the network and its privateBrowsingId is correct.
   is(response.topic, "http-on-examine-response", "The favicon image should be loaded through the network again.");
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -48,40 +48,46 @@ namespace {
  */
 nsresult
 FetchPageInfo(const RefPtr<Database>& aDB,
               PageData& _page)
 {
   MOZ_ASSERT(_page.spec.Length(), "Must have a non-empty spec!");
   MOZ_ASSERT(!NS_IsMainThread());
 
-  // This query finds the bookmarked uri we want to set the icon for,
-  // walking up to two redirect levels.
+  // The subquery finds the bookmarked uri we want to set the icon for,
+  // walking up redirects.
   nsCString query = nsPrintfCString(
     "SELECT h.id, pi.id, h.guid, ( "
-      "SELECT h.url FROM moz_bookmarks b WHERE b.fk = h.id "
-      "UNION ALL " // Union not directly bookmarked pages.
-      "SELECT url FROM moz_places WHERE id = ( "
-        "SELECT COALESCE(grandparent.place_id, parent.place_id) as r_place_id "
-        "FROM moz_historyvisits dest "
-        "LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit "
-                                          "AND dest.visit_type IN (%d, %d) "
-        "LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
-          "AND parent.visit_type IN (%d, %d) "
-        "WHERE dest.place_id = h.id "
-        "AND EXISTS(SELECT 1 FROM moz_bookmarks b WHERE b.fk = r_place_id) "
-        "LIMIT 1 "
+      "WITH RECURSIVE "
+      "destinations(visit_type, from_visit, place_id, rev_host, bm) AS ( "
+        "SELECT v.visit_type, v.from_visit, p.id, p.rev_host, b.id "
+        "FROM moz_places p  "
+        "LEFT JOIN moz_historyvisits v ON v.place_id = p.id  "
+        "LEFT JOIN moz_bookmarks b ON b.fk = p.id "
+        "WHERE p.id = h.id "
+        "UNION "
+        "SELECT src.visit_type, src.from_visit, src.place_id, p.rev_host, b.id "
+        "FROM moz_places p "
+        "JOIN moz_historyvisits src ON src.place_id = p.id "
+        "JOIN destinations dest ON dest.from_visit = src.id AND dest.visit_type IN (%d, %d) "
+        "LEFT JOIN moz_bookmarks b ON b.fk = src.place_id "
+        "WHERE instr(p.rev_host, dest.rev_host) = 1 "
+           "OR instr(dest.rev_host, p.rev_host) = 1 "
       ") "
+      "SELECT url "
+      "FROM moz_places p "
+      "JOIN destinations r ON r.place_id = p.id "
+      "WHERE bm NOTNULL "
+      "LIMIT 1 "
     "), fixup_url(get_unreversed_host(h.rev_host)) AS host "
     "FROM moz_places h "
     "LEFT JOIN moz_pages_w_icons pi ON page_url_hash = hash(:page_url) AND page_url = :page_url "
     "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url",
     nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
-    nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY,
-    nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
     nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
   );
 
   nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(query);
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
@@ -537,16 +543,29 @@ AsyncFetchAndSetIconForPage::Run()
   NS_ENSURE_STATE(DB);
   nsresult rv = FetchIconInfo(DB, 0, mIcon);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool isInvalidIcon = !mIcon.payloads.Length() || PR_Now() > mIcon.expiration;
   bool fetchIconFromNetwork = mIcon.fetchMode == FETCH_ALWAYS ||
                               (mIcon.fetchMode == FETCH_IF_MISSING && isInvalidIcon);
 
+  // Check if we can associate the icon to this page.
+  rv = FetchPageInfo(DB, mPage);
+  if (NS_FAILED(rv)) {
+    if (rv == NS_ERROR_NOT_AVAILABLE) {
+      // We have never seen this page.  If we can add the page to history,
+      // we will try to do it later, otherwise just bail out.
+      if (!mPage.canAddToHistory) {
+        return NS_OK;
+      }
+    }
+    return rv;
+  }
+
   if (!fetchIconFromNetwork) {
     // There is already a valid icon or we don't want to fetch a new one,
     // directly proceed with association.
     RefPtr<AsyncAssociateIconToPage> event =
         new AsyncAssociateIconToPage(mIcon, mPage, mCallback);
     // We're already on the async thread.
     return event->Run();
   }
@@ -822,45 +841,36 @@ AsyncAssociateIconToPage::AsyncAssociate
 {
   // May be created in both threads.
 }
 
 NS_IMETHODIMP
 AsyncAssociateIconToPage::Run()
 {
   MOZ_ASSERT(!NS_IsMainThread());
-
-  RefPtr<Database> DB = Database::GetDatabase();
-  NS_ENSURE_STATE(DB);
-  nsresult rv = FetchPageInfo(DB, mPage);
-  if (rv == NS_ERROR_NOT_AVAILABLE){
-    // We have never seen this page.  If we can add the page to history,
-    // we will try to do it later, otherwise just bail out.
-    if (!mPage.canAddToHistory) {
-      return NS_OK;
-    }
-  }
-  else {
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  MOZ_ASSERT(!mPage.guid.IsEmpty(), "Page info should have been fetched already");
+  MOZ_ASSERT(mPage.canAddToHistory || !mPage.bookmarkedSpec.IsEmpty(),
+             "The page should be addable to history or a bookmark");
 
   bool shouldUpdateIcon = mIcon.status & ICON_STATUS_CHANGED;
   if (!shouldUpdateIcon) {
     for (const auto& payload : mIcon.payloads) {
       // If the entry is missing from the database, we should add it.
       if (payload.id == 0) {
         shouldUpdateIcon = true;
         break;
       }
     }
   }
 
+  RefPtr<Database> DB = Database::GetDatabase();
+  NS_ENSURE_STATE(DB);
   mozStorageTransaction transaction(DB->MainConn(), false,
                                     mozIStorageConnection::TRANSACTION_IMMEDIATE);
-
+  nsresult rv;
   if (shouldUpdateIcon) {
     rv = SetIconInfo(DB, mIcon);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mIcon.status = (mIcon.status & ~(ICON_STATUS_CACHED)) | ICON_STATUS_SAVED;
   }
 
   // If the page does not have an id, don't try to insert a new one, cause we
@@ -949,16 +959,33 @@ AsyncAssociateIconToPage::Run()
   rv = transaction.Commit();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Finally, dispatch an event to the main thread to notify observers.
   nsCOMPtr<nsIRunnable> event = new NotifyIconObservers(mIcon, mPage, mCallback);
   rv = NS_DispatchToMainThread(event);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // If there is a bookmarked page that redirects to this one, try to update its
+  // icon as well.
+  if (!mPage.bookmarkedSpec.IsEmpty() &&
+      !mPage.bookmarkedSpec.Equals(mPage.spec)) {
+    // Create a new page struct to avoid polluting it with old data.
+    PageData bookmarkedPage;
+    bookmarkedPage.spec = mPage.bookmarkedSpec;
+    RefPtr<Database> DB = Database::GetDatabase();
+    if (DB && NS_SUCCEEDED(FetchPageInfo(DB, bookmarkedPage))) {
+      // This will be silent, so be sure to not pass in the current callback.
+      nsMainThreadPtrHandle<nsIFaviconDataCallback> nullCallback;
+      RefPtr<AsyncAssociateIconToPage> event =
+          new AsyncAssociateIconToPage(mIcon, bookmarkedPage, nullCallback);
+      Unused << event->Run();
+    }
+  }
+
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// AsyncGetFaviconURLForPage
 
 AsyncGetFaviconURLForPage::AsyncGetFaviconURLForPage(
   const nsACString& aPageSpec
@@ -1123,17 +1150,25 @@ NotifyIconObservers::Run()
   nsCOMPtr<nsIURI> iconURI;
   if (!mIcon.spec.IsEmpty()) {
     MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(iconURI), mIcon.spec));
     if (iconURI)
     {
       // Notify observers only if something changed.
       if (mIcon.status & ICON_STATUS_SAVED ||
           mIcon.status & ICON_STATUS_ASSOCIATED) {
-        SendGlobalNotifications(iconURI);
+        nsCOMPtr<nsIURI> pageURI;
+        MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(pageURI), mPage.spec));
+        if (pageURI) {
+          nsFaviconService* favicons = nsFaviconService::GetFaviconService();
+          MOZ_ASSERT(favicons);
+          if (favicons) {
+            (void)favicons->SendFaviconNotifications(pageURI, iconURI, mPage.guid);
+          }
+        }
       }
     }
   }
 
   if (!mCallback) {
     return NS_OK;
   }
 
@@ -1142,48 +1177,16 @@ NotifyIconObservers::Run()
     return mCallback->OnComplete(iconURI, payload.data.Length(),
                                  TO_INTBUFFER(payload.data), payload.mimeType,
                                  payload.width);
   }
   return mCallback->OnComplete(iconURI, 0, TO_INTBUFFER(EmptyCString()),
                                EmptyCString(), 0);
 }
 
-void
-NotifyIconObservers::SendGlobalNotifications(nsIURI* aIconURI)
-{
-  nsCOMPtr<nsIURI> pageURI;
-  MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(pageURI), mPage.spec));
-  if (pageURI) {
-    nsFaviconService* favicons = nsFaviconService::GetFaviconService();
-    MOZ_ASSERT(favicons);
-    if (favicons) {
-      (void)favicons->SendFaviconNotifications(pageURI, aIconURI, mPage.guid);
-    }
-  }
-
-  // If the page is bookmarked and the bookmarked url is different from the
-  // updated one, start a new task to update its icon as well.
-  if (!mPage.bookmarkedSpec.IsEmpty() &&
-      !mPage.bookmarkedSpec.Equals(mPage.spec)) {
-    // Create a new page struct to avoid polluting it with old data.
-    PageData bookmarkedPage;
-    bookmarkedPage.spec = mPage.bookmarkedSpec;
-
-    RefPtr<Database> DB = Database::GetDatabase();
-    if (!DB)
-      return;
-    // This will be silent, so be sure to not pass in the current callback.
-    nsMainThreadPtrHandle<nsIFaviconDataCallback> nullCallback;
-    RefPtr<AsyncAssociateIconToPage> event =
-        new AsyncAssociateIconToPage(mIcon, bookmarkedPage, nullCallback);
-    DB->DispatchToAsyncThread(event);
-  }
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 //// FetchAndConvertUnsupportedPayloads
 
 FetchAndConvertUnsupportedPayloads::FetchAndConvertUnsupportedPayloads(
   mozIStorageConnection* aDBConn)
   : Runnable("places::FetchAndConvertUnsupportedPayloads")
   , mDB(aDBConn)
 {
--- a/toolkit/components/places/FaviconHelpers.h
+++ b/toolkit/components/places/FaviconHelpers.h
@@ -322,18 +322,16 @@ public:
   NotifyIconObservers(const IconData& aIcon,
                       const PageData& aPage,
                       const nsMainThreadPtrHandle<nsIFaviconDataCallback>& aCallback);
 
 private:
   nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
   IconData mIcon;
   PageData mPage;
-
-  void SendGlobalNotifications(nsIURI* aIconURI);
 };
 
 /**
  * Fetches and converts unsupported payloads. This is used during the initial
  * migration of icons from the old to the new store.
  */
 class FetchAndConvertUnsupportedPayloads final : public Runnable
 {
--- a/toolkit/components/places/tests/browser/browser.ini
+++ b/toolkit/components/places/tests/browser/browser.ini
@@ -8,18 +8,16 @@ support-files =
 
 [browser_bug399606.js]
 [browser_bug461710.js]
 [browser_bug646422.js]
 [browser_bug680727.js]
 [browser_colorAnalyzer.js]
 [browser_double_redirect.js]
 [browser_favicon_privatebrowsing_perwindowpb.js]
-[browser_favicon_setAndFetchFaviconForPage.js]
-[browser_favicon_setAndFetchFaviconForPage_failures.js]
 [browser_history_post.js]
 [browser_notfound.js]
 [browser_onvisit_title_null_for_navigation.js]
 support-files =
   empty_page.html
 [browser_redirect.js]
 [browser_multi_redirect_frecency.js]
 [browser_settitle.js]
--- a/toolkit/components/places/tests/browser/head.js
+++ b/toolkit/components/places/tests/browser/head.js
@@ -85,53 +85,16 @@ NavHistoryObserver.prototype = {
   onPageChanged() {},
   onDeleteVisits() {},
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsINavHistoryObserver,
   ])
 };
 
 /**
- * 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, aWindow,
-                               aCallback) {
-  let historyObserver = {
-    __proto__: NavHistoryObserver.prototype,
-    onPageChanged: function WFFC_onPageChanged(aURI, aWhat, aValue, aGUID) {
-      if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
-        return;
-      }
-      aWindow.PlacesUtils.history.removeObserver(this);
-
-      ok(aURI.equals(aExpectedPageURI),
-        "Check URIs are equal for the page which favicon changed");
-      is(aValue, aExpectedFaviconURI.spec,
-        "Check changed favicon URI is the expected");
-      checkGuidForURI(aURI, aGUID);
-
-      if (aCallback) {
-        aCallback();
-      }
-    }
-  };
-  aWindow.PlacesUtils.history.addObserver(historyObserver);
-}
-
-/**
  * Asynchronously adds visits to a page, invoking a callback function when done.
  *
  * @param aPlaceInfo
  *        Either an nsIURI, in such a case a single LINK visit will be added.
  *        Or can be an object describing the visit to add, or an array
  *        of these objects:
  *          { uri: nsIURI of the page,
  *            transition: one of the TRANSITION_* from nsINavHistoryService,
@@ -178,144 +141,12 @@ function addVisits(aPlaceInfo, aWindow, 
       handleCompletion: function UP_handleCompletion() {
         if (aCallback)
           aCallback();
       }
     }
   );
 }
 
-/**
- * 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.
- * @param aCallback
- *        This function is called after the check finished.
- */
-function checkFaviconDataForPage(aPageURI, aExpectedMimeType, aExpectedData,
-  aWindow, aCallback) {
-  aWindow.PlacesUtils.favicons.getFaviconDataForPage(aPageURI,
-    function(aURI, aDataLen, aData, aMimeType) {
-      is(aExpectedMimeType, aMimeType, "Check expected MimeType");
-      is(aExpectedData.length, aData.length,
-        "Check favicon data for the given page matches the provided data");
-      checkGuidForURI(aPageURI);
-      aCallback();
-    });
-}
-
-/**
- * Tests that a guid was set in moz_places for a given uri.
- *
- * @param aURI
- *        The uri to check.
- * @param [optional] aGUID
- *        The expected guid in the database.
- */
-function checkGuidForURI(aURI, aGUID) {
-  let guid = doGetGuidForURI(aURI);
-  if (aGUID) {
-    doCheckValidPlacesGuid(aGUID);
-    is(guid, aGUID, "Check equal guid for URIs");
-  }
-}
-
-/**
- * Retrieves the guid for a given uri.
- *
- * @param aURI
- *        The uri to check.
- * @return the associated the guid.
- */
-function doGetGuidForURI(aURI) {
-  let stmt = DBConn().createStatement(
-    `SELECT guid
-       FROM moz_places
-       WHERE url_hash = hash(:url) AND url = :url`
-  );
-  stmt.params.url = aURI.spec;
-  ok(stmt.executeStep(), "Check get guid for uri from moz_places");
-  let guid = stmt.row.guid;
-  stmt.finalize();
-  doCheckValidPlacesGuid(guid);
-  return guid;
-}
-
-/**
- * Tests if a given guid is valid for use in Places or not.
- *
- * @param aGuid
- *        The guid to test.
- */
-function doCheckValidPlacesGuid(aGuid) {
-  ok(/^[a-zA-Z0-9\-_]{12}$/.test(aGuid), "Check guid for valid places");
-}
-
-/**
- * Gets the database connection.  If the Places connection is invalid it will
- * try to create a new connection.
- *
- * @param [optional] aForceNewConnection
- *        Forces creation of a new connection to the database.  When a
- *        connection is asyncClosed it cannot anymore schedule async statements,
- *        though connectionReady will keep returning true (Bug 726990).
- *
- * @return The database connection or null if unable to get one.
- */
-function DBConn(aForceNewConnection) {
-  let gDBConn;
-  if (!aForceNewConnection) {
-    let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-      .DBConnection;
-    if (db.connectionReady)
-      return db;
-  }
-
-  // If the Places database connection has been closed, create a new connection.
-  if (!gDBConn || aForceNewConnection) {
-    let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
-    file.append("places.sqlite");
-    let dbConn = gDBConn = Services.storage.openDatabase(file);
-
-    // Be sure to cleanly close this connection.
-    Services.obs.addObserver(function DBCloseCallback(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(DBCloseCallback, aTopic);
-      dbConn.asyncClose();
-    }, "profile-before-change");
-  }
-
-  return gDBConn.connectionReady ? gDBConn : null;
-}
-
 function whenNewWindowLoaded(aOptions, aCallback) {
   BrowserTestUtils.waitForNewWindow().then(aCallback);
   OpenBrowserWindow(aOptions);
 }
-
-function waitForCondition(condition, nextTest, errorMsg) {
-  let tries = 0;
-  let interval = setInterval(function() {
-    if (tries >= 30) {
-      ok(false, errorMsg);
-      moveOn();
-    }
-    let conditionPassed;
-    try {
-      conditionPassed = condition();
-    } catch (e) {
-      ok(false, e + "\n" + e.stack);
-      conditionPassed = false;
-    }
-    if (conditionPassed) {
-      moveOn();
-    }
-    tries++;
-  }, 200);
-  function moveOn() {
-    clearInterval(interval);
-    nextTest();
-  }
-}
rename from toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage.js
rename to toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
--- a/toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage.js
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
@@ -1,152 +1,97 @@
 /* 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/. */
 
 // This file tests the normal operation of setAndFetchFaviconForPage.
-function test() {
-  // Initialization
-  waitForExplicitFinish();
-  let windowsToClose = [];
-  let favIconLocation =
-    "http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png";
-  let favIconURI = NetUtil.newURI(favIconLocation);
-  let favIconMimeType = "image/png";
-  let pageURI;
-  let favIconData;
 
-  function testOnWindow(aOptions, aCallback) {
-    whenNewWindowLoaded(aOptions, function(aWin) {
-      windowsToClose.push(aWin);
-      executeSoon(() => aCallback(aWin));
-    });
-  }
+let gTests = [
+  {
+    desc: "Normal test",
+    href: "http://example.com/normal",
+    loadType: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+    async setup() {
+      await PlacesTestUtils.addVisits({uri: this.href, transition: TRANSITION_TYPED});
+    }
+  },
+  {
+    desc: "Bookmarked about: uri",
+    href: "about:testAboutURI_bookmarked",
+    loadType: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+    async setup() {
+      await PlacesUtils.bookmarks.insert({
+        parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+        url: this.href
+      });
+    }
+  },
+  {
+    desc: "Bookmarked in private window",
+    href: "http://example.com/privateBrowsing_bookmarked",
+    loadType: PlacesUtils.favicons.FAVICON_LOAD_PRIVATE,
+    async setup() {
+      await PlacesUtils.bookmarks.insert({
+        parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+        url: this.href
+      });
+    }
+  },
+  {
+    desc: "Bookmarked with disabled history",
+    href: "http://example.com/disabledHistory_bookmarked",
+    loadType: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+    async setup() {
+      await PlacesUtils.bookmarks.insert({
+        parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+        url: this.href
+      });
+      Services.prefs.setBoolPref("places.history.enabled", false);
+    },
+    clean() {
+      Services.prefs.setBoolPref("places.history.enabled", true);
+    }
+  },
+];
 
-  // This function is called after calling finish() on the test.
-  registerCleanupFunction(function() {
-    windowsToClose.forEach(function(aWin) {
-      aWin.close();
-    });
+add_task(async function() {
+  let faviconURI = SMALLPNG_DATA_URI;
+  let faviconMimeType = "image/png";
+
+  registerCleanupFunction(async function() {
+    await PlacesUtils.bookmarks.eraseEverything();
+    await PlacesUtils.history.clear();
   });
 
-  function getIconFile(aCallback) {
-    NetUtil.asyncFetch({
-      uri: favIconLocation,
-      loadUsingSystemPrincipal: true,
-      contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON
-    }, function(inputStream, status) {
-        if (!Components.isSuccessCode(status)) {
-          ok(false, "Could not get the icon file");
-          // Handle error.
-          return;
-        }
-
-        // Check the returned size versus the expected size.
-        let size = inputStream.available();
-        favIconData = NetUtil.readInputStreamToString(inputStream, size);
-        is(size, favIconData.length, "Check correct icon size");
-        // Check that the favicon loaded correctly before starting the actual tests.
-        is(favIconData.length, 344, "Check correct icon length (344)");
+  for (let test of gTests) {
+    info(test.desc);
+    let pageURI = Services.io.newURI(test.href);
 
-        if (aCallback) {
-          aCallback();
-        } else {
-          finish();
-        }
-      });
-  }
-
-  function testNormal(aWindow, aCallback) {
-    pageURI = NetUtil.newURI("http://example.com/normal");
-    waitForFaviconChanged(pageURI, favIconURI, aWindow,
-      function testNormalCallback() {
-        checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow,
-          aCallback);
-      }
-    );
-
-    addVisits({uri: pageURI, transition: TRANSITION_TYPED}, aWindow,
-      function() {
-        aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
-          true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
-          Services.scriptSecurityManager.getSystemPrincipal());
-      }
-    );
-  }
+    await test.setup();
 
-  function testAboutURIBookmarked(aWindow, aCallback) {
-    pageURI = NetUtil.newURI("about:testAboutURI_bookmarked");
-    waitForFaviconChanged(pageURI, favIconURI, aWindow,
-      function testAboutURIBookmarkedCallback() {
-        checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow,
-          aCallback);
-      }
-    );
-
-    aWindow.PlacesUtils.bookmarks.insertBookmark(
-      aWindow.PlacesUtils.unfiledBookmarksFolderId, pageURI,
-      aWindow.PlacesUtils.bookmarks.DEFAULT_INDEX, pageURI.spec);
-    aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
-      true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
-      Services.scriptSecurityManager.getSystemPrincipal());
-  }
-
-  function testPrivateBrowsingBookmarked(aWindow, aCallback) {
-    pageURI = NetUtil.newURI("http://example.com/privateBrowsing_bookmarked");
-    waitForFaviconChanged(pageURI, favIconURI, aWindow,
-      function testPrivateBrowsingBookmarkedCallback() {
-        checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow,
-          aCallback);
-      }
-    );
+    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");
 
-    aWindow.PlacesUtils.bookmarks.insertBookmark(
-      aWindow.PlacesUtils.unfiledBookmarksFolderId, pageURI,
-      aWindow.PlacesUtils.bookmarks.DEFAULT_INDEX, pageURI.spec);
-    aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
-      true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_PRIVATE, null,
-      Services.scriptSecurityManager.getSystemPrincipal());
-  }
-
-  function testDisabledHistoryBookmarked(aWindow, aCallback) {
-    pageURI = NetUtil.newURI("http://example.com/disabledHistory_bookmarked");
-    waitForFaviconChanged(pageURI, favIconURI, aWindow,
-      function testDisabledHistoryBookmarkedCallback() {
-        checkFaviconDataForPage(pageURI, favIconMimeType, favIconData, aWindow,
-          aCallback);
-      }
-    );
-
-    // Disable history while changing the favicon.
-    aWindow.Services.prefs.setBoolPref("places.history.enabled", false);
-
-    aWindow.PlacesUtils.bookmarks.insertBookmark(
-      aWindow.PlacesUtils.unfiledBookmarksFolderId, pageURI,
-      aWindow.PlacesUtils.bookmarks.DEFAULT_INDEX, pageURI.spec);
-    aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, favIconURI,
-      true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+    PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, faviconURI,
+      true, test.private, null,
       Services.scriptSecurityManager.getSystemPrincipal());
 
-    // The setAndFetchFaviconForPage function calls CanAddURI synchronously, thus
-    // we can set the preference back to true immediately.  We don't clear the
-    // preference because not all products enable Places by default.
-    aWindow.Services.prefs.setBoolPref("places.history.enabled", true);
-  }
+    await promise;
 
-  getIconFile(function() {
-    testOnWindow({}, function(aWin) {
-      testNormal(aWin, function() {
-        testOnWindow({}, function(aWin2) {
-          testAboutURIBookmarked(aWin2, function() {
-            testOnWindow({private: true}, function(aWin3) {
-              testPrivateBrowsingBookmarked(aWin3, function() {
-                testOnWindow({}, function(aWin4) {
-                  testDisabledHistoryBookmarked(aWin4, finish);
-                });
-              });
-            });
-          });
-        });
-      });
-    });
-  });
-}
+    Assert.equal(pageGuid, await PlacesTestUtils.fieldInDB(pageURI, "guid"),
+                 "Page guid is correct");
+    let {dataLen, data, mimeType} = await PlacesUtils.promiseFaviconData(pageURI.spec);
+    Assert.equal(faviconMimeType, mimeType, "Check expected MimeType");
+    Assert.equal(SMALLPNG_DATA_LEN, data.length,
+      "Check favicon data for the given page matches the provided data");
+    Assert.equal(dataLen, data.length,
+      "Check favicon dataLen for the given page matches the provided data");
+
+    if (test.clean) {
+      await test.clean();
+    }
+  }
+});
rename from toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage_failures.js
rename to toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js
--- a/toolkit/components/places/tests/browser/browser_favicon_setAndFetchFaviconForPage_failures.js
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js
@@ -1,263 +1,97 @@
 /* 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/. */
 
-/* eslint max-nested-callbacks: ["warn", 17] */
-
 /**
  * This file tests setAndFetchFaviconForPage when it is called with invalid
  * arguments, and when no favicon is stored for the given arguments.
  */
-function test() {
-  // Initialization
-  waitForExplicitFinish();
-  let windowsToClose = [];
-  let favIcon16Location =
-    "http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal16.png";
-  let favIcon32Location =
-    "http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal32.png";
-  let favIcon16URI = NetUtil.newURI(favIcon16Location);
-  let favIcon32URI = NetUtil.newURI(favIcon32Location);
-  let lastPageURI = NetUtil.newURI("http://example.com/verification");
-  // This error icon must stay in sync with FAVICON_ERRORPAGE_URL in
-  // nsIFaviconService.idl, aboutCertError.xhtml and netError.xhtml.
-  let favIconErrorPageURI =
-    NetUtil.newURI("chrome://global/skin/icons/warning-16.png");
-  let favIconsResultCount = 0;
 
-  function testOnWindow(aOptions, aCallback) {
-    whenNewWindowLoaded(aOptions, function(aWin) {
-      windowsToClose.push(aWin);
-      executeSoon(() => aCallback(aWin));
-    });
-  }
-
-  // This function is called after calling finish() on the test.
-  registerCleanupFunction(function() {
-    windowsToClose.forEach(function(aWin) {
-      aWin.close();
-    });
+let faviconURI = Services.io.newURI("http://example.org/tests/toolkit/components/places/tests/browser/favicon-normal16.png");
+add_task(async function() {
+  registerCleanupFunction(async function() {
+    await PlacesUtils.bookmarks.eraseEverything();
+    await PlacesUtils.history.clear();
   });
 
-  function checkFavIconsDBCount(aCallback) {
-    let stmt = DBConn().createAsyncStatement("SELECT icon_url FROM moz_icons");
-    stmt.executeAsync({
-      handleResult: function final_handleResult(aResultSet) {
-        while (aResultSet.getNextRow()) {
-          favIconsResultCount++;
-        }
-      },
-      handleError: function final_handleError(aError) {
-        throw ("Unexpected error (" + aError.result + "): " + aError.message);
-      },
-      handleCompletion: function final_handleCompletion(aReason) {
-        // begin testing
-        info("Previous records in moz_icons: " + favIconsResultCount);
-        if (aCallback) {
-          aCallback();
-        }
-      }
-    });
-    stmt.finalize();
-  }
+  // 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");
 
-  function testNullPageURI(aWindow, aCallback) {
-    try {
-      aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(null, favIcon16URI,
-        true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
-        Services.scriptSecurityManager.getSystemPrincipal());
-      throw ("Exception expected because aPageURI is null.");
-    } catch (ex) {
-      // We expected an exception.
-      ok(true, "Exception expected because aPageURI is null");
-    }
-
-    if (aCallback) {
-      aCallback();
-    }
-  }
-
-  function testNullFavIconURI(aWindow, aCallback) {
-    try {
-      aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(
-        NetUtil.newURI("http://example.com/null_faviconURI"), null,
-        true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
-        null, Services.scriptSecurityManager.getSystemPrincipal());
-      throw ("Exception expected because aFaviconURI is null.");
-    } catch (ex) {
-      // We expected an exception.
-      ok(true, "Exception expected because aFaviconURI is null.");
-    }
+  info("Test null page uri");
+  Assert.throws(() => {
+    PlacesUtils.favicons.setAndFetchFaviconForPage(null, faviconURI,
+      true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+      Services.scriptSecurityManager.getSystemPrincipal());
+  }, /NS_ERROR_ILLEGAL_VALUE/, "Exception expected because aPageURI is null");
 
-    if (aCallback) {
-      aCallback();
-    }
-  }
-
-  function testAboutURI(aWindow, aCallback) {
-    aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(
-      NetUtil.newURI("about:testAboutURI"), favIcon16URI,
-      true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+  info("Test null favicon uri");
+  Assert.throws(() => {
+    PlacesUtils.favicons.setAndFetchFaviconForPage(
+      Services.io.newURI("http://example.com/null_faviconURI"), null,
+      true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
       null, Services.scriptSecurityManager.getSystemPrincipal());
-
-    if (aCallback) {
-      aCallback();
-    }
-  }
-
-  function testPrivateBrowsingNonBookmarkedURI(aWindow, aCallback) {
-    let pageURI = NetUtil.newURI("http://example.com/privateBrowsing");
-    addVisits({ uri: pageURI, transitionType: TRANSITION_TYPED }, aWindow,
-      function() {
-        aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
-          favIcon16URI, true,
-          aWindow.PlacesUtils.favicons.FAVICON_LOAD_PRIVATE, null,
-          Services.scriptSecurityManager.getSystemPrincipal());
+    }, /NS_ERROR_ILLEGAL_VALUE/, "Exception expected because aFaviconURI is null.");
 
-        if (aCallback) {
-          aCallback();
-        }
-    });
-  }
-
-  function testDisabledHistory(aWindow, aCallback) {
-    let pageURI = NetUtil.newURI("http://example.com/disabledHistory");
-    addVisits({ uri: pageURI, transition: TRANSITION_TYPED }, aWindow,
-      function() {
-        aWindow.Services.prefs.setBoolPref("places.history.enabled", false);
+  info("Test about uri");
+  PlacesUtils.favicons.setAndFetchFaviconForPage(
+    Services.io.newURI("about:testAboutURI"), faviconURI,
+    true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+    null, Services.scriptSecurityManager.getSystemPrincipal());
 
-        aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
-          favIcon16URI, true,
-          aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
-          Services.scriptSecurityManager.getSystemPrincipal());
-
-        // The setAndFetchFaviconForPage function calls CanAddURI synchronously, thus
-        // we can set the preference back to true immediately . We don't clear the
-        // preference because not all products enable Places by default.
-        aWindow.Services.prefs.setBoolPref("places.history.enabled", true);
-
-        if (aCallback) {
-          aCallback();
-        }
-    });
-  }
+  info("Test private browsing non bookmarked uri");
+  let pageURI = Services.io.newURI("http://example.com/privateBrowsing");
+  await PlacesTestUtils.addVisits({ uri: pageURI, transitionType: TRANSITION_TYPED });
+  PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, faviconURI, true,
+    PlacesUtils.favicons.FAVICON_LOAD_PRIVATE, null,
+    Services.scriptSecurityManager.getSystemPrincipal());
 
-  function testErrorIcon(aWindow, aCallback) {
-    let pageURI = NetUtil.newURI("http://example.com/errorIcon");
-    addVisits({ uri: pageURI, transition: TRANSITION_TYPED }, aWindow,
-      function() {
-        aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
-          favIconErrorPageURI, true,
-          aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
-          Services.scriptSecurityManager.getSystemPrincipal());
-
-      if (aCallback) {
-        aCallback();
-      }
-    });
-  }
+  info("Test disabled history");
+  pageURI = Services.io.newURI("http://example.com/disabledHistory");
+  await PlacesTestUtils.addVisits({ uri: pageURI, transition: TRANSITION_TYPED });
+  Services.prefs.setBoolPref("places.history.enabled", false);
 
-  function testNonExistingPage(aWindow, aCallback) {
-    aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(
-      NetUtil.newURI("http://example.com/nonexistingPage"), favIcon16URI,
-      true, aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
-      Services.scriptSecurityManager.getSystemPrincipal());
-
-    if (aCallback) {
-      aCallback();
-    }
-  }
+  PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
+    faviconURI, true,
+    PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+    Services.scriptSecurityManager.getSystemPrincipal());
 
-  function testFinalVerification(aWindow, aCallback) {
-    // Only the last test should raise the onPageChanged notification,
-    // executing the waitForFaviconChanged callback.
-    waitForFaviconChanged(lastPageURI, favIcon32URI, aWindow,
-      function final_callback() {
-        // Check that only one record corresponding to the last favicon is present.
-        let resultCount = 0;
-        let stmt = DBConn().createAsyncStatement("SELECT icon_url FROM moz_icons");
-        stmt.executeAsync({
-          handleResult: function final_handleResult(aResultSet) {
+  // The setAndFetchFaviconForPage function calls CanAddURI synchronously, thus
+  // we can set the preference back to true immediately.
+  Services.prefs.setBoolPref("places.history.enabled", true);
 
-            // If the moz_icons DB had been previously loaded (before our
-            // test began), we should focus only in the URI we are testing and
-            // skip the URIs not related to our test.
-            if (favIconsResultCount > 0) {
-              for (let row; (row = aResultSet.getNextRow()); ) {
-                if (favIcon32URI.spec === row.getResultByIndex(0)) {
-                  is(favIcon32URI.spec, row.getResultByIndex(0),
-                    "Check equal favicons");
-                  resultCount++;
-                }
-              }
-            } else {
-              for (let row; (row = aResultSet.getNextRow()); ) {
-                is(favIcon32URI.spec, row.getResultByIndex(0),
-                  "Check equal favicons");
-                resultCount++;
-              }
-            }
-          },
-          handleError: function final_handleError(aError) {
-            throw ("Unexpected error (" + aError.result + "): " + aError.message);
-          },
-          handleCompletion: function final_handleCompletion(aReason) {
-            is(Ci.mozIStorageStatementCallback.REASON_FINISHED, aReason,
-              "Check reasons are equal");
-            is(1, resultCount, "Check result count");
-            if (aCallback) {
-              aCallback();
-            }
-          }
-        });
-        stmt.finalize();
-    });
+  info("Test error icon");
+  // This error icon must stay in sync with FAVICON_ERRORPAGE_URL in
+  // nsIFaviconService.idl, aboutCertError.xhtml and netError.xhtml.
+  let faviconErrorPageURI =
+    Services.io.newURI("chrome://global/skin/icons/warning-16.png");
+  pageURI = Services.io.newURI("http://example.com/errorIcon");
+  await PlacesTestUtils.addVisits({ uri: pageURI, transition: TRANSITION_TYPED });
+  PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI,
+    faviconErrorPageURI, true,
+    PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+    Services.scriptSecurityManager.getSystemPrincipal());
 
-    // This is the only test that should cause the waitForFaviconChanged
-    // callback to be invoked.  In turn, the callback will invoke
-    // finish() causing the tests to finish.
-    addVisits({ uri: lastPageURI, transition: TRANSITION_TYPED }, aWindow,
-      function() {
-        aWindow.PlacesUtils.favicons.setAndFetchFaviconForPage(lastPageURI,
-          favIcon32URI, true,
-          aWindow.PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
-          Services.scriptSecurityManager.getSystemPrincipal());
-    });
-  }
+  info("Test nonexisting page");
+  PlacesUtils.favicons.setAndFetchFaviconForPage(
+    Services.io.newURI("http://example.com/nonexistingPage"), faviconURI,
+    true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+    Services.scriptSecurityManager.getSystemPrincipal());
 
-  checkFavIconsDBCount(function() {
-    testOnWindow({}, function(aWin) {
-      testNullPageURI(aWin, function() {
-        testOnWindow({}, function(aWin2) {
-          testNullFavIconURI(aWin2, function() {
-            testOnWindow({}, function(aWin3) {
-              testAboutURI(aWin3, function() {
-                testOnWindow({private: true}, function(aWin4) {
-                  testPrivateBrowsingNonBookmarkedURI(aWin4, function() {
-                    testOnWindow({}, function(aWin5) {
-                      testDisabledHistory(aWin5, function() {
-                        testOnWindow({}, function(aWin6) {
-                          testErrorIcon(aWin6, function() {
-                            testOnWindow({}, function(aWin7) {
-                              testNonExistingPage(aWin7, function() {
-                                testOnWindow({}, function(aWin8) {
-                                  testFinalVerification(aWin8, function() {
-                                    finish();
-                                  });
-                                });
-                              });
-                            });
-                          });
-                        });
-                      });
-                    });
-                  });
-                });
-              });
-            });
-          });
-        });
-      });
-    });
-  });
-}
+  info("Final sanity check");
+  // This is the only test that should cause the waitForFaviconChanged
+  // callback to be invoked.
+  await PlacesTestUtils.addVisits({ uri: lastPageURI, transition: TRANSITION_TYPED });
+  PlacesUtils.favicons.setAndFetchFaviconForPage(lastPageURI,
+    SMALLPNG_DATA_URI, true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+    Services.scriptSecurityManager.getSystemPrincipal());
+
+  await promiseIconChanged;
+
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_redirects.js
@@ -0,0 +1,71 @@
+/* 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/. */
+
+// This file tests setAndFetchFaviconForPage on bookmarked redirects.
+
+add_task(async function same_host_redirect() {
+  // Add a bookmarked page that redirects to another page, set a favicon on the
+  // latter and check the former gets it too, if they are in the same host.
+  let srcUrl = "http://bookmarked.com/";
+  let destUrl = "https://other.bookmarked.com/";
+  await PlacesTestUtils.addVisits([
+    { uri: srcUrl, transition: TRANSITION_LINK },
+    { uri: destUrl, transition: TRANSITION_REDIRECT_TEMPORARY, referrer: srcUrl }
+  ]);
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: srcUrl
+  });
+
+  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");
+
+  PlacesUtils.favicons.setAndFetchFaviconForPage(Services.io.newURI(destUrl),
+    SMALLPNG_DATA_URI, true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+    Services.scriptSecurityManager.getSystemPrincipal());
+
+  await promise;
+
+  // The favicon should be set also on the bookmarked url that redirected.
+  let {dataLen} = await PlacesUtils.promiseFaviconData(srcUrl);
+  Assert.equal(dataLen, SMALLPNG_DATA_LEN, "Check favicon dataLen");
+});
+
+add_task(async function other_host_redirect() {
+  // Add a bookmarked page that redirects to another page, set a favicon on the
+  // latter and check the former gets it too, if they are in the same host.
+  let srcUrl = "http://first.com/";
+  let destUrl = "https://notfirst.com/";
+  await PlacesTestUtils.addVisits([
+    { uri: srcUrl, transition: TRANSITION_LINK },
+    { uri: destUrl, transition: TRANSITION_REDIRECT_TEMPORARY, referrer: srcUrl }
+  ]);
+  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"),
+    new Promise((resolve, reject) => do_timeout(300, () => reject(new Error("timeout"))))
+  ]);
+
+  PlacesUtils.favicons.setAndFetchFaviconForPage(Services.io.newURI(destUrl),
+    SMALLPNG_DATA_URI, true, PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, null,
+    Services.scriptSecurityManager.getSystemPrincipal());
+
+  await Assert.rejects(promise, /timeout/);
+});
--- a/toolkit/components/places/tests/favicons/xpcshell.ini
+++ b/toolkit/components/places/tests/favicons/xpcshell.ini
@@ -36,9 +36,12 @@ support-files =
 [test_incremental_vacuum.js]
 [test_moz-anno_favicon_mime_type.js]
 [test_multiple_frames.js]
 [test_page-icon_protocol.js]
 [test_query_result_favicon_changed_on_child.js]
 [test_replaceFaviconData.js]
 [test_replaceFaviconDataFromDataURL.js]
 [test_root_icons.js]
+[test_setAndFetchFaviconForPage.js]
+[test_setAndFetchFaviconForPage_failures.js]
+[test_setAndFetchFaviconForPage_redirects.js]
 [test_svg_favicon.js]
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -42,16 +42,18 @@ XPCOMUtils.defineLazyModuleGetters(this,
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "SMALLPNG_DATA_URI", function() {
   return NetUtil.newURI(
          "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
          "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==");
 });
+const SMALLPNG_DATA_LEN = 67;
+
 XPCOMUtils.defineLazyGetter(this, "SMALLSVG_DATA_URI", function() {
   return NetUtil.newURI(
          "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy5" +
          "3My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBmaWxs" +
          "PSIjNDI0ZTVhIj4NCiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iN" +
          "DQiIHN0cm9rZT0iIzQyNGU1YSIgc3Ryb2tlLXdpZHRoPSIxMSIgZmlsbD" +
          "0ibm9uZSIvPg0KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjI0LjYiIHI9IjY" +
          "uNCIvPg0KICA8cmVjdCB4PSI0NSIgeT0iMzkuOSIgd2lkdGg9IjEwLjEi" +
--- a/toolkit/components/places/tests/history/test_remove.js
+++ b/toolkit/components/places/tests/history/test_remove.js
@@ -26,21 +26,21 @@ add_task(async function test_remove_sing
     let uri = NetUtil.newURI("http://mozilla.com/test_browserhistory/test_remove/" + Math.random());
     let title = "Visit " + Math.random();
     await PlacesTestUtils.addVisits({uri, title});
     Assert.ok(visits_in_database(uri), "History entry created");
 
     let removeArg = await filter(uri);
 
     if (options.addBookmark) {
-      PlacesUtils.bookmarks.insertBookmark(
-        PlacesUtils.unfiledBookmarksFolderId,
-        uri,
-        PlacesUtils.bookmarks.DEFAULT_INDEX,
-        "test bookmark");
+      await PlacesUtils.bookmarks.insert({
+        parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+        url: uri,
+        title: "test bookmark"
+      });
     }
 
     let shouldRemove = !options.addBookmark;
     let observer;
     let promiseObserved = new Promise((resolve, reject) => {
       observer = {
         onBeginUpdateBatch() {},
         onEndUpdateBatch() {},
--- a/toolkit/components/places/tests/history/test_removeMany.js
+++ b/toolkit/components/places/tests/history/test_removeMany.js
@@ -39,21 +39,21 @@ add_task(async function test_remove_many
       onDeleteURICalled: false,
     };
     info("Pushing: " + uri.spec);
     pages.push(page);
 
     await PlacesTestUtils.addVisits(page);
     page.guid = do_get_guid_for_uri(uri);
     if (hasBookmark) {
-      PlacesUtils.bookmarks.insertBookmark(
-        PlacesUtils.unfiledBookmarksFolderId,
-        uri,
-        PlacesUtils.bookmarks.DEFAULT_INDEX,
-        "test bookmark " + i);
+      await PlacesUtils.bookmarks.insert({
+        parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+        url: uri,
+        title: "test bookmark " + i
+      });
     }
     Assert.ok(page_in_database(uri), "Page added");
   }
 
   info("Mixing key types and introducing dangling keys");
   let keys = [];
   for (let i = 0; i < SIZE; ++i) {
     if (i % 4 == 0) {
--- a/toolkit/components/places/tests/history/test_removeVisits.js
+++ b/toolkit/components/places/tests/history/test_removeVisits.js
@@ -1,8 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
 const JS_NOW = Date.now();
 const DB_NOW = JS_NOW * 1000;
 const TEST_URI = uri("http://example.com/");
 
 async function cleanup() {
   await PlacesUtils.history.clear();
   await PlacesUtils.bookmarks.eraseEverything();
   // This is needed to remove place: entries.
@@ -59,21 +62,21 @@ add_task(async function remove_visits_ou
 
   info("Add 10 visits for the URI from way in the past.");
   let visits = [];
   for (let i = 0; i < 10; i++) {
     visits.push({ uri: TEST_URI, visitDate: DB_NOW - 100000 - (i * 1000) });
   }
   await PlacesTestUtils.addVisits(visits);
   info("Bookmark the URI.");
-  PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                       TEST_URI,
-                                       PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                       "bookmark title");
-  await PlacesTestUtils.promiseAsyncUpdates();
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: TEST_URI,
+    title: "bookmark title"
+  });
 
   info("Remove visits using timerange outside the URI's visits.");
   let filter = {
     beginDate: new Date(JS_NOW - 10),
     endDate: new Date(JS_NOW)
   };
   await PlacesUtils.history.removeVisitsByFilter(filter);
   await PlacesTestUtils.promiseAsyncUpdates();
@@ -155,21 +158,21 @@ add_task(async function remove_visits_bo
 
   info("Add 10 visits for the URI from now to 9 usecs in the past.");
   let visits = [];
   for (let i = 0; i < 10; i++) {
     visits.push({ uri: TEST_URI, visitDate: DB_NOW - (i * 1000) });
   }
   await PlacesTestUtils.addVisits(visits);
   info("Bookmark the URI.");
-  PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                       TEST_URI,
-                                       PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                       "bookmark title");
-  await PlacesTestUtils.promiseAsyncUpdates();
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: TEST_URI,
+    title: "bookmark title"
+  });
 
   info("Remove the 5 most recent visits.");
   let filter = {
     beginDate: new Date(JS_NOW - 4),
     endDate: new Date(JS_NOW)
   };
   await PlacesUtils.history.removeVisitsByFilter(filter);
   await PlacesTestUtils.promiseAsyncUpdates();
@@ -243,20 +246,21 @@ add_task(async function remove_all_visit
 
   info("Add some visits for the URI.");
   let visits = [];
   for (let i = 0; i < 10; i++) {
     visits.push({ uri: TEST_URI, visitDate: DB_NOW - (i * 1000) });
   }
   await PlacesTestUtils.addVisits(visits);
   info("Bookmark the URI.");
-  PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                       TEST_URI,
-                                       PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                       "bookmark title");
+  await PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+    url: TEST_URI,
+    title: "bookmark title"
+  });
   await PlacesTestUtils.promiseAsyncUpdates();
   let initialFrecency = frecencyForUrl(TEST_URI);
 
   info("Remove all visits.");
   let filter = {
     beginDate: new Date(JS_NOW - 10),
     endDate: new Date(JS_NOW)
   };
--- a/toolkit/components/places/tests/queries/test_history_queries_tags_liveUpdate.js
+++ b/toolkit/components/places/tests/queries/test_history_queries_tags_liveUpdate.js
@@ -140,46 +140,48 @@ add_task(function visits_searchterm_quer
       PlacesUtils.tagging.tagURI(uri, ["test-tag"]);
       Assert.equal(node.tags, "test-tag");
       PlacesUtils.tagging.untagURI(uri, ["test-tag"]);
       Assert.equal(node.tags, null);
     }
   });
 });
 
-add_task(function pages_searchterm_is_tag_query() {
+add_task(async function pages_searchterm_is_tag_query() {
   let [query, options] = newQueryWithOptions();
   query.searchTerms = "test-tag";
-  testQueryContents(query, options, function(root) {
+  let root;
+  testQueryContents(query, options, rv => root = rv);
+  compareArrayToResult([], root);
+  for (let data of gTestData) {
+    let uri = NetUtil.newURI(data.uri);
+    await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+      url: uri,
+      title: data.title
+    });
+    PlacesUtils.tagging.tagURI(uri, ["test-tag"]);
+    compareArrayToResult([data], root);
+    PlacesUtils.tagging.untagURI(uri, ["test-tag"]);
     compareArrayToResult([], root);
-    gTestData.forEach(function(data) {
-      let uri = NetUtil.newURI(data.uri);
-      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                           uri,
-                                           PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                           data.title);
-      PlacesUtils.tagging.tagURI(uri, ["test-tag"]);
-      compareArrayToResult([data], root);
-      PlacesUtils.tagging.untagURI(uri, ["test-tag"]);
-      compareArrayToResult([], root);
-    });
-  });
+  }
 });
 
-add_task(function visits_searchterm_is_tag_query() {
+add_task(async function visits_searchterm_is_tag_query() {
   let [query, options] = newQueryWithOptions();
   query.searchTerms = "test-tag";
   options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_VISIT;
-  testQueryContents(query, options, function(root) {
+  let root;
+  testQueryContents(query, options, rv => root = rv);
+  compareArrayToResult([], root);
+  for (let data of gTestData) {
+    let uri = NetUtil.newURI(data.uri);
+    await PlacesUtils.bookmarks.insert({
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+      url: uri,
+      title: data.title
+    });
+    PlacesUtils.tagging.tagURI(uri, ["test-tag"]);
+    compareArrayToResult([data], root);
+    PlacesUtils.tagging.untagURI(uri, ["test-tag"]);
     compareArrayToResult([], root);
-    gTestData.forEach(function(data) {
-      let uri = NetUtil.newURI(data.uri);
-      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                           uri,
-                                           PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                           data.title);
-      PlacesUtils.tagging.tagURI(uri, ["test-tag"]);
-      compareArrayToResult([data], root);
-      PlacesUtils.tagging.untagURI(uri, ["test-tag"]);
-      compareArrayToResult([], root);
-    });
-  });
+  }
 });