bug 977177 - Fallback to the root domain icon. r=adw draft
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 28 Mar 2017 11:50:53 +0200
changeset 561153 f50e0d4d0ab71a0b218c3d7caaa0da149838d148
parent 561152 628e74ae66c33aa039b5f2e57c38709c2d452b3d
child 561154 d3015836abd5f7737cab42a3b102b5d718c692d6
push id53651
push usermak77@bonardo.net
push dateWed, 12 Apr 2017 09:15:32 +0000
reviewersadw
bugs977177
milestone55.0a1
bug 977177 - Fallback to the root domain icon. r=adw When an icon for a specific page is not available, try to fallback for the root domain icon. This is valid for both getFaviconUrlForPage and getFaviconDataforPage, as well as the page-icon: protocol. MozReview-Commit-ID: JC4cx1PAY38
toolkit/components/places/FaviconHelpers.cpp
toolkit/components/places/FaviconHelpers.h
toolkit/components/places/mozIAsyncFavicons.idl
toolkit/components/places/nsFaviconService.cpp
toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js
toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js
toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js
toolkit/components/places/tests/favicons/test_page-icon_protocol.js
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -364,46 +364,73 @@ FetchIconInfo(const RefPtr<Database>& aD
   }
 
   return NS_OK;
 }
 
 nsresult
 FetchIconPerSpec(const RefPtr<Database>& aDB,
                  const nsACString& aPageSpec,
+                 const nsACString& aPageHost,
                  IconData& aIconData,
                  uint16_t aPreferredWidth)
 {
   MOZ_ASSERT(!aPageSpec.IsEmpty(), "Page spec must not be empty.");
   MOZ_ASSERT(!NS_IsMainThread());
 
+  enum IconType {
+    ePerfectMatch,
+    eRootMatch
+  };
+
   nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
     "/* do not warn (bug no: not worth having a compound index) */ "
-    "SELECT width, icon_url "
+    "SELECT width, icon_url, :type_perfect_match AS type "
     "FROM moz_icons i "
     "JOIN moz_icons_to_pages ON i.id = icon_id "
     "JOIN moz_pages_w_icons p ON p.id = page_id "
     "WHERE page_url_hash = hash(:url) AND page_url = :url "
-    "ORDER BY width DESC "
+    "UNION ALL "
+    "SELECT width, icon_url, :type_root_match AS type " // Fallback root domain icon.
+    "FROM moz_icons i "
+    "WHERE fixed_icon_url_hash = hash(fixup_url(:root_icon_url)) "
+    "ORDER BY type ASC, width DESC "
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
-  nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"),
-                                aPageSpec);
+  nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("type_perfect_match"),
+                                      ePerfectMatch);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("type_root_match"), eRootMatch);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPageSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+  nsAutoCString rootIconFixedUrl(aPageHost);
+  if (!rootIconFixedUrl.IsEmpty()) {
+    rootIconFixedUrl.AppendLiteral("/favicon.ico");
+  }
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("root_icon_url"),
+                                  rootIconFixedUrl);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Return the biggest icon close to the preferred width. It may be bigger
   // or smaller if the preferred width isn't found.
   bool hasResult;
   while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
     int32_t width;
     rv = stmt->GetInt32(0, &width);
+    int32_t t;
+    rv = stmt->GetInt32(2, &t);
     NS_ENSURE_SUCCESS(rv, rv);
-    if (width < aPreferredWidth && !aIconData.spec.IsEmpty()) {
+    IconType type = t == 0 ? ePerfectMatch : eRootMatch;
+    if (!aIconData.spec.IsEmpty() &&
+        (width < aPreferredWidth || type == eRootMatch)) {
+      // We found the best match, or we already found a match so we don't need
+      // to fallback to the root domain icon.
       break;
     }
     rv = stmt->GetUTF8String(1, aIconData.spec);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
@@ -886,34 +913,36 @@ AsyncAssociateIconToPage::Run()
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// AsyncGetFaviconURLForPage
 
 AsyncGetFaviconURLForPage::AsyncGetFaviconURLForPage(
   const nsACString& aPageSpec
+, const nsACString& aPageHost
 , uint16_t aPreferredWidth
 , nsIFaviconDataCallback* aCallback
 ) : mPreferredWidth(aPreferredWidth == 0 ? UINT16_MAX : aPreferredWidth)
   , mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
 {
   MOZ_ASSERT(NS_IsMainThread());
   mPageSpec.Assign(aPageSpec);
+  mPageHost.Assign(aPageHost);
 }
 
 NS_IMETHODIMP
 AsyncGetFaviconURLForPage::Run()
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   RefPtr<Database> DB = Database::GetDatabase();
   NS_ENSURE_STATE(DB);
   IconData iconData;
-  nsresult rv = FetchIconPerSpec(DB, mPageSpec, iconData, mPreferredWidth);
+  nsresult rv = FetchIconPerSpec(DB, mPageSpec, mPageHost, iconData, mPreferredWidth);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Now notify our callback of the icon spec we retrieved, even if empty.
   PageData pageData;
   pageData.spec.Assign(mPageSpec);
 
   nsCOMPtr<nsIRunnable> event =
     new NotifyIconObservers(iconData, pageData, mCallback);
@@ -923,34 +952,36 @@ AsyncGetFaviconURLForPage::Run()
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// AsyncGetFaviconDataForPage
 
 AsyncGetFaviconDataForPage::AsyncGetFaviconDataForPage(
   const nsACString& aPageSpec
+, const nsACString& aPageHost
 ,  uint16_t aPreferredWidth
 , nsIFaviconDataCallback* aCallback
 ) : mPreferredWidth(aPreferredWidth == 0 ? UINT16_MAX : aPreferredWidth)
   , mCallback(new nsMainThreadPtrHolder<nsIFaviconDataCallback>(aCallback))
  {
   MOZ_ASSERT(NS_IsMainThread());
   mPageSpec.Assign(aPageSpec);
+  mPageHost.Assign(aPageHost);
 }
 
 NS_IMETHODIMP
 AsyncGetFaviconDataForPage::Run()
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   RefPtr<Database> DB = Database::GetDatabase();
   NS_ENSURE_STATE(DB);
   IconData iconData;
-  nsresult rv = FetchIconPerSpec(DB, mPageSpec, iconData, mPreferredWidth);
+  nsresult rv = FetchIconPerSpec(DB, mPageSpec, mPageHost, iconData, mPreferredWidth);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!iconData.spec.IsEmpty()) {
     rv = FetchIconInfo(DB, mPreferredWidth, iconData);
     if (NS_FAILED(rv)) {
       iconData.spec.Truncate();
     }
   }
--- a/toolkit/components/places/FaviconHelpers.h
+++ b/toolkit/components/places/FaviconHelpers.h
@@ -209,29 +209,33 @@ class AsyncGetFaviconURLForPage final : 
 public:
   NS_DECL_NSIRUNNABLE
 
   /**
    * Constructor.
    *
    * @param aPageSpec
    *        URL of the page whose favicon's URL we're fetching
+   * @param aPageHost
+   *        Host of the page whose favicon's URL we're fetching
    * @param aCallback
    *        function to be called once finished
    * @param aPreferredWidth
    *        The preferred size for the icon
    */
   AsyncGetFaviconURLForPage(const nsACString& aPageSpec,
+                            const nsACString& aPageHost,
                             uint16_t aPreferredWidth,
                             nsIFaviconDataCallback* aCallback);
 
 private:
   uint16_t mPreferredWidth;
   nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
   nsCString mPageSpec;
+  nsCString mPageHost;
 };
 
 
 /**
  * Asynchronously tries to get the URL and data of a page's favicon, then
  * notifies the given observer.
  */
 class AsyncGetFaviconDataForPage final : public Runnable
@@ -239,30 +243,34 @@ class AsyncGetFaviconDataForPage final :
 public:
   NS_DECL_NSIRUNNABLE
 
   /**
    * Constructor.
    *
    * @param aPageSpec
    *        URL of the page whose favicon URL and data we're fetching
+   * @param aPageHost
+   *        Host of the page whose favicon's URL we're fetching
    * @param aPreferredWidth
    *        The preferred size of the icon.  We will try to return an icon close
    *        to this size.
    * @param aCallback
    *        function to be called once finished
    */
   AsyncGetFaviconDataForPage(const nsACString& aPageSpec,
+                             const nsACString& aPageHost,
                              uint16_t aPreferredWidth,
                              nsIFaviconDataCallback* aCallback);
 
 private:
   uint16_t mPreferredWidth;
   nsMainThreadPtrHandle<nsIFaviconDataCallback> mCallback;
   nsCString mPageSpec;
+  nsCString mPageHost;
 };
 
 class AsyncReplaceFaviconData final : public Runnable
 {
 public:
   NS_DECL_NSIRUNNABLE
 
   explicit AsyncReplaceFaviconData(const IconData& aIcon);
--- a/toolkit/components/places/mozIAsyncFavicons.idl
+++ b/toolkit/components/places/mozIAsyncFavicons.idl
@@ -9,17 +9,17 @@ interface nsIURI;
 interface nsIFaviconDataCallback;
 interface nsIPrincipal;
 interface mozIPlacesPendingOperation;
 
 [scriptable, uuid(a9c81797-9133-4823-b55f-3646e67cfd41)]
 interface mozIAsyncFavicons : nsISupports
 {
   /**
-   * Declares that a given page uses a favicon with the given URI and 
+   * Declares that a given page uses a favicon with the given URI and
    * attempts to fetch and save the icon data by loading the favicon URI
    * through an async network request.
    *
    * If the icon data already exists, we won't try to reload the icon unless
    * aForceReload is true.  Similarly, if the icon is in the failed favicon
    * cache we won't do anything unless aForceReload is true, in which case
    * we'll try to reload the favicon.
    *
@@ -139,22 +139,24 @@ interface mozIAsyncFavicons : nsISupport
    * Retrieves the favicon URI associated to the given page, if any.
    *
    * @param aPageURI
    *        URI of the page whose favicon URI we're looking up.
    * @param aCallback
    *        This callback is always invoked to notify the result of the lookup.
    *        The aURI parameter will be the favicon URI, or null when no favicon
    *        is associated with the page or an error occurred while fetching it.
+   *        aDataLen will be always 0, aData will be an empty array, and
+   *        aMimeType will be an empty string, regardless of whether a favicon
+   *        was found.
    * @param aPreferredWidth
    *        The preferred icon width, 0 for the biggest available.
    *
-   * @note When the callback is invoked, aDataLen will be always 0, aData will
-   *       be an empty array, and aMimeType will be an empty string, regardless
-   *       of whether a favicon is associated with the page.
+   * @note If a favicon specific to this page cannot be found, this will try to
+   *       fallback to the /favicon.ico for the root domain.
    *
    * @see nsIFaviconDataCallback in nsIFaviconService.idl.
    */
   void getFaviconURLForPage(in nsIURI aPageURI,
                             in nsIFaviconDataCallback aCallback,
                             [optional] in unsigned short aPreferredWidth);
 
   /**
@@ -169,15 +171,17 @@ interface mozIAsyncFavicons : nsISupport
    *        parameter will be the favicon URI, or null when no favicon is
    *        associated with the page or an error occurred while fetching it.  If
    *        aURI is not null, the other parameters may contain the favicon data.
    *        However, if no favicon data is currently associated with the favicon
    *        URI, aDataLen will be 0, aData will be an empty array, and aMimeType
    *        will be an empty string.
    * @param aPreferredWidth
    *        The preferred icon width, 0 for the biggest available.
+   * @note If a favicon specific to this page cannot be found, this will try to
+   *       fallback to the /favicon.ico for the root domain.
    *
    * @see nsIFaviconDataCallback in nsIFaviconService.idl.
    */
   void getFaviconDataForPage(in nsIURI aPageURI,
                              in nsIFaviconDataCallback aCallback,
                              [optional] in unsigned short aPreferredWidth);
 };
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -474,19 +474,22 @@ nsFaviconService::GetFaviconURLForPage(n
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_ARG(aPageURI);
   NS_ENSURE_ARG(aCallback);
 
   nsAutoCString pageSpec;
   nsresult rv = aPageURI->GetSpec(pageSpec);
   NS_ENSURE_SUCCESS(rv, rv);
+  nsAutoCString pageHost;
+  // It's expected that some domains may not have a host.
+  Unused << aPageURI->GetHost(pageHost);
 
   RefPtr<AsyncGetFaviconURLForPage> event =
-    new AsyncGetFaviconURLForPage(pageSpec, aPreferredWidth, aCallback);
+    new AsyncGetFaviconURLForPage(pageSpec, pageHost, aPreferredWidth, aCallback);
 
   RefPtr<Database> DB = Database::GetDatabase();
   NS_ENSURE_STATE(DB);
   DB->DispatchToAsyncThread(event);
 
   return NS_OK;
 }
 
@@ -497,19 +500,22 @@ nsFaviconService::GetFaviconDataForPage(
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_ARG(aPageURI);
   NS_ENSURE_ARG(aCallback);
 
   nsAutoCString pageSpec;
   nsresult rv = aPageURI->GetSpec(pageSpec);
   NS_ENSURE_SUCCESS(rv, rv);
+  nsAutoCString pageHost;
+  // It's expected that some domains may not have a host.
+  Unused << aPageURI->GetHost(pageHost);
 
   RefPtr<AsyncGetFaviconDataForPage> event =
-    new AsyncGetFaviconDataForPage(pageSpec, aPreferredWidth, aCallback);
+    new AsyncGetFaviconDataForPage(pageSpec, pageHost, aPreferredWidth, aCallback);
   RefPtr<Database> DB = Database::GetDatabase();
   NS_ENSURE_STATE(DB);
   DB->DispatchToAsyncThread(event);
 
   return NS_OK;
 }
 
 nsresult
--- a/toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js
+++ b/toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js
@@ -1,15 +1,15 @@
 /**
  * This file tests the size ref on the icons protocols.
  */
 
 const PAGE_URL = "http://icon.mozilla.org/";
-const ICON16_URL = "http://mochi.test:8888/tests/toolkit/components/places/tests/browser/favicon-normal16.png";
-const ICON32_URL = "http://mochi.test:8888/tests/toolkit/components/places/tests/browser/favicon-normal32.png";
+const ICON16_URL = "http://places.test/favicon-normal16.png";
+const ICON32_URL = "http://places.test/favicon-normal32.png";
 
 add_task(function* () {
   yield PlacesTestUtils.addVisits(PAGE_URL);
   // Add 2 differently sized favicons for this page.
 
   let data = readFileData(do_get_file("favicon-normal16.png"));
   PlacesUtils.favicons.replaceFaviconData(NetUtil.newURI(ICON16_URL),
                                           data, data.length, "image/png");
--- a/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js
+++ b/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js
@@ -1,54 +1,110 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/**
- * This file tests getFaviconDataForPage.
- */
-
-// Globals
-
 const FAVICON_URI = NetUtil.newURI(do_get_file("favicon-normal32.png"));
 const FAVICON_DATA = readFileData(do_get_file("favicon-normal32.png"));
 const FAVICON_MIMETYPE = "image/png";
-
-// Tests
+const ICON16_URL = "http://places.test/favicon-normal16.png";
+const ICON32_URL = "http://places.test/favicon-normal32.png";
 
-function run_test() {
-  // Check that the favicon loaded correctly before starting the actual tests.
+add_task(function* test_normal() {
   do_check_eq(FAVICON_DATA.length, 344);
-  run_next_test();
-}
-
-add_test(function test_normal() {
   let pageURI = NetUtil.newURI("http://example.com/normal");
 
-  PlacesTestUtils.addVisits(pageURI).then(function() {
+  yield PlacesTestUtils.addVisits(pageURI);
+  yield new Promise(resolve => {
     PlacesUtils.favicons.setAndFetchFaviconForPage(
       pageURI, FAVICON_URI, true,
         PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
         function() {
         PlacesUtils.favicons.getFaviconDataForPage(pageURI,
           function(aURI, aDataLen, aData, aMimeType) {
             do_check_true(aURI.equals(FAVICON_URI));
             do_check_eq(FAVICON_DATA.length, aDataLen);
             do_check_true(compareArrays(FAVICON_DATA, aData));
             do_check_eq(FAVICON_MIMETYPE, aMimeType);
-            run_next_test();
+            resolve();
           });
       }, Services.scriptSecurityManager.getSystemPrincipal());
   });
 });
 
-add_test(function test_missing() {
+add_task(function* test_missing() {
   let pageURI = NetUtil.newURI("http://example.com/missing");
 
-  PlacesUtils.favicons.getFaviconDataForPage(pageURI,
-    function(aURI, aDataLen, aData, aMimeType) {
-      // Check also the expected data types.
-      do_check_true(aURI === null);
-      do_check_true(aDataLen === 0);
-      do_check_true(aData.length === 0);
-      do_check_true(aMimeType === "");
-      run_next_test();
-    });
+  yield new Promise(resolve => {
+    PlacesUtils.favicons.getFaviconDataForPage(pageURI,
+      function(aURI, aDataLen, aData, aMimeType) {
+        // Check also the expected data types.
+        do_check_true(aURI === null);
+        do_check_true(aDataLen === 0);
+        do_check_true(aData.length === 0);
+        do_check_true(aMimeType === "");
+        resolve();
+      });
+  });
 });
+
+add_task(function* test_fallback() {
+  const ROOT_URL = "https://www.example.com/";
+  const ROOT_ICON_URL = ROOT_URL + "favicon.ico";
+  const SUBPAGE_URL = ROOT_URL + "/missing";
+
+  do_print("Set icon for the root");
+  yield PlacesTestUtils.addVisits(ROOT_URL);
+  let data = readFileData(do_get_file("favicon-normal16.png"));
+  PlacesUtils.favicons.replaceFaviconData(NetUtil.newURI(ROOT_ICON_URL),
+                                          data, data.length, "image/png");
+  yield setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
+
+  do_print("check fallback icons");
+  yield new Promise(resolve => {
+    PlacesUtils.favicons.getFaviconDataForPage(NetUtil.newURI(ROOT_URL),
+      (aURI, aDataLen, aData, aMimeType) => {
+        Assert.equal(aURI.spec, ROOT_ICON_URL);
+        Assert.equal(aDataLen, data.length);
+        Assert.deepEqual(aData, data);
+        Assert.equal(aMimeType, "image/png");
+        resolve();
+      });
+  });
+  yield new Promise(resolve => {
+    PlacesUtils.favicons.getFaviconDataForPage(NetUtil.newURI(SUBPAGE_URL),
+      (aURI, aDataLen, aData, aMimeType) => {
+        Assert.equal(aURI.spec, ROOT_ICON_URL);
+        Assert.equal(aDataLen, data.length);
+        Assert.deepEqual(aData, data);
+        Assert.equal(aMimeType, "image/png");
+        resolve();
+      });
+  });
+
+  do_print("Now add a proper icon for the page");
+  yield PlacesTestUtils.addVisits(SUBPAGE_URL);
+  let data32 = readFileData(do_get_file("favicon-normal32.png"));
+  PlacesUtils.favicons.replaceFaviconData(NetUtil.newURI(ICON32_URL),
+                                          data32, data32.length, "image/png");
+  yield setFaviconForPage(SUBPAGE_URL, ICON32_URL);
+
+  do_print("check no fallback icons");
+  yield new Promise(resolve => {
+    PlacesUtils.favicons.getFaviconDataForPage(NetUtil.newURI(ROOT_URL),
+      (aURI, aDataLen, aData, aMimeType) => {
+        Assert.equal(aURI.spec, ROOT_ICON_URL);
+        Assert.equal(aDataLen, data.length);
+        Assert.deepEqual(aData, data);
+        Assert.equal(aMimeType, "image/png");
+        resolve();
+      });
+  });
+  yield new Promise(resolve => {
+    PlacesUtils.favicons.getFaviconDataForPage(NetUtil.newURI(SUBPAGE_URL),
+      (aURI, aDataLen, aData, aMimeType) => {
+        Assert.equal(aURI.spec, ICON32_URL);
+        Assert.equal(aDataLen, data32.length);
+        Assert.deepEqual(aData, data32);
+        Assert.equal(aMimeType, "image/png");
+        resolve();
+      });
+  });
+});
--- a/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js
+++ b/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js
@@ -1,48 +1,76 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-/**
- * This file tests getFaviconURLForPage.
- */
-
-// Tests
+const ICON16_URL = "http://places.test/favicon-normal16.png";
+const ICON32_URL = "http://places.test/favicon-normal32.png";
 
-function run_test() {
-  run_next_test();
-}
-
-add_test(function test_normal() {
+add_task(function* test_normal() {
   let pageURI = NetUtil.newURI("http://example.com/normal");
 
-  PlacesTestUtils.addVisits(pageURI).then(function() {
+  yield PlacesTestUtils.addVisits(pageURI);
+  yield new Promise(resolve => {
     PlacesUtils.favicons.setAndFetchFaviconForPage(
       pageURI, SMALLPNG_DATA_URI, true,
         PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
         function() {
         PlacesUtils.favicons.getFaviconURLForPage(pageURI,
           function(aURI, aDataLen, aData, aMimeType) {
             do_check_true(aURI.equals(SMALLPNG_DATA_URI));
 
             // Check also the expected data types.
             do_check_true(aDataLen === 0);
             do_check_true(aData.length === 0);
             do_check_true(aMimeType === "");
-            run_next_test();
+            resolve();
           });
       }, Services.scriptSecurityManager.getSystemPrincipal());
   });
 });
 
-add_test(function test_missing() {
+add_task(function* test_missing() {
   let pageURI = NetUtil.newURI("http://example.com/missing");
 
-  PlacesUtils.favicons.getFaviconURLForPage(pageURI,
-    function(aURI, aDataLen, aData, aMimeType) {
-      // Check also the expected data types.
-      do_check_true(aURI === null);
-      do_check_true(aDataLen === 0);
-      do_check_true(aData.length === 0);
-      do_check_true(aMimeType === "");
-      run_next_test();
-    });
+  yield new Promise(resolve => {
+    PlacesUtils.favicons.getFaviconURLForPage(pageURI,
+      function(aURI, aDataLen, aData, aMimeType) {
+        // Check also the expected data types.
+        do_check_true(aURI === null);
+        do_check_true(aDataLen === 0);
+        do_check_true(aData.length === 0);
+        do_check_true(aMimeType === "");
+        resolve();
+      });
+  });
 });
+
+add_task(function* test_fallback() {
+  const ROOT_URL = "https://www.example.com/";
+  const ROOT_ICON_URL = ROOT_URL + "favicon.ico";
+  const SUBPAGE_URL = ROOT_URL + "/missing";
+
+  do_print("Set icon for the root");
+  yield PlacesTestUtils.addVisits(ROOT_URL);
+  let data = readFileData(do_get_file("favicon-normal16.png"));
+  PlacesUtils.favicons.replaceFaviconData(NetUtil.newURI(ROOT_ICON_URL),
+                                          data, data.length, "image/png");
+  yield setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
+
+  do_print("check fallback icons");
+  Assert.equal(yield getFaviconUrlForPage(ROOT_URL), ROOT_ICON_URL,
+               "The root should have its favicon");
+  Assert.equal(yield getFaviconUrlForPage(SUBPAGE_URL), ROOT_ICON_URL,
+               "The page should fallback to the root icon");
+
+  do_print("Now add a proper icon for the page");
+  yield PlacesTestUtils.addVisits(SUBPAGE_URL);
+  let data32 = readFileData(do_get_file("favicon-normal32.png"));
+  PlacesUtils.favicons.replaceFaviconData(NetUtil.newURI(ICON32_URL),
+                                          data32, data32.length, "image/png");
+  yield setFaviconForPage(SUBPAGE_URL, ICON32_URL);
+
+  do_print("check no fallback icons");
+  Assert.equal(yield getFaviconUrlForPage(ROOT_URL), ROOT_ICON_URL,
+               "The root should still have its favicon");
+  Assert.equal(yield getFaviconUrlForPage(SUBPAGE_URL), ICON32_URL,
+               "The page should also have its icon");
+});
--- a/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
+++ b/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
@@ -45,22 +45,28 @@ add_task(function* setup() {
 
   gDefaultFavicon = yield fetchIconForSpec(PlacesUtils.favicons.defaultFavicon);
   gFavicon = yield fetchIconForSpec(ICON_DATA);
 });
 
 add_task(function* known_url() {
   let {data, contentType} = yield fetchIconForSpec(TEST_URI.spec);
   Assert.equal(contentType, gFavicon.contentType);
-  Assert.ok(data == gFavicon.data, "Got the favicon data");
+  Assert.deepEqual(data, gFavicon.data, "Got the favicon data");
 });
 
 add_task(function* unknown_url() {
   let {data, contentType} = yield fetchIconForSpec("http://www.moz.org/");
   Assert.equal(contentType, gDefaultFavicon.contentType);
-  Assert.ok(data == gDefaultFavicon.data, "Got the default favicon data");
+  Assert.deepEqual(data, gDefaultFavicon.data, "Got the default favicon data");
 });
 
 add_task(function* invalid_url() {
   let {data, contentType} = yield fetchIconForSpec("test");
   Assert.equal(contentType, gDefaultFavicon.contentType);
   Assert.ok(data == gDefaultFavicon.data, "Got the default favicon data");
 });
+
+add_task(function* subpage_url_fallback() {
+  let {data, contentType} = yield fetchIconForSpec("http://mozilla.org/missing");
+  Assert.equal(contentType, gFavicon.contentType);
+  Assert.deepEqual(data, gFavicon.data, "Got the root favicon data");
+});