Bug 977177 - Split ico files into native frames. r=adw draft
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 28 Mar 2017 17:30:28 +0200
changeset 561154 d3015836abd5f7737cab42a3b102b5d718c692d6
parent 561153 f50e0d4d0ab71a0b218c3d7caaa0da149838d148
child 561155 213ff3e89ae978aa1e9bc3993ff51a17481d7789
push id53651
push usermak77@bonardo.net
push dateWed, 12 Apr 2017 09:15:32 +0000
reviewersadw
bugs977177
milestone55.0a1
Bug 977177 - Split ico files into native frames. r=adw When optimizing an ico file, split it into its single resources and pick only the sizes we care about. This also de-dupes same size resources. The migration path doesn't split ico files, since it's a performance hot path and we should try to reduce the I/O load at that time. The worst case is that some icons may not look that much crisp until the next page reload. Note that while the "resource" naming would be more appropriate for ico files, compared to "frame" that is more appropriate for animations, the patch still uses the frame name, cause it's far less generic and can be more easily associated with the concept of a graphical asset. Regardless, it's not exposed in any public API. MozReview-Commit-ID: 3vrGXzJDfjX
toolkit/components/places/FaviconHelpers.cpp
toolkit/components/places/FaviconHelpers.h
toolkit/components/places/nsFaviconService.cpp
toolkit/components/places/tests/favicons/expected-favicon-big48.ico.png
toolkit/components/places/tests/favicons/favicon-multi-frame16.png
toolkit/components/places/tests/favicons/favicon-multi-frame32.png
toolkit/components/places/tests/favicons/favicon-multi-frame64.png
toolkit/components/places/tests/favicons/favicon-multi.ico
toolkit/components/places/tests/favicons/test_favicons_conversions.js
toolkit/components/places/tests/favicons/test_multiple_frames.js
toolkit/components/places/tests/favicons/xpcshell.ini
toolkit/components/places/tests/head_common.js
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -305,17 +305,17 @@ FetchIconInfo(const RefPtr<Database>& aD
   }
 
   nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
     "/* do not warn (bug no: not worth having a compound index) */ "
     "SELECT id, expire_ms, data, width "
     "FROM moz_icons "
     "WHERE fixed_icon_url_hash = hash(fixup_url(:url)) "
       "AND icon_url = :url "
-    "ORDER BY width ASC "
+    "ORDER BY width DESC "
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
   DebugOnly<nsresult> rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"),
                                            _icon.spec);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
@@ -875,35 +875,31 @@ AsyncAssociateIconToPage::Run()
   // Then we can create the relations.
   nsCOMPtr<mozIStorageStatement> stmt;
   stmt = DB->GetStatement(
     "INSERT OR IGNORE INTO moz_icons_to_pages (page_id, icon_id) "
     "VALUES ((SELECT id from moz_pages_w_icons WHERE page_url_hash = hash(:page_url) AND page_url = :page_url), "
             ":icon_id) "
   );
   NS_ENSURE_STATE(stmt);
-  mozStorageStatementScoper scoper(stmt);
-  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
-  rv = stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
-  NS_ENSURE_SUCCESS(rv, rv);
+
+  // For some reason using BindingParamsArray here fails execution, so we must
+  // execute the statements one by one.
+  // In the future we may want to investigate the reasons, sounds like related
+  // to contraints.
   for (const auto& payload : mIcon.payloads) {
+    mozStorageStatementScoper scoper(stmt);
     nsCOMPtr<mozIStorageBindingParams> params;
-    rv = paramsArray->NewBindingParams(getter_AddRefs(params));
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = URIBinder::Bind(params, NS_LITERAL_CSTRING("page_url"), mPage.spec);
+    rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec);
     NS_ENSURE_SUCCESS(rv, rv);
-    rv = params->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), payload.id);
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), payload.id);
     NS_ENSURE_SUCCESS(rv, rv);
-    rv = paramsArray->AddParams(params);
+    rv = stmt->Execute();
     NS_ENSURE_SUCCESS(rv, rv);
   }
-  rv = stmt->BindParameters(paramsArray);
-  NS_ENSURE_SUCCESS(rv, rv);
-  rv = stmt->Execute();
-  NS_ENSURE_SUCCESS(rv, rv);
 
   mIcon.status |= ICON_STATUS_ASSOCIATED;
 
   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);
--- a/toolkit/components/places/FaviconHelpers.h
+++ b/toolkit/components/places/FaviconHelpers.h
@@ -115,16 +115,31 @@ struct PageData
   nsCString spec;
   nsCString bookmarkedSpec;
   nsString revHost;
   bool canAddToHistory; // False for disabled history and unsupported schemas.
   nsCString guid;
 };
 
 /**
+ * Info for a frame.
+ */
+struct FrameData
+{
+  FrameData(uint16_t aIndex, uint16_t aWidth)
+  : index(aIndex)
+  , width(aWidth)
+  {
+  }
+
+  uint16_t index;
+  uint16_t width;
+};
+
+/**
  * Async fetches icon from database or network, associates it with the required
  * page and finally notifies the change.
  */
 class AsyncFetchAndSetIconForPage final : public Runnable
                                         , public nsIStreamListener
                                         , public nsIInterfaceRequestor
                                         , public nsIChannelEventSink
                                         , public mozIPlacesPendingOperation
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -52,16 +52,79 @@ using namespace mozilla::places;
  */
 class ExpireFaviconsStatementCallbackNotifier : public AsyncStatementCallback
 {
 public:
   ExpireFaviconsStatementCallbackNotifier();
   NS_IMETHOD HandleCompletion(uint16_t aReason);
 };
 
+namespace {
+
+/**
+ * Extracts and filters native sizes from the given container, based on the
+ * list of sizes we are supposed to retain.
+ * All calculation is done considering square sizes and the largest side.
+ * In case of multiple frames of the same size, only the first one is retained.
+ */
+nsresult
+GetFramesInfoForContainer(imgIContainer* aContainer,
+                           nsTArray<FrameData>& aFramesInfo) {
+  // Don't extract frames from animated images.
+  bool animated;
+  nsresult rv = aContainer->GetAnimated(&animated);
+  if (NS_FAILED(rv) || !animated) {
+    nsTArray<nsIntSize> nativeSizes;
+    rv = aContainer->GetNativeSizes(nativeSizes);
+    if (NS_SUCCEEDED(rv) && nativeSizes.Length() > 1) {
+      for (uint32_t i = 0; i < nativeSizes.Length(); ++i) {
+        nsIntSize nativeSize = nativeSizes[i];
+        // Only retain square frames.
+        if (nativeSize.width != nativeSize.height) {
+          continue;
+        }
+        // Check if it's one of the sizes we care about.
+        auto end = std::end(sFaviconSizes);
+        uint16_t* matchingSize = std::find(std::begin(sFaviconSizes), end,
+                                          nativeSize.width);
+        if (matchingSize != end) {
+          // We must avoid duped sizes, an image could contain multiple frames of
+          // the same size, but we can only store one. We could use an hashtable,
+          // but considered the average low number of frames, we'll just do a
+          // linear search.
+          bool dupe = false;
+          for (const auto& frameInfo : aFramesInfo) {
+            if (frameInfo.width == *matchingSize) {
+              dupe = true;
+              break;
+            }
+          }
+          if (!dupe) {
+            aFramesInfo.AppendElement(FrameData(i, *matchingSize));
+          }
+        }
+      }
+    }
+  }
+
+  if (aFramesInfo.Length() == 0) {
+    // Always have at least the default size.
+    int32_t width;
+    rv = aContainer->GetWidth(&width);
+    NS_ENSURE_SUCCESS(rv, rv);
+    int32_t height;
+    rv = aContainer->GetHeight(&height);
+    NS_ENSURE_SUCCESS(rv, rv);
+    // For non-square images, pick the largest side.
+    aFramesInfo.AppendElement(FrameData(0, std::max(width, height)));
+  }
+  return NS_OK;
+}
+
+} // namespace
 
 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsFaviconService, gFaviconService)
 
 NS_IMPL_CLASSINFO(nsFaviconService, nullptr, 0, NS_FAVICONSERVICE_CID)
 NS_IMPL_ISUPPORTS_CI(
   nsFaviconService
 , nsIFaviconService
 , mozIAsyncFavicons
@@ -672,57 +735,56 @@ nsFaviconService::OptimizeIconSizes(Icon
   NS_ENSURE_SUCCESS(rv, rv);
 
   // decode image
   nsCOMPtr<imgIContainer> container;
   rv = GetImgTools()->DecodeImageData(stream, payload.mimeType,
                                       getter_AddRefs(container));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  IconPayload newPayload;
-  newPayload.mimeType = NS_LITERAL_CSTRING(PNG_MIME_TYPE);
-  // TODO: for ico files we should extract every single payload.
-  int32_t width;
-  rv = container->GetWidth(&width);
-  NS_ENSURE_SUCCESS(rv, rv);
-  int32_t height;
-  rv = container->GetHeight(&height);
+  // For ICO files, we must evaluate each of the frames we care about.
+  nsTArray<FrameData> framesInfo;
+  rv = GetFramesInfoForContainer(container, framesInfo);
   NS_ENSURE_SUCCESS(rv, rv);
-  // For non-square images, pick the largest side.
-  int32_t originalSize = std::max(width, height);
-  newPayload.width = originalSize;
-  for (uint16_t size : sFaviconSizes) {
-    if (size <= originalSize) {
-      newPayload.width = size;
-      break;
+
+  for (const auto& frameInfo : framesInfo) {
+    IconPayload newPayload;
+    newPayload.mimeType = NS_LITERAL_CSTRING(PNG_MIME_TYPE);
+    newPayload.width = frameInfo.width;
+    for (uint16_t size : sFaviconSizes) {
+      if (size <= frameInfo.width) {
+        newPayload.width = size;
+        break;
+      }
     }
-  }
 
-  // If the original payload is png and the size is the same, no reason to
-  // rescale the image.
-  if (newPayload.mimeType.Equals(payload.mimeType) &&
-      newPayload.width == originalSize) {
-    newPayload.data = payload.data;
-  } else {
-    // scale and recompress
-    nsCOMPtr<nsIInputStream> iconStream;
-    rv = GetImgTools()->EncodeScaledImage(container,
-                                          newPayload.mimeType,
-                                          newPayload.width,
-                                          newPayload.width,
-                                          EmptyString(),
-                                          getter_AddRefs(iconStream));
-    NS_ENSURE_SUCCESS(rv, rv);
-    // Read the stream into the new buffer.
-    rv = NS_ConsumeStream(iconStream, UINT32_MAX, newPayload.data);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+    // If the original payload is png and the size is the same, no reason to
+    // rescale the image.
+    if (newPayload.mimeType.Equals(payload.mimeType) &&
+        newPayload.width == frameInfo.width) {
+      newPayload.data = payload.data;
+    } else {
+      // Scale and recompress.
+      // Since EncodeScaledImage use SYNC_DECODE, it will pick the best frame.
+      nsCOMPtr<nsIInputStream> iconStream;
+      rv = GetImgTools()->EncodeScaledImage(container,
+                                            newPayload.mimeType,
+                                            newPayload.width,
+                                            newPayload.width,
+                                            EmptyString(),
+                                            getter_AddRefs(iconStream));
+      NS_ENSURE_SUCCESS(rv, rv);
+      // Read the stream into the new buffer.
+      rv = NS_ConsumeStream(iconStream, UINT32_MAX, newPayload.data);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
 
-  if (newPayload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
-    aIcon.payloads.AppendElement(newPayload);
+    if (newPayload.data.Length() < nsIFaviconService::MAX_FAVICON_BUFFER_SIZE) {
+      aIcon.payloads.AppendElement(newPayload);
+    }
   }
 
   return NS_OK;
 }
 
 nsresult
 nsFaviconService::GetFaviconDataAsync(const nsCString& aFaviconURI,
                                       mozIStorageStatementCallback *aCallback)
index 0d6c1b14a0306baa609f5d3545b4118a637e4e8c..5f2db533000e2299bed19843ebc7132c10882115
GIT binary patch
literal 2358
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
new file mode 100644
index 0000000000000000000000000000000000000000..9f64ce06b0cf228598569576b2d74096ecab90ec
GIT binary patch
literal 325
zc$@)60lNN)P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0003DNkl<ZIE}s2
zKdwbU6bA6S1v^kGG%AH9NJOQQNGw35pj6p{!~!(5q7fB^4M;RX{E0*?K=8dz;&F2&
z@8(T6-<<QynMvk_Kd#r;fubnh7VrCxIF8}EE|z6E18v*FvMi7!dD~pqHAGQF7>3xk
z?fnD{!(5uDX#&S_===Wj6<C(N6e)^=AP8<J`v}bQ41yrwc^))PgQ}`%n&vqWMG+{9
zLRD1&fFwyk)AaEU9|6zvU>L?J<9QxgmOTT)Fodq_r_3}>_`d%P=(-MqAY6bnP45AU
zqJBpZ1Oa7P9(zfWu&(PF7{?K|ZG)z1IF5s9nvRBH7$D0sEX#svn#l9~IQ#xz_;=t3
XiCU|Fpb3#R00000NkvXXu0mjf0KSNt
new file mode 100644
index 0000000000000000000000000000000000000000..f9406345b1001297a7a51184946c4ed9dacc580b
GIT binary patch
literal 754
zc$@+90uB9%P)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F80008HNkl<ZScUDB
zJxe1|6owCi5ED?4AQ~`=Bti_MO%y?}v=9YN(9R+WC|D>eQz;gTiui$z7_d+aku<5T
z#HkFTq9T5P5Qty@!RFawaaqXR%#1F(TX^6$_kGWM9`4*b!apwg2KWZ}2Ka9Q4i69M
z>FL4m_fubAPc$0k;^M+G&@c=J2L}m-LbSBBFg`xc%gc*1fQ5wx5{U%aY?k}`d$zW=
z@Or(tTrN&ePnBVFb8|E_G!T!+iN#`6R8$}$c)eaOFE8x@oSd96Ha1pZJUct1q@;wd
zt}fG{OeRBre}BP#Z*MOmLStj2J%D5~X=(%*7#N_etjttvYim>X1p)y?giI!54Y0Ph
zrVLtHS)sJFv|!&b40K&r_N7uOM1+Zn32T62wZ6ViYin!4{_N~5cXxNnzMGpHM1-NC
zp|1h7x3{yuzwe|4fXBy2M8x!N*#?-OpC=xVJ01={pR|aWeqLJu$z+mHDD<h}082|t
zhzM6#SIz)7Ha774{fG!6LNFLqzUN}q+}uns7&O%^PvH6aneFXus;jGi{Z!uG-fR!g
zX0wP0J3Bj{1Nf-d*H`N5>JSm8rlxEIbar+UiA0pTqgU{8nVOoKZ28maG;X(>hlhth
z0{{>XhjF{zmcD#GPi18#>2$iN&oRK{<fQU<{CjnDbTBhBW9f4Yu)MrXAP^|(866#^
zudmNmo@0RF;bBHbMwEIgl|s`rrG9;V{WXB9sw!@8Z%wu1<70e2AGuu4RDXYeCy_`L
z2T+9rba!{Ny1Htr<#IV{YHBbHL)jmVMmab*Py;+YJs~30*48pTJ<ZY45$EUU?C$Oo
ziA31k+*F2Znuf>YAsh~yuJZD7JRVO$U%?6Jx{lB1Lqu@7TxgnxuIuFUdFAlEy**;F
knB`hrT(k!G+w~3b1KJ0&e7R7VDgXcg07*qoM6N<$g5+6Q2LJ#7
new file mode 100644
index 0000000000000000000000000000000000000000..9db56436e70b45806255d0ab151b9d7d13d8455d
GIT binary patch
literal 1827
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
R@J|2$002ovPDHLkV1oYabkG0*
new file mode 100644
index 0000000000000000000000000000000000000000..e98adcafebd6f2cff1dce1ff480748d641fdc9af
GIT binary patch
literal 3860
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
I`2V5)KY+v(%>V!Z
--- a/toolkit/components/places/tests/favicons/test_favicons_conversions.js
+++ b/toolkit/components/places/tests/favicons/test_favicons_conversions.js
@@ -24,108 +24,101 @@ var isWindows = ("@mozilla.org/windows-r
  *        If false, the icon should be stored as is.  If true, the expected data
  *        is loaded from a file named "expected-" + aFileName + ".png".
  * @param aVaryOnWindows
  *        Indicates that the content of the converted image can be different on
  *        Windows and should not be checked on that platform.
  * @param aCallback
  *        This function is called after the check finished.
  */
-function checkFaviconDataConversion(aFileName, aFileMimeType, aFileLength,
-                                    aExpectConversion, aVaryOnWindows,
-                                    aCallback) {
+function* checkFaviconDataConversion(aFileName, aFileMimeType, aFileLength,
+                                    aExpectConversion, aVaryOnWindows) {
   let pageURI = NetUtil.newURI("http://places.test/page/" + aFileName);
-  PlacesTestUtils.addVisits({ uri: pageURI, transition: TRANSITION_TYPED }).then(
-    function() {
-      let faviconURI = NetUtil.newURI("http://places.test/icon/" + aFileName);
-      let fileData = readFileOfLength(aFileName, aFileLength);
+  yield PlacesTestUtils.addVisits({ uri: pageURI, transition: TRANSITION_TYPED });
+  let faviconURI = NetUtil.newURI("http://places.test/icon/" + aFileName);
+  let fileData = readFileOfLength(aFileName, aFileLength);
 
-      PlacesUtils.favicons.replaceFaviconData(faviconURI, fileData, fileData.length,
-                                              aFileMimeType);
-      PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, faviconURI, true,
-        PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
-        function CFDC_verify(aURI, aDataLen, aData, aMimeType) {
-          if (!aExpectConversion) {
-            do_check_true(compareArrays(aData, fileData));
-            do_check_eq(aMimeType, aFileMimeType);
-          } else {
-            if (!aVaryOnWindows || !isWindows) {
-              let expectedFile = do_get_file("expected-" + aFileName + ".png");
-              do_check_true(compareArrays(aData, readFileData(expectedFile)));
-            }
-            do_check_eq(aMimeType, "image/png");
+  PlacesUtils.favicons.replaceFaviconData(faviconURI, fileData, fileData.length,
+                                          aFileMimeType);
+  yield new Promise(resolve => {
+    PlacesUtils.favicons.setAndFetchFaviconForPage(pageURI, faviconURI, true,
+      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+      (aURI, aDataLen, aData, aMimeType) => {
+        if (!aExpectConversion) {
+          do_check_true(compareArrays(aData, fileData));
+          do_check_eq(aMimeType, aFileMimeType);
+        } else {
+          if (!aVaryOnWindows || !isWindows) {
+            let expectedFile = do_get_file("expected-" + aFileName + ".png");
+            do_check_true(compareArrays(aData, readFileData(expectedFile)));
           }
-
-          aCallback();
-        }, Services.scriptSecurityManager.getSystemPrincipal());
-    });
-}
-
-// Tests
-
-function run_test() {
-  run_next_test();
+          do_check_eq(aMimeType, "image/png");
+        }
+        resolve();
+      }, Services.scriptSecurityManager.getSystemPrincipal());
+  });
 }
 
-add_test(function test_storing_a_normal_16x16_icon() {
+
+add_task(function* test_storing_a_normal_16x16_icon() {
   // 16x16 png, 286 bytes.
   // optimized: no
-  checkFaviconDataConversion("favicon-normal16.png", "image/png", 286,
-                             false, false, run_next_test);
+  yield checkFaviconDataConversion("favicon-normal16.png", "image/png", 286,
+                                   false, false);
 });
 
-add_test(function test_storing_a_normal_32x32_icon() {
+add_task(function* test_storing_a_normal_32x32_icon() {
   // 32x32 png, 344 bytes.
   // optimized: no
-  checkFaviconDataConversion("favicon-normal32.png", "image/png", 344,
-                             false, false, run_next_test);
+  yield checkFaviconDataConversion("favicon-normal32.png", "image/png", 344,
+                                   false, false);
 });
 
-add_test(function test_storing_a_big_16x16_icon() {
+add_task(function* test_storing_a_big_16x16_icon() {
   //  in: 16x16 ico, 1406 bytes.
   // optimized: yes
-  checkFaviconDataConversion("favicon-big16.ico", "image/x-icon", 1406,
-                             true, false, run_next_test);
+  yield checkFaviconDataConversion("favicon-big16.ico", "image/x-icon", 1406,
+                                   true, false);
 });
 
-add_test(function test_storing_an_oversize_4x4_icon() {
+add_task(function* test_storing_an_oversize_4x4_icon() {
   //  in: 4x4 jpg, 4751 bytes.
   // optimized: yes
-  checkFaviconDataConversion("favicon-big4.jpg", "image/jpeg", 4751,
-                             true, false, run_next_test);
+  yield checkFaviconDataConversion("favicon-big4.jpg", "image/jpeg", 4751,
+                                   true, false);
 });
 
-add_test(function test_storing_an_oversize_32x32_icon() {
+add_task(function* test_storing_an_oversize_32x32_icon() {
   //  in: 32x32 jpg, 3494 bytes.
   // optimized: yes
-  checkFaviconDataConversion("favicon-big32.jpg", "image/jpeg", 3494,
-                             true, true, run_next_test);
+  yield checkFaviconDataConversion("favicon-big32.jpg", "image/jpeg", 3494,
+                                   true, true);
 });
 
-add_test(function test_storing_an_oversize_48x48_icon() {
+add_task(function* test_storing_an_oversize_48x48_icon() {
   //  in: 48x48 ico, 56646 bytes.
   // (howstuffworks.com icon, contains 13 icons with sizes from 16x16 to
   // 48x48 in varying depths)
   // optimized: yes
-  checkFaviconDataConversion("favicon-big48.ico", "image/x-icon", 56646,
-                             true, false, run_next_test);
+  yield checkFaviconDataConversion("favicon-big48.ico", "image/x-icon", 56646,
+                                   true, false);
 });
 
-add_test(function test_storing_an_oversize_64x64_icon() {
+add_task(function* test_storing_an_oversize_64x64_icon() {
   //  in: 64x64 png, 10698 bytes.
   // optimized: yes
-  checkFaviconDataConversion("favicon-big64.png", "image/png", 10698,
-                             true, false, run_next_test);
+  yield checkFaviconDataConversion("favicon-big64.png", "image/png", 10698,
+                                   true, false);
 });
 
-add_test(function test_scaling_an_oversize_160x3_icon() {
+add_task(function* test_scaling_an_oversize_160x3_icon() {
   //  in: 160x3 jpg, 5095 bytes.
   // optimized: yes
-  checkFaviconDataConversion("favicon-scale160x3.jpg", "image/jpeg", 5095,
-                             true, false, run_next_test);
+  yield checkFaviconDataConversion("favicon-scale160x3.jpg", "image/jpeg", 5095,
+                                   true, false);
 });
 
-add_test(function test_scaling_an_oversize_3x160_icon() {
+add_task(function* test_scaling_an_oversize_3x160_icon() {
   //  in: 3x160 jpg, 5059 bytes.
   // optimized: yes
-  checkFaviconDataConversion("favicon-scale3x160.jpg", "image/jpeg", 5059,
-                             true, false, run_next_test);
+  yield checkFaviconDataConversion("favicon-scale3x160.jpg", "image/jpeg", 5059,
+                                   true, false);
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_multiple_frames.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests support for icons with multiple frames (like .ico files).
+ */
+
+add_task(function* () {
+  //  in: 48x48 ico, 56646 bytes.
+  // (howstuffworks.com icon, contains 13 icons with sizes from 16x16 to
+  // 48x48 in varying depths)
+  let pageURI = NetUtil.newURI("http://places.test/page/");
+  yield PlacesTestUtils.addVisits(pageURI);
+  let faviconURI = NetUtil.newURI("http://places.test/icon/favicon-multi.ico");
+  // Fake window.
+  let win = { devicePixelRatio: 1.0 };
+  let icoData = readFileData(do_get_file("favicon-multi.ico"));
+  PlacesUtils.favicons.replaceFaviconData(faviconURI, icoData, icoData.length,
+                                          "image/x-icon");
+  yield setFaviconForPage(pageURI, faviconURI);
+
+  for (let size of [16, 32, 64]) {
+    let file = do_get_file(`favicon-multi-frame${size}.png`);
+    let data = readFileData(file);
+
+    do_print("Check getFaviconDataForPage");
+    let icon = yield getFaviconDataForPage(pageURI, size);
+    Assert.equal(icon.mimeType, "image/png");
+    Assert.deepEqual(icon.data, data);
+
+    do_print("Check moz-anno:favicon protocol");
+    yield compareFavicons(
+      Services.io.newFileURI(file),
+      PlacesUtils.urlWithSizeRef(win, PlacesUtils.favicons.getFaviconLinkForIcon(faviconURI).spec, size)
+    );
+
+    do_print("Check page-icon protocol");
+    yield compareFavicons(
+      Services.io.newFileURI(file),
+      PlacesUtils.urlWithSizeRef(win, "page-icon:" + pageURI.spec, size)
+    );
+  }
+});
--- a/toolkit/components/places/tests/favicons/xpcshell.ini
+++ b/toolkit/components/places/tests/favicons/xpcshell.ini
@@ -9,25 +9,30 @@ support-files =
   expected-favicon-big64.png.png
   expected-favicon-scale160x3.jpg.png
   expected-favicon-scale3x160.jpg.png
   favicon-big16.ico
   favicon-big32.jpg
   favicon-big4.jpg
   favicon-big48.ico
   favicon-big64.png
+  favicon-multi.ico
+  favicon-multi-frame16.png
+  favicon-multi-frame32.png
+  favicon-multi-frame64.png
   favicon-normal16.png
   favicon-normal32.png
   favicon-scale160x3.jpg
   favicon-scale3x160.jpg
 
 [test_expireAllFavicons.js]
 [test_expire_on_new_icons.js]
 [test_favicons_conversions.js]
 [test_favicons_protocols_ref.js]
 [test_getFaviconDataForPage.js]
 [test_getFaviconURLForPage.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_svg_favicon.js]
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -884,16 +884,26 @@ function getFaviconUrlForPage(page, widt
                                           : NetUtil.newURI(new URL(page).href);
   return new Promise(resolve => {
     PlacesUtils.favicons.getFaviconURLForPage(pageURI, iconURI => {
       resolve(iconURI.spec);
     }, width);
   });
 }
 
+function getFaviconDataForPage(page, width = 0) {
+  let pageURI = page instanceof Ci.nsIURI ? page
+                                          : NetUtil.newURI(new URL(page).href);
+  return new Promise(resolve => {
+    PlacesUtils.favicons.getFaviconDataForPage(pageURI, (iconUri, len, data, mimeType) => {
+      resolve({ data, mimeType });
+    }, width);
+  });
+}
+
 /**
  * Asynchronously compares contents from 2 favicon urls.
  */
 function* compareFavicons(icon1, icon2, msg) {
   icon1 = new URL(icon1 instanceof Ci.nsIURI ? icon1.spec : icon1);
   icon2 = new URL(icon2 instanceof Ci.nsIURI ? icon2.spec : icon2);
 
   function getIconData(icon) {