summaryrefslogtreecommitdiffstats
path: root/toolkit/components/places/tests/favicons
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/places/tests/favicons')
-rw-r--r--toolkit/components/places/tests/favicons/expected-favicon-animated16.png.pngbin0 -> 360 bytes
-rw-r--r--toolkit/components/places/tests/favicons/expected-favicon-big16.ico.pngbin0 -> 520 bytes
-rw-r--r--toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.pngbin0 -> 3026 bytes
-rw-r--r--toolkit/components/places/tests/favicons/expected-favicon-big4.jpg.pngbin0 -> 87 bytes
-rw-r--r--toolkit/components/places/tests/favicons/expected-favicon-big48.ico.pngbin0 -> 2973 bytes
-rw-r--r--toolkit/components/places/tests/favicons/expected-favicon-big64.png.pngbin0 -> 10698 bytes
-rw-r--r--toolkit/components/places/tests/favicons/expected-favicon-scale160x3.jpg.pngbin0 -> 887 bytes
-rw-r--r--toolkit/components/places/tests/favicons/expected-favicon-scale3x160.jpg.pngbin0 -> 1057 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-animated16.pngbin0 -> 1791 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-big16.icobin0 -> 1406 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-big32.jpgbin0 -> 3494 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-big4.jpgbin0 -> 4751 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-big48.icobin0 -> 56646 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-big64.pngbin0 -> 10698 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-multi-frame16.pngbin0 -> 412 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-multi-frame32.pngbin0 -> 935 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-multi-frame64.pngbin0 -> 2125 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-multi.icobin0 -> 3860 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-normal16.pngbin0 -> 286 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-normal32.pngbin0 -> 344 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-scale160x3.jpgbin0 -> 5095 bytes
-rw-r--r--toolkit/components/places/tests/favicons/favicon-scale3x160.jpgbin0 -> 5059 bytes
-rw-r--r--toolkit/components/places/tests/favicons/head_favicons.js81
-rw-r--r--toolkit/components/places/tests/favicons/noise.pngbin0 -> 159476 bytes
-rw-r--r--toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js88
-rw-r--r--toolkit/components/places/tests/favicons/test_copyFavicons.js166
-rw-r--r--toolkit/components/places/tests/favicons/test_expireAllFavicons.js38
-rw-r--r--toolkit/components/places/tests/favicons/test_expire_migrated_icons.js30
-rw-r--r--toolkit/components/places/tests/favicons/test_expire_on_new_icons.js151
-rw-r--r--toolkit/components/places/tests/favicons/test_favicons_conversions.js192
-rw-r--r--toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js114
-rw-r--r--toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js131
-rw-r--r--toolkit/components/places/tests/favicons/test_getFaviconLinkForIcon.js35
-rw-r--r--toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js101
-rw-r--r--toolkit/components/places/tests/favicons/test_heavy_favicon.js36
-rw-r--r--toolkit/components/places/tests/favicons/test_incremental_vacuum.js48
-rw-r--r--toolkit/components/places/tests/favicons/test_multiple_frames.js46
-rw-r--r--toolkit/components/places/tests/favicons/test_page-icon_protocol.js243
-rw-r--r--toolkit/components/places/tests/favicons/test_query_result_favicon_changed_on_child.js153
-rw-r--r--toolkit/components/places/tests/favicons/test_replaceFaviconData.js395
-rw-r--r--toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js537
-rw-r--r--toolkit/components/places/tests/favicons/test_root_icons.js246
-rw-r--r--toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js123
-rw-r--r--toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js156
-rw-r--r--toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_redirects.js89
-rw-r--r--toolkit/components/places/tests/favicons/test_svg_favicon.js34
-rw-r--r--toolkit/components/places/tests/favicons/xpcshell.toml72
47 files changed, 3305 insertions, 0 deletions
diff --git a/toolkit/components/places/tests/favicons/expected-favicon-animated16.png.png b/toolkit/components/places/tests/favicons/expected-favicon-animated16.png.png
new file mode 100644
index 0000000000..22f825c500
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/expected-favicon-animated16.png.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big16.ico.png b/toolkit/components/places/tests/favicons/expected-favicon-big16.ico.png
new file mode 100644
index 0000000000..fa61cc5046
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/expected-favicon-big16.ico.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png
new file mode 100644
index 0000000000..42640cbb53
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big4.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-big4.jpg.png
new file mode 100644
index 0000000000..81d1b8ae19
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/expected-favicon-big4.jpg.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big48.ico.png b/toolkit/components/places/tests/favicons/expected-favicon-big48.ico.png
new file mode 100644
index 0000000000..7983889098
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/expected-favicon-big48.ico.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png b/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png
new file mode 100644
index 0000000000..2756cf0cb3
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/expected-favicon-scale160x3.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-scale160x3.jpg.png
new file mode 100644
index 0000000000..fc464f8e99
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/expected-favicon-scale160x3.jpg.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/expected-favicon-scale3x160.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-scale3x160.jpg.png
new file mode 100644
index 0000000000..c1412038a3
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/expected-favicon-scale3x160.jpg.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-animated16.png b/toolkit/components/places/tests/favicons/favicon-animated16.png
new file mode 100644
index 0000000000..8913387fc9
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-animated16.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-big16.ico b/toolkit/components/places/tests/favicons/favicon-big16.ico
new file mode 100644
index 0000000000..d44438903b
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-big16.ico
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-big32.jpg b/toolkit/components/places/tests/favicons/favicon-big32.jpg
new file mode 100644
index 0000000000..b2131bf0c1
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-big32.jpg
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-big4.jpg b/toolkit/components/places/tests/favicons/favicon-big4.jpg
new file mode 100644
index 0000000000..b84fcd35a6
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-big4.jpg
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-big48.ico b/toolkit/components/places/tests/favicons/favicon-big48.ico
new file mode 100644
index 0000000000..f22522411d
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-big48.ico
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-big64.png b/toolkit/components/places/tests/favicons/favicon-big64.png
new file mode 100644
index 0000000000..2756cf0cb3
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-big64.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-multi-frame16.png b/toolkit/components/places/tests/favicons/favicon-multi-frame16.png
new file mode 100644
index 0000000000..519e08cc21
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-multi-frame16.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-multi-frame32.png b/toolkit/components/places/tests/favicons/favicon-multi-frame32.png
new file mode 100644
index 0000000000..5ae61de789
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-multi-frame32.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-multi-frame64.png b/toolkit/components/places/tests/favicons/favicon-multi-frame64.png
new file mode 100644
index 0000000000..57123f351b
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-multi-frame64.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-multi.ico b/toolkit/components/places/tests/favicons/favicon-multi.ico
new file mode 100644
index 0000000000..e98adcafeb
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-multi.ico
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-normal16.png b/toolkit/components/places/tests/favicons/favicon-normal16.png
new file mode 100644
index 0000000000..62b69a3d03
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-normal16.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-normal32.png b/toolkit/components/places/tests/favicons/favicon-normal32.png
new file mode 100644
index 0000000000..5535363c94
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-normal32.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-scale160x3.jpg b/toolkit/components/places/tests/favicons/favicon-scale160x3.jpg
new file mode 100644
index 0000000000..422ee7ea0b
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-scale160x3.jpg
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/favicon-scale3x160.jpg b/toolkit/components/places/tests/favicons/favicon-scale3x160.jpg
new file mode 100644
index 0000000000..e8514966a0
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/favicon-scale3x160.jpg
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/head_favicons.js b/toolkit/components/places/tests/favicons/head_favicons.js
new file mode 100644
index 0000000000..d8109c66e0
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/head_favicons.js
@@ -0,0 +1,81 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Import common head.
+{
+ /* import-globals-from ../head_common.js */
+ let commonFile = do_get_file("../head_common.js", false);
+ let uri = Services.io.newFileURI(commonFile);
+ Services.scriptloader.loadSubScript(uri.spec, this);
+}
+
+// Put any other stuff relative to this test folder below.
+
+const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+/**
+ * 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,
+ aCallback
+) {
+ PlacesUtils.favicons.getFaviconDataForPage(
+ aPageURI,
+ async function (aURI, aDataLen, aData, aMimeType) {
+ Assert.equal(aExpectedMimeType, aMimeType);
+ Assert.ok(compareArrays(aExpectedData, aData));
+ await check_guid_for_uri(aPageURI);
+ aCallback();
+ }
+ );
+}
+
+/**
+ * Checks that the given page has no associated favicon.
+ *
+ * @param aPageURI
+ * nsIURI object for the page to check.
+ * @param aCallback
+ * This function is called after the check finished.
+ */
+function checkFaviconMissingForPage(aPageURI, aCallback) {
+ PlacesUtils.favicons.getFaviconURLForPage(
+ aPageURI,
+ function (aURI, aDataLen, aData, aMimeType) {
+ Assert.ok(aURI === null);
+ aCallback();
+ }
+ );
+}
+
+function promiseFaviconMissingForPage(aPageURI) {
+ return new Promise(resolve => checkFaviconMissingForPage(aPageURI, resolve));
+}
+
+function promiseFaviconChanged(aExpectedPageURI, aExpectedFaviconURI) {
+ return new Promise(resolve => {
+ PlacesTestUtils.waitForNotification("favicon-changed", async events => {
+ for (let e of events) {
+ if (e.url == aExpectedPageURI.spec) {
+ Assert.equal(e.faviconUrl, aExpectedFaviconURI.spec);
+ await check_guid_for_uri(aExpectedPageURI, e.pageGuid);
+ resolve();
+ }
+ }
+ });
+ });
+}
diff --git a/toolkit/components/places/tests/favicons/noise.png b/toolkit/components/places/tests/favicons/noise.png
new file mode 100644
index 0000000000..d6876295cd
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/noise.png
Binary files differ
diff --git a/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js b/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js
new file mode 100644
index 0000000000..1c95d63f94
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_cached-favicon_mime_type.js
@@ -0,0 +1,88 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 ensures that the mime type is set for cached-favicon channels of favicons
+ * properly. Added with work in bug 481227.
+ */
+
+const testFaviconData =
+ "data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%06%00%00%00%1F%F3%FFa%00%00%00%04gAMA%00%00%AF%C87%05%8A%E9%00%00%00%19tEXtSoftware%00Adobe%20ImageReadyq%C9e%3C%00%00%01%D6IDATx%DAb%FC%FF%FF%3F%03%25%00%20%80%98%909%EF%DF%BFg%EF%EC%EC%FC%AD%AC%AC%FC%DF%95%91%F1%BF%89%89%C9%7F%20%FF%D7%EA%D5%AB%B7%DF%BBwO%16%9B%01%00%01%C4%00r%01%08%9F9s%C6%CD%D8%D8%F8%BF%0B%03%C3%FF3%40%BC%0A%88%EF%02q%1A%10%BB%40%F1%AAU%ABv%C1%D4%C30%40%00%81%89%993g%3E%06%1A%F6%3F%14%AA%11D%97%03%F1%7Fc%08%0D%E2%2B))%FD%17%04%89%A1%19%00%10%40%0C%D00%F8%0F3%00%C8%F8%BF%1B%E4%0Ac%88a%E5%60%17%19%FF%0F%0D%0D%05%1B%02v%D9%DD%BB%0A0%03%00%02%08%AC%B9%A3%A3%E3%17%03%D4v%90%01%EF%18%106%C3%0Cz%07%C5%BB%A1%DE%82y%07%20%80%A0%A6%08B%FCn%0C1%60%26%D4%20d%C3VA%C3%06%26%BE%0A%EA-%80%00%82%B9%E0%F7L4%0D%EF%90%F8%C6%60%2F%0A%82%BD%01%13%07%0700%D0%01%02%88%11%E4%02P%B41%DC%BB%C7%D0%014%0D%E8l%06W%20%06%BA%88%A1%1C%1AS%15%40%7C%16%CA6.%2Fgx%BFg%0F%83%CB%D9%B3%0C%7B%80%7C%80%00%02%BB%00%E8%9F%ED%20%1B%3A%A0%A6%9F%81%DA%DC%01%C5%B0%80%ED%80%FA%BF%BC%BC%FC%3F%83%12%90%9D%96%F6%1F%20%80%18%DE%BD%7B%C7%0E%8E%05AD%20%FEGr%A6%A0%A0%E0%7F%25P%80%02%9D%0F%D28%13%18%23%C6%C0%B0%02E%3D%C8%F5%00%01%04%8F%05P%A8%BA%40my%87%E4%12c%A8%8D%20%8B%D0%D3%00%08%03%04%10%9C%01R%E4%82d%3B%C8%A0%99%C6%90%90%C6%A5%19%84%01%02%08%9E%17%80%C9x%F7%7B%A0%DBVC%F9%A0%C0%5C%7D%16%2C%CE%00%F4%C6O%5C%99%09%20%800L%04y%A5%03%1A%95%A0%80%05%05%14.%DBA%18%20%80%18)%CD%CE%00%01%06%00%0C'%94%C7%C0k%C9%2C%00%00%00%00IEND%AEB%60%82";
+const testIconURI = uri("http://mozilla.org/favicon.png");
+
+function streamListener(aExpectedContentType) {
+ this._expectedContentType = aExpectedContentType;
+ this.done = Promise.withResolvers();
+}
+streamListener.prototype = {
+ onStartRequest() {},
+ onStopRequest(aRequest, aContext, aStatusCode) {
+ let channel = aRequest.QueryInterface(Ci.nsIChannel);
+ Assert.equal(
+ channel.contentType,
+ this._expectedContentType,
+ "The channel content type is the expected one"
+ );
+ this.done.resolve();
+ },
+ onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
+ aRequest.cancel(Cr.NS_ERROR_ABORT);
+ throw Components.Exception("", Cr.NS_ERROR_ABORT);
+ },
+};
+
+add_task(async function () {
+ info("Test that the default icon has the right content type.");
+ let channel = NetUtil.newChannel({
+ uri: PlacesUtils.favicons.defaultFavicon,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON,
+ });
+ let listener = new streamListener(
+ PlacesUtils.favicons.defaultFaviconMimeType
+ );
+ channel.asyncOpen(listener);
+ await listener.done.promise;
+});
+
+add_task(async function () {
+ info(
+ "Test icon URI that we don't know anything about. Will serve the default icon."
+ );
+ let channel = NetUtil.newChannel({
+ uri: PlacesUtils.favicons.getFaviconLinkForIcon(testIconURI).spec,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON,
+ });
+ let listener = new streamListener(
+ PlacesUtils.favicons.defaultFaviconMimeType
+ );
+ channel.asyncOpen(listener);
+ await listener.done.promise;
+});
+
+add_task(async function () {
+ info("Test that the content type of a favicon we add is correct.");
+ let testURI = uri("http://mozilla.org/");
+ // Add the data before opening
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ testIconURI,
+ testFaviconData,
+ 0,
+ systemPrincipal
+ );
+ await PlacesTestUtils.addVisits(testURI);
+ await setFaviconForPage(testURI, testIconURI);
+ // Open the channel
+ let channel = NetUtil.newChannel({
+ uri: PlacesUtils.favicons.getFaviconLinkForIcon(testIconURI).spec,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON,
+ });
+ let listener = new streamListener("image/png");
+ channel.asyncOpen(listener);
+ await listener.done.promise;
+});
diff --git a/toolkit/components/places/tests/favicons/test_copyFavicons.js b/toolkit/components/places/tests/favicons/test_copyFavicons.js
new file mode 100644
index 0000000000..687b799a4b
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_copyFavicons.js
@@ -0,0 +1,166 @@
+const TEST_URI1 = Services.io.newURI("http://mozilla.com/");
+const TEST_URI2 = Services.io.newURI("http://places.com/");
+const TEST_URI3 = Services.io.newURI("http://bookmarked.com/");
+const LOAD_NON_PRIVATE = PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE;
+const LOAD_PRIVATE = PlacesUtils.favicons.FAVICON_LOAD_PRIVATE;
+
+function copyFavicons(source, dest, inPrivate) {
+ return new Promise(resolve => {
+ PlacesUtils.favicons.copyFavicons(
+ source,
+ dest,
+ inPrivate ? LOAD_PRIVATE : LOAD_NON_PRIVATE,
+ resolve
+ );
+ });
+}
+
+function promisePageChanged(url) {
+ return PlacesTestUtils.waitForNotification("favicon-changed", events =>
+ events.some(e => e.url == url)
+ );
+}
+
+add_task(async function test_copyFavicons_inputcheck() {
+ Assert.throws(
+ () => PlacesUtils.favicons.copyFavicons(null, TEST_URI2, LOAD_PRIVATE),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+ Assert.throws(
+ () => PlacesUtils.favicons.copyFavicons(TEST_URI1, null, LOAD_PRIVATE),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+ Assert.throws(
+ () => PlacesUtils.favicons.copyFavicons(TEST_URI1, TEST_URI2, 3),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+ Assert.throws(
+ () => PlacesUtils.favicons.copyFavicons(TEST_URI1, TEST_URI2, -1),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+ Assert.throws(
+ () => PlacesUtils.favicons.copyFavicons(TEST_URI1, TEST_URI2, null),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+});
+
+add_task(async function test_copyFavicons_noop() {
+ info("Unknown uris");
+ Assert.equal(
+ await copyFavicons(TEST_URI1, TEST_URI2, false),
+ null,
+ "Icon should not have been copied"
+ );
+
+ info("Unknown dest uri");
+ await PlacesTestUtils.addVisits(TEST_URI1);
+ Assert.equal(
+ await copyFavicons(TEST_URI1, TEST_URI2, false),
+ null,
+ "Icon should not have been copied"
+ );
+
+ info("Unknown dest uri");
+ await PlacesTestUtils.addVisits(TEST_URI1);
+ Assert.equal(
+ await copyFavicons(TEST_URI1, TEST_URI2, false),
+ null,
+ "Icon should not have been copied"
+ );
+
+ info("Unknown dest uri, source has icon");
+ await setFaviconForPage(TEST_URI1, SMALLPNG_DATA_URI);
+ Assert.equal(
+ await copyFavicons(TEST_URI1, TEST_URI2, false),
+ null,
+ "Icon should not have been copied"
+ );
+
+ info("Known uris, source has icon, private");
+ await PlacesTestUtils.addVisits(TEST_URI2);
+ Assert.equal(
+ await copyFavicons(TEST_URI1, TEST_URI2, true),
+ null,
+ "Icon should not have been copied"
+ );
+
+ PlacesUtils.favicons.expireAllFavicons();
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_copyFavicons() {
+ info("Normal copy across 2 pages");
+ await PlacesTestUtils.addVisits(TEST_URI1);
+ await setFaviconForPage(TEST_URI1, SMALLPNG_DATA_URI);
+ await setFaviconForPage(TEST_URI1, SMALLSVG_DATA_URI);
+ await PlacesTestUtils.addVisits(TEST_URI2);
+ let promiseChange = promisePageChanged(TEST_URI2.spec);
+ Assert.equal(
+ (await copyFavicons(TEST_URI1, TEST_URI2, false)).spec,
+ SMALLSVG_DATA_URI.spec,
+ "Icon should have been copied"
+ );
+ await promiseChange;
+ Assert.equal(
+ await getFaviconUrlForPage(TEST_URI2, 1),
+ SMALLPNG_DATA_URI.spec,
+ "Small icon found"
+ );
+ Assert.equal(
+ await getFaviconUrlForPage(TEST_URI2),
+ SMALLSVG_DATA_URI.spec,
+ "Large icon found"
+ );
+
+ info("Private copy to a bookmarked page");
+ await PlacesUtils.bookmarks.insert({
+ url: TEST_URI3,
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+ });
+ promiseChange = promisePageChanged(TEST_URI3.spec);
+ Assert.equal(
+ (await copyFavicons(TEST_URI1, TEST_URI3, true)).spec,
+ SMALLSVG_DATA_URI.spec,
+ "Icon should have been copied"
+ );
+ await promiseChange;
+ Assert.equal(
+ await getFaviconUrlForPage(TEST_URI3, 1),
+ SMALLPNG_DATA_URI.spec,
+ "Small icon found"
+ );
+ Assert.equal(
+ await getFaviconUrlForPage(TEST_URI3),
+ SMALLSVG_DATA_URI.spec,
+ "Large icon found"
+ );
+
+ PlacesUtils.favicons.expireAllFavicons();
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_copyFavicons_overlap() {
+ info("Copy to a page that has one of the favicons already");
+ await PlacesTestUtils.addVisits(TEST_URI1);
+ await setFaviconForPage(TEST_URI1, SMALLPNG_DATA_URI);
+ await setFaviconForPage(TEST_URI1, SMALLSVG_DATA_URI);
+ await PlacesTestUtils.addVisits(TEST_URI2);
+ await setFaviconForPage(TEST_URI2, SMALLPNG_DATA_URI);
+ let promiseChange = promisePageChanged(TEST_URI2.spec);
+ Assert.equal(
+ (await copyFavicons(TEST_URI1, TEST_URI2, false)).spec,
+ SMALLSVG_DATA_URI.spec,
+ "Icon should have been copied"
+ );
+ await promiseChange;
+ Assert.equal(
+ await getFaviconUrlForPage(TEST_URI2, 1),
+ SMALLPNG_DATA_URI.spec,
+ "Small icon found"
+ );
+ Assert.equal(
+ await getFaviconUrlForPage(TEST_URI2),
+ SMALLSVG_DATA_URI.spec,
+ "Large icon found"
+ );
+});
diff --git a/toolkit/components/places/tests/favicons/test_expireAllFavicons.js b/toolkit/components/places/tests/favicons/test_expireAllFavicons.js
new file mode 100644
index 0000000000..73c3ca6e4b
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_expireAllFavicons.js
@@ -0,0 +1,38 @@
+/**
+ * This file tests that favicons are correctly expired by expireAllFavicons.
+ */
+
+"use strict";
+
+const TEST_PAGE_URI = NetUtil.newURI("http://example.com/");
+const BOOKMARKED_PAGE_URI = NetUtil.newURI("http://example.com/bookmarked");
+
+add_task(async function test_expireAllFavicons() {
+ // Add a visited page.
+ await PlacesTestUtils.addVisits({
+ uri: TEST_PAGE_URI,
+ transition: TRANSITION_TYPED,
+ });
+
+ // Set a favicon for our test page.
+ await setFaviconForPage(TEST_PAGE_URI, SMALLPNG_DATA_URI);
+
+ // Add a page with a bookmark.
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: BOOKMARKED_PAGE_URI,
+ title: "Test bookmark",
+ });
+
+ // Set a favicon for our bookmark.
+ await setFaviconForPage(BOOKMARKED_PAGE_URI, SMALLPNG_DATA_URI);
+
+ // Start expiration only after data has been saved in the database.
+ let promise = promiseTopicObserved(PlacesUtils.TOPIC_FAVICONS_EXPIRED);
+ PlacesUtils.favicons.expireAllFavicons();
+ await promise;
+
+ // Check that the favicons for the pages we added were removed.
+ await promiseFaviconMissingForPage(TEST_PAGE_URI);
+ await promiseFaviconMissingForPage(BOOKMARKED_PAGE_URI);
+});
diff --git a/toolkit/components/places/tests/favicons/test_expire_migrated_icons.js b/toolkit/components/places/tests/favicons/test_expire_migrated_icons.js
new file mode 100644
index 0000000000..00516a2a0b
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_expire_migrated_icons.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests that favicons migrated from a previous profile, having a 0
+ * expiration, will be properly expired when fetching new ones.
+ */
+
+add_task(async function test_storing_a_normal_16x16_icon() {
+ const PAGE_URL = "http://places.test";
+ await PlacesTestUtils.addVisits(PAGE_URL);
+ await setFaviconForPage(PAGE_URL, SMALLPNG_DATA_URI);
+
+ // Now set expiration to 0 and change the payload.
+ info("Set expiration to 0 and replace favicon data");
+ await PlacesUtils.withConnectionWrapper("Change favicons payload", db => {
+ return db.execute(`UPDATE moz_icons SET expire_ms = 0, data = "test"`);
+ });
+
+ let { data, mimeType } = await getFaviconDataForPage(PAGE_URL);
+ Assert.equal(mimeType, "image/png");
+ Assert.deepEqual(
+ data,
+ "test".split("").map(c => c.charCodeAt(0))
+ );
+
+ info("Refresh favicon");
+ await setFaviconForPage(PAGE_URL, SMALLPNG_DATA_URI, false);
+ await compareFavicons("page-icon:" + PAGE_URL, SMALLPNG_DATA_URI);
+});
diff --git a/toolkit/components/places/tests/favicons/test_expire_on_new_icons.js b/toolkit/components/places/tests/favicons/test_expire_on_new_icons.js
new file mode 100644
index 0000000000..d5a7c42ba3
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_expire_on_new_icons.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ * https://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests that adding new icons for a page expired old ones.
+ */
+
+add_task(async function test_expire_associated() {
+ const TEST_URL = "http://mozilla.com/";
+ await PlacesTestUtils.addVisits(TEST_URL);
+ const TEST_URL2 = "http://test.mozilla.com/";
+ await PlacesTestUtils.addVisits(TEST_URL2);
+
+ let favicons = [
+ {
+ name: "favicon-normal16.png",
+ mimeType: "image/png",
+ expired: true,
+ },
+ {
+ name: "favicon-normal32.png",
+ mimeType: "image/png",
+ },
+ {
+ name: "favicon-big64.png",
+ mimeType: "image/png",
+ },
+ ];
+
+ for (let icon of favicons) {
+ let data = readFileData(do_get_file(icon.name));
+ PlacesUtils.favicons.replaceFaviconData(
+ NetUtil.newURI(TEST_URL + icon.name),
+ data,
+ icon.mimeType
+ );
+ await setFaviconForPage(TEST_URL, TEST_URL + icon.name);
+ if (icon.expired) {
+ await expireIconRelationsForPage(TEST_URL);
+ // Add the same icon to another page.
+ PlacesUtils.favicons.replaceFaviconData(
+ NetUtil.newURI(TEST_URL + icon.name),
+ data,
+ icon.mimeType,
+ icon.expire
+ );
+ await setFaviconForPage(TEST_URL2, TEST_URL + icon.name);
+ }
+ }
+
+ // Only the second and the third icons should have survived.
+ Assert.equal(
+ await getFaviconUrlForPage(TEST_URL, 16),
+ TEST_URL + favicons[1].name,
+ "Should retrieve the 32px icon, not the 16px one."
+ );
+ Assert.equal(
+ await getFaviconUrlForPage(TEST_URL, 64),
+ TEST_URL + favicons[2].name,
+ "Should retrieve the 64px icon"
+ );
+
+ // The expired icon for page 2 should have survived.
+ Assert.equal(
+ await getFaviconUrlForPage(TEST_URL2, 16),
+ TEST_URL + favicons[0].name,
+ "Should retrieve the expired 16px icon"
+ );
+});
+
+add_task(async function test_expire_root() {
+ async function countEntries(tablename) {
+ await PlacesTestUtils.promiseAsyncUpdates();
+ let db = await PlacesUtils.promiseDBConnection();
+ let rows = await db.execute("SELECT * FROM " + tablename);
+ return rows.length;
+ }
+
+ // Clear the database.
+ let promise = promiseTopicObserved(PlacesUtils.TOPIC_FAVICONS_EXPIRED);
+ PlacesUtils.favicons.expireAllFavicons();
+ await promise;
+
+ Assert.equal(await countEntries("moz_icons"), 0, "There should be no icons");
+
+ let pageURI = NetUtil.newURI("http://root.mozilla.com/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ // Insert an expired icon.
+ let iconURI = NetUtil.newURI(pageURI.spec + "favicon-normal16.png");
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ iconURI,
+ SMALLPNG_DATA_URI.spec,
+ 0,
+ systemPrincipal
+ );
+ await setFaviconForPage(pageURI, iconURI);
+ Assert.equal(
+ await countEntries("moz_icons_to_pages"),
+ 1,
+ "There should be 1 association"
+ );
+ // Set an expired time on the icon-page relation.
+ await expireIconRelationsForPage(pageURI.spec);
+
+ // Now insert a new root icon.
+ let rootIconURI = NetUtil.newURI(pageURI.spec + "favicon.ico");
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ rootIconURI,
+ SMALLPNG_DATA_URI.spec,
+ 0,
+ systemPrincipal
+ );
+ await setFaviconForPage(pageURI, rootIconURI);
+
+ // Only the root icon should have survived.
+ Assert.equal(
+ await getFaviconUrlForPage(pageURI, 16),
+ rootIconURI.spec,
+ "Should retrieve the root icon."
+ );
+ Assert.equal(
+ await countEntries("moz_icons_to_pages"),
+ 0,
+ "There should be no associations"
+ );
+});
+
+async function expireIconRelationsForPage(url) {
+ // Set an expired time on the icon-page relation.
+ await PlacesUtils.withConnectionWrapper("expireFavicon", async db => {
+ await db.execute(
+ `
+ UPDATE moz_icons_to_pages SET expire_ms = 0
+ WHERE page_id = (SELECT id FROM moz_pages_w_icons WHERE page_url = :url)
+ `,
+ { url }
+ );
+ // Also ensure the icon is not expired, here we should only replace entries
+ // based on their association expiration, not the icon expiration.
+ let count = (
+ await db.execute(
+ `
+ SELECT count(*) FROM moz_icons
+ WHERE expire_ms < strftime('%s','now','localtime','utc') * 1000
+ `
+ )
+ )[0].getResultByIndex(0);
+ Assert.equal(count, 0, "All the icons should have future expiration");
+ });
+}
diff --git a/toolkit/components/places/tests/favicons/test_favicons_conversions.js b/toolkit/components/places/tests/favicons/test_favicons_conversions.js
new file mode 100644
index 0000000000..28a0fffb7f
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_favicons_conversions.js
@@ -0,0 +1,192 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests the image conversions done by the favicon service.
+ */
+
+// Globals
+
+// The pixel values we get on Windows are sometimes +/- 1 value compared to
+// other platforms, so we need to skip some image content tests.
+var isWindows = "@mozilla.org/windows-registry-key;1" in Cc;
+
+/**
+ * Checks the conversion of the given test image file.
+ *
+ * @param aFileName
+ * File that contains the favicon image, located in the test folder.
+ * @param aFileMimeType
+ * MIME type of the image contained in the file.
+ * @param aFileLength
+ * Expected length of the file.
+ * @param aExpectConversion
+ * 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.
+ */
+async function checkFaviconDataConversion(
+ aFileName,
+ aFileMimeType,
+ aFileLength,
+ aExpectConversion,
+ aVaryOnWindows
+) {
+ let pageURI = NetUtil.newURI("http://places.test/page/" + aFileName);
+ await 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, aFileMimeType);
+ await new Promise(resolve => {
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ pageURI,
+ faviconURI,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ (aURI, aDataLen, aData, aMimeType) => {
+ if (!aExpectConversion) {
+ Assert.ok(compareArrays(aData, fileData));
+ Assert.equal(aMimeType, aFileMimeType);
+ } else {
+ if (!aVaryOnWindows || !isWindows) {
+ let expectedFile = do_get_file("expected-" + aFileName + ".png");
+ Assert.ok(compareArrays(aData, readFileData(expectedFile)));
+ }
+ Assert.equal(aMimeType, "image/png");
+ }
+ resolve();
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+}
+
+add_task(async function test_storing_a_normal_16x16_icon() {
+ // 16x16 png, 286 bytes.
+ // optimized: no
+ await checkFaviconDataConversion(
+ "favicon-normal16.png",
+ "image/png",
+ 286,
+ false,
+ false
+ );
+});
+
+add_task(async function test_storing_a_normal_32x32_icon() {
+ // 32x32 png, 344 bytes.
+ // optimized: no
+ await checkFaviconDataConversion(
+ "favicon-normal32.png",
+ "image/png",
+ 344,
+ false,
+ false
+ );
+});
+
+add_task(async function test_storing_a_big_16x16_icon() {
+ // in: 16x16 ico, 1406 bytes.
+ // optimized: yes
+ await checkFaviconDataConversion(
+ "favicon-big16.ico",
+ "image/x-icon",
+ 1406,
+ true,
+ false
+ );
+});
+
+add_task(async function test_storing_an_oversize_4x4_icon() {
+ // in: 4x4 jpg, 4751 bytes.
+ // optimized: yes
+ await checkFaviconDataConversion(
+ "favicon-big4.jpg",
+ "image/jpeg",
+ 4751,
+ true,
+ false
+ );
+});
+
+add_task(async function test_storing_an_oversize_32x32_icon() {
+ // in: 32x32 jpg, 3494 bytes.
+ // optimized: yes
+ await checkFaviconDataConversion(
+ "favicon-big32.jpg",
+ "image/jpeg",
+ 3494,
+ true,
+ true
+ );
+});
+
+add_task(async 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
+ await checkFaviconDataConversion(
+ "favicon-big48.ico",
+ "image/x-icon",
+ 56646,
+ true,
+ false
+ );
+});
+
+add_task(async function test_storing_an_oversize_64x64_icon() {
+ // in: 64x64 png, 10698 bytes.
+ // optimized: yes
+ await checkFaviconDataConversion(
+ "favicon-big64.png",
+ "image/png",
+ 10698,
+ true,
+ false
+ );
+});
+
+add_task(async function test_scaling_an_oversize_160x3_icon() {
+ // in: 160x3 jpg, 5095 bytes.
+ // optimized: yes
+ await checkFaviconDataConversion(
+ "favicon-scale160x3.jpg",
+ "image/jpeg",
+ 5095,
+ true,
+ false
+ );
+});
+
+add_task(async function test_scaling_an_oversize_3x160_icon() {
+ // in: 3x160 jpg, 5059 bytes.
+ // optimized: yes
+ await checkFaviconDataConversion(
+ "favicon-scale3x160.jpg",
+ "image/jpeg",
+ 5059,
+ true,
+ false
+ );
+});
+
+add_task(async function test_animated_16x16_icon() {
+ // in: 16x16 apng, 1791 bytes.
+ // optimized: yes
+ await checkFaviconDataConversion(
+ "favicon-animated16.png",
+ "image/png",
+ 1791,
+ true,
+ false
+ );
+});
diff --git a/toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js b/toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js
new file mode 100644
index 0000000000..aa0241a3d2
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_favicons_protocols_ref.js
@@ -0,0 +1,114 @@
+/**
+ * This file tests the size ref on the icons protocols.
+ */
+
+const PAGE_URL = "http://icon.mozilla.org/";
+const ICON16_URL = "http://places.test/favicon-normal16.png";
+const ICON32_URL = "http://places.test/favicon-normal32.png";
+
+add_task(async function () {
+ await PlacesTestUtils.addVisits(PAGE_URL);
+ // Add 2 differently sized favicons for this page.
+
+ let data = readFileData(do_get_file("favicon-normal16.png"));
+ PlacesUtils.favicons.replaceFaviconData(
+ Services.io.newURI(ICON16_URL),
+ data,
+ "image/png"
+ );
+ await setFaviconForPage(PAGE_URL, ICON16_URL);
+ data = readFileData(do_get_file("favicon-normal32.png"));
+ PlacesUtils.favicons.replaceFaviconData(
+ Services.io.newURI(ICON32_URL),
+ data,
+ "image/png"
+ );
+ await setFaviconForPage(PAGE_URL, ICON32_URL);
+
+ const PAGE_ICON_URL = "page-icon:" + PAGE_URL;
+
+ await compareFavicons(
+ PAGE_ICON_URL,
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Not specifying a ref should return the bigger icon"
+ );
+ // Fake window object.
+ let win = { devicePixelRatio: 1.0 };
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 16),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON16_URL)),
+ "Size=16 should return the 16px icon"
+ );
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 32),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Size=32 should return the 32px icon"
+ );
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 33),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Size=33 should return the 32px icon"
+ );
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 17),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Size=17 should return the 32px icon"
+ );
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 1),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON16_URL)),
+ "Size=1 should return the 16px icon"
+ );
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 0),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Size=0 should return the bigger icon"
+ );
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, -1),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Invalid size should return the bigger icon"
+ );
+
+ // Add the icon also for the page with ref.
+ await PlacesTestUtils.addVisits(PAGE_URL + "#other§=12");
+ await setFaviconForPage(PAGE_URL + "#other§=12", ICON16_URL, false);
+ await setFaviconForPage(PAGE_URL + "#other§=12", ICON32_URL, false);
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL + "#other§=12", 16),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON16_URL)),
+ "Pre-existing refs should be retained"
+ );
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL + "#other§=12", 32),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Pre-existing refs should be retained"
+ );
+
+ // If the ref-ed url is unknown, should still try to fetch icon for the unref-ed url.
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL + "#randomstuff", 32),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Non-existing refs should be ignored"
+ );
+
+ win = { devicePixelRatio: 1.1 };
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 16),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Size=16 with HIDPI should return the 32px icon"
+ );
+ await compareFavicons(
+ PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 32),
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON32_URL)),
+ "Size=32 with HIDPI should return the 32px icon"
+ );
+
+ // Check setting a different default preferred size works.
+ PlacesUtils.favicons.setDefaultIconURIPreferredSize(16);
+ await compareFavicons(
+ PAGE_ICON_URL,
+ PlacesUtils.favicons.getFaviconLinkForIcon(Services.io.newURI(ICON16_URL)),
+ "Not specifying a ref should return the set default size icon"
+ );
+});
diff --git a/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js b/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js
new file mode 100644
index 0000000000..80f498f33f
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+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";
+const ICON32_URL = "http://places.test/favicon-normal32.png";
+
+add_task(async function test_normal() {
+ Assert.equal(FAVICON_DATA.length, 344);
+ let pageURI = NetUtil.newURI("http://example.com/normal");
+
+ await PlacesTestUtils.addVisits(pageURI);
+ await 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) {
+ Assert.ok(aURI.equals(FAVICON_URI));
+ Assert.equal(FAVICON_DATA.length, aDataLen);
+ Assert.ok(compareArrays(FAVICON_DATA, aData));
+ Assert.equal(FAVICON_MIMETYPE, aMimeType);
+ resolve();
+ }
+ );
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+});
+
+add_task(async function test_missing() {
+ let pageURI = NetUtil.newURI("http://example.com/missing");
+
+ await new Promise(resolve => {
+ PlacesUtils.favicons.getFaviconDataForPage(
+ pageURI,
+ function (aURI, aDataLen, aData, aMimeType) {
+ // Check also the expected data types.
+ Assert.ok(aURI === null);
+ Assert.ok(aDataLen === 0);
+ Assert.ok(aData.length === 0);
+ Assert.ok(aMimeType === "");
+ resolve();
+ }
+ );
+ });
+});
+
+add_task(async function test_fallback() {
+ const ROOT_URL = "https://www.example.com/";
+ const ROOT_ICON_URL = ROOT_URL + "favicon.ico";
+ const SUBPAGE_URL = ROOT_URL + "/missing";
+
+ info("Set icon for the root");
+ await PlacesTestUtils.addVisits(ROOT_URL);
+ let data = readFileData(do_get_file("favicon-normal16.png"));
+ PlacesUtils.favicons.replaceFaviconData(
+ NetUtil.newURI(ROOT_ICON_URL),
+ data,
+ "image/png"
+ );
+ await setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
+
+ info("check fallback icons");
+ await 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();
+ }
+ );
+ });
+ await 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();
+ }
+ );
+ });
+
+ info("Now add a proper icon for the page");
+ await PlacesTestUtils.addVisits(SUBPAGE_URL);
+ let data32 = readFileData(do_get_file("favicon-normal32.png"));
+ PlacesUtils.favicons.replaceFaviconData(
+ NetUtil.newURI(ICON32_URL),
+ data32,
+ "image/png"
+ );
+ await setFaviconForPage(SUBPAGE_URL, ICON32_URL);
+
+ info("check no fallback icons");
+ await 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();
+ }
+ );
+ });
+ await 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();
+ }
+ );
+ });
+});
diff --git a/toolkit/components/places/tests/favicons/test_getFaviconLinkForIcon.js b/toolkit/components/places/tests/favicons/test_getFaviconLinkForIcon.js
new file mode 100644
index 0000000000..e60a59bd80
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_getFaviconLinkForIcon.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test getFaviconLinkForIcon API.
+ */
+
+add_task(async function test_basic() {
+ // Check these protocols are pass-through.
+ for (let protocol of ["http://", "https://"]) {
+ let url = PlacesUtils.favicons.getFaviconLinkForIcon(
+ Services.io.newURI(protocol + "test/test.png")
+ ).spec;
+ Assert.equal(url, "cached-favicon:" + protocol + "test/test.png");
+ }
+});
+
+add_task(async function test_directRequestProtocols() {
+ // Check these protocols are pass-through.
+ for (let protocol of [
+ "about:",
+ "cached-favicon:",
+ "chrome://",
+ "data:",
+ "file:///",
+ "moz-page-thumb://",
+ "page-icon:",
+ "resource://",
+ ]) {
+ let url = PlacesUtils.favicons.getFaviconLinkForIcon(
+ Services.io.newURI(protocol + "test/test.png")
+ ).spec;
+ Assert.equal(url, protocol + "test/test.png");
+ }
+});
diff --git a/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js b/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js
new file mode 100644
index 0000000000..e8f459cb08
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const ICON32_URL = "http://places.test/favicon-normal32.png";
+
+add_task(async function test_normal() {
+ let pageURI = NetUtil.newURI("http://example.com/normal");
+
+ await PlacesTestUtils.addVisits(pageURI);
+ await 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) {
+ Assert.ok(aURI.equals(SMALLPNG_DATA_URI));
+
+ // Check also the expected data types.
+ Assert.ok(aDataLen === 0);
+ Assert.ok(aData.length === 0);
+ Assert.ok(aMimeType === "");
+ resolve();
+ }
+ );
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+});
+
+add_task(async function test_missing() {
+ let pageURI = NetUtil.newURI("http://example.com/missing");
+
+ await new Promise(resolve => {
+ PlacesUtils.favicons.getFaviconURLForPage(
+ pageURI,
+ function (aURI, aDataLen, aData, aMimeType) {
+ // Check also the expected data types.
+ Assert.ok(aURI === null);
+ Assert.ok(aDataLen === 0);
+ Assert.ok(aData.length === 0);
+ Assert.ok(aMimeType === "");
+ resolve();
+ }
+ );
+ });
+});
+
+add_task(async function test_fallback() {
+ const ROOT_URL = "https://www.example.com/";
+ const ROOT_ICON_URL = ROOT_URL + "favicon.ico";
+ const SUBPAGE_URL = ROOT_URL + "/missing";
+
+ info("Set icon for the root");
+ await PlacesTestUtils.addVisits(ROOT_URL);
+ let data = readFileData(do_get_file("favicon-normal16.png"));
+ PlacesUtils.favicons.replaceFaviconData(
+ NetUtil.newURI(ROOT_ICON_URL),
+ data,
+ "image/png"
+ );
+ await setFaviconForPage(ROOT_URL, ROOT_ICON_URL);
+
+ info("check fallback icons");
+ Assert.equal(
+ await getFaviconUrlForPage(ROOT_URL),
+ ROOT_ICON_URL,
+ "The root should have its favicon"
+ );
+ Assert.equal(
+ await getFaviconUrlForPage(SUBPAGE_URL),
+ ROOT_ICON_URL,
+ "The page should fallback to the root icon"
+ );
+
+ info("Now add a proper icon for the page");
+ await PlacesTestUtils.addVisits(SUBPAGE_URL);
+ let data32 = readFileData(do_get_file("favicon-normal32.png"));
+ PlacesUtils.favicons.replaceFaviconData(
+ NetUtil.newURI(ICON32_URL),
+ data32,
+ "image/png"
+ );
+ await setFaviconForPage(SUBPAGE_URL, ICON32_URL);
+
+ info("check no fallback icons");
+ Assert.equal(
+ await getFaviconUrlForPage(ROOT_URL),
+ ROOT_ICON_URL,
+ "The root should still have its favicon"
+ );
+ Assert.equal(
+ await getFaviconUrlForPage(SUBPAGE_URL),
+ ICON32_URL,
+ "The page should also have its icon"
+ );
+});
diff --git a/toolkit/components/places/tests/favicons/test_heavy_favicon.js b/toolkit/components/places/tests/favicons/test_heavy_favicon.js
new file mode 100644
index 0000000000..09adcaf6fa
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_heavy_favicon.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests a png with a large file size that can't fit MAX_FAVICON_BUFFER_SIZE,
+ * it should be downsized until it can be stored, rather than thrown away.
+ */
+
+add_task(async function () {
+ let file = do_get_file("noise.png");
+ let icon = {
+ file,
+ uri: NetUtil.newURI(file),
+ data: readFileData(file),
+ mimetype: "image/png",
+ };
+
+ // If this should fail, it means MAX_FAVICON_BUFFER_SIZE has been made bigger
+ // than this icon. For this test to make sense the icon shoul always be
+ // bigger than MAX_FAVICON_BUFFER_SIZE. Please update the icon!
+ Assert.ok(
+ icon.data.length > Ci.nsIFaviconService.MAX_FAVICON_BUFFER_SIZE,
+ "The test icon file size must be larger than Ci.nsIFaviconService.MAX_FAVICON_BUFFER_SIZE"
+ );
+
+ let pageURI = uri("http://foo.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ PlacesUtils.favicons.replaceFaviconData(icon.uri, icon.data, icon.mimetype);
+ await setFaviconForPage(pageURI, icon.uri);
+ Assert.equal(
+ await getFaviconUrlForPage(pageURI),
+ icon.uri.spec,
+ "A resampled version of the icon should be stored"
+ );
+});
diff --git a/toolkit/components/places/tests/favicons/test_incremental_vacuum.js b/toolkit/components/places/tests/favicons/test_incremental_vacuum.js
new file mode 100644
index 0000000000..ab93121d47
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_incremental_vacuum.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests incremental vacuum of the favicons database.
+
+const { PlacesDBUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PlacesDBUtils.sys.mjs"
+);
+
+add_task(async function () {
+ let icon = {
+ file: do_get_file("noise.png"),
+ mimetype: "image/png",
+ };
+
+ let url = "http://foo.bar/";
+ await PlacesTestUtils.addVisits(url);
+ for (let i = 0; i < 10; ++i) {
+ let iconUri = NetUtil.newURI("http://mozilla.org/" + i);
+ let data = readFileData(icon.file);
+ PlacesUtils.favicons.replaceFaviconData(iconUri, data, icon.mimetype);
+ await setFaviconForPage(url, iconUri);
+ }
+
+ let promise = TestUtils.topicObserved("places-favicons-expired");
+ PlacesUtils.favicons.expireAllFavicons();
+ await promise;
+
+ let db = await PlacesUtils.promiseDBConnection();
+ let state = (
+ await db.execute("PRAGMA favicons.auto_vacuum")
+ )[0].getResultByIndex(0);
+ Assert.equal(state, 2, "auto_vacuum should be incremental");
+ let count = (
+ await db.execute("PRAGMA favicons.freelist_count")
+ )[0].getResultByIndex(0);
+ info(`Found ${count} freelist pages`);
+ let log = await PlacesDBUtils.incrementalVacuum();
+ info(log);
+ let newCount = (
+ await db.execute("PRAGMA favicons.freelist_count")
+ )[0].getResultByIndex(0);
+ info(`Found ${newCount} freelist pages`);
+ Assert.ok(
+ newCount < count,
+ "The number of freelist pages should have reduced"
+ );
+});
diff --git a/toolkit/components/places/tests/favicons/test_multiple_frames.js b/toolkit/components/places/tests/favicons/test_multiple_frames.js
new file mode 100644
index 0000000000..5c7f585715
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_multiple_frames.js
@@ -0,0 +1,46 @@
+/* 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(async 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/");
+ await 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, "image/x-icon");
+ await setFaviconForPage(pageURI, faviconURI);
+
+ for (let size of [16, 32, 64]) {
+ let file = do_get_file(`favicon-multi-frame${size}.png`);
+ let data = readFileData(file);
+
+ info("Check getFaviconDataForPage");
+ let icon = await getFaviconDataForPage(pageURI, size);
+ Assert.equal(icon.mimeType, "image/png");
+ Assert.deepEqual(icon.data, data);
+
+ info("Check cached-favicon protocol");
+ await compareFavicons(
+ Services.io.newFileURI(file),
+ PlacesUtils.urlWithSizeRef(
+ win,
+ PlacesUtils.favicons.getFaviconLinkForIcon(faviconURI).spec,
+ size
+ )
+ );
+
+ info("Check page-icon protocol");
+ await compareFavicons(
+ Services.io.newFileURI(file),
+ PlacesUtils.urlWithSizeRef(win, "page-icon:" + pageURI.spec, size)
+ );
+ }
+});
diff --git a/toolkit/components/places/tests/favicons/test_page-icon_protocol.js b/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
new file mode 100644
index 0000000000..932040bafb
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_page-icon_protocol.js
@@ -0,0 +1,243 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ICON_DATAURL =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
+const TEST_URI = NetUtil.newURI("http://mozilla.org/");
+const ICON_URI = NetUtil.newURI("http://mozilla.org/favicon.ico");
+
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+
+const PAGE_ICON_TEST_URLS = [
+ "page-icon:http://example.com/",
+ "page-icon:http://a-site-never-before-seen.test",
+ // For the following, the page-icon protocol is expected to successfully
+ // return the default favicon.
+ "page-icon:test",
+ "page-icon:",
+ "page-icon:chrome://something.html",
+ "page-icon:foo://bar/baz",
+];
+
+XPCShellContentUtils.init(this);
+
+const HTML = String.raw`<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+ Hello from example.com!
+</body>
+</html>`;
+
+const server = XPCShellContentUtils.createHttpServer({
+ hosts: ["example.com"],
+});
+
+server.registerPathHandler("/", (request, response) => {
+ response.setHeader("Content-Type", "text/html");
+ response.write(HTML);
+});
+
+function fetchIconForSpec(spec) {
+ return new Promise((resolve, reject) => {
+ NetUtil.asyncFetch(
+ {
+ uri: NetUtil.newURI(spec),
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE_FAVICON,
+ },
+ (input, status, request) => {
+ if (!Components.isSuccessCode(status)) {
+ reject(new Error("unable to load icon"));
+ return;
+ }
+
+ try {
+ let data = NetUtil.readInputStreamToString(input, input.available());
+ let contentType = request.QueryInterface(Ci.nsIChannel).contentType;
+ input.close();
+ resolve({ data, contentType });
+ } catch (ex) {
+ reject(ex);
+ }
+ }
+ );
+ });
+}
+
+var gDefaultFavicon;
+var gFavicon;
+
+add_task(async function setup() {
+ await PlacesTestUtils.addVisits(TEST_URI);
+
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ ICON_URI,
+ ICON_DATAURL,
+ (Date.now() + 8640000) * 1000,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ await new Promise(resolve => {
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ TEST_URI,
+ ICON_URI,
+ false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ resolve,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ gDefaultFavicon = await fetchIconForSpec(
+ PlacesUtils.favicons.defaultFavicon.spec
+ );
+ gFavicon = await fetchIconForSpec(ICON_DATAURL);
+});
+
+add_task(async function known_url() {
+ let { data, contentType } = await fetchIconForSpec(
+ "page-icon:" + TEST_URI.spec
+ );
+ Assert.equal(contentType, gFavicon.contentType);
+ Assert.deepEqual(data, gFavicon.data, "Got the favicon data");
+});
+
+add_task(async function unknown_url() {
+ let { data, contentType } = await fetchIconForSpec(
+ "page-icon:http://www.moz.org/"
+ );
+ Assert.equal(contentType, gDefaultFavicon.contentType);
+ Assert.deepEqual(data, gDefaultFavicon.data, "Got the default favicon data");
+});
+
+add_task(async function invalid_url() {
+ let { data, contentType } = await fetchIconForSpec("page-icon:test");
+ Assert.equal(contentType, gDefaultFavicon.contentType);
+ Assert.ok(data == gDefaultFavicon.data, "Got the default favicon data");
+});
+
+add_task(async function subpage_url_fallback() {
+ let { data, contentType } = await fetchIconForSpec(
+ "page-icon:http://mozilla.org/missing"
+ );
+ Assert.equal(contentType, gFavicon.contentType);
+ Assert.deepEqual(data, gFavicon.data, "Got the root favicon data");
+});
+
+add_task(async function svg_icon() {
+ let faviconURI = NetUtil.newURI("http://places.test/favicon.svg");
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ faviconURI,
+ SMALLSVG_DATA_URI.spec,
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ await setFaviconForPage(TEST_URI, faviconURI);
+ let svgIcon = await fetchIconForSpec(SMALLSVG_DATA_URI.spec);
+ info(svgIcon.contentType);
+ let pageIcon = await fetchIconForSpec("page-icon:" + TEST_URI.spec);
+ Assert.equal(svgIcon.contentType, pageIcon.contentType);
+ Assert.deepEqual(svgIcon.data, pageIcon.data, "Got the root favicon data");
+});
+
+add_task(async function page_with_ref() {
+ for (let url of [
+ "http://places.test.ref/#myref",
+ "http://places.test.ref/#!&b=16",
+ "http://places.test.ref/#",
+ ]) {
+ await PlacesTestUtils.addVisits(url);
+ await setFaviconForPage(url, ICON_URI, false);
+ let { data, contentType } = await fetchIconForSpec("page-icon:" + url);
+ Assert.equal(contentType, gFavicon.contentType);
+ Assert.deepEqual(data, gFavicon.data, "Got the favicon data");
+ await PlacesUtils.history.remove(url);
+ }
+});
+
+/**
+ * Tests that page-icon does not work in a normal content process.
+ */
+add_task(async function page_content_process() {
+ let contentPage = await XPCShellContentUtils.loadContentPage(
+ "http://example.com/",
+ {
+ remote: true,
+ }
+ );
+ Assert.notEqual(
+ contentPage.browsingContext.currentRemoteType,
+ "privilegedabout"
+ );
+
+ await contentPage.spawn([PAGE_ICON_TEST_URLS], async URLS => {
+ // We expect each of these URLs to produce an error event when
+ // we attempt to load them in this process type.
+ /* global content */
+ for (let url of URLS) {
+ let img = content.document.createElement("img");
+ img.src = url;
+ let imgPromise = new Promise((resolve, reject) => {
+ img.addEventListener("error", e => {
+ Assert.ok(true, "Got expected load error.");
+ resolve();
+ });
+ img.addEventListener("load", e => {
+ Assert.ok(false, "Did not expect a successful load.");
+ reject();
+ });
+ });
+ content.document.body.appendChild(img);
+ await imgPromise;
+ }
+ });
+
+ await contentPage.close();
+});
+
+/**
+ * Tests that page-icon does work for privileged about content process
+ */
+add_task(async function page_privileged_about_content_process() {
+ // about:certificate loads in the privileged about content process.
+ let contentPage = await XPCShellContentUtils.loadContentPage(
+ "about:certificate",
+ {
+ remote: true,
+ }
+ );
+ Assert.equal(
+ contentPage.browsingContext.currentRemoteType,
+ "privilegedabout"
+ );
+
+ await contentPage.spawn([PAGE_ICON_TEST_URLS], async URLS => {
+ // We expect each of these URLs to load correctly in this process
+ // type.
+ for (let url of URLS) {
+ let img = content.document.createElement("img");
+ img.src = url;
+ let imgPromise = new Promise((resolve, reject) => {
+ img.addEventListener("error", e => {
+ Assert.ok(false, "Did not expect an error. ");
+ reject();
+ });
+ img.addEventListener("load", e => {
+ Assert.ok(true, "Got expected load event.");
+ resolve();
+ });
+ });
+ content.document.body.appendChild(img);
+ await imgPromise;
+ }
+ });
+
+ await contentPage.close();
+});
diff --git a/toolkit/components/places/tests/favicons/test_query_result_favicon_changed_on_child.js b/toolkit/components/places/tests/favicons/test_query_result_favicon_changed_on_child.js
new file mode 100644
index 0000000000..4e5c55e50a
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_query_result_favicon_changed_on_child.js
@@ -0,0 +1,153 @@
+/**
+ * Test for bug 451499 <https://bugzilla.mozilla.org/show_bug.cgi?id=451499>:
+ * Wrong folder icon appears on queries.
+ */
+
+"use strict";
+
+add_task(async function test_query_result_favicon_changed_on_child() {
+ // Bookmark our test page, so it will appear in the query resultset.
+ const PAGE_URI = Services.io.newURI("http://example.com/test_query_result");
+ await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "test_bookmark",
+ url: PAGE_URI,
+ });
+
+ // Get the last 10 bookmarks added to the menu or the toolbar.
+ let query = PlacesUtils.history.getNewQuery();
+ query.setParents([
+ PlacesUtils.bookmarks.menuGuid,
+ PlacesUtils.bookmarks.toolbarGuid,
+ ]);
+
+ let options = PlacesUtils.history.getNewQueryOptions();
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS;
+ options.maxResults = 10;
+ options.excludeQueries = 1;
+ options.sortingMode = options.SORT_BY_DATE_DESCENDING;
+
+ let result = PlacesUtils.history.executeQuery(query, options);
+ let resultObserver = {
+ containerStateChanged(aContainerNode, aOldState, aNewState) {
+ if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
+ // We set a favicon on PAGE_URI while the container is open. The
+ // favicon for the page must have data associated with it in order for
+ // the icon changed notifications to be sent, so we use a valid image
+ // data URI.
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ PAGE_URI,
+ SMALLPNG_DATA_URI,
+ false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ }
+ },
+ nodeIconChanged(aNode) {
+ if (PlacesUtils.nodeIsContainer(aNode)) {
+ do_throw(
+ "The icon should be set only for the page," +
+ " not for the containing query."
+ );
+ }
+ },
+ };
+ Object.setPrototypeOf(resultObserver, NavHistoryResultObserver.prototype);
+ result.addObserver(resultObserver);
+
+ // Open the container and wait for containerStateChanged. We should start
+ // observing before setting |containerOpen| as that's caused by the
+ // setAndFetchFaviconForPage() call caused by the containerStateChanged
+ // observer above.
+ let promise = promiseFaviconChanged(PAGE_URI, SMALLPNG_DATA_URI);
+ result.root.containerOpen = true;
+ await promise;
+
+ // We must wait for the asynchronous database thread to finish the
+ // operation, and then for the main thread to process any pending
+ // notifications that came from the asynchronous thread, before we can be
+ // sure that nodeIconChanged was not invoked in the meantime.
+ await PlacesTestUtils.promiseAsyncUpdates();
+ result.removeObserver(resultObserver);
+
+ // Free the resources immediately.
+ result.root.containerOpen = false;
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+});
+
+add_task(
+ async function test_query_result_favicon_changed_not_affect_lastmodified() {
+ // Bookmark our test page, so it will appear in the query resultset.
+ const PAGE_URI2 = Services.io.newURI(
+ "http://example.com/test_query_result"
+ );
+ let bm = await PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ title: "test_bookmark",
+ url: PAGE_URI2,
+ });
+
+ let result = PlacesUtils.getFolderContents(PlacesUtils.bookmarks.menuGuid);
+
+ Assert.equal(
+ result.root.childCount,
+ 1,
+ "Should have only one item in the query"
+ );
+ Assert.equal(
+ result.root.getChild(0).uri,
+ PAGE_URI2.spec,
+ "Should have the correct child"
+ );
+ Assert.equal(
+ result.root.getChild(0).lastModified,
+ PlacesUtils.toPRTime(bm.lastModified),
+ "Should have the expected last modified date."
+ );
+
+ let promise = promiseFaviconChanged(PAGE_URI2, SMALLPNG_DATA_URI);
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ PAGE_URI2,
+ SMALLPNG_DATA_URI,
+ false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ await promise;
+
+ // Open the container and wait for containerStateChanged. We should start
+ // observing before setting |containerOpen| as that's caused by the
+ // setAndFetchFaviconForPage() call caused by the containerStateChanged
+ // observer above.
+
+ // We must wait for the asynchronous database thread to finish the
+ // operation, and then for the main thread to process any pending
+ // notifications that came from the asynchronous thread, before we can be
+ // sure that nodeIconChanged was not invoked in the meantime.
+ await PlacesTestUtils.promiseAsyncUpdates();
+
+ Assert.equal(
+ result.root.childCount,
+ 1,
+ "Should have only one item in the query"
+ );
+ Assert.equal(
+ result.root.getChild(0).uri,
+ PAGE_URI2.spec,
+ "Should have the correct child"
+ );
+ Assert.equal(
+ result.root.getChild(0).lastModified,
+ PlacesUtils.toPRTime(bm.lastModified),
+ "Should not have changed the last modified date."
+ );
+
+ // Free the resources immediately.
+ result.root.containerOpen = false;
+ }
+);
diff --git a/toolkit/components/places/tests/favicons/test_replaceFaviconData.js b/toolkit/components/places/tests/favicons/test_replaceFaviconData.js
new file mode 100644
index 0000000000..2e9835eaa9
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_replaceFaviconData.js
@@ -0,0 +1,395 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests for replaceFaviconData()
+ */
+
+var iconsvc = PlacesUtils.favicons;
+
+var originalFavicon = {
+ file: do_get_file("favicon-normal16.png"),
+ uri: uri(do_get_file("favicon-normal16.png")),
+ data: readFileData(do_get_file("favicon-normal16.png")),
+ mimetype: "image/png",
+};
+
+var uniqueFaviconId = 0;
+function createFavicon(fileName) {
+ let tempdir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+
+ // remove any existing file at the path we're about to copy to
+ let outfile = tempdir.clone();
+ outfile.append(fileName);
+ try {
+ outfile.remove(false);
+ } catch (e) {}
+
+ originalFavicon.file.copyToFollowingLinks(tempdir, fileName);
+
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ stream.init(outfile, 0x02 | 0x08 | 0x10, 0o600, 0);
+
+ // append some data that sniffers/encoders will ignore that will distinguish
+ // the different favicons we'll create
+ uniqueFaviconId++;
+ let uniqueStr = "uid:" + uniqueFaviconId;
+ stream.write(uniqueStr, uniqueStr.length);
+ stream.close();
+
+ Assert.equal(outfile.leafName.substr(0, fileName.length), fileName);
+
+ return {
+ file: outfile,
+ uri: uri(outfile),
+ data: readFileData(outfile),
+ mimetype: "image/png",
+ };
+}
+
+function checkCallbackSucceeded(
+ callbackMimetype,
+ callbackData,
+ sourceMimetype,
+ sourceData
+) {
+ Assert.equal(callbackMimetype, sourceMimetype);
+ Assert.ok(compareArrays(callbackData, sourceData));
+}
+
+function run_test() {
+ // check that the favicon loaded correctly
+ Assert.equal(originalFavicon.data.length, 286);
+ run_next_test();
+}
+
+add_task(async function test_replaceFaviconData_validHistoryURI() {
+ info("test replaceFaviconData for valid history uri");
+
+ let pageURI = uri("http://test1.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let favicon = createFavicon("favicon1.png");
+
+ iconsvc.replaceFaviconData(favicon.uri, favicon.data, favicon.mimetype);
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ favicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconData_validHistoryURI_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ dump("GOT " + aMimeType + "\n");
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ favicon.mimetype,
+ favicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ favicon.mimetype,
+ favicon.data,
+ function test_replaceFaviconData_validHistoryURI_callback() {
+ favicon.file.remove(false);
+ resolve();
+ }
+ );
+ },
+ systemPrincipal
+ );
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_replaceFaviconData_overrideDefaultFavicon() {
+ info("test replaceFaviconData to override a later setAndFetchFaviconForPage");
+
+ let pageURI = uri("http://test2.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let firstFavicon = createFavicon("favicon2.png");
+ let secondFavicon = createFavicon("favicon3.png");
+
+ iconsvc.replaceFaviconData(
+ firstFavicon.uri,
+ secondFavicon.data,
+ secondFavicon.mimetype
+ );
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ firstFavicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconData_overrideDefaultFavicon_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ secondFavicon.mimetype,
+ secondFavicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ secondFavicon.mimetype,
+ secondFavicon.data,
+ function test_replaceFaviconData_overrideDefaultFavicon_callback() {
+ firstFavicon.file.remove(false);
+ secondFavicon.file.remove(false);
+ resolve();
+ }
+ );
+ },
+ systemPrincipal
+ );
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_replaceFaviconData_replaceExisting() {
+ info(
+ "test replaceFaviconData to override a previous setAndFetchFaviconForPage"
+ );
+
+ let pageURI = uri("http://test3.bar");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let firstFavicon = createFavicon("favicon4.png");
+ let secondFavicon = createFavicon("favicon5.png");
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ firstFavicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconData_replaceExisting_firstSet_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ firstFavicon.mimetype,
+ firstFavicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ firstFavicon.mimetype,
+ firstFavicon.data,
+ function test_replaceFaviconData_overrideDefaultFavicon_firstCallback() {
+ iconsvc.replaceFaviconData(
+ firstFavicon.uri,
+ secondFavicon.data,
+ secondFavicon.mimetype
+ );
+ PlacesTestUtils.promiseAsyncUpdates().then(() => {
+ checkFaviconDataForPage(
+ pageURI,
+ secondFavicon.mimetype,
+ secondFavicon.data,
+ function test_replaceFaviconData_overrideDefaultFavicon_secondCallback() {
+ firstFavicon.file.remove(false);
+ secondFavicon.file.remove(false);
+ resolve();
+ },
+ systemPrincipal
+ );
+ });
+ }
+ );
+ },
+ systemPrincipal
+ );
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_replaceFaviconData_unrelatedReplace() {
+ info("test replaceFaviconData to not make unrelated changes");
+
+ let pageURI = uri("http://test4.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let favicon = createFavicon("favicon6.png");
+ let unrelatedFavicon = createFavicon("favicon7.png");
+
+ iconsvc.replaceFaviconData(
+ unrelatedFavicon.uri,
+ unrelatedFavicon.data,
+ unrelatedFavicon.mimetype
+ );
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ favicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconData_unrelatedReplace_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ favicon.mimetype,
+ favicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ favicon.mimetype,
+ favicon.data,
+ function test_replaceFaviconData_unrelatedReplace_callback() {
+ favicon.file.remove(false);
+ unrelatedFavicon.file.remove(false);
+ resolve();
+ }
+ );
+ },
+ systemPrincipal
+ );
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_replaceFaviconData_badInputs() {
+ info("test replaceFaviconData to throw on bad inputs");
+ let icon = createFavicon("favicon8.png");
+
+ Assert.throws(
+ () => iconsvc.replaceFaviconData(icon.uri, icon.data, ""),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+ Assert.throws(
+ () => iconsvc.replaceFaviconData(icon.uri, icon.data, "not-an-image"),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+ Assert.throws(
+ () => iconsvc.replaceFaviconData(null, icon.data, icon.mimetype),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+ Assert.throws(
+ () => iconsvc.replaceFaviconData(icon.uri, [], icon.mimetype),
+ /NS_ERROR_ILLEGAL_VALUE/
+ );
+ Assert.throws(
+ () => iconsvc.replaceFaviconData(icon.uri, null, icon.mimetype),
+ /NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY/
+ );
+
+ icon.file.remove(false);
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_replaceFaviconData_twiceReplace() {
+ info("test replaceFaviconData on multiple replacements");
+
+ let pageURI = uri("http://test5.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let firstFavicon = createFavicon("favicon9.png");
+ let secondFavicon = createFavicon("favicon10.png");
+
+ iconsvc.replaceFaviconData(
+ firstFavicon.uri,
+ firstFavicon.data,
+ firstFavicon.mimetype
+ );
+ iconsvc.replaceFaviconData(
+ firstFavicon.uri,
+ secondFavicon.data,
+ secondFavicon.mimetype
+ );
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ firstFavicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconData_twiceReplace_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ secondFavicon.mimetype,
+ secondFavicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ secondFavicon.mimetype,
+ secondFavicon.data,
+ function test_replaceFaviconData_twiceReplace_callback() {
+ firstFavicon.file.remove(false);
+ secondFavicon.file.remove(false);
+ resolve();
+ },
+ systemPrincipal
+ );
+ },
+ systemPrincipal
+ );
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_replaceFaviconData_rootOverwrite() {
+ info("test replaceFaviconData doesn't overwrite root = 1");
+
+ async function getRootValue(url) {
+ let db = await PlacesUtils.promiseDBConnection();
+ let rows = await db.execute(
+ "SELECT root FROM moz_icons WHERE icon_url = :url",
+ { url }
+ );
+ return rows[0].getResultByName("root");
+ }
+
+ const PAGE_URL = "http://rootoverwrite.bar/";
+ let pageURI = Services.io.newURI(PAGE_URL);
+ const ICON_URL = "http://rootoverwrite.bar/favicon.ico";
+ let iconURI = Services.io.newURI(ICON_URL);
+
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let icon = createFavicon("favicon9.png");
+ PlacesUtils.favicons.replaceFaviconData(iconURI, icon.data, icon.mimetype);
+ await PlacesTestUtils.addFavicons(new Map([[PAGE_URL, ICON_URL]]));
+
+ Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1");
+ let icon2 = createFavicon("favicon10.png");
+ PlacesUtils.favicons.replaceFaviconData(iconURI, icon2.data, icon2.mimetype);
+ // replaceFaviconData doesn't have a callback, but we must wait its updated.
+ await PlacesTestUtils.promiseAsyncUpdates();
+ Assert.equal(await getRootValue(ICON_URL), 1, "Check root did not change");
+
+ await PlacesUtils.history.clear();
+});
diff --git a/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js b/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js
new file mode 100644
index 0000000000..c1b83fc8a7
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js
@@ -0,0 +1,537 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * Tests for replaceFaviconData()
+ */
+
+var iconsvc = PlacesUtils.favicons;
+
+var originalFavicon = {
+ file: do_get_file("favicon-normal16.png"),
+ uri: uri(do_get_file("favicon-normal16.png")),
+ data: readFileData(do_get_file("favicon-normal16.png")),
+ mimetype: "image/png",
+};
+
+var uniqueFaviconId = 0;
+function createFavicon(fileName) {
+ let tempdir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+
+ // remove any existing file at the path we're about to copy to
+ let outfile = tempdir.clone();
+ outfile.append(fileName);
+ try {
+ outfile.remove(false);
+ } catch (e) {}
+
+ originalFavicon.file.copyToFollowingLinks(tempdir, fileName);
+
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ stream.init(outfile, 0x02 | 0x08 | 0x10, 0o600, 0);
+
+ // append some data that sniffers/encoders will ignore that will distinguish
+ // the different favicons we'll create
+ uniqueFaviconId++;
+ let uniqueStr = "uid:" + uniqueFaviconId;
+ stream.write(uniqueStr, uniqueStr.length);
+ stream.close();
+
+ Assert.equal(outfile.leafName.substr(0, fileName.length), fileName);
+
+ return {
+ file: outfile,
+ uri: uri(outfile),
+ data: readFileData(outfile),
+ mimetype: "image/png",
+ };
+}
+
+function createDataURLForFavicon(favicon) {
+ return "data:" + favicon.mimetype + ";base64," + toBase64(favicon.data);
+}
+
+function checkCallbackSucceeded(
+ callbackMimetype,
+ callbackData,
+ sourceMimetype,
+ sourceData
+) {
+ Assert.equal(callbackMimetype, sourceMimetype);
+ Assert.ok(compareArrays(callbackData, sourceData));
+}
+
+function run_test() {
+ // check that the favicon loaded correctly
+ Assert.equal(originalFavicon.data.length, 286);
+ run_next_test();
+}
+
+add_task(async function test_replaceFaviconDataFromDataURL_validHistoryURI() {
+ info("test replaceFaviconDataFromDataURL for valid history uri");
+
+ let pageURI = uri("http://test1.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let favicon = createFavicon("favicon1.png");
+ iconsvc.replaceFaviconDataFromDataURL(
+ favicon.uri,
+ createDataURLForFavicon(favicon),
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ favicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconDataFromDataURL_validHistoryURI_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ favicon.mimetype,
+ favicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ favicon.mimetype,
+ favicon.data,
+ function test_replaceFaviconDataFromDataURL_validHistoryURI_callback() {
+ favicon.file.remove(false);
+ resolve();
+ }
+ );
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(
+ async function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon() {
+ info(
+ "test replaceFaviconDataFromDataURL to override a later setAndFetchFaviconForPage"
+ );
+
+ let pageURI = uri("http://test2.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let firstFavicon = createFavicon("favicon2.png");
+ let secondFavicon = createFavicon("favicon3.png");
+
+ iconsvc.replaceFaviconDataFromDataURL(
+ firstFavicon.uri,
+ createDataURLForFavicon(secondFavicon),
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ firstFavicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ secondFavicon.mimetype,
+ secondFavicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ secondFavicon.mimetype,
+ secondFavicon.data,
+ function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon_callback() {
+ firstFavicon.file.remove(false);
+ secondFavicon.file.remove(false);
+ resolve();
+ }
+ );
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ await PlacesUtils.history.clear();
+ }
+);
+
+add_task(async function test_replaceFaviconDataFromDataURL_replaceExisting() {
+ info(
+ "test replaceFaviconDataFromDataURL to override a previous setAndFetchFaviconForPage"
+ );
+
+ let pageURI = uri("http://test3.bar");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let firstFavicon = createFavicon("favicon4.png");
+ let secondFavicon = createFavicon("favicon5.png");
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ firstFavicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconDataFromDataURL_replaceExisting_firstSet_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ firstFavicon.mimetype,
+ firstFavicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ firstFavicon.mimetype,
+ firstFavicon.data,
+ function test_replaceFaviconDataFromDataURL_replaceExisting_firstCallback() {
+ iconsvc.replaceFaviconDataFromDataURL(
+ firstFavicon.uri,
+ createDataURLForFavicon(secondFavicon),
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ secondFavicon.mimetype,
+ secondFavicon.data,
+ function test_replaceFaviconDataFromDataURL_replaceExisting_secondCallback() {
+ firstFavicon.file.remove(false);
+ secondFavicon.file.remove(false);
+ resolve();
+ }
+ );
+ }
+ );
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_replaceFaviconDataFromDataURL_unrelatedReplace() {
+ info("test replaceFaviconDataFromDataURL to not make unrelated changes");
+
+ let pageURI = uri("http://test4.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let favicon = createFavicon("favicon6.png");
+ let unrelatedFavicon = createFavicon("favicon7.png");
+
+ iconsvc.replaceFaviconDataFromDataURL(
+ unrelatedFavicon.uri,
+ createDataURLForFavicon(unrelatedFavicon),
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ favicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconDataFromDataURL_unrelatedReplace_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ favicon.mimetype,
+ favicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ favicon.mimetype,
+ favicon.data,
+ function test_replaceFaviconDataFromDataURL_unrelatedReplace_callback() {
+ favicon.file.remove(false);
+ unrelatedFavicon.file.remove(false);
+ resolve();
+ }
+ );
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_replaceFaviconDataFromDataURL_badInputs() {
+ info("test replaceFaviconDataFromDataURL to throw on bad inputs");
+
+ let favicon = createFavicon("favicon8.png");
+
+ let ex = null;
+ try {
+ iconsvc.replaceFaviconDataFromDataURL(
+ favicon.uri,
+ "",
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ } catch (e) {
+ ex = e;
+ } finally {
+ Assert.ok(!!ex);
+ }
+
+ ex = null;
+ try {
+ iconsvc.replaceFaviconDataFromDataURL(
+ null,
+ createDataURLForFavicon(favicon),
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ } catch (e) {
+ ex = e;
+ } finally {
+ Assert.ok(!!ex);
+ }
+
+ favicon.file.remove(false);
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(async function test_replaceFaviconDataFromDataURL_twiceReplace() {
+ info("test replaceFaviconDataFromDataURL on multiple replacements");
+
+ let pageURI = uri("http://test5.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let firstFavicon = createFavicon("favicon9.png");
+ let secondFavicon = createFavicon("favicon10.png");
+
+ iconsvc.replaceFaviconDataFromDataURL(
+ firstFavicon.uri,
+ createDataURLForFavicon(firstFavicon),
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ iconsvc.replaceFaviconDataFromDataURL(
+ firstFavicon.uri,
+ createDataURLForFavicon(secondFavicon),
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ firstFavicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconDataFromDataURL_twiceReplace_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ secondFavicon.mimetype,
+ secondFavicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ secondFavicon.mimetype,
+ secondFavicon.data,
+ function test_replaceFaviconDataFromDataURL_twiceReplace_callback() {
+ firstFavicon.file.remove(false);
+ secondFavicon.file.remove(false);
+ resolve();
+ }
+ );
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ await PlacesUtils.history.clear();
+});
+
+add_task(
+ async function test_replaceFaviconDataFromDataURL_afterRegularAssign() {
+ info("test replaceFaviconDataFromDataURL after replaceFaviconData");
+
+ let pageURI = uri("http://test6.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let firstFavicon = createFavicon("favicon11.png");
+ let secondFavicon = createFavicon("favicon12.png");
+
+ iconsvc.replaceFaviconData(
+ firstFavicon.uri,
+ firstFavicon.data,
+ firstFavicon.mimetype
+ );
+ iconsvc.replaceFaviconDataFromDataURL(
+ firstFavicon.uri,
+ createDataURLForFavicon(secondFavicon),
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ firstFavicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconDataFromDataURL_afterRegularAssign_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ secondFavicon.mimetype,
+ secondFavicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ secondFavicon.mimetype,
+ secondFavicon.data,
+ function test_replaceFaviconDataFromDataURL_afterRegularAssign_callback() {
+ firstFavicon.file.remove(false);
+ secondFavicon.file.remove(false);
+ resolve();
+ }
+ );
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ await PlacesUtils.history.clear();
+ }
+);
+
+add_task(
+ async function test_replaceFaviconDataFromDataURL_beforeRegularAssign() {
+ info("test replaceFaviconDataFromDataURL before replaceFaviconData");
+
+ let pageURI = uri("http://test7.bar/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let firstFavicon = createFavicon("favicon13.png");
+ let secondFavicon = createFavicon("favicon14.png");
+
+ iconsvc.replaceFaviconDataFromDataURL(
+ firstFavicon.uri,
+ createDataURLForFavicon(firstFavicon),
+ 0,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ iconsvc.replaceFaviconData(
+ firstFavicon.uri,
+ secondFavicon.data,
+ secondFavicon.mimetype
+ );
+
+ await new Promise(resolve => {
+ iconsvc.setAndFetchFaviconForPage(
+ pageURI,
+ firstFavicon.uri,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ function test_replaceFaviconDataFromDataURL_beforeRegularAssign_check(
+ aURI,
+ aDataLen,
+ aData,
+ aMimeType
+ ) {
+ checkCallbackSucceeded(
+ aMimeType,
+ aData,
+ secondFavicon.mimetype,
+ secondFavicon.data
+ );
+ checkFaviconDataForPage(
+ pageURI,
+ secondFavicon.mimetype,
+ secondFavicon.data,
+ function test_replaceFaviconDataFromDataURL_beforeRegularAssign_callback() {
+ firstFavicon.file.remove(false);
+ secondFavicon.file.remove(false);
+ resolve();
+ }
+ );
+ },
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ await PlacesUtils.history.clear();
+ }
+);
+
+/* toBase64 copied from image/test/unit/test_encoder_png.js */
+
+/* Convert data (an array of integers) to a Base64 string. */
+const toBase64Table =
+ // eslint-disable-next-line no-useless-concat
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789+/";
+const base64Pad = "=";
+function toBase64(data) {
+ let result = "";
+ let length = data.length;
+ let i;
+ // Convert every three bytes to 4 ascii characters.
+ for (i = 0; i < length - 2; i += 3) {
+ result += toBase64Table[data[i] >> 2];
+ result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
+ result += toBase64Table[((data[i + 1] & 0x0f) << 2) + (data[i + 2] >> 6)];
+ result += toBase64Table[data[i + 2] & 0x3f];
+ }
+
+ // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
+ if (length % 3) {
+ i = length - (length % 3);
+ result += toBase64Table[data[i] >> 2];
+ if (length % 3 == 2) {
+ result += toBase64Table[((data[i] & 0x03) << 4) + (data[i + 1] >> 4)];
+ result += toBase64Table[(data[i + 1] & 0x0f) << 2];
+ result += base64Pad;
+ } else {
+ result += toBase64Table[(data[i] & 0x03) << 4];
+ result += base64Pad + base64Pad;
+ }
+ }
+
+ return result;
+}
diff --git a/toolkit/components/places/tests/favicons/test_root_icons.js b/toolkit/components/places/tests/favicons/test_root_icons.js
new file mode 100644
index 0000000000..f0487cc162
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_root_icons.js
@@ -0,0 +1,246 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests root icons associations and expiration
+ */
+
+add_task(async function () {
+ let pageURI = NetUtil.newURI("http://www.places.test/page/");
+ await PlacesTestUtils.addVisits(pageURI);
+ let faviconURI = NetUtil.newURI("http://www.places.test/favicon.ico");
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ faviconURI,
+ SMALLPNG_DATA_URI.spec,
+ 0,
+ systemPrincipal
+ );
+ await setFaviconForPage(pageURI, faviconURI);
+
+ // Sanity checks.
+ Assert.equal(await getFaviconUrlForPage(pageURI), faviconURI.spec);
+ Assert.equal(
+ await getFaviconUrlForPage("https://places.test/somethingelse/"),
+ faviconURI.spec
+ );
+
+ // Check database entries.
+ await PlacesTestUtils.promiseAsyncUpdates();
+ let db = await PlacesUtils.promiseDBConnection();
+ let rows = await db.execute("SELECT * FROM moz_icons");
+ Assert.equal(rows.length, 1, "There should only be 1 icon entry");
+ Assert.equal(
+ rows[0].getResultByName("root"),
+ 1,
+ "It should be marked as a root icon"
+ );
+ rows = await db.execute("SELECT * FROM moz_pages_w_icons");
+ Assert.equal(rows.length, 0, "There should be no page entry");
+ rows = await db.execute("SELECT * FROM moz_icons_to_pages");
+ Assert.equal(rows.length, 0, "There should be no relation entry");
+
+ // Add another pages to the same host. The icon should not be removed.
+ await PlacesTestUtils.addVisits("http://places.test/page2/");
+ await PlacesUtils.history.remove(pageURI);
+
+ // Still works since the icon has not been removed.
+ Assert.equal(await getFaviconUrlForPage(pageURI), faviconURI.spec);
+
+ // Remove all the pages for the given domain.
+ await PlacesUtils.history.remove("http://places.test/page2/");
+ // The icon should be removed along with the domain.
+ rows = await db.execute("SELECT * FROM moz_icons");
+ Assert.equal(rows.length, 0, "The icon should have been removed");
+});
+
+add_task(async function test_removePagesByTimeframe() {
+ const BASE_URL = "http://www.places.test";
+ // Add a visit in the past with no directly associated icon.
+ let oldPageURI = NetUtil.newURI(`${BASE_URL}/old/`);
+ await PlacesTestUtils.addVisits({
+ uri: oldPageURI,
+ visitDate: new Date(Date.now() - 86400000),
+ });
+ // And another more recent visit.
+ let pageURI = NetUtil.newURI(`${BASE_URL}/page/`);
+ await PlacesTestUtils.addVisits({
+ uri: pageURI,
+ visitDate: new Date(Date.now() - 7200000),
+ });
+
+ // Add a normal icon to the most recent page.
+ let faviconURI = NetUtil.newURI(`${BASE_URL}/page/favicon.ico`);
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ faviconURI,
+ SMALLSVG_DATA_URI.spec,
+ 0,
+ systemPrincipal
+ );
+ await setFaviconForPage(pageURI, faviconURI);
+ // Add a root icon to the most recent page.
+ let rootIconURI = NetUtil.newURI(`${BASE_URL}/favicon.ico`);
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ rootIconURI,
+ SMALLPNG_DATA_URI.spec,
+ 0,
+ systemPrincipal
+ );
+ await setFaviconForPage(pageURI, rootIconURI);
+
+ // Sanity checks.
+ Assert.equal(
+ await getFaviconUrlForPage(pageURI),
+ faviconURI.spec,
+ "Should get the biggest icon"
+ );
+ Assert.equal(
+ await getFaviconUrlForPage(pageURI, 1),
+ rootIconURI.spec,
+ "Should get the smallest icon"
+ );
+ Assert.equal(
+ await getFaviconUrlForPage(oldPageURI),
+ rootIconURI.spec,
+ "Should get the root icon"
+ );
+
+ info("Removing the newer page, not the old one");
+ await PlacesUtils.history.removeByFilter({
+ beginDate: new Date(Date.now() - 14400000),
+ endDate: new Date(),
+ });
+ await PlacesTestUtils.promiseAsyncUpdates();
+
+ let db = await PlacesUtils.promiseDBConnection();
+ let rows = await db.execute("SELECT * FROM moz_icons");
+ Assert.equal(rows.length, 1, "There should only be 1 icon entry");
+ Assert.equal(
+ rows[0].getResultByName("root"),
+ 1,
+ "It should be marked as a root icon"
+ );
+ rows = await db.execute("SELECT * FROM moz_pages_w_icons");
+ Assert.equal(rows.length, 0, "There should be no page entry");
+ rows = await db.execute("SELECT * FROM moz_icons_to_pages");
+ Assert.equal(rows.length, 0, "There should be no relation entry");
+
+ await PlacesUtils.history.removeByFilter({
+ beginDate: new Date(0),
+ endDt: new Date(),
+ });
+ await PlacesTestUtils.promiseAsyncUpdates();
+ rows = await db.execute("SELECT * FROM moz_icons");
+ // Debug logging for possible intermittent failure (bug 1358368).
+ if (rows.length) {
+ dump_table("moz_icons");
+ }
+ Assert.equal(rows.length, 0, "There should be no icon entry");
+});
+
+add_task(async function test_different_host() {
+ let pageURI = NetUtil.newURI("http://places.test/page/");
+ await PlacesTestUtils.addVisits(pageURI);
+ let faviconURI = NetUtil.newURI("http://mozilla.test/favicon.ico");
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ faviconURI,
+ SMALLPNG_DATA_URI.spec,
+ 0,
+ systemPrincipal
+ );
+ await setFaviconForPage(pageURI, faviconURI);
+
+ Assert.equal(
+ await getFaviconUrlForPage(pageURI),
+ faviconURI.spec,
+ "Should get the png icon"
+ );
+ // Check the icon is not marked as a root icon in the database.
+ let db = await PlacesUtils.promiseDBConnection();
+ let rows = await db.execute(
+ "SELECT root FROM moz_icons WHERE icon_url = :url",
+ { url: faviconURI.spec }
+ );
+ Assert.strictEqual(rows[0].getResultByName("root"), 0);
+});
+
+add_task(async function test_same_size() {
+ // Add two icons with the same size, one is a root icon. Check that the
+ // non-root icon is preferred when a smaller size is requested.
+ let data = readFileData(do_get_file("favicon-normal32.png"));
+ let pageURI = NetUtil.newURI("http://new_places.test/page/");
+ await PlacesTestUtils.addVisits(pageURI);
+
+ let faviconURI = NetUtil.newURI("http://new_places.test/favicon.ico");
+ PlacesUtils.favicons.replaceFaviconData(faviconURI, data, "image/png");
+ await setFaviconForPage(pageURI, faviconURI);
+ faviconURI = NetUtil.newURI("http://new_places.test/another_icon.ico");
+ PlacesUtils.favicons.replaceFaviconData(faviconURI, data, "image/png");
+ await setFaviconForPage(pageURI, faviconURI);
+
+ Assert.equal(
+ await getFaviconUrlForPage(pageURI, 20),
+ faviconURI.spec,
+ "Should get the non-root icon"
+ );
+});
+
+add_task(async function test_root_on_different_host() {
+ async function getRootValue(url) {
+ let db = await PlacesUtils.promiseDBConnection();
+ let rows = await db.execute(
+ "SELECT root FROM moz_icons WHERE icon_url = :url",
+ { url }
+ );
+ return rows[0].getResultByName("root");
+ }
+
+ // Check that a root icon associated to 2 domains is not removed when the
+ // root domain is removed.
+ const TEST_URL1 = "http://places1.test/page/";
+ let pageURI1 = NetUtil.newURI(TEST_URL1);
+ await PlacesTestUtils.addVisits(pageURI1);
+
+ const TEST_URL2 = "http://places2.test/page/";
+ let pageURI2 = NetUtil.newURI(TEST_URL2);
+ await PlacesTestUtils.addVisits(pageURI2);
+
+ // Root favicon for TEST_URL1.
+ const ICON_URL = "http://places1.test/favicon.ico";
+ let iconURI = NetUtil.newURI(ICON_URL);
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ iconURI,
+ SMALLPNG_DATA_URI.spec,
+ 0,
+ systemPrincipal
+ );
+ await setFaviconForPage(pageURI1, iconURI);
+ Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1");
+ Assert.equal(
+ await getFaviconUrlForPage(pageURI1, 16),
+ ICON_URL,
+ "The icon should been found"
+ );
+
+ // Same favicon for TEST_URL2.
+ PlacesUtils.favicons.replaceFaviconDataFromDataURL(
+ iconURI,
+ SMALLPNG_DATA_URI.spec,
+ 0,
+ systemPrincipal
+ );
+ await setFaviconForPage(pageURI2, iconURI);
+ Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1");
+ Assert.equal(
+ await getFaviconUrlForPage(pageURI2, 16),
+ ICON_URL,
+ "The icon should be found"
+ );
+
+ await PlacesUtils.history.remove(pageURI1);
+
+ Assert.equal(
+ await getFaviconUrlForPage(pageURI2, 16),
+ ICON_URL,
+ "The icon should not have been removed"
+ );
+});
diff --git a/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
new file mode 100644
index 0000000000..1b4ea87ec0
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage.js
@@ -0,0 +1,123 @@
+/* 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.
+
+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);
+ },
+ },
+];
+
+add_task(async function () {
+ let faviconURI = SMALLPNG_DATA_URI;
+ let faviconMimeType = "image/png";
+
+ registerCleanupFunction(async function () {
+ await PlacesUtils.bookmarks.eraseEverything();
+ await PlacesUtils.history.clear();
+ });
+
+ for (let test of gTests) {
+ info(test.desc);
+ let pageURI = PlacesUtils.toURI(test.href);
+
+ await test.setup();
+
+ let pageGuid;
+ let promise = PlacesTestUtils.waitForNotification(
+ "favicon-changed",
+ events =>
+ events.some(e => {
+ if (e.url == pageURI.spec && e.faviconUrl == faviconURI.spec) {
+ pageGuid = e.pageGuid;
+ return true;
+ }
+ return false;
+ })
+ );
+
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ pageURI,
+ faviconURI,
+ true,
+ test.private,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ await promise;
+
+ Assert.equal(
+ pageGuid,
+ await PlacesTestUtils.getDatabaseValue("moz_places", "guid", {
+ url: pageURI,
+ }),
+ "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();
+ }
+ }
+});
diff --git a/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js
new file mode 100644
index 0000000000..d5b16bd670
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_failures.js
@@ -0,0 +1,156 @@
+/* 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 when it is called with invalid
+ * arguments, and when no favicon is stored for the given arguments.
+ */
+
+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();
+ });
+
+ // 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(
+ "favicon-changed",
+ events =>
+ events.some(
+ e => e.url == lastPageURI.spec && e.faviconUrl == SMALLPNG_DATA_URI.spec
+ )
+ );
+
+ 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"
+ );
+
+ 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()
+ );
+ },
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Exception expected because aFaviconURI is null."
+ );
+
+ info("Test about uri");
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ Services.io.newURI("about:testAboutURI"),
+ faviconURI,
+ true,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+
+ 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()
+ );
+
+ 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);
+
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ pageURI,
+ faviconURI,
+ true,
+ 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.
+ Services.prefs.setBoolPref("places.history.enabled", true);
+
+ info("Test error icon");
+ // This error icon must stay in sync with FAVICON_ERRORPAGE_URL in
+ // nsIFaviconService.idl and aboutNetError.html.
+ let faviconErrorPageURI = Services.io.newURI(
+ "chrome://global/skin/icons/info.svg"
+ );
+ 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()
+ );
+
+ 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()
+ );
+
+ 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;
+});
diff --git a/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_redirects.js b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_redirects.js
new file mode 100644
index 0000000000..feda238f97
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_setAndFetchFaviconForPage_redirects.js
@@ -0,0 +1,89 @@
+/* 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("favicon-changed", events =>
+ events.some(e => e.url == srcUrl && e.faviconUrl == SMALLPNG_DATA_URI.spec)
+ );
+
+ 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("favicon-changed", events =>
+ events.some(
+ e => e.url == srcUrl && e.faviconUrl == SMALLPNG_DATA_URI.spec
+ )
+ ),
+ 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/);
+});
diff --git a/toolkit/components/places/tests/favicons/test_svg_favicon.js b/toolkit/components/places/tests/favicons/test_svg_favicon.js
new file mode 100644
index 0000000000..8d9f2edf11
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/test_svg_favicon.js
@@ -0,0 +1,34 @@
+const PAGEURI = NetUtil.newURI("http://deliciousbacon.com/");
+
+add_task(async function () {
+ // First, add a history entry or else Places can't save a favicon.
+ await PlacesTestUtils.addVisits({
+ uri: PAGEURI,
+ transition: TRANSITION_LINK,
+ visitDate: Date.now() * 1000,
+ });
+
+ await new Promise(resolve => {
+ function onSetComplete(aURI, aDataLen, aData, aMimeType, aWidth) {
+ equal(aURI.spec, SMALLSVG_DATA_URI.spec, "setFavicon aURI check");
+ equal(aDataLen, 263, "setFavicon aDataLen check");
+ equal(aMimeType, "image/svg+xml", "setFavicon aMimeType check");
+ dump(aWidth);
+ resolve();
+ }
+
+ PlacesUtils.favicons.setAndFetchFaviconForPage(
+ PAGEURI,
+ SMALLSVG_DATA_URI,
+ false,
+ PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
+ onSetComplete,
+ Services.scriptSecurityManager.getSystemPrincipal()
+ );
+ });
+
+ let data = await PlacesUtils.promiseFaviconData(PAGEURI.spec);
+ equal(data.uri.spec, SMALLSVG_DATA_URI.spec, "getFavicon aURI check");
+ equal(data.dataLen, 263, "getFavicon aDataLen check");
+ equal(data.mimeType, "image/svg+xml", "getFavicon aMimeType check");
+});
diff --git a/toolkit/components/places/tests/favicons/xpcshell.toml b/toolkit/components/places/tests/favicons/xpcshell.toml
new file mode 100644
index 0000000000..997aa48e0b
--- /dev/null
+++ b/toolkit/components/places/tests/favicons/xpcshell.toml
@@ -0,0 +1,72 @@
+[DEFAULT]
+head = "head_favicons.js"
+skip-if = ["os == 'android'"]
+support-files = [
+ "expected-favicon-animated16.png.png",
+ "expected-favicon-big32.jpg.png",
+ "expected-favicon-big4.jpg.png",
+ "expected-favicon-big16.ico.png",
+ "expected-favicon-big48.ico.png",
+ "expected-favicon-big64.png.png",
+ "expected-favicon-scale160x3.jpg.png",
+ "expected-favicon-scale3x160.jpg.png",
+ "favicon-animated16.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",
+ "noise.png",
+]
+
+["test_cached-favicon_mime_type.js"]
+
+["test_copyFavicons.js"]
+
+["test_expireAllFavicons.js"]
+
+["test_expire_migrated_icons.js"]
+
+["test_expire_on_new_icons.js"]
+
+["test_favicons_conversions.js"]
+
+["test_favicons_protocols_ref.js"]
+
+["test_getFaviconDataForPage.js"]
+
+["test_getFaviconLinkForIcon.js"]
+
+["test_getFaviconURLForPage.js"]
+
+["test_heavy_favicon.js"]
+
+["test_incremental_vacuum.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"]