diff options
Diffstat (limited to 'toolkit/components/places/tests/favicons')
46 files changed, 3247 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 Binary files differnew file mode 100644 index 0000000000..22f825c500 --- /dev/null +++ b/toolkit/components/places/tests/favicons/expected-favicon-animated16.png.png diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big16.ico.png b/toolkit/components/places/tests/favicons/expected-favicon-big16.ico.png Binary files differnew file mode 100644 index 0000000000..fa61cc5046 --- /dev/null +++ b/toolkit/components/places/tests/favicons/expected-favicon-big16.ico.png diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png Binary files differnew file mode 100644 index 0000000000..42640cbb53 --- /dev/null +++ b/toolkit/components/places/tests/favicons/expected-favicon-big32.jpg.png diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big4.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-big4.jpg.png Binary files differnew file mode 100644 index 0000000000..81d1b8ae19 --- /dev/null +++ b/toolkit/components/places/tests/favicons/expected-favicon-big4.jpg.png diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big48.ico.png b/toolkit/components/places/tests/favicons/expected-favicon-big48.ico.png Binary files differnew file mode 100644 index 0000000000..7983889098 --- /dev/null +++ b/toolkit/components/places/tests/favicons/expected-favicon-big48.ico.png diff --git a/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png b/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png Binary files differnew file mode 100644 index 0000000000..2756cf0cb3 --- /dev/null +++ b/toolkit/components/places/tests/favicons/expected-favicon-big64.png.png diff --git a/toolkit/components/places/tests/favicons/expected-favicon-scale160x3.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-scale160x3.jpg.png Binary files differnew file mode 100644 index 0000000000..fc464f8e99 --- /dev/null +++ b/toolkit/components/places/tests/favicons/expected-favicon-scale160x3.jpg.png diff --git a/toolkit/components/places/tests/favicons/expected-favicon-scale3x160.jpg.png b/toolkit/components/places/tests/favicons/expected-favicon-scale3x160.jpg.png Binary files differnew file mode 100644 index 0000000000..c1412038a3 --- /dev/null +++ b/toolkit/components/places/tests/favicons/expected-favicon-scale3x160.jpg.png diff --git a/toolkit/components/places/tests/favicons/favicon-animated16.png b/toolkit/components/places/tests/favicons/favicon-animated16.png Binary files differnew file mode 100644 index 0000000000..8913387fc9 --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-animated16.png diff --git a/toolkit/components/places/tests/favicons/favicon-big16.ico b/toolkit/components/places/tests/favicons/favicon-big16.ico Binary files differnew file mode 100644 index 0000000000..d44438903b --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-big16.ico diff --git a/toolkit/components/places/tests/favicons/favicon-big32.jpg b/toolkit/components/places/tests/favicons/favicon-big32.jpg Binary files differnew file mode 100644 index 0000000000..b2131bf0c1 --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-big32.jpg diff --git a/toolkit/components/places/tests/favicons/favicon-big4.jpg b/toolkit/components/places/tests/favicons/favicon-big4.jpg Binary files differnew file mode 100644 index 0000000000..b84fcd35a6 --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-big4.jpg diff --git a/toolkit/components/places/tests/favicons/favicon-big48.ico b/toolkit/components/places/tests/favicons/favicon-big48.ico Binary files differnew file mode 100644 index 0000000000..f22522411d --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-big48.ico diff --git a/toolkit/components/places/tests/favicons/favicon-big64.png b/toolkit/components/places/tests/favicons/favicon-big64.png Binary files differnew file mode 100644 index 0000000000..2756cf0cb3 --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-big64.png diff --git a/toolkit/components/places/tests/favicons/favicon-multi-frame16.png b/toolkit/components/places/tests/favicons/favicon-multi-frame16.png Binary files differnew file mode 100644 index 0000000000..519e08cc21 --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-multi-frame16.png diff --git a/toolkit/components/places/tests/favicons/favicon-multi-frame32.png b/toolkit/components/places/tests/favicons/favicon-multi-frame32.png Binary files differnew file mode 100644 index 0000000000..5ae61de789 --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-multi-frame32.png diff --git a/toolkit/components/places/tests/favicons/favicon-multi-frame64.png b/toolkit/components/places/tests/favicons/favicon-multi-frame64.png Binary files differnew file mode 100644 index 0000000000..57123f351b --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-multi-frame64.png diff --git a/toolkit/components/places/tests/favicons/favicon-multi.ico b/toolkit/components/places/tests/favicons/favicon-multi.ico Binary files differnew file mode 100644 index 0000000000..e98adcafeb --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-multi.ico diff --git a/toolkit/components/places/tests/favicons/favicon-normal16.png b/toolkit/components/places/tests/favicons/favicon-normal16.png Binary files differnew file mode 100644 index 0000000000..62b69a3d03 --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-normal16.png diff --git a/toolkit/components/places/tests/favicons/favicon-normal32.png b/toolkit/components/places/tests/favicons/favicon-normal32.png Binary files differnew file mode 100644 index 0000000000..5535363c94 --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-normal32.png diff --git a/toolkit/components/places/tests/favicons/favicon-scale160x3.jpg b/toolkit/components/places/tests/favicons/favicon-scale160x3.jpg Binary files differnew file mode 100644 index 0000000000..422ee7ea0b --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-scale160x3.jpg diff --git a/toolkit/components/places/tests/favicons/favicon-scale3x160.jpg b/toolkit/components/places/tests/favicons/favicon-scale3x160.jpg Binary files differnew file mode 100644 index 0000000000..e8514966a0 --- /dev/null +++ b/toolkit/components/places/tests/favicons/favicon-scale3x160.jpg 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 Binary files differnew file mode 100644 index 0000000000..d6876295cd --- /dev/null +++ b/toolkit/components/places/tests/favicons/noise.png 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..a8e3774830 --- /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( + NetUtil.newURI(ICON16_URL), + data, + "image/png" + ); + await setFaviconForPage(PAGE_URL, ICON16_URL); + data = readFileData(do_get_file("favicon-normal32.png")); + PlacesUtils.favicons.replaceFaviconData( + NetUtil.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(NetUtil.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(NetUtil.newURI(ICON16_URL)), + "Size=16 should return the 16px icon" + ); + await compareFavicons( + PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 32), + PlacesUtils.favicons.getFaviconLinkForIcon(NetUtil.newURI(ICON32_URL)), + "Size=32 should return the 32px icon" + ); + await compareFavicons( + PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 33), + PlacesUtils.favicons.getFaviconLinkForIcon(NetUtil.newURI(ICON32_URL)), + "Size=33 should return the 32px icon" + ); + await compareFavicons( + PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 17), + PlacesUtils.favicons.getFaviconLinkForIcon(NetUtil.newURI(ICON32_URL)), + "Size=17 should return the 32px icon" + ); + await compareFavicons( + PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 1), + PlacesUtils.favicons.getFaviconLinkForIcon(NetUtil.newURI(ICON16_URL)), + "Size=1 should return the 16px icon" + ); + await compareFavicons( + PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 0), + PlacesUtils.favicons.getFaviconLinkForIcon(NetUtil.newURI(ICON32_URL)), + "Size=0 should return the bigger icon" + ); + await compareFavicons( + PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, -1), + PlacesUtils.favicons.getFaviconLinkForIcon(NetUtil.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(NetUtil.newURI(ICON16_URL)), + "Pre-existing refs should be retained" + ); + await compareFavicons( + PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL + "#other§=12", 32), + PlacesUtils.favicons.getFaviconLinkForIcon(NetUtil.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(NetUtil.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(NetUtil.newURI(ICON32_URL)), + "Size=16 with HIDPI should return the 32px icon" + ); + await compareFavicons( + PlacesUtils.urlWithSizeRef(win, PAGE_ICON_URL, 32), + PlacesUtils.favicons.getFaviconLinkForIcon(NetUtil.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(NetUtil.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_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_moz-anno_favicon_mime_type.js b/toolkit/components/places/tests/favicons/test_moz-anno_favicon_mime_type.js new file mode 100644 index 0000000000..59e87a8225 --- /dev/null +++ b/toolkit/components/places/tests/favicons/test_moz-anno_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 moz-anno 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 = PromiseUtils.defer(); +} +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_multiple_frames.js b/toolkit/components/places/tests/favicons/test_multiple_frames.js new file mode 100644 index 0000000000..eb59e7f9c6 --- /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 moz-anno: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..1901ca86a7 --- /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.xhtml. + 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.ini b/toolkit/components/places/tests/favicons/xpcshell.ini new file mode 100644 index 0000000000..86616c1631 --- /dev/null +++ b/toolkit/components/places/tests/favicons/xpcshell.ini @@ -0,0 +1,49 @@ +[DEFAULT] +head = head_favicons.js +skip-if = toolkit == '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_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_getFaviconURLForPage.js] +[test_heavy_favicon.js] +[test_incremental_vacuum.js] +[test_moz-anno_favicon_mime_type.js] +[test_multiple_frames.js] +[test_page-icon_protocol.js] +[test_query_result_favicon_changed_on_child.js] +[test_replaceFaviconData.js] +[test_replaceFaviconDataFromDataURL.js] +[test_root_icons.js] +[test_setAndFetchFaviconForPage.js] +[test_setAndFetchFaviconForPage_failures.js] +[test_setAndFetchFaviconForPage_redirects.js] +[test_svg_favicon.js] |