diff --git a/browser/actors/AboutReaderParent.sys.mjs b/browser/actors/AboutReaderParent.sys.mjs index 60ab6a191758..16801ea514fe 100644 --- a/browser/actors/AboutReaderParent.sys.mjs +++ b/browser/actors/AboutReaderParent.sys.mjs @@ -101,13 +101,25 @@ export class AboutReaderParent extends JSWindowActorParent { let preferredWidth = message.data.preferredWidth || 0; let uri = Services.io.newURI(message.data.url); - let result = await lazy.PlacesUtils.favicons.getFaviconForPage( - uri, - preferredWidth - ); + let result = await new Promise(resolve => { + lazy.PlacesUtils.favicons.getFaviconURLForPage( + uri, + iconUri => { + if (iconUri) { + resolve({ + url: message.data.url, + faviconUrl: iconUri.spec, + }); + } else { + resolve(null); + } + }, + preferredWidth + ); + }); this.callListeners(message); - return result && { url: uri.spec, faviconUrl: result.uri.spec }; + return result; } catch (ex) { console.error( "Error requesting favicon URL for about:reader content: ", diff --git a/browser/base/content/test/favicons/browser_favicon_nostore.js b/browser/base/content/test/favicons/browser_favicon_nostore.js index a26170ff2e85..24a54461c478 100644 --- a/browser/base/content/test/favicons/browser_favicon_nostore.js +++ b/browser/base/content/test/favicons/browser_favicon_nostore.js @@ -31,10 +31,17 @@ add_task(async function browser_loader() { // Ensure the favicon has not been stored. /* eslint-disable mozilla/no-arbitrary-setTimeout */ await new Promise(resolve => setTimeout(resolve, 1000)); - let favicon = await PlacesUtils.favicons.getFaviconForPage( - Services.io.newURI(PAGE_URL) - ); - Assert.ok(!favicon); + await new Promise((resolve, reject) => { + PlacesUtils.favicons.getFaviconURLForPage( + Services.io.newURI(PAGE_URL), + foundIconURI => { + if (foundIconURI) { + reject(new Error("An icon has been stored " + foundIconURI.spec)); + } + resolve(); + } + ); + }); BrowserTestUtils.removeTab(tab); }); @@ -61,10 +68,17 @@ async function later_addition(iconUrl) { // Ensure the favicon has not been stored. /* eslint-disable mozilla/no-arbitrary-setTimeout */ await new Promise(resolve => setTimeout(resolve, 1000)); - let favicon = await PlacesUtils.favicons.getFaviconForPage( - Services.io.newURI(PAGE_URL) - ); - Assert.ok(!favicon); + await new Promise((resolve, reject) => { + PlacesUtils.favicons.getFaviconURLForPage( + Services.io.newURI(PAGE_URL), + foundIconURI => { + if (foundIconURI) { + reject(new Error("An icon has been stored " + foundIconURI.spec)); + } + resolve(); + } + ); + }); BrowserTestUtils.removeTab(tab); } @@ -113,10 +127,13 @@ add_task(async function root_icon_stored() { }, async function () { await TestUtils.waitForCondition(async () => { - let favicon = await PlacesUtils.favicons.getFaviconForPage( - Services.io.newURI("http://www.nostore.com/page") + let uri = await new Promise(resolve => + PlacesUtils.favicons.getFaviconURLForPage( + Services.io.newURI("http://www.nostore.com/page"), + resolve + ) ); - return favicon?.uri.spec == "http://www.nostore.com/favicon.ico"; + return uri?.spec == "http://www.nostore.com/favicon.ico"; }, "wait for the favicon to be stored"); Assert.ok(await noStorePromise, "Should have received no-store header"); } @@ -157,10 +174,13 @@ add_task(async function root_icon_after_pageshow_stored() { }, async function () { await TestUtils.waitForCondition(async () => { - let favicon = await PlacesUtils.favicons.getFaviconForPage( - Services.io.newURI("http://rootafterpageshow.com/page") + let uri = await new Promise(resolve => + PlacesUtils.favicons.getFaviconURLForPage( + Services.io.newURI("http://rootafterpageshow.com/page"), + resolve + ) ); - return favicon?.uri.spec == "http://rootafterpageshow.com/favicon.ico"; + return uri?.spec == "http://rootafterpageshow.com/favicon.ico"; }, "wait for the favicon to be stored"); } ); diff --git a/browser/base/content/test/favicons/browser_favicon_store.js b/browser/base/content/test/favicons/browser_favicon_store.js index 43500e0a69d5..a183effe1af6 100644 --- a/browser/base/content/test/favicons/browser_favicon_store.js +++ b/browser/base/content/test/favicons/browser_favicon_store.js @@ -21,14 +21,22 @@ async function test_icon(pageUrl, iconUrl) { // Ensure the favicon has been stored. await storedIconPromise; - let favicon = await PlacesUtils.favicons.getFaviconForPage( - Services.io.newURI(pageUrl) - ); - Assert.equal( - favicon.uri.spec, - iconUrl, - "Should have stored the expected icon." - ); + await new Promise((resolve, reject) => { + PlacesUtils.favicons.getFaviconURLForPage( + Services.io.newURI(pageUrl), + foundIconURI => { + if (foundIconURI) { + Assert.equal( + foundIconURI.spec, + iconUrl, + "Should have stored the expected icon." + ); + resolve(); + } + reject(); + } + ); + }); }); } diff --git a/browser/components/enterprisepolicies/tests/browser/browser_policy_bookmarks.js b/browser/components/enterprisepolicies/tests/browser/browser_policy_bookmarks.js index c109a130f28a..ddb7114adffb 100644 --- a/browser/components/enterprisepolicies/tests/browser/browser_policy_bookmarks.js +++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_bookmarks.js @@ -220,9 +220,12 @@ add_task(async function test_initial_bookmarks() { add_task(async function checkFavicon() { let bookmark1url = CURRENT_POLICY.policies.Bookmarks[0].URL; - let result = await PlacesUtils.favicons.getFaviconForPage( - Services.io.newURI(bookmark1url) - ); + let result = await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + Services.io.newURI(bookmark1url), + (uri, _, data) => resolve({ uri, data }) + ); + }); is( result.uri.spec, @@ -232,7 +235,7 @@ add_task(async function checkFavicon() { // data is an array of octets, which will be a bit hard to compare against // FAVICON_DATA, which is base64 encoded. Checking the expected length should // be good indication that this is working properly. - is(result.rawData.length, 464, "Favicon data has the correct length"); + is(result.data.length, 464, "Favicon data has the correct length"); let faviconsExpiredNotification = TestUtils.topicObserved( "places-favicons-expired" @@ -244,11 +247,15 @@ add_task(async function checkFavicon() { add_task(async function checkNetworkFavicon() { let bookmarkURL = CURRENT_POLICY.policies.Bookmarks[1].URL; - let result = await PlacesUtils.favicons.getFaviconForPage( - Services.io.newURI(bookmarkURL) - ); + let result = await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + Services.io.newURI(bookmarkURL), + (uri, _, data) => resolve({ uri, data }) + ); + }); - is(result, null, "Favicon should not be loaded"); + is(result.uri, null, "Favicon should not be loaded"); + is(result.data.length, 0, "Favicon data should be empty"); }); add_task(async function test_remove_Bookmark_2() { diff --git a/browser/components/migration/tests/unit/head_migration.js b/browser/components/migration/tests/unit/head_migration.js index dbe62ee64056..9b056e66701d 100644 --- a/browser/components/migration/tests/unit/head_migration.js +++ b/browser/components/migration/tests/unit/head_migration.js @@ -88,6 +88,22 @@ async function promiseMigration( return Promise.all(promises); } +/** + * Function that returns a favicon url for a given page url + * + * @param {string} uri + * The Bookmark URI + * @returns {string} faviconURI + * The Favicon URI + */ +async function getFaviconForPageURI(uri) { + let faviconURI = await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage(uri, favURI => { + resolve(favURI); + }); + }); + return faviconURI; +} /** * Takes an array of page URIs and checks that the favicon was imported for each page URI @@ -96,8 +112,8 @@ async function promiseMigration( */ async function assertFavicons(pageURIs) { for (let uri of pageURIs) { - let favicon = await PlacesUtils.favicons.getFaviconForPage(uri); - Assert.ok(favicon, `Got favicon for ${favicon.uri.spec}`); + let faviconURI = await getFaviconForPageURI(uri); + Assert.ok(faviconURI, `Got favicon for ${uri.spec}`); } } @@ -112,12 +128,17 @@ async function assertFavicons(pageURIs) { * Expected mime type of the favicon. */ async function assertFavicon(pageURI, expectedImageData, expectedMimeType) { - let result = await PlacesUtils.favicons.getFaviconForPage( - Services.io.newURI(pageURI) - ); + let result = await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + Services.io.newURI(pageURI), + (faviconURI, dataLen, imageData, mimeType) => { + resolve({ faviconURI, dataLen, imageData, mimeType }); + } + ); + }); Assert.ok(!!result, `Got favicon for ${pageURI}`); Assert.equal( - result.rawData.join(","), + result.imageData.join(","), expectedImageData.join(","), "Image data is correct" ); diff --git a/browser/components/places/tests/unit/test_browserGlue_distribution.js b/browser/components/places/tests/unit/test_browserGlue_distribution.js index 0ee2e4579d95..8beb942c26f6 100644 --- a/browser/components/places/tests/unit/test_browserGlue_distribution.js +++ b/browser/components/places/tests/unit/test_browserGlue_distribution.js @@ -89,8 +89,17 @@ add_task(async function () { Assert.equal(menuItem.title, "Menu Link After"); // Check no favicon exists for this bookmark - let favicon = await PlacesUtils.favicons.getFaviconForPage(menuItem.url.URI); - Assert.equal(favicon, null, "Favicon should not be found"); + await Assert.rejects( + waitForResolvedPromise( + () => { + return PlacesUtils.promiseFaviconData(menuItem.url.href); + }, + "Favicon not found", + 10 + ), + /Favicon\snot\sfound/, + "Favicon not found" + ); // Check the custom bookmarks exist on toolbar. let toolbarItem = await PlacesUtils.bookmarks.fetch({ @@ -100,12 +109,21 @@ add_task(async function () { Assert.equal(toolbarItem.title, "Toolbar Link Before"); // Check the custom favicon exist for this bookmark - favicon = await PlacesUtils.favicons.getFaviconForPage(toolbarItem.url.URI); - Assert.ok(favicon, "Favicon should be found"); - Assert.equal(favicon.uri.spec, "https://example.org/favicon.png"); - Assert.greater(favicon.rawData.length, 0); - Assert.equal(favicon.mimeType, "image/png"); - Assert.equal(favicon.dataURI.spec, SMALLPNG_DATA_URI.spec); + let faviconItem = await waitForResolvedPromise( + () => { + return PlacesUtils.promiseFaviconData(toolbarItem.url.href); + }, + "Favicon not found", + 10 + ); + Assert.equal(faviconItem.uri.spec, "https://example.org/favicon.png"); + Assert.greater(faviconItem.dataLen, 0); + Assert.equal(faviconItem.mimeType, "image/png"); + + let base64Icon = + "data:image/png;base64," + + base64EncodeString(String.fromCharCode.apply(String, faviconItem.data)); + Assert.equal(base64Icon, SMALLPNG_DATA_URI.spec); toolbarItem = await PlacesUtils.bookmarks.fetch({ parentGuid: PlacesUtils.bookmarks.toolbarGuid, diff --git a/browser/components/shell/nsGNOMEShellSearchProvider.cpp b/browser/components/shell/nsGNOMEShellSearchProvider.cpp index 1b721ecaa55a..64d3d888ce81 100644 --- a/browser/components/shell/nsGNOMEShellSearchProvider.cpp +++ b/browser/components/shell/nsGNOMEShellSearchProvider.cpp @@ -8,6 +8,7 @@ #include "nsGNOMEShellSearchProvider.h" #include "nsToolkitCompsCID.h" +#include "nsIFaviconService.h" #include "base/message_loop.h" // for MessageLoop #include "base/task.h" // for NewRunnableMethod, etc #include "mozilla/gfx/2D.h" @@ -23,7 +24,6 @@ #include "nsIOpenTabsProvider.h" #include "imgIContainer.h" #include "imgITools.h" -#include "mozilla/places/nsFaviconService.h" using namespace mozilla; using namespace mozilla::gfx; @@ -62,6 +62,27 @@ static const char* introspect_template = "\n" "\n"; +class AsyncFaviconDataReady final : public nsIFaviconDataCallback { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIFAVICONDATACALLBACK + + AsyncFaviconDataReady(RefPtr aSearchResult, + int aIconIndex, int aTimeStamp) + : mSearchResult(std::move(aSearchResult)), + mIconIndex(aIconIndex), + mTimeStamp(aTimeStamp) {} + + private: + ~AsyncFaviconDataReady() {} + + RefPtr mSearchResult; + int mIconIndex; + int mTimeStamp; +}; + +NS_IMPL_ISUPPORTS(AsyncFaviconDataReady, nsIFaviconDataCallback) + // Inspired by SurfaceToPackedBGRA static UniquePtr SurfaceToPackedRGBA(DataSourceSurface* aSurface) { IntSize size = aSurface->GetSize(); @@ -95,35 +116,22 @@ static UniquePtr SurfaceToPackedRGBA(DataSourceSurface* aSurface) { return imageBuffer; } -static nsresult UpdateHistoryIcon( - const places::FaviconPromise::ResolveOrRejectValue& aPromiseResult, - const RefPtr& aSearchResult, - int aIconIndex, int aTimeStamp) { +NS_IMETHODIMP +AsyncFaviconDataReady::OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen, + const uint8_t* aData, + const nsACString& aMimeType, + uint16_t aWidth) { // This is a callback from some previous search so we don't want it - if (aTimeStamp != aSearchResult->GetTimeStamp()) { + if (mTimeStamp != mSearchResult->GetTimeStamp() || !aData || !aDataLen) { return NS_ERROR_FAILURE; } - nsCOMPtr favicon = - aPromiseResult.IsResolve() ? aPromiseResult.ResolveValue() : nullptr; - if (!favicon) { - return NS_ERROR_FAILURE; - } - - // Get favicon content. - nsTArray rawData; - nsresult rv = favicon->GetRawData(rawData); - NS_ENSURE_SUCCESS(rv, rv); - nsAutoCString mimeType; - rv = favicon->GetMimeType(mimeType); - NS_ENSURE_SUCCESS(rv, rv); - // Decode the image from the format it was returned to us in (probably PNG) nsCOMPtr container; nsCOMPtr imgtool = do_CreateInstance("@mozilla.org/image/tools;1"); - rv = imgtool->DecodeImageFromBuffer( - reinterpret_cast(rawData.Elements()), rawData.Length(), - mimeType, getter_AddRefs(container)); + nsresult rv = imgtool->DecodeImageFromBuffer( + reinterpret_cast(aData), aDataLen, aMimeType, + getter_AddRefs(container)); NS_ENSURE_SUCCESS(rv, rv); RefPtr surface = container->GetFrame( @@ -141,9 +149,9 @@ static nsresult UpdateHistoryIcon( return NS_ERROR_OUT_OF_MEMORY; } - aSearchResult->SetHistoryIcon(aTimeStamp, std::move(data), + mSearchResult->SetHistoryIcon(mTimeStamp, std::move(data), surface->GetSize().width, - surface->GetSize().height, aIconIndex); + surface->GetSize().height, mIconIndex); return NS_OK; } @@ -424,7 +432,8 @@ void nsGNOMEShellHistorySearchResult::HandleSearchResultReply() { nsresult rv = mHistResultContainer->GetChildCount(&childCount); if (NS_SUCCEEDED(rv) && childCount > 0) { // Obtain the favicon service and get the favicon for the specified page - auto* favIconSvc = nsFaviconService::GetFaviconService(); + nsCOMPtr favIconSvc( + do_GetService("@mozilla.org/browser/favicon-service;1")); nsCOMPtr ios(do_GetService(NS_IOSERVICE_CONTRACTID)); if (childCount > MAX_SEARCH_RESULTS_NUM) { @@ -444,15 +453,11 @@ void nsGNOMEShellHistorySearchResult::HandleSearchResultReply() { nsAutoCString uri; child->GetUri(uri); - RefPtr self = this; nsCOMPtr iconIri; ios->NewURI(uri, nullptr, nullptr, getter_AddRefs(iconIri)); - favIconSvc->AsyncGetFaviconForPage(iconIri)->Then( - GetMainThreadSerialEventTarget(), __func__, - [self, iconIndex = i, timeStamp = mTimeStamp]( - const places::FaviconPromise::ResolveOrRejectValue& aResult) { - UpdateHistoryIcon(aResult, self, iconIndex, timeStamp); - }); + nsCOMPtr callback = + new AsyncFaviconDataReady(this, i, mTimeStamp); + favIconSvc->GetFaviconDataForPage(iconIri, callback, 0); bool isOpen = false; for (const auto& openuri : mOpenTabs) { diff --git a/browser/components/topsites/TopSites.sys.mjs b/browser/components/topsites/TopSites.sys.mjs index f0319a69ea1a..d3aa680b9fab 100644 --- a/browser/components/topsites/TopSites.sys.mjs +++ b/browser/components/topsites/TopSites.sys.mjs @@ -1199,16 +1199,18 @@ export class FaviconProvider { * @param {nsIURI} uri * Page to check for favicon data * @returns {object} - * Favicon info object. If there is no data in DB, return null. + * A promise of an object (possibly null) containing the data */ - async getFaviconInfo(uri) { - let favicon = await lazy.PlacesUtils.favicons.getFaviconForPage( - uri, - lazy.NewTabUtils.activityStreamProvider.THUMB_FAVICON_SIZE + getFaviconInfo(uri) { + return new Promise(resolve => + lazy.PlacesUtils.favicons.getFaviconDataForPage( + uri, + // Package up the icon data in an object if we have it; otherwise null + (iconUri, faviconLength, favicon, mimeType, faviconSize) => + resolve(iconUri ? { iconUri, faviconSize } : null), + lazy.NewTabUtils.activityStreamProvider.THUMB_FAVICON_SIZE + ) ); - return favicon - ? { iconUri: favicon.uri, faviconSize: favicon.width } - : null; } /** diff --git a/browser/components/topsites/test/unit/test_FaviconProvider.js b/browser/components/topsites/test/unit/test_FaviconProvider.js index 2d12dc60fa22..b3881a1ced07 100644 --- a/browser/components/topsites/test/unit/test_FaviconProvider.js +++ b/browser/components/topsites/test/unit/test_FaviconProvider.js @@ -110,9 +110,9 @@ add_task(async function test_fetchIcon_with_valid_favicon() { await feed.fetchIcon(TEST_PAGE_URL.spec); info("Check the database"); - const result = await PlacesUtils.favicons.getFaviconForPage(TEST_PAGE_URL); + const result = await PlacesUtils.promiseFaviconData(TEST_PAGE_URL); Assert.equal(result.mimeType, "image/svg+xml"); - Assert.equal(result.width, 65535); + Assert.equal(result.size, 65535); info("Clean up"); await PlacesTestUtils.clearFavicons(); @@ -141,13 +141,14 @@ add_task(async function test_fetchIcon_with_invalid_favicon() { await feed.fetchIcon(TEST_PAGE_URL.spec); info("Check the database"); - const result = await PlacesUtils.favicons.getFaviconForPage(TEST_PAGE_URL); + const result = await PlacesUtils.promiseFaviconData(TEST_PAGE_URL); // eslint-disable-next-line no-use-before-define const expectedFaviconData = readFileData(TEST_FAVICON_FILE); Assert.equal(result.uri.spec, `${TEST_FAVICON_URL.spec}#tippytop`); - Assert.deepEqual(result.rawData, expectedFaviconData); + Assert.equal(result.dataLen, expectedFaviconData.length); + Assert.deepEqual(result.data, expectedFaviconData); Assert.equal(result.mimeType, "image/png"); - Assert.equal(result.width, 16); + Assert.equal(result.size, 16); info("Clean up"); await PlacesTestUtils.clearFavicons(); @@ -185,16 +186,15 @@ add_task(async function test_fetchIconFromRedirects_with_valid_favicon() { info("Check the database"); await TestUtils.waitForCondition(async () => { - const result = await PlacesUtils.favicons.getFaviconForPage(destination); + const result = await PlacesUtils.promiseFaviconData(destination); return !!result; }); - const sourceResult = - await PlacesUtils.favicons.getFaviconForPage(TEST_PAGE_URL); - const destinationResult = - await PlacesUtils.favicons.getFaviconForPage(destination); - Assert.deepEqual(destinationResult.rawData, sourceResult.rawData); + const sourceResult = await PlacesUtils.promiseFaviconData(TEST_PAGE_URL); + const destinationResult = await PlacesUtils.promiseFaviconData(destination); + Assert.equal(destinationResult.dataLen, sourceResult.dataLen); + Assert.deepEqual(destinationResult.data, sourceResult.data); Assert.equal(destinationResult.mimeType, sourceResult.mimeType); - Assert.equal(destinationResult.width, sourceResult.width); + Assert.equal(destinationResult.size, sourceResult.size); info("Clean up"); await PlacesTestUtils.clearFavicons(); @@ -264,7 +264,9 @@ add_task(async function test_fetchIcon_withNetworkFetch() { // Set up mocks PlacesUtils.favicons = { - getFaviconForPage: sandbox.stub().returns(Promise.resolve(null)), + getFaviconDataForPage: sandbox + .stub() + .callsArgWith(1, null, 0, null, null, 0), setFaviconForPage: sandbox.spy(), copyFavicons: sandbox.spy(), }; @@ -296,10 +298,9 @@ add_task(async function test_fetchIcon_withInvalidDataInDb() { const sandbox = sinon.createSandbox(); // Set up mocks PlacesUtils.favicons = { - // Invalid since no width. - getFaviconForPage: sandbox + getFaviconDataForPage: sandbox .stub() - .returns(Promise.resolve({ iconUri: { spec: FAKE_SMALLPNG_DATA_URI } })), + .callsArgWith(1, { spec: FAKE_SMALLPNG_DATA_URI }, 0, null, null, 0), setFaviconForPage: sandbox.spy(), copyFavicons: sandbox.spy(), }; @@ -334,12 +335,16 @@ add_task(async function test_fetchIcon_withValidDataInDb() { const sandbox = sinon.createSandbox(); // Set up mocks PlacesUtils.favicons = { - getFaviconForPage: sandbox.stub().returns( - Promise.resolve({ - iconUri: { spec: FAKE_SMALLPNG_DATA_URI }, - width: 100, - }) - ), + getFaviconDataForPage: sandbox + .stub() + .callsArgWith( + 1, + { spec: FAKE_SMALLPNG_DATA_URI }, + 100, + ["dummy icon data"], + "image/png", + 16 + ), setFaviconForPage: sandbox.spy(), copyFavicons: sandbox.spy(), }; @@ -365,7 +370,9 @@ add_task(async function test_fetchIcon_withNoTippyTopData() { let feed = new FaviconProvider(); // Set up mocks PlacesUtils.favicons = { - getFaviconForPage: sandbox.stub().returns(Promise.resolve(null)), + getFaviconDataForPage: sandbox + .stub() + .callsArgWith(1, null, 0, null, null, 0), setFaviconForPage: sandbox.spy(), copyFavicons: sandbox.spy(), }; diff --git a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_tab_favicons.js b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_tab_favicons.js index 5a176c855310..e4acd1727d3f 100644 --- a/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_tab_favicons.js +++ b/devtools/client/aboutdebugging/test/browser/browser_aboutdebugging_tab_favicons.js @@ -42,11 +42,10 @@ add_task(async function () { ".qa-debug-target-item-icon" ); - // Note this relies on PlaceUtils.favicons.getFaviconForPage() returning the - // favicon that has same data-url as the one provided in the test page. If the - // implementation changes and PlaceUtils returns a different base64 from the - // one we defined, we can instead load the image and check a few pixels to - // verify it matches the expected icon. + // Note this relies on PlaceUtils.promiseFaviconData returning the same data-url as the + // one provided in the test page. If the implementation changes and PlaceUtils returns a + // different base64 from the one we defined, we can instead load the image and check a + // few pixels to verify it matches the expected icon. is( faviconTabIcon.src, EXPECTED_FAVICON, diff --git a/devtools/client/performance-new/@types/gecko.d.ts b/devtools/client/performance-new/@types/gecko.d.ts index 159644e10fe5..7d4a3d8544cf 100644 --- a/devtools/client/performance-new/@types/gecko.d.ts +++ b/devtools/client/performance-new/@types/gecko.d.ts @@ -232,23 +232,19 @@ declare namespace MockedExports { interface FaviconData { uri: nsIURI; - rawData: number[]; + dataLen: number; + data: number[]; mimeType: string; - width: number; + size: number; } const PlaceUtilsSYSMJS: { PlacesUtils: { - favicons: { - getFaviconForPage: ( - pageUrl: nsIURI, - preferredWidth?: number - ) => Promise; - // TS-TODO: Add the rest. - }, - toURI: ( - string | URL | nsIURI - ) => nsIURI + promiseFaviconData: ( + pageUrl: string | URL | nsIURI, + preferredWidth?: number + ) => Promise; + // TS-TODO: Add the rest. }; }; diff --git a/devtools/client/performance-new/shared/background.sys.mjs b/devtools/client/performance-new/shared/background.sys.mjs index e3ac68f36816..0f53908a51b4 100644 --- a/devtools/client/performance-new/shared/background.sys.mjs +++ b/devtools/client/performance-new/shared/background.sys.mjs @@ -460,18 +460,17 @@ async function getPageFavicons(pageUrls) { } // Get the data of favicons and return them. - const { favicons, toURI } = lazy.PlacesUtils(); + const { promiseFaviconData } = lazy.PlacesUtils(); const promises = pageUrls.map(pageUrl => - favicons - .getFaviconForPage(toURI(pageUrl), /* preferredWidth = */ 32) + promiseFaviconData(pageUrl, /* preferredWidth = */ 32) .then(favicon => { // Check if data is found in the database and return it if so. - if (favicon.rawData.length) { + if (favicon.dataLen > 0 && favicon.data) { return { // PlacesUtils returns a number array for the data. Converting it to // the Uint8Array here to send it to the tab more efficiently. - data: new Uint8Array(favicon.rawData).buffer, + data: new Uint8Array(favicon.data).buffer, mimeType: favicon.mimeType, }; } diff --git a/devtools/server/actors/descriptors/tab.js b/devtools/server/actors/descriptors/tab.js index 9d9d18c28d3e..b7954d13099d 100644 --- a/devtools/server/actors/descriptors/tab.js +++ b/devtools/server/actors/descriptors/tab.js @@ -221,10 +221,10 @@ class TabDescriptorActor extends Actor { } try { - const favicon = await lazy.PlacesUtils.favicons.getFaviconForPage( - lazy.PlacesUtils.toURI(this._getUrl()) + const { data } = await lazy.PlacesUtils.promiseFaviconData( + this._getUrl() ); - return favicon.rawData; + return data; } catch (e) { // Favicon unavailable for this url. return null; diff --git a/remote/cdp/targets/TabTarget.sys.mjs b/remote/cdp/targets/TabTarget.sys.mjs index 0bd8038547dc..016b934cb368 100644 --- a/remote/cdp/targets/TabTarget.sys.mjs +++ b/remote/cdp/targets/TabTarget.sys.mjs @@ -2,6 +2,8 @@ * 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + import { Target } from "chrome://remote/content/cdp/targets/Target.sys.mjs"; const lazy = {}; @@ -12,6 +14,13 @@ ChromeUtils.defineESModuleGetters(lazy, { TabSession: "chrome://remote/content/cdp/sessions/TabSession.sys.mjs", }); +XPCOMUtils.defineLazyServiceGetter( + lazy, + "Favicons", + "@mozilla.org/browser/favicon-service;1", + "nsIFaviconService" +); + /** * Target for a local tab or a remoted frame. */ @@ -83,6 +92,19 @@ export class TabTarget extends Target { return null; } + /** @returns {Promise} */ + get faviconUrl() { + return new Promise(resolve => { + lazy.Favicons.getFaviconURLForPage(this.browser.currentURI, url => { + if (url) { + resolve(url.spec); + } else { + resolve(null); + } + }); + }); + } + get title() { return this.browsingContext.currentWindowGlobal.documentTitle; } diff --git a/services/sync/modules/engines/tabs.sys.mjs b/services/sync/modules/engines/tabs.sys.mjs index be26b199b0f5..9f1054b772df 100644 --- a/services/sync/modules/engines/tabs.sys.mjs +++ b/services/sync/modules/engines/tabs.sys.mjs @@ -425,10 +425,9 @@ export const TabProvider = { encoder.encode(thisTab.title + thisTab.lastUsed + url).byteLength + 100; // Use the favicon service for the icon url - we can wait for the promises at the end. - let iconPromise = lazy.PlacesUtils.favicons - .getFaviconForPage(lazy.PlacesUtils.toURI(url)) - .then(favicon => { - thisTab.icon = favicon.uri.spec; + let iconPromise = lazy.PlacesUtils.promiseFaviconData(url) + .then(iconData => { + thisTab.icon = iconData.uri.spec; }) .catch(() => { log.trace( diff --git a/toolkit/components/credentialmanagement/CredentialChooserService.sys.mjs b/toolkit/components/credentialmanagement/CredentialChooserService.sys.mjs index 5109074b2b0e..38c64ff4f40d 100644 --- a/toolkit/components/credentialmanagement/CredentialChooserService.sys.mjs +++ b/toolkit/components/credentialmanagement/CredentialChooserService.sys.mjs @@ -39,9 +39,7 @@ XPCOMUtils.defineLazyPreferenceGetter( */ async function setIconToFavicon(icon, origin) { try { - let iconData = await lazy.PlacesUtils.favicons.getFaviconForPage( - lazy.PlacesUtils.toURI(origin) - ); + let iconData = await lazy.PlacesUtils.promiseFaviconData(origin); icon.src = iconData.uri.spec; } catch { icon.src = "chrome://global/skin/icons/defaultFavicon.svg"; diff --git a/toolkit/components/places/BookmarkHTMLUtils.sys.mjs b/toolkit/components/places/BookmarkHTMLUtils.sys.mjs index d3e78d361528..1e92707a6dd1 100644 --- a/toolkit/components/places/BookmarkHTMLUtils.sys.mjs +++ b/toolkit/components/places/BookmarkHTMLUtils.sys.mjs @@ -77,6 +77,17 @@ const MICROSEC_PER_SEC = 1000000; const EXPORT_INDENT = " "; // four spaces +function base64EncodeString(aString) { + let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + stream.setByteStringData(aString); + let encoder = Cc["@mozilla.org/scriptablebase64encoder;1"].createInstance( + Ci.nsIScriptableBase64Encoder + ); + return encoder.encodeToString(stream, aString.length); +} + /** * Provides HTML escaping for use in HTML attributes and body of the bookmarks * file, compatible with the old bookmarks system. @@ -1043,19 +1054,21 @@ BookmarkExporter.prototype = { if (!aItem.iconUri) { return; } - + let favicon; try { - let favicon = await PlacesUtils.favicons.getFaviconForPage( - PlacesUtils.toURI(aItem.uri) - ); - - this._writeAttribute("ICON_URI", escapeUrl(favicon.uri.spec)); - - if (favicon?.rawData.length && !favicon.uri.schemeIs("chrome")) { - this._writeAttribute("ICON", favicon.dataURI.spec); - } + favicon = await PlacesUtils.promiseFaviconData(aItem.uri); } catch (ex) { console.error("Unexpected Error trying to fetch icon data"); + return; + } + + this._writeAttribute("ICON_URI", escapeUrl(favicon.uri.spec)); + + if (!favicon.uri.schemeIs("chrome") && favicon.dataLen > 0) { + let faviconContents = + "data:image/png;base64," + + base64EncodeString(String.fromCharCode.apply(String, favicon.data)); + this._writeAttribute("ICON", faviconContents); } }, }; diff --git a/toolkit/components/places/FaviconHelpers.cpp b/toolkit/components/places/FaviconHelpers.cpp index 34caa7b4edbb..f64c0d6c4c03 100644 --- a/toolkit/components/places/FaviconHelpers.cpp +++ b/toolkit/components/places/FaviconHelpers.cpp @@ -868,6 +868,83 @@ AsyncSetIconForPage::Run() { return (rv = event.Run()); } +//////////////////////////////////////////////////////////////////////////////// +//// AsyncGetFaviconURLForPage + +AsyncGetFaviconURLForPage::AsyncGetFaviconURLForPage( + const nsCOMPtr& aPageURI, uint16_t aPreferredWidth, + nsIFaviconDataCallback* aCallback) + : Runnable("places::AsyncGetFaviconURLForPage"), + mPreferredWidth(aPreferredWidth == 0 ? UINT16_MAX : aPreferredWidth), + mCallback(new nsMainThreadPtrHolder( + "AsyncGetFaviconURLForPage::mCallback", aCallback)), + mPageURI(aPageURI) { + MOZ_ASSERT(NS_IsMainThread()); +} + +NS_IMETHODIMP +AsyncGetFaviconURLForPage::Run() { + MOZ_ASSERT(!NS_IsMainThread()); + + RefPtr DB = Database::GetDatabase(); + NS_ENSURE_STATE(DB); + IconData iconData; + nsresult rv = FetchIconPerSpec(DB, mPageURI, iconData, mPreferredWidth); + NS_ENSURE_SUCCESS(rv, rv); + + // Now notify our callback of the icon spec we retrieved, even if empty. + PageData pageData; + mPageURI->GetSpec(pageData.spec); + + nsCOMPtr event = + new NotifyIconObservers(iconData, pageData, mCallback); + rv = NS_DispatchToMainThread(event); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +//// AsyncGetFaviconDataForPage + +AsyncGetFaviconDataForPage::AsyncGetFaviconDataForPage( + const nsCOMPtr& aPageURI, uint16_t aPreferredWidth, + nsIFaviconDataCallback* aCallback) + : Runnable("places::AsyncGetFaviconDataForPage"), + mPreferredWidth(aPreferredWidth == 0 ? UINT16_MAX : aPreferredWidth), + mCallback(new nsMainThreadPtrHolder( + "AsyncGetFaviconDataForPage::mCallback", aCallback)), + mPageURI(aPageURI) { + MOZ_ASSERT(NS_IsMainThread()); +} + +NS_IMETHODIMP +AsyncGetFaviconDataForPage::Run() { + MOZ_ASSERT(!NS_IsMainThread()); + + RefPtr DB = Database::GetDatabase(); + NS_ENSURE_STATE(DB); + IconData iconData; + nsresult rv = FetchIconPerSpec(DB, mPageURI, iconData, mPreferredWidth); + NS_ENSURE_SUCCESS(rv, rv); + + if (!iconData.spec.IsEmpty()) { + rv = FetchIconInfo(DB, mPreferredWidth, iconData); + if (NS_FAILED(rv)) { + iconData.spec.Truncate(); + } + } + + PageData pageData; + mPageURI->GetSpec(pageData.spec); + + nsCOMPtr event = + new NotifyIconObservers(iconData, pageData, mCallback); + rv = NS_DispatchToMainThread(event); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + //////////////////////////////////////////////////////////////////////////////// //// AsyncGetFaviconForPageRunnable diff --git a/toolkit/components/places/FaviconHelpers.h b/toolkit/components/places/FaviconHelpers.h index 3cfedebb2391..871a0a3891ca 100644 --- a/toolkit/components/places/FaviconHelpers.h +++ b/toolkit/components/places/FaviconHelpers.h @@ -159,6 +159,63 @@ class AsyncSetIconForPage final : public Runnable { PageData mPage; }; +/** + * Asynchronously tries to get the URL of a page's favicon, then notifies the + * given observer. + */ +class AsyncGetFaviconURLForPage final : public Runnable { + public: + NS_DECL_NSIRUNNABLE + + /** + * Constructor. + * + * @param aPageURI + * URI of the page whose favicon's URL we're fetching + * @param aCallback + * function to be called once finished + * @param aPreferredWidth + * The preferred size for the icon + */ + AsyncGetFaviconURLForPage(const nsCOMPtr& aPageURI, + uint16_t aPreferredWidth, + nsIFaviconDataCallback* aCallback); + + private: + uint16_t mPreferredWidth; + nsMainThreadPtrHandle mCallback; + nsCOMPtr mPageURI; +}; + +/** + * Asynchronously tries to get the URL and data of a page's favicon, then + * notifies the given observer. + */ +class AsyncGetFaviconDataForPage final : public Runnable { + public: + NS_DECL_NSIRUNNABLE + + /** + * Constructor. + * + * @param aPageURI + * URI of the page whose favicon's URL we're fetching + * @param aPreferredWidth + * The preferred size of the icon. We will try to return an icon close + * to this size. + * @param aCallback + * function to be called once finished + */ + AsyncGetFaviconDataForPage(const nsCOMPtr& aPageURI, + uint16_t aPreferredWidth, + nsIFaviconDataCallback* aCallback); + + private: + uint16_t mPreferredWidth; + nsMainThreadPtrHandle mCallback; + nsCOMPtr mPageURI; +}; + using FaviconPromise = mozilla::MozPromise, nsresult, true>; diff --git a/toolkit/components/places/PageIconProtocolHandler.cpp b/toolkit/components/places/PageIconProtocolHandler.cpp index 19bc8cbcbc3e..068a6aea0c5f 100644 --- a/toolkit/components/places/PageIconProtocolHandler.cpp +++ b/toolkit/components/places/PageIconProtocolHandler.cpp @@ -39,39 +39,6 @@ struct FaviconMetadata { uint16_t mWidth = 0; }; -static nsresult GetFaviconMetadata( - const FaviconPromise::ResolveOrRejectValue& aResult, - FaviconMetadata& aMetadata) { - if (aResult.IsReject()) { - return NS_ERROR_NOT_AVAILABLE; - } - - nsCOMPtr favicon = aResult.ResolveValue(); - if (!favicon) { - return NS_ERROR_NOT_AVAILABLE; - } - - nsTArray rawData; - favicon->GetRawData(rawData); - - if (rawData.IsEmpty()) { - return NS_ERROR_NOT_AVAILABLE; - } - - nsCOMPtr stream; - nsresult rv = NS_NewByteInputStream( - getter_AddRefs(stream), - AsChars(Span{rawData.Elements(), rawData.Length()}), NS_ASSIGNMENT_COPY); - NS_ENSURE_SUCCESS(rv, rv); - - favicon->GetWidth(&aMetadata.mWidth); - favicon->GetMimeType(aMetadata.mContentType); - aMetadata.mStream = stream; - aMetadata.mContentLength = rawData.Length(); - - return NS_OK; -} - void RecordIconSizeTelemetry(nsIURI* uri, const FaviconMetadata& metadata) { uint16_t preferredSize = INT16_MAX; auto* faviconService = nsFaviconService::GetFaviconService(); @@ -166,6 +133,66 @@ static nsresult StreamDefaultFavicon(nsIURI* aURI, nsILoadInfo* aLoadInfo, return NS_OK; } +namespace { + +class FaviconDataCallback final : public nsIFaviconDataCallback { + public: + FaviconDataCallback(nsIURI* aURI, nsILoadInfo* aLoadInfo) + : mURI(aURI), mLoadInfo(aLoadInfo) { + MOZ_ASSERT(aURI); + MOZ_ASSERT(aLoadInfo); + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIFAVICONDATACALLBACK + + RefPtr Promise() { + return mPromiseHolder.Ensure(__func__); + } + + private: + ~FaviconDataCallback(); + nsCOMPtr mURI; + MozPromiseHolder mPromiseHolder; + nsCOMPtr mLoadInfo; +}; + +NS_IMPL_ISUPPORTS(FaviconDataCallback, nsIFaviconDataCallback); + +FaviconDataCallback::~FaviconDataCallback() { + mPromiseHolder.RejectIfExists(NS_ERROR_FAILURE, __func__); +} + +NS_IMETHODIMP FaviconDataCallback::OnComplete(nsIURI* aURI, uint32_t aDataLen, + const uint8_t* aData, + const nsACString& aMimeType, + uint16_t aWidth) { + if (!aDataLen) { + mPromiseHolder.Reject(NS_ERROR_NOT_AVAILABLE, __func__); + return NS_OK; + } + + nsCOMPtr inputStream; + nsresult rv = + NS_NewByteInputStream(getter_AddRefs(inputStream), + AsChars(Span{aData, aDataLen}), NS_ASSIGNMENT_COPY); + if (NS_FAILED(rv)) { + mPromiseHolder.Reject(rv, __func__); + return rv; + } + + FaviconMetadata metadata; + metadata.mStream = inputStream; + metadata.mContentType = aMimeType; + metadata.mContentLength = aDataLen; + metadata.mWidth = aWidth; + mPromiseHolder.Resolve(std::move(metadata), __func__); + + return NS_OK; +} + +} // namespace + NS_IMPL_ISUPPORTS(PageIconProtocolHandler, nsIProtocolHandler, nsISupportsWeakReference); @@ -235,49 +262,52 @@ nsresult PageIconProtocolHandler::NewChannelInternal(nsIURI* aURI, nsresult rv = channel->SetLoadInfo(aLoadInfo); NS_ENSURE_SUCCESS(rv, rv); - GetFaviconData(aURI)->Then( - GetMainThreadSerialEventTarget(), __func__, - [pipeOut, channel, uri = nsCOMPtr{aURI}, loadInfo = nsCOMPtr{aLoadInfo}]( - const FaviconPromise::ResolveOrRejectValue& aResult) { - FaviconMetadata metadata; - if (NS_SUCCEEDED(GetFaviconMetadata(aResult, metadata))) { - channel->SetContentType(metadata.mContentType); - channel->SetContentLength(metadata.mContentLength); + GetFaviconData(aURI, aLoadInfo) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [pipeOut, channel, + uri = nsCOMPtr{aURI}](const FaviconMetadata& aMetadata) { + channel->SetContentType(aMetadata.mContentType); + channel->SetContentLength(aMetadata.mContentLength); - nsresult rv; - const nsCOMPtr target = - do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); - if (NS_WARN_IF(NS_FAILED(rv))) { - channel->CancelWithReason(NS_BINDING_ABORTED, - "GetFaviconData failed"_ns); - return; - } + nsresult rv; + const nsCOMPtr target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); - rv = NS_AsyncCopy(metadata.mStream, pipeOut, target); - if (NS_WARN_IF(NS_FAILED(rv))) { - return; - } - RecordIconSizeTelemetry(uri, metadata); - } else { - // There are a few reasons why this might fail. For example, one - // reason is that the URI might not actually be properly parsable. - // In that case, we'll try one last time to stream the default - // favicon before giving up. - channel->SetContentType(nsLiteralCString(FAVICON_DEFAULT_MIMETYPE)); - channel->SetContentLength(-1); - Unused << StreamDefaultFavicon(uri, loadInfo, pipeOut); - } - }); + if (NS_WARN_IF(NS_FAILED(rv))) { + channel->CancelWithReason(NS_BINDING_ABORTED, + "GetFaviconData failed"_ns); + return; + } + rv = NS_AsyncCopy(aMetadata.mStream, pipeOut, target); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + RecordIconSizeTelemetry(uri, aMetadata); + }, + [uri = nsCOMPtr{aURI}, loadInfo = nsCOMPtr{aLoadInfo}, pipeOut, + channel](nsresult aRv) { + // There are a few reasons why this might fail. For example, one + // reason is that the URI might not actually be properly parsable. + // In that case, we'll try one last time to stream the default + // favicon before giving up. + channel->SetContentType(nsLiteralCString(FAVICON_DEFAULT_MIMETYPE)); + channel->SetContentLength(-1); + Unused << StreamDefaultFavicon(uri, loadInfo, pipeOut); + }); channel.forget(aOutChannel); return NS_OK; } -RefPtr PageIconProtocolHandler::GetFaviconData( - nsIURI* aPageIconURI) { +RefPtr PageIconProtocolHandler::GetFaviconData( + nsIURI* aPageIconURI, nsILoadInfo* aLoadInfo) { auto* faviconService = nsFaviconService::GetFaviconService(); if (MOZ_UNLIKELY(!faviconService)) { - return FaviconPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__); + return FaviconMetadataPromise::CreateAndReject(NS_ERROR_UNEXPECTED, + __func__); } uint16_t preferredSize = 0; @@ -285,16 +315,26 @@ RefPtr PageIconProtocolHandler::GetFaviconData( nsCOMPtr pageURI; nsresult rv; - // NOTE: We don't need to strip #size= fragments because - // GetFaviconDataForPage strips them when doing the database lookup. - nsAutoCString pageQuery; - aPageIconURI->GetPathQueryRef(pageQuery); - rv = NS_NewURI(getter_AddRefs(pageURI), pageQuery); - if (NS_FAILED(rv)) { - return FaviconPromise::CreateAndReject(rv, __func__); + { + // NOTE: We don't need to strip #size= fragments because + // GetFaviconDataForPage strips them when doing the database lookup. + nsAutoCString pageQuery; + aPageIconURI->GetPathQueryRef(pageQuery); + rv = NS_NewURI(getter_AddRefs(pageURI), pageQuery); + if (NS_FAILED(rv)) { + return FaviconMetadataPromise::CreateAndReject(rv, __func__); + } } - return faviconService->AsyncGetFaviconForPage(pageURI, preferredSize); + auto faviconCallback = + MakeRefPtr(aPageIconURI, aLoadInfo); + rv = faviconService->GetFaviconDataForPage(pageURI, faviconCallback, + preferredSize); + if (NS_FAILED(rv)) { + return FaviconMetadataPromise::CreateAndReject(rv, __func__); + } + + return faviconCallback->Promise(); } RefPtr PageIconProtocolHandler::NewStream( @@ -329,28 +369,26 @@ RefPtr PageIconProtocolHandler::NewStream( nsCOMPtr loadInfo(aLoadInfo); RefPtr self = this; - GetFaviconData(uri)->Then( - GetMainThreadSerialEventTarget(), __func__, - [self, uri, loadInfo, outerPromise, childURI = nsCOMPtr{aChildURI}]( - const FaviconPromise::ResolveOrRejectValue& aResult) { - FaviconMetadata metadata; - if (NS_SUCCEEDED(GetFaviconMetadata(aResult, metadata))) { - RecordIconSizeTelemetry(childURI, metadata); - RemoteStreamInfo info(metadata.mStream, metadata.mContentType, - metadata.mContentLength); - outerPromise->Resolve(std::move(info), __func__); - } else { - nsCOMPtr pipeIn; - nsCOMPtr pipeOut; - self->GetStreams(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut)); - - RemoteStreamInfo info(pipeIn, - nsLiteralCString(FAVICON_DEFAULT_MIMETYPE), -1); - Unused << StreamDefaultFavicon(uri, loadInfo, pipeOut); - outerPromise->Resolve(std::move(info), __func__); - } - }); + GetFaviconData(uri, loadInfo) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [outerPromise, + childURI = nsCOMPtr{aChildURI}](const FaviconMetadata& aMetadata) { + RecordIconSizeTelemetry(childURI, aMetadata); + RemoteStreamInfo info(aMetadata.mStream, aMetadata.mContentType, + aMetadata.mContentLength); + outerPromise->Resolve(std::move(info), __func__); + }, + [self, uri, loadInfo, outerPromise](nsresult aRv) { + nsCOMPtr pipeIn; + nsCOMPtr pipeOut; + self->GetStreams(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut)); + RemoteStreamInfo info( + pipeIn, nsLiteralCString(FAVICON_DEFAULT_MIMETYPE), -1); + Unused << StreamDefaultFavicon(uri, loadInfo, pipeOut); + outerPromise->Resolve(std::move(info), __func__); + }); return outerPromise; } diff --git a/toolkit/components/places/PageIconProtocolHandler.h b/toolkit/components/places/PageIconProtocolHandler.h index f07bd6b49ee7..7cfeeae99bcc 100644 --- a/toolkit/components/places/PageIconProtocolHandler.h +++ b/toolkit/components/places/PageIconProtocolHandler.h @@ -10,7 +10,6 @@ #include "mozilla/MozPromise.h" #include "mozilla/StaticPtr.h" #include "mozilla/net/RemoteStreamGetter.h" -#include "mozilla/places/nsFaviconService.h" #include "nsIProtocolHandler.h" #include "nsThreadUtils.h" #include "nsWeakReference.h" @@ -18,6 +17,8 @@ namespace mozilla::places { struct FaviconMetadata; +using FaviconMetadataPromise = + mozilla::MozPromise; using net::RemoteStreamPromise; @@ -73,7 +74,8 @@ class PageIconProtocolHandler final : public nsIProtocolHandler, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal); - RefPtr GetFaviconData(nsIURI* aPageIconURI); + RefPtr GetFaviconData(nsIURI* aPageIconURI, + nsILoadInfo* aLoadInfo); nsresult NewChannelInternal(nsIURI*, nsILoadInfo*, nsIChannel**); diff --git a/toolkit/components/places/PlacesUtils.sys.mjs b/toolkit/components/places/PlacesUtils.sys.mjs index 724de411f365..3bd441c71bf2 100644 --- a/toolkit/components/places/PlacesUtils.sys.mjs +++ b/toolkit/components/places/PlacesUtils.sys.mjs @@ -1621,6 +1621,37 @@ export var PlacesUtils = { return db.executeBeforeShutdown(name, task); }, + /** + * Gets favicon data for a given page url. + * + * @param {string|URL|nsIURI} aPageUrl + * Url of the page to get favicon for. + * @param {number} [preferredWidth] + * The preferred width of the favicon in pixels. The default value of 0 + * returns the largest icon available. + * @returns {Promise<{uri: nsIURI, dataLen: number, data: number[], mimeType: string, size: number}>} + * Resolves an object representing a favicon entry. + * Rejects if the given url has no associated favicon. + */ + promiseFaviconData(aPageUrl, preferredWidth = 0) { + return new Promise((resolve, reject) => { + if (!(aPageUrl instanceof Ci.nsIURI)) { + aPageUrl = PlacesUtils.toURI(aPageUrl); + } + PlacesUtils.favicons.getFaviconDataForPage( + aPageUrl, + function (uri, dataLen, data, mimeType, size) { + if (uri) { + resolve({ uri, dataLen, data, mimeType, size }); + } else { + reject(); + } + }, + preferredWidth + ); + }); + }, + /** * Returns the passed URL with a #size ref for the specified size and * devicePixelRatio. diff --git a/toolkit/components/places/nsFaviconService.cpp b/toolkit/components/places/nsFaviconService.cpp index 94997dcf8d9e..f9ad4d2bffb5 100644 --- a/toolkit/components/places/nsFaviconService.cpp +++ b/toolkit/components/places/nsFaviconService.cpp @@ -417,6 +417,53 @@ nsFaviconService::SetFaviconForPage(nsIURI* aPageURI, nsIURI* aFaviconURI, return NS_OK; } +NS_IMETHODIMP +nsFaviconService::GetFaviconURLForPage(nsIURI* aPageURI, + nsIFaviconDataCallback* aCallback, + uint16_t aPreferredWidth) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG(aPageURI); + NS_ENSURE_ARG(aCallback); + // Use the default value, may be UINT16_MAX if a default is not set. + if (aPreferredWidth == 0) { + aPreferredWidth = mDefaultIconURIPreferredSize; + } + + nsCOMPtr pageURI = GetExposableURI(aPageURI); + + RefPtr event = + new AsyncGetFaviconURLForPage(pageURI, aPreferredWidth, aCallback); + + RefPtr DB = Database::GetDatabase(); + NS_ENSURE_STATE(DB); + DB->DispatchToAsyncThread(event); + + return NS_OK; +} + +NS_IMETHODIMP +nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI, + nsIFaviconDataCallback* aCallback, + uint16_t aPreferredWidth) { + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG(aPageURI); + NS_ENSURE_ARG(aCallback); + // Use the default value, may be UINT16_MAX if a default is not set. + if (aPreferredWidth == 0) { + aPreferredWidth = mDefaultIconURIPreferredSize; + } + + nsCOMPtr pageURI = GetExposableURI(aPageURI); + + RefPtr event = + new AsyncGetFaviconDataForPage(pageURI, aPreferredWidth, aCallback); + RefPtr DB = Database::GetDatabase(); + NS_ENSURE_STATE(DB); + DB->DispatchToAsyncThread(event); + + return NS_OK; +} + NS_IMETHODIMP nsFaviconService::GetFaviconForPage(nsIURI* aPageURI, uint16_t aPreferredWidth, JSContext* aContext = nullptr, diff --git a/toolkit/components/places/nsIFaviconService.idl b/toolkit/components/places/nsIFaviconService.idl index 028f60096d50..9aa35d70fc22 100644 --- a/toolkit/components/places/nsIFaviconService.idl +++ b/toolkit/components/places/nsIFaviconService.idl @@ -120,6 +120,58 @@ interface nsIFaviconService : nsISupports [optional] in boolean isRichIcon ); + /** + * Retrieves the favicon URI associated to the given page, if any. + * + * @param aPageURI + * URI of the page whose favicon URI we're looking up. + * @param aCallback + * This callback is always invoked to notify the result of the lookup. + * The aURI parameter will be the favicon URI, or null when no favicon + * is associated with the page or an error occurred while fetching it. + * aDataLen will be always 0, aData will be an empty array, and + * aMimeType will be an empty string, regardless of whether a favicon + * was found. + * @param [optional] aPreferredWidth + * The preferred icon width, skip or pass 0 for the default value, + * set through setDefaultIconURIPreferredSize. + * + * @note If a favicon specific to this page cannot be found, this will try to + * fallback to the /favicon.ico for the root domain. + * + * @see nsIFaviconDataCallback in nsIFaviconService.idl. + */ + void getFaviconURLForPage(in nsIURI aPageURI, + in nsIFaviconDataCallback aCallback, + [optional] in unsigned short aPreferredWidth); + + /** + * Retrieves the favicon URI and data associated to the given page, if any. + * If the page icon is not available, it will try to return the root domain + * icon data, when it's known. + * + * @param aPageURI + * URI of the page whose favicon URI and data we're looking up. + * @param aCallback + * This callback is always invoked to notify the result of the lookup. The aURI + * parameter will be the favicon URI, or null when no favicon is + * associated with the page or an error occurred while fetching it. If + * aURI is not null, the other parameters may contain the favicon data. + * However, if no favicon data is currently associated with the favicon + * URI, aDataLen will be 0, aData will be an empty array, and aMimeType + * will be an empty string. + * @param [optional] aPreferredWidth + * The preferred icon width, skip or pass 0 for the default value, + * set through setDefaultIconURIPreferredSize. + * @note If a favicon specific to this page cannot be found, this will try to + * fallback to the /favicon.ico for the root domain. + * + * @see nsIFaviconDataCallback in nsIFaviconService.idl. + */ + void getFaviconDataForPage(in nsIURI aPageURI, + in nsIFaviconDataCallback aCallback, + [optional] in unsigned short aPreferredWidth); + /** * Retrieves the favicon URI and data URL associated to the given page, if any. * If the page icon is not available, it will try to return the root domain diff --git a/toolkit/components/places/tests/PlacesTestUtils.sys.mjs b/toolkit/components/places/tests/PlacesTestUtils.sys.mjs index 5eaf11d24008..8fa521568ff1 100644 --- a/toolkit/components/places/tests/PlacesTestUtils.sys.mjs +++ b/toolkit/components/places/tests/PlacesTestUtils.sys.mjs @@ -176,22 +176,6 @@ export var PlacesTestUtils = Object.freeze({ ); }, - /* - * Helper function to call PlacesUtils.favicons.getFaviconForPage(). This - * function throws an error if the status of - * PlacesUtils.favicons.setFaviconForPage() is not success. - * - * @param {string or URL or nsIURI} pageURI - * @param {Number} [optional] preferredWidth - * @return {Promise} resolved with favicon data - */ - getFaviconForPage(pageURI, preferredWidth = 0) { - return lazy.PlacesUtils.favicons.getFaviconForPage( - lazy.PlacesUtils.toURI(pageURI), - preferredWidth - ); - }, - /** * Get favicon data for given URL from database. * diff --git a/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js b/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js index 9642b647d9f6..fd1890c0ec17 100644 --- a/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js +++ b/toolkit/components/places/tests/browser/browser_favicon_privatebrowsing_perwindowpb.js @@ -33,10 +33,14 @@ function test() { } testOnWindow(true, function (win) { - waitForTabLoad(win, async function () { - let favicon = await PlacesTestUtils.getFaviconForPage(pageURI); - is(favicon, null, "No result should be found"); - finish(); + waitForTabLoad(win, function () { + PlacesUtils.favicons.getFaviconURLForPage( + NetUtil.newURI(pageURI), + function (uri) { + is(uri, null, "No result should be found"); + finish(); + } + ); }); }); } diff --git a/toolkit/components/places/tests/expiration/test_debug_expiration.js b/toolkit/components/places/tests/expiration/test_debug_expiration.js index 59dcfa7abbda..22657e6b24b7 100644 --- a/toolkit/components/places/tests/expiration/test_debug_expiration.js +++ b/toolkit/components/places/tests/expiration/test_debug_expiration.js @@ -357,9 +357,8 @@ add_task(async function test_expire_icons() { if (entry.icon) { await PlacesTestUtils.setFaviconForPage(entry.page, entry.icon, dataUrl); - let favicon = await PlacesTestUtils.getFaviconForPage(entry.page); Assert.equal( - favicon.uri.spec, + await getFaviconUrlForPage(entry.page), entry.icon, "Sanity check the icon exists" ); @@ -395,9 +394,8 @@ add_task(async function test_expire_icons() { }); } if (entry.icon) { - let favicon = await PlacesTestUtils.getFaviconForPage(entry.page); Assert.equal( - favicon.uri.spec, + await getFaviconUrlForPage(entry.page), entry.icon, "Sanity check the initial icon value" ); @@ -411,19 +409,30 @@ add_task(async function test_expire_icons() { for (let entry of entries) { Assert.ok(page_in_database(entry.page)); - let favicon = await PlacesTestUtils.getFaviconForPage(entry.page); if (!entry.removed) { - Assert.equal(favicon.uri.spec, entry.icon, entry.desc); + Assert.equal( + await getFaviconUrlForPage(entry.page), + entry.icon, + entry.desc + ); continue; } if (entry.root) { - Assert.equal(favicon.uri.spec, entry.root, entry.desc); + Assert.equal( + await getFaviconUrlForPage(entry.page), + entry.root, + entry.desc + ); continue; } if (entry.icon) { - await Assert.equal(favicon, null, entry.desc); + await Assert.rejects( + getFaviconUrlForPage(entry.page), + /Unable to find an icon/, + entry.desc + ); continue; } diff --git a/toolkit/components/places/tests/favicons/head_favicons.js b/toolkit/components/places/tests/favicons/head_favicons.js index 8252742ddb0c..73dd2a61ed60 100644 --- a/toolkit/components/places/tests/favicons/head_favicons.js +++ b/toolkit/components/places/tests/favicons/head_favicons.js @@ -28,18 +28,26 @@ let uniqueFaviconId = 0; * @param aExpectedData * Expected icon data, expressed as an array of byte values. * If set null, skip the test for the favicon data. + * @param aCallback + * This function is called after the check finished. */ -async function checkFaviconDataForPage( +function checkFaviconDataForPage( aPageURI, aExpectedMimeType, - aExpectedData + aExpectedData, + aCallback ) { - let favicon = await PlacesTestUtils.getFaviconForPage(aPageURI); - Assert.equal(aExpectedMimeType, favicon.mimeType); - if (aExpectedData) { - Assert.ok(compareArrays(aExpectedData, favicon.rawData)); - } - await check_guid_for_uri(aPageURI); + PlacesUtils.favicons.getFaviconDataForPage( + aPageURI, + async function (aURI, aDataLen, aData, aMimeType) { + Assert.equal(aExpectedMimeType, aMimeType); + if (aExpectedData) { + Assert.ok(compareArrays(aExpectedData, aData)); + } + await check_guid_for_uri(aPageURI); + aCallback(); + } + ); } /** @@ -47,10 +55,18 @@ async function checkFaviconDataForPage( * * @param aPageURI * nsIURI object for the page to check. + * @param aCallback + * This function is called after the check finished. */ -async function checkFaviconMissingForPage(aPageURI) { - let favicon = await PlacesTestUtils.getFaviconForPage(aPageURI); - Assert.ok(!favicon); +function checkFaviconMissingForPage(aPageURI, aCallback) { + PlacesUtils.favicons.getFaviconURLForPage(aPageURI, function (aURI) { + Assert.ok(aURI === null); + aCallback(); + }); +} + +function promiseFaviconMissingForPage(aPageURI) { + return new Promise(resolve => checkFaviconMissingForPage(aPageURI, resolve)); } function promiseFaviconChanged(aExpectedPageURI, aExpectedFaviconURI) { diff --git a/toolkit/components/places/tests/favicons/test_copyFavicons.js b/toolkit/components/places/tests/favicons/test_copyFavicons.js index 1b6aa7b4ed4c..db1273016dfc 100644 --- a/toolkit/components/places/tests/favicons/test_copyFavicons.js +++ b/toolkit/components/places/tests/favicons/test_copyFavicons.js @@ -117,12 +117,12 @@ add_task(async function test_copyFavicons() { ); await promiseChange; Assert.equal( - (await PlacesTestUtils.getFaviconForPage(TEST_URI2, 1)).uri.spec, + await getFaviconUrlForPage(TEST_URI2, 1), SMALLPNG_DATA_URI.spec, "Small icon found" ); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(TEST_URI2)).uri.spec, + await getFaviconUrlForPage(TEST_URI2), SMALLSVG_DATA_URI.spec, "Large icon found" ); @@ -140,12 +140,12 @@ add_task(async function test_copyFavicons() { ); await promiseChange; Assert.equal( - (await PlacesTestUtils.getFaviconForPage(TEST_URI3, 1)).uri.spec, + await getFaviconUrlForPage(TEST_URI3, 1), SMALLPNG_DATA_URI.spec, "Small icon found" ); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(TEST_URI3)).uri.spec, + await getFaviconUrlForPage(TEST_URI3), SMALLSVG_DATA_URI.spec, "Large icon found" ); @@ -181,12 +181,12 @@ add_task(async function test_copyFavicons_overlap() { ); await promiseChange; Assert.equal( - (await PlacesTestUtils.getFaviconForPage(TEST_URI2, 1)).uri.spec, + await getFaviconUrlForPage(TEST_URI2, 1), SMALLPNG_DATA_URI.spec, "Small icon found" ); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(TEST_URI2)).uri.spec, + 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 index c703d6bb390d..77732162acf9 100644 --- a/toolkit/components/places/tests/favicons/test_expireAllFavicons.js +++ b/toolkit/components/places/tests/favicons/test_expireAllFavicons.js @@ -41,6 +41,6 @@ add_task(async function test_expireAllFavicons() { await promise; // Check that the favicons for the pages we added were removed. - await checkFaviconMissingForPage(TEST_PAGE_URI); - await checkFaviconMissingForPage(BOOKMARKED_PAGE_URI); + 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 index c22f729fa855..21320a4e72e1 100644 --- a/toolkit/components/places/tests/favicons/test_expire_migrated_icons.js +++ b/toolkit/components/places/tests/favicons/test_expire_migrated_icons.js @@ -28,10 +28,10 @@ add_task(async function test_storing_a_normal_16x16_icon() { return db.execute(`UPDATE moz_icons SET expire_ms = 0, data = "test"`); }); - let favicon = await PlacesTestUtils.getFaviconForPage(PAGE_URL); - Assert.equal(favicon.mimeType, "image/png"); + let { data, mimeType } = await getFaviconDataForPage(PAGE_URL); + Assert.equal(mimeType, "image/png"); Assert.deepEqual( - favicon.rawData, + data, "test".split("").map(c => c.charCodeAt(0)) ); 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 index b6102cd8093d..f0089d737bf6 100644 --- a/toolkit/components/places/tests/favicons/test_expire_on_new_icons.js +++ b/toolkit/components/places/tests/favicons/test_expire_on_new_icons.js @@ -50,19 +50,19 @@ add_task(async function test_expire_associated() { // Only the second and the third icons should have survived. Assert.equal( - (await PlacesTestUtils.getFaviconForPage(TEST_URL, 16)).uri.spec, + await getFaviconUrlForPage(TEST_URL, 16), TEST_URL + favicons[1].name, "Should retrieve the 32px icon, not the 16px one." ); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(TEST_URL, 64)).uri.spec, + 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 PlacesTestUtils.getFaviconForPage(TEST_URL2, 16)).uri.spec, + await getFaviconUrlForPage(TEST_URL2, 16), TEST_URL + favicons[0].name, "Should retrieve the expired 16px icon" ); @@ -107,7 +107,7 @@ add_task(async function test_expire_root() { // Only the root icon should have survived. Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI, 16)).uri.spec, + await getFaviconUrlForPage(pageURI, 16), rootIconURI.spec, "Should retrieve the root icon." ); diff --git a/toolkit/components/places/tests/favicons/test_favicons_conversions.js b/toolkit/components/places/tests/favicons/test_favicons_conversions.js index 76474454c1a7..e8f36b574ea8 100644 --- a/toolkit/components/places/tests/favicons/test_favicons_conversions.js +++ b/toolkit/components/places/tests/favicons/test_favicons_conversions.js @@ -53,25 +53,27 @@ async function checkFaviconDataConversion( fileDataURL ); - if (!aExpectConversion) { - await checkFaviconDataForPage(pageURI, aFileMimeType, fileData); - } else if (!aVaryOnWindows || !isWindows) { - let allowMissing = AppConstants.USE_LIBZ_RS; - let expectedFile = do_get_file( - "expected-" + - aFileName + - (AppConstants.USE_LIBZ_RS ? ".libz-rs.png" : ".png"), - allowMissing - ); - if (!expectedFile.exists()) { - expectedFile = do_get_file("expected-" + aFileName + ".png"); + await new Promise(resolve => { + if (!aExpectConversion) { + checkFaviconDataForPage(pageURI, aFileMimeType, fileData, resolve); + } else if (!aVaryOnWindows || !isWindows) { + let allowMissing = AppConstants.USE_LIBZ_RS; + let expectedFile = do_get_file( + "expected-" + + aFileName + + (AppConstants.USE_LIBZ_RS ? ".libz-rs.png" : ".png"), + allowMissing + ); + if (!expectedFile.exists()) { + expectedFile = do_get_file("expected-" + aFileName + ".png"); + } + let expectedData = readFileData(expectedFile); + checkFaviconDataForPage(pageURI, "image/png", expectedData, resolve); + } else { + // Not check the favicon data. + checkFaviconDataForPage(pageURI, "image/png", null, resolve); } - let expectedData = readFileData(expectedFile); - await checkFaviconDataForPage(pageURI, "image/png", expectedData); - } else { - // Not check the favicon data. - await checkFaviconDataForPage(pageURI, "image/png", null); - } + }); } add_task(async function test_storing_a_normal_16x16_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 000000000000..d4f8d4e35213 --- /dev/null +++ b/toolkit/components/places/tests/favicons/test_getFaviconDataForPage.js @@ -0,0 +1,457 @@ +/* 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"; + +const FAVICON16_DATA = readFileData(do_get_file("favicon-normal16.png")); +const ICON16_URL = "http://places.test/favicon-normal16.png"; + +const FAVICON64_DATA = readFileData(do_get_file("favicon-big64.png")); +const ICON64_URL = "http://places.test/favicon-big64.png"; + +const SVG_DATA = readFileData(do_get_file("favicon-svg.svg")); +const SVG_URL = "http://example.com/favicon.svg"; + +const MIMETYPE_PNG = "image/png"; +const MIMETYPE_SVG = "image/svg+xml"; + +add_task(async function test_normal() { + Assert.equal(FAVICON_DATA.length, 344); + let pageURI = NetUtil.newURI("http://example.com/normal"); + let dataURL = await PlacesTestUtils.fileDataToDataURL( + FAVICON_DATA, + "image/png" + ); + + await PlacesTestUtils.addVisits(pageURI); + + await PlacesUtils.favicons.setFaviconForPage(pageURI, FAVICON_URI, dataURL); + + await new Promise(resolve => { + 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(); + } + ); + }); +}); + +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")); + let dataURL = await PlacesTestUtils.fileDataToDataURL(data, "image/png"); + await PlacesTestUtils.setFaviconForPage(ROOT_URL, ROOT_ICON_URL, dataURL); + + 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")); + let dataURL32 = await PlacesTestUtils.fileDataToDataURL(data32, "image/png"); + await PlacesTestUtils.setFaviconForPage(SUBPAGE_URL, ICON32_URL, dataURL32); + + 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(); + } + ); + }); +}); + +add_task(async function test_richIconPrioritizationBelowThreshold() { + await runFaviconTest( + "https://example.com/test_prioritization_below_threshold", + [ + { + url: ICON16_URL, + data: FAVICON16_DATA, + isRich: false, + mimetype: MIMETYPE_PNG, + }, + { + url: ICON32_URL, + data: FAVICON_DATA, + isRich: true, + mimetype: MIMETYPE_PNG, + }, + ], + ICON16_URL, + FAVICON16_DATA, + 12 + ); +}); + +add_task(async function test_richIconPrioritizationAboveThreshold() { + await runFaviconTest( + "https://example.com/test_prioritization_below_threshold", + [ + { + url: ICON16_URL, + data: FAVICON16_DATA, + isRich: false, + mimetype: MIMETYPE_PNG, + }, + { + url: ICON32_URL, + data: FAVICON_DATA, + isRich: true, + mimetype: MIMETYPE_PNG, + }, + ], + ICON32_URL, + FAVICON_DATA, + 72 + ); +}); + +add_task(async function test_sizeSelection() { + // Icons set: + // - 16px non-rich + // - 64px non-rich + // - 32px rich + const testCases = [ + // Should select 16px icon, non-rich icons are prioritized for + // preferred size <= 64px, and (24 - 16) = 8 <= (64 - 24) / 4 = 10. + // Therefore smaller icon is selected. + { + expectedURI: ICON16_URL, + expectedData: FAVICON16_DATA, + preferredSize: 24, + }, + // Should select 64px icon, non-rich icons are prioritized for + // preferred size <= 64px, and (64 - 32) / 4 = 8 < (32 - 16) = 16. + // Therefore, larger icon is selected. + { + expectedURI: ICON64_URL, + expectedData: FAVICON64_DATA, + preferredSize: 32, + }, + // Should select 64px icon, no discrimination between rich/non-rich for + // preferred size > 64px. + { + expectedURI: ICON64_URL, + expectedData: FAVICON64_DATA, + preferredSize: 80, + }, + ]; + + for (const { expectedURI, expectedData, preferredSize } of testCases) { + await runFaviconTest( + "https://example.com/test_size_selection", + [ + { + url: ICON16_URL, + data: FAVICON16_DATA, + isRich: false, + mimetype: MIMETYPE_PNG, + }, + { + url: ICON64_URL, + data: FAVICON64_DATA, + isRich: false, + mimetype: MIMETYPE_PNG, + }, + { + url: ICON32_URL, + data: FAVICON_DATA, + isRich: true, + mimetype: MIMETYPE_PNG, + }, + ], + expectedURI, + expectedData, + preferredSize + ); + } +}); + +add_task(async function test_sizeSelectionRichOnly() { + // Should select 16px icon, since there are no non-rich icons found, + // we return the best-sized rich icon. + await runFaviconTest( + "https://example.com/test_size_selection_rich_only", + [ + { + url: ICON16_URL, + data: FAVICON16_DATA, + isRich: true, + mimetype: MIMETYPE_PNG, + }, + { + url: ICON64_URL, + data: FAVICON64_DATA, + isRich: true, + mimetype: MIMETYPE_PNG, + }, + { + url: ICON32_URL, + data: FAVICON_DATA, + isRich: true, + mimetype: MIMETYPE_PNG, + }, + ], + ICON16_URL, + FAVICON16_DATA, + 17 + ); +}); + +add_task(async function test_svg() { + // Selected non-svg is not a perfect fit, so we prefer SVG. + await runFaviconTest( + "https://example.com/test_icon_selection_svg", + [ + { + url: SVG_URL, + data: SVG_DATA, + isRich: false, + mimetype: MIMETYPE_SVG, + }, + { + url: ICON32_URL, + data: FAVICON_DATA, + isRich: false, + mimetype: MIMETYPE_PNG, + }, + ], + SVG_URL, + SVG_DATA, + 31 + ); + + // Selected non-svg is a perfect fit, so we prefer it. + await runFaviconTest( + "https://example.com/test_icon_selection_svg", + [ + { + url: SVG_URL, + data: SVG_DATA, + isRich: false, + mimetype: MIMETYPE_SVG, + }, + { + url: ICON32_URL, + data: FAVICON_DATA, + isRich: false, + mimetype: MIMETYPE_PNG, + }, + ], + ICON32_URL, + FAVICON_DATA, + 32 + ); + + // Selected non-svg is a perfect fit, but it is rich so we prefer SVG. + await runFaviconTest( + "https://example.com/test_icon_selection_svg", + [ + { + url: SVG_URL, + data: SVG_DATA, + isRich: false, + mimetype: MIMETYPE_SVG, + }, + { + url: ICON32_URL, + data: FAVICON_DATA, + isRich: true, + mimetype: MIMETYPE_PNG, + }, + ], + SVG_URL, + SVG_DATA, + 32 + ); + + // Selected non-svg is not a perfect fit, but SVG is rich, so we prefer non-SVG. + await runFaviconTest( + "https://example.com/test_icon_selection_svg", + [ + { + url: SVG_URL, + data: SVG_DATA, + isRich: true, + mimetype: MIMETYPE_SVG, + }, + { + url: ICON32_URL, + data: FAVICON_DATA, + isRich: false, + mimetype: MIMETYPE_PNG, + }, + ], + ICON32_URL, + FAVICON_DATA, + 31 + ); + + // Selected non-SVG is a perfect fit and it is rich, and SVG is also rich, + // so we prefer the original non-SVG selection. + await runFaviconTest( + "https://example.com/test_icon_selection_svg", + [ + { + url: SVG_URL, + data: SVG_DATA, + isRich: true, + mimetype: MIMETYPE_SVG, + }, + { + url: ICON32_URL, + data: FAVICON_DATA, + isRich: true, + mimetype: MIMETYPE_PNG, + }, + ], + ICON32_URL, + FAVICON_DATA, + 32 + ); + + // When requested size is above threshold we have no preference when it comes to richness. + await runFaviconTest( + "https://example.com/test_icon_selection_svg", + [ + { + url: SVG_URL, + data: SVG_DATA, + isRich: true, + mimetype: MIMETYPE_SVG, + }, + { + url: ICON64_URL, + data: FAVICON64_DATA, + isRich: false, + mimetype: MIMETYPE_PNG, + }, + ], + SVG_URL, + SVG_DATA, + 65 + ); + + // Prefer non-rich SVG when requested size is below threshold. + await runFaviconTest( + "https://example.com/test_icon_selection_svg", + [ + { + url: SVG_URL + "#2", + data: SVG_DATA, + isRich: true, + mimetype: MIMETYPE_SVG, + }, + { + url: SVG_URL, + data: SVG_DATA, + isRich: false, + mimetype: MIMETYPE_SVG, + }, + ], + SVG_URL, + SVG_DATA, + 32 + ); +}); + +async function runFaviconTest( + PAGE_URL, + iconData, + expectedURI, + expectedData, + preferredSize +) { + await PlacesTestUtils.clearFavicons(); + await PlacesTestUtils.addVisits(PAGE_URL); + + for (const { url, data, isRich, mimetype } of iconData) { + const dataURL = await PlacesTestUtils.fileDataToDataURL(data, mimetype); + await PlacesTestUtils.setFaviconForPage(PAGE_URL, url, dataURL, 0, isRich); + } + + await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + NetUtil.newURI(PAGE_URL), + (aURI, aDataLen, aData) => { + Assert.equal(aURI.spec, expectedURI); + Assert.equal(aDataLen, expectedData.length); + Assert.deepEqual(aData, expectedData); + resolve(); + }, + preferredSize + ); + }); +} diff --git a/toolkit/components/places/tests/favicons/test_getFaviconForPage.js b/toolkit/components/places/tests/favicons/test_getFaviconForPage.js index d4027e84a477..ec81f611b9a5 100644 --- a/toolkit/components/places/tests/favicons/test_getFaviconForPage.js +++ b/toolkit/components/places/tests/favicons/test_getFaviconForPage.js @@ -28,7 +28,7 @@ add_task(async function test_basic() { await IOUtils.remove(favicon.file.path); }); -add_task(async function test_svg_basic() { +add_task(async function test_svg() { const favicon = ""; const pageURI = uri("http://example.com/"); @@ -96,157 +96,7 @@ async function doTestGetFaviconForPage({ await PlacesTestUtils.clearFavicons(); } -add_task(async function test_fallback() { - const rootPageURI = uri("http://example.com/"); - const rootFaviconURI = uri("http://example.com/favicon.ico"); - const favicon16File = do_get_file("favicon-normal16.png"); - const favicon16DataURI = await createDataURLForFavicon({ - data: readFileData(favicon16File), - mimeType: "image/png", - }); - - info("Set icon for the root"); - await PlacesTestUtils.addVisits(rootPageURI); - await PlacesUtils.favicons.setFaviconForPage( - rootPageURI, - rootFaviconURI, - favicon16DataURI - ); - - info("Check fallback icons"); - const subPageURI = uri("http://example.com/missing"); - Assert.equal( - (await PlacesUtils.favicons.getFaviconForPage(subPageURI)).uri.spec, - rootFaviconURI.spec - ); - - info("Now add a new icon for the subpage"); - await PlacesTestUtils.addVisits(subPageURI); - const subFaviconURI = uri("http://example.com/favicon.png"); - const favicon32File = do_get_file("favicon-normal32.png"); - const favicon32DataURI = await createDataURLForFavicon({ - data: readFileData(favicon32File), - mimeType: "image/png", - }); - await PlacesTestUtils.setFaviconForPage( - subPageURI, - subFaviconURI, - favicon32DataURI - ); - - info("Check no fallback icons"); - Assert.equal( - (await PlacesUtils.favicons.getFaviconForPage(rootPageURI)).uri.spec, - rootFaviconURI.spec - ); - Assert.equal( - (await PlacesUtils.favicons.getFaviconForPage(subPageURI)).uri.spec, - subFaviconURI.spec - ); - - await PlacesUtils.history.clear(); - await PlacesTestUtils.clearFavicons(); -}); - -add_task(async function test_fallback_no_root_icon() { - const rootPageURI = uri("http://example.com/"); - const subPageURIs = [ - uri("http://example.com/page"), - uri("http://example.com/about"), - uri("http://example.com/home"), - ]; - const favicon32File = do_get_file("favicon-normal32.png"); - const favicon32DataURI = await createDataURLForFavicon({ - data: readFileData(favicon32File), - mimeType: "image/png", - }); - - for (let i = 0; i < 10; i++) { - await PlacesTestUtils.addVisits(subPageURIs[0]); - } - await PlacesTestUtils.addVisits(subPageURIs[1]); - await PlacesTestUtils.addVisits(subPageURIs[2]); - - for (let uri of subPageURIs) { - await PlacesTestUtils.setFaviconForPage( - uri, - uri.spec + "/favicon.ico", - favicon32DataURI - ); - } - - await PlacesTestUtils.addVisits(rootPageURI); - - Assert.equal( - (await PlacesUtils.favicons.getFaviconForPage(rootPageURI)).uri.spec, - subPageURIs[0].spec + "/favicon.ico", - "No root icon, should use icon from most frecent subpage" - ); -}); - -add_task(async function test_fallback_no_root_icon_with_port() { - const pageURI1 = uri("http://example.com:3000"); - const subPageURI1 = uri("http://example.com:3000/subpage"); - const subFaviconURI1 = uri("http://example.com:3000/subpage/favicon.ico"); - const pageURI2 = uri("http://example.com:5000"); - const subPageURI2 = uri("http://example.com:5000/subpage"); - const subFaviconURI2 = uri("http://example.com:5000/subpage/favicon.ico"); - - const favicon32File = do_get_file("favicon-normal32.png"); - const favicon32DataURI = await createDataURLForFavicon({ - data: readFileData(favicon32File), - mimeType: "image/png", - }); - - await PlacesTestUtils.addVisits(subPageURI1); - await PlacesTestUtils.addVisits(Array(10).fill(subPageURI2)); - - await PlacesTestUtils.setFaviconForPage( - subPageURI1, - subFaviconURI1, - favicon32DataURI - ); - - await PlacesTestUtils.addVisits(subPageURI1); - await PlacesTestUtils.addVisits(subPageURI2); - - Assert.equal( - (await PlacesUtils.favicons.getFaviconForPage(pageURI1)).uri.spec, - subFaviconURI1.spec, - "No root icon, should use icon from most frecent subpage" - ); - - Assert.equal( - await PlacesUtils.favicons.getFaviconForPage(pageURI2), - null, - "Should return null since no icons exist for root or its subpages" - ); - - await PlacesTestUtils.setFaviconForPage( - subPageURI2, - subFaviconURI2, - favicon32DataURI - ); - - await PlacesTestUtils.addVisits("http://localhost:5000/other_subpage"); - - await PlacesTestUtils.setFaviconForPage( - "http://localhost:5000/other_subpage", - "http://localhost:5000/other_subpage/favicon.ico", - favicon32DataURI - ); - - Assert.equal( - (await PlacesUtils.favicons.getFaviconForPage(subPageURI2)).uri.spec, - subFaviconURI2.spec, - "No root icon, should use icon from most frecent subpage" - ); - - await PlacesUtils.history.clear(); - await PlacesTestUtils.clearFavicons(); -}); - -add_task(async function test_rich_priority_below_threshold() { +add_task(async function test_rich_priority() { const pageURI = uri("http://example.com/"); await PlacesTestUtils.addVisits(pageURI); @@ -332,423 +182,6 @@ add_task(async function test_rich_priority_above_threshold() { await PlacesTestUtils.clearFavicons(); }); -add_task(async function test_size_selection() { - const pageURI = uri("http://example.com/"); - await PlacesTestUtils.addVisits(pageURI); - - // Icons set: - // - 16px non-rich - // - 32px rich - // - 64px non-rich - const favicon16File = do_get_file("favicon-normal16.png"); - const favicon16URI = uri("http://example.com/favicon16.png"); - const favicon16DataURI = await createDataURLForFavicon({ - data: readFileData(favicon16File), - mimeType: "image/png", - }); - const favicon32File = do_get_file("favicon-normal32.png"); - const favicon32URI = uri("http://example.com/favicon32.png"); - const favicon32DataURI = await createDataURLForFavicon({ - data: readFileData(favicon32File), - mimeType: "image/png", - }); - const favicon64File = do_get_file("favicon-big64.png"); - const favicon64URI = uri("http://example.com/favicon64.png"); - const favicon64DataURI = await createDataURLForFavicon({ - data: readFileData(favicon64File), - mimeType: "image/png", - }); - await PlacesUtils.favicons.setFaviconForPage( - pageURI, - favicon16URI, - favicon16DataURI, - 0, - false // Non-rich - ); - await PlacesUtils.favicons.setFaviconForPage( - pageURI, - favicon32URI, - favicon32DataURI, - 0, - true // Rich - ); - await PlacesUtils.favicons.setFaviconForPage( - pageURI, - favicon64URI, - favicon64DataURI, - 0, - false // Non-rich - ); - - // Should select 16px icon, non-rich icons are prioritized for preferred - // size <= 64px, and (24 - 16) = 8 <= (64 - 24) / 4 = 10. - // Therefore smaller icon is selected. - const faviconFor24 = await PlacesUtils.favicons.getFaviconForPage( - pageURI, - 24 - ); - Assert.equal(faviconFor24.uri.spec, favicon16URI.spec); - Assert.equal(faviconFor24.dataURI.spec, favicon16DataURI.spec); - Assert.equal(faviconFor24.mimeType, "image/png"); - Assert.equal(faviconFor24.width, 16); - - // Should select 64px icon, non-rich icons are prioritized for preferred - // size <= 64px, and (64 - 32) / 4 = 8 < (32 - 16) = 16. - // Therefore, larger icon is selected. - const faviconFor32 = await PlacesUtils.favicons.getFaviconForPage( - pageURI, - 32 - ); - Assert.equal(faviconFor32.uri.spec, favicon64URI.spec); - Assert.equal(faviconFor32.dataURI.spec, favicon64DataURI.spec); - Assert.equal(faviconFor32.mimeType, "image/png"); - Assert.equal(faviconFor32.width, 64); - - // Should select 64px icon, no discrimination between rich/non-rich for - // preferred size > 64px. - const faviconFor80 = await PlacesUtils.favicons.getFaviconForPage( - pageURI, - 80 - ); - Assert.equal(faviconFor80.uri.spec, favicon64URI.spec); - Assert.equal(faviconFor80.dataURI.spec, favicon64DataURI.spec); - Assert.equal(faviconFor80.mimeType, "image/png"); - Assert.equal(faviconFor80.width, 64); - - await PlacesUtils.history.clear(); - await PlacesTestUtils.clearFavicons(); -}); - -add_task(async function test_size_selection_rich_only() { - const pageURI = uri("http://example.com/"); - await PlacesTestUtils.addVisits(pageURI); - - const favicon16File = do_get_file("favicon-normal16.png"); - const favicon16URI = uri("http://example.com/favicon16.png"); - const favicon16DataURI = await createDataURLForFavicon({ - data: readFileData(favicon16File), - mimeType: "image/png", - }); - const favicon32File = do_get_file("favicon-normal32.png"); - const favicon32URI = uri("http://example.com/favicon32.png"); - const favicon32DataURI = await createDataURLForFavicon({ - data: readFileData(favicon32File), - mimeType: "image/png", - }); - const favicon64File = do_get_file("favicon-big64.png"); - const favicon64URI = uri("http://example.com/favicon64.png"); - const favicon64DataURI = await createDataURLForFavicon({ - data: readFileData(favicon64File), - mimeType: "image/png", - }); - await PlacesUtils.favicons.setFaviconForPage( - pageURI, - favicon16URI, - favicon16DataURI, - 0, - true - ); - await PlacesUtils.favicons.setFaviconForPage( - pageURI, - favicon32URI, - favicon32DataURI, - 0, - true - ); - await PlacesUtils.favicons.setFaviconForPage( - pageURI, - favicon64URI, - favicon64DataURI, - 0, - true - ); - - // Should select 16px icon, since there are no non-rich icons found, - // we return the best-sized rich icon. - const faviconFor17 = await PlacesUtils.favicons.getFaviconForPage( - pageURI, - 17 - ); - Assert.equal(faviconFor17.uri.spec, favicon16URI.spec); - Assert.equal(faviconFor17.dataURI.spec, favicon16DataURI.spec); - Assert.equal(faviconFor17.mimeType, "image/png"); - Assert.equal(faviconFor17.width, 16); - - await PlacesUtils.history.clear(); - await PlacesTestUtils.clearFavicons(); -}); - -add_task(async function test_svg_selection() { - const pageURI = uri("http://example.com/"); - - // SVG - const faviconSVG = ""; - const faviconSVGURI = uri("http://example.com/favicon.svg"); - const faviconSVGDataURI = uri(`data:image/svg+xml;utf8,${faviconSVG}`); - const faviconSVGDataURIBase64 = uri( - `data:image/svg+xml;base64,${base64EncodeString(faviconSVG)}` - ); - // 32px png - const favicon32File = do_get_file("favicon-normal32.png"); - const favicon32URI = uri("http://example.com/favicon32.png"); - const favicon32DataURI = await createDataURLForFavicon({ - data: readFileData(favicon32File), - mimeType: "image/png", - }); - // 64px png - const favicon64File = do_get_file("favicon-big64.png"); - const favicon64URI = uri("http://example.com/favicon64.png"); - const favicon64DataURI = await createDataURLForFavicon({ - data: readFileData(favicon64File), - mimeType: "image/png", - }); - - info("Selected non-svg is not a perfect fit, so we prefer SVG"); - await doTestSVGSelection({ - pageURI, - favicon1: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURI, - isRich: false, - }, - favicon2: { - uri: favicon32URI, - dataURI: favicon32DataURI, - isRich: false, - }, - preferredSize: 31, - expected: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURIBase64, - mimeType: "image/svg+xml", - width: 65535, - }, - }); - - info("Selected non-svg is a perfect fit, so we prefer it"); - await doTestSVGSelection({ - pageURI, - favicon1: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURI, - isRich: false, - }, - favicon2: { - uri: favicon32URI, - dataURI: favicon32DataURI, - isRich: false, - }, - preferredSize: 32, - expected: { - uri: favicon32URI, - dataURI: favicon32DataURI, - mimeType: "image/png", - width: 32, - }, - }); - - info("Selected non-svg is a perfect fit, but it is rich so we prefer SVG"); - await doTestSVGSelection({ - pageURI, - favicon1: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURI, - isRich: false, - }, - favicon2: { - uri: favicon32URI, - dataURI: favicon32DataURI, - isRich: true, - }, - preferredSize: 32, - expected: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURIBase64, - mimeType: "image/svg+xml", - width: 65535, - }, - }); - - info( - "Selected non-svg is not a perfect fit, but SVG is rich, so we prefer non-SVG" - ); - await doTestSVGSelection({ - pageURI, - favicon1: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURI, - isRich: true, - }, - favicon2: { - uri: favicon32URI, - dataURI: favicon32DataURI, - isRich: false, - }, - preferredSize: 31, - expected: { - uri: favicon32URI, - dataURI: favicon32DataURI, - mimeType: "image/png", - width: 32, - }, - }); - - info( - "Selected non-SVG is a perfect fit and it is rich, and SVG is also rich, so we prefer the original non-SVG selection" - ); - await doTestSVGSelection({ - pageURI, - favicon1: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURI, - isRich: true, - }, - favicon2: { - uri: favicon32URI, - dataURI: favicon32DataURI, - isRich: true, - }, - preferredSize: 32, - expected: { - uri: favicon32URI, - dataURI: favicon32DataURI, - mimeType: "image/png", - width: 32, - }, - }); - - info( - "When requested size is above threshold we have no preference when it comes to richness" - ); - await doTestSVGSelection({ - pageURI, - favicon1: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURI, - isRich: true, - }, - favicon2: { - uri: favicon64URI, - dataURI: favicon64DataURI, - isRich: false, - }, - preferredSize: 65, - expected: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURIBase64, - mimeType: "image/svg+xml", - width: 65535, - }, - }); - - info("Prefer non-rich SVG when requested size is below threshold"); - await doTestSVGSelection({ - pageURI, - favicon1: { - uri: uri("http://example.com/favicon.svg#2"), - dataURI: faviconSVGDataURI, - isRich: true, - }, - favicon2: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURI, - isRich: false, - }, - preferredSize: 32, - expected: { - uri: faviconSVGURI, - dataURI: faviconSVGDataURIBase64, - mimeType: "image/svg+xml", - width: 65535, - }, - }); -}); - -async function doTestSVGSelection({ - pageURI, - favicon1, - favicon2, - preferredSize, - expected, -}) { - await PlacesTestUtils.addVisits(pageURI); - - await PlacesUtils.favicons.setFaviconForPage( - pageURI, - favicon1.uri, - favicon1.dataURI, - 0, - favicon1.isRich - ); - await PlacesUtils.favicons.setFaviconForPage( - pageURI, - favicon2.uri, - favicon2.dataURI, - 0, - favicon2.isRich - ); - - const result = await PlacesUtils.favicons.getFaviconForPage( - pageURI, - preferredSize - ); - Assert.equal(result.uri.spec, expected.uri.spec); - Assert.equal(result.dataURI.spec, expected.dataURI.spec); - Assert.equal(result.mimeType, expected.mimeType); - Assert.equal(result.width, expected.width); - - await PlacesUtils.history.clear(); - await PlacesTestUtils.clearFavicons(); -} - -add_task(async function test_port() { - const pageURI = uri("http://example.com/"); - const faviconURI = uri("http://example.com/favicon.ico"); - const faviconFile = do_get_file("favicon-normal32.png"); - const faviconDataURI = await createDataURLForFavicon({ - data: readFileData(faviconFile), - mimeType: "image/png", - }); - - const pageURIWithPort = uri("http://example.com:5000/"); - const faviconURIWithPort = uri("http://example.com:5000/favicon.ico"); - const faviconFileWithPort = do_get_file("favicon-normal16.png"); - const faviconDataURIWithPort = await createDataURLForFavicon({ - data: readFileData(faviconFileWithPort), - mimeType: "image/png", - }); - - info("Set icon for the URL with the port"); - await PlacesTestUtils.addVisits(pageURIWithPort); - await PlacesTestUtils.setFaviconForPage( - pageURIWithPort, - faviconURIWithPort, - faviconDataURIWithPort - ); - Assert.equal( - (await PlacesUtils.favicons.getFaviconForPage(pageURIWithPort)).uri.spec, - faviconURIWithPort.spec, - "The favicon of the URL with the port should be chosen" - ); - - info("Set icon for the URL without the port"); - await PlacesTestUtils.addVisits(pageURI); - await PlacesTestUtils.setFaviconForPage(pageURI, faviconURI, faviconDataURI); - Assert.equal( - (await PlacesUtils.favicons.getFaviconForPage(pageURIWithPort)).uri.spec, - faviconURIWithPort.spec, - "The favicon of the URL with the port should still be chosen when both are defined" - ); - Assert.equal( - (await PlacesUtils.favicons.getFaviconForPage(pageURI)).uri.spec, - faviconURI.spec, - "The favicon of the URL without the port should be chosen correctly when there is an icon defined for the url with a port" - ); - - await PlacesUtils.history.clear(); - await PlacesTestUtils.clearFavicons(); -}); - add_task(async function test_no_favicon() { const pageURI = uri("http://example.com/"); const result = await PlacesUtils.favicons.getFaviconForPage(pageURI); 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 000000000000..46952f1326c5 --- /dev/null +++ b/toolkit/components/places/tests/favicons/test_getFaviconURLForPage.js @@ -0,0 +1,237 @@ +/* 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 PlacesTestUtils.setFaviconForPage( + pageURI, + SMALLPNG_DATA_URI, + SMALLPNG_DATA_URI + ); + + await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + 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 === "image/png"); + resolve(); + } + ); + }); +}); + +add_task(async function test_missing() { + PlacesTestUtils.clearFavicons(); + 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 dataURL = await readFileDataAsDataURL( + do_get_file("favicon-normal16.png"), + "image/png" + ); + await PlacesTestUtils.setFaviconForPage(ROOT_URL, ROOT_ICON_URL, dataURL); + + 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 dataURL32 = await readFileDataAsDataURL( + do_get_file("favicon-normal32.png"), + "image/png" + ); + await PlacesTestUtils.setFaviconForPage(SUBPAGE_URL, ICON32_URL, dataURL32); + + 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" + ); +}); + +add_task(async function test_URIsWithPort() { + const URL_WITH_PORT = "https://www.example.com:5000/"; + const ICON_URL = URL_WITH_PORT + "favicon.ico"; + const URL_WITHOUT_PORT = "https://www.example.com/"; + const ICON_URL_NO_PORT = URL_WITHOUT_PORT + "favicon.ico"; + + info("Set icon for the URL with the port"); + await PlacesTestUtils.addVisits(URL_WITH_PORT); + let dataURL = await readFileDataAsDataURL( + do_get_file("favicon-normal16.png"), + "image/png" + ); + await PlacesTestUtils.setFaviconForPage(URL_WITH_PORT, ICON_URL, dataURL); + + Assert.equal( + await getFaviconUrlForPage(URL_WITH_PORT), + ICON_URL, + "The favicon of the URL with the port should be chosen" + ); + + info("Set icon for the URL without the port"); + await PlacesTestUtils.addVisits(URL_WITHOUT_PORT); + let dataURL32 = await readFileDataAsDataURL( + do_get_file("favicon-normal32.png"), + "image/png" + ); + await PlacesTestUtils.setFaviconForPage( + URL_WITHOUT_PORT, + ICON_URL_NO_PORT, + dataURL32 + ); + + Assert.equal( + await getFaviconUrlForPage(URL_WITH_PORT), + ICON_URL, + "The favicon of the URL with the port should still be chosen when both are defined" + ); + + Assert.equal( + await getFaviconUrlForPage(URL_WITHOUT_PORT), + ICON_URL_NO_PORT, + "The favicon of the URL without the port should be chosen correctly when there is an icon defined for the url with a port" + ); +}); + +add_task(async function test_noRootIconFallback() { + await PlacesTestUtils.clearFavicons(); + const ROOT_URL = "http://test.com"; + const SUBPAGE_URLS = [ + ROOT_URL + "/page", + ROOT_URL + "/about", + ROOT_URL + "/home", + ]; + + for (let i = 0; i < 10; i++) { + await PlacesTestUtils.addVisits(SUBPAGE_URLS[0]); + } + await PlacesTestUtils.addVisits(SUBPAGE_URLS[1]); + await PlacesTestUtils.addVisits(SUBPAGE_URLS[2]); + + let dataURL32 = await readFileDataAsDataURL( + do_get_file("favicon-normal32.png"), + "image/png" + ); + + for (let url of SUBPAGE_URLS) { + await PlacesTestUtils.setFaviconForPage( + url, + url + "/favicon.ico", + dataURL32 + ); + } + + await PlacesTestUtils.addVisits(ROOT_URL); + + Assert.equal( + await getFaviconUrlForPage(ROOT_URL), + SUBPAGE_URLS[0] + "/favicon.ico", + "No root icon, should use icon from most frecent subpage" + ); +}); + +add_task(async function test_noRootIconFallbackURIsWithPort() { + await PlacesTestUtils.clearFavicons(); + const TEST_URI = "http://localhost:3000"; + const TEST_URI_2 = "http://localhost:5000"; + const TEST_URI_SUBPAGE = "http://localhost:3000/subpage"; + const TEST_URI_2_SUBPAGE = "http://localhost:5000/subpage"; + + await PlacesTestUtils.addVisits(TEST_URI_SUBPAGE); + await PlacesTestUtils.addVisits(Array(10).fill(TEST_URI_2_SUBPAGE)); + + let dataURL32 = await readFileDataAsDataURL( + do_get_file("favicon-normal32.png"), + "image/png" + ); + + await PlacesTestUtils.setFaviconForPage( + TEST_URI_SUBPAGE, + TEST_URI_SUBPAGE + "/favicon.ico", + dataURL32 + ); + + await PlacesTestUtils.addVisits(TEST_URI); + await PlacesTestUtils.addVisits(TEST_URI_2); + + Assert.equal( + await getFaviconUrlForPage(TEST_URI), + TEST_URI_SUBPAGE + "/favicon.ico", + "No root icon, should use icon from most frecent subpage" + ); + + try { + await getFaviconUrlForPage(TEST_URI_2); + Assert.ok(false, "No root icon for root or root subpages"); + } catch (error) { + Assert.ok( + true, + "Should return error since no icons exist for root or its subpages" + ); + } + + await PlacesTestUtils.setFaviconForPage( + TEST_URI_2_SUBPAGE, + TEST_URI_2_SUBPAGE + "/favicon.ico", + dataURL32 + ); + + await PlacesTestUtils.addVisits("http://localhost:5000/other_subpage"); + + await PlacesTestUtils.setFaviconForPage( + "http://localhost:5000/other_subpage", + "http://localhost:5000/other_subpage/favicon.ico", + dataURL32 + ); + + Assert.equal( + await getFaviconUrlForPage(TEST_URI_2), + TEST_URI_2_SUBPAGE + "/favicon.ico", + "No root icon, should use icon from most frecent subpage" + ); +}); diff --git a/toolkit/components/places/tests/favicons/test_heavy_favicon.js b/toolkit/components/places/tests/favicons/test_heavy_favicon.js index e2871276a958..6d82ac2be1a0 100644 --- a/toolkit/components/places/tests/favicons/test_heavy_favicon.js +++ b/toolkit/components/places/tests/favicons/test_heavy_favicon.js @@ -32,7 +32,7 @@ add_task(async function () { ); await PlacesTestUtils.setFaviconForPage(pageURI.spec, icon.uri.spec, dataURI); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI)).uri.spec, + await getFaviconUrlForPage(pageURI), icon.uri.spec, "A resampled version of the icon should be stored" ); diff --git a/toolkit/components/places/tests/favicons/test_multiple_frames.js b/toolkit/components/places/tests/favicons/test_multiple_frames.js index 8afee2bc7657..218de304e135 100644 --- a/toolkit/components/places/tests/favicons/test_multiple_frames.js +++ b/toolkit/components/places/tests/favicons/test_multiple_frames.js @@ -38,9 +38,9 @@ add_task(async function () { let data = readFileData(file); info("Check getFaviconDataForPage"); - let icon = await PlacesTestUtils.getFaviconForPage(pageURI, size); + let icon = await getFaviconDataForPage(pageURI, size); Assert.equal(icon.mimeType, "image/png"); - Assert.deepEqual(icon.rawData, data); + Assert.deepEqual(icon.data, data); info("Check cached-favicon protocol"); await compareFavicons( diff --git a/toolkit/components/places/tests/favicons/test_root_icons.js b/toolkit/components/places/tests/favicons/test_root_icons.js index 333a7a1aa9b8..5a101ed6a607 100644 --- a/toolkit/components/places/tests/favicons/test_root_icons.js +++ b/toolkit/components/places/tests/favicons/test_root_icons.js @@ -16,16 +16,9 @@ add_task(async function () { ); // Sanity checks. + Assert.equal(await getFaviconUrlForPage(pageURI), faviconURI.spec); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI)).uri.spec, - faviconURI.spec - ); - Assert.equal( - ( - await PlacesTestUtils.getFaviconForPage( - "https://places.test/somethingelse/" - ) - ).uri.spec, + await getFaviconUrlForPage("https://places.test/somethingelse/"), faviconURI.spec ); @@ -49,10 +42,7 @@ add_task(async function () { await PlacesUtils.history.remove(pageURI); // Still works since the icon has not been removed. - Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI)).uri.spec, - faviconURI.spec - ); + Assert.equal(await getFaviconUrlForPage(pageURI), faviconURI.spec); // Remove all the pages for the given domain. await PlacesUtils.history.remove("http://places.test/page2/"); @@ -93,17 +83,17 @@ add_task(async function test_removePagesByTimeframe() { // Sanity checks. Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI)).uri.spec, + await getFaviconUrlForPage(pageURI), faviconURI.spec, "Should get the biggest icon" ); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI, 1)).uri.spec, + await getFaviconUrlForPage(pageURI, 1), rootIconURI.spec, "Should get the smallest icon" ); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(oldPageURI)).uri.spec, + await getFaviconUrlForPage(oldPageURI), rootIconURI.spec, "Should get the root icon" ); @@ -152,7 +142,7 @@ add_task(async function test_different_host() { ); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI)).uri.spec, + await getFaviconUrlForPage(pageURI), faviconURI.spec, "Should get the png icon" ); @@ -189,7 +179,7 @@ add_task(async function test_same_size() { ); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI, 20)).uri.spec, + await getFaviconUrlForPage(pageURI, 20), faviconURI.spec, "Should get the non-root icon" ); @@ -221,7 +211,7 @@ add_task(async function test_root_on_different_host() { await PlacesTestUtils.setFaviconForPage(pageURI1, iconURI, SMALLPNG_DATA_URI); Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1"); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI1, 16)).uri.spec, + await getFaviconUrlForPage(pageURI1, 16), ICON_URL, "The icon should been found" ); @@ -230,7 +220,7 @@ add_task(async function test_root_on_different_host() { await PlacesTestUtils.setFaviconForPage(pageURI2, iconURI, SMALLPNG_DATA_URI); Assert.equal(await getRootValue(ICON_URL), 1, "Check root == 1"); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI2, 16)).uri.spec, + await getFaviconUrlForPage(pageURI2, 16), ICON_URL, "The icon should be found" ); @@ -238,7 +228,7 @@ add_task(async function test_root_on_different_host() { await PlacesUtils.history.remove(pageURI1); Assert.equal( - (await PlacesTestUtils.getFaviconForPage(pageURI2, 16)).uri.spec, + await getFaviconUrlForPage(pageURI2, 16), ICON_URL, "The icon should not have been removed" ); diff --git a/toolkit/components/places/tests/favicons/test_setFaviconForPage.js b/toolkit/components/places/tests/favicons/test_setFaviconForPage.js index 8f03c1318b6e..b84276d27f5c 100644 --- a/toolkit/components/places/tests/favicons/test_setFaviconForPage.js +++ b/toolkit/components/places/tests/favicons/test_setFaviconForPage.js @@ -57,11 +57,21 @@ add_task(async function test_replaceExisting() { firstFaviconDataURL ); - await checkFaviconDataForPage( - pageURI, - firstFavicon.mimeType, - firstFavicon.data - ); + await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + pageURI, + function (aURI, aDataLen, aData, aMimeType) { + Assert.equal(aMimeType, firstFavicon.mimeType); + Assert.ok(compareArrays(aData, firstFavicon.data)); + checkFaviconDataForPage( + pageURI, + firstFavicon.mimeType, + firstFavicon.data, + resolve + ); + } + ); + }); await doTestSetFaviconForPage({ pageURI, @@ -312,14 +322,8 @@ add_task(async function test_sameHostRedirect() { await promise; // The favicon should be set also on the bookmarked url that redirected. - let favicon = await PlacesUtils.favicons.getFaviconForPage( - PlacesUtils.toURI(srcUrl) - ); - Assert.equal( - favicon.rawData.length, - SMALLPNG_DATA_LEN, - "Check favicon dataLen" - ); + let { dataLen } = await PlacesUtils.promiseFaviconData(srcUrl); + Assert.equal(dataLen, SMALLPNG_DATA_LEN, "Check favicon dataLen"); await PlacesUtils.bookmarks.eraseEverything(); await PlacesUtils.history.clear(); @@ -379,11 +383,14 @@ async function doTestSetFaviconForPage({ info("Check the result of setFaviconForPage"); Assert.equal(result, null, "If succeeded, the promise has no data"); - await checkFaviconDataForPage( - pageURI, - expectedFaviconMimeType, - expectedFaviconData - ); + await new Promise(resolve => { + checkFaviconDataForPage( + pageURI, + expectedFaviconMimeType, + expectedFaviconData, + resolve + ); + }); } add_task(async function test_incorrectMimeTypeDataURI() { diff --git a/toolkit/components/places/tests/favicons/test_svg_favicon.js b/toolkit/components/places/tests/favicons/test_svg_favicon.js index 4c699e9a612d..9716af5689d2 100644 --- a/toolkit/components/places/tests/favicons/test_svg_favicon.js +++ b/toolkit/components/places/tests/favicons/test_svg_favicon.js @@ -14,12 +14,24 @@ add_task(async function () { SMALLSVG_DATA_URI ); - let favicon = await PlacesTestUtils.getFaviconForPage(PAGEURI); - Assert.equal( - favicon.uri.spec, - SMALLSVG_DATA_URI.spec, - "setFavicon aURI check" - ); - Assert.equal(favicon.rawData.length, 263, "setFavicon aDataLen check"); - Assert.equal(favicon.mimeType, "image/svg+xml", "setFavicon aMimeType check"); + await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + PAGEURI, + function (aURI, aDataLen, aData, aMimeType) { + Assert.equal( + aURI.spec, + SMALLSVG_DATA_URI.spec, + "setFavicon aURI check" + ); + Assert.equal(aDataLen, 263, "setFavicon aDataLen check"); + Assert.equal(aMimeType, "image/svg+xml", "setFavicon aMimeType check"); + resolve(); + } + ); + }); + + let data = await PlacesUtils.promiseFaviconData(PAGEURI.spec); + equal(data.uri.spec, SMALLSVG_DATA_URI.spec, "getFavicon aURI check"); + equal(data.dataLen, 263, "getFavicon aDataLen check"); + equal(data.mimeType, "image/svg+xml", "getFavicon aMimeType check"); }); diff --git a/toolkit/components/places/tests/favicons/xpcshell.toml b/toolkit/components/places/tests/favicons/xpcshell.toml index 6f318f02f28f..48d8ed9f9384 100644 --- a/toolkit/components/places/tests/favicons/xpcshell.toml +++ b/toolkit/components/places/tests/favicons/xpcshell.toml @@ -52,10 +52,14 @@ support-files = [ ["test_favicons_protocols_ref.js"] +["test_getFaviconDataForPage.js"] + ["test_getFaviconForPage.js"] ["test_getFaviconLinkForIcon.js"] +["test_getFaviconURLForPage.js"] + ["test_heavy_favicon.js"] ["test_incremental_vacuum.js"] diff --git a/toolkit/components/places/tests/head_common.js b/toolkit/components/places/tests/head_common.js index 6e4d9a7f3b6d..d670389eec96 100644 --- a/toolkit/components/places/tests/head_common.js +++ b/toolkit/components/places/tests/head_common.js @@ -734,6 +734,38 @@ function sortBy(array, prop) { return array.sort((a, b) => compareAscending(a[prop], b[prop])); } +function getFaviconUrlForPage(page, width = 0) { + let pageURI = + page instanceof Ci.nsIURI ? page : NetUtil.newURI(new URL(page).href); + return new Promise((resolve, reject) => { + PlacesUtils.favicons.getFaviconURLForPage( + pageURI, + iconURI => { + if (iconURI) { + resolve(iconURI.spec); + } else { + reject("Unable to find an icon for " + pageURI.spec); + } + }, + width + ); + }); +} + +function getFaviconDataForPage(page, width = 0) { + let pageURI = + page instanceof Ci.nsIURI ? page : NetUtil.newURI(new URL(page).href); + return new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + pageURI, + (iconUri, len, data, mimeType) => { + resolve({ data, mimeType }); + }, + width + ); + }); +} + /** * Asynchronously compares contents from 2 favicon urls. */ diff --git a/toolkit/components/places/tests/maintenance/test_preventive_maintenance.js b/toolkit/components/places/tests/maintenance/test_preventive_maintenance.js index a20656e4219a..2a3f57718db1 100644 --- a/toolkit/components/places/tests/maintenance/test_preventive_maintenance.js +++ b/toolkit/components/places/tests/maintenance/test_preventive_maintenance.js @@ -2654,8 +2654,12 @@ tests.push({ }); Assert.equal(pageInfo.annotations.get("anno"), "anno"); - let favicon = await PlacesTestUtils.getFaviconForPage(this._uri2); - Assert.equal(favicon.uri.spec, SMALLPNG_DATA_URI.spec); + await new Promise(resolve => { + PlacesUtils.favicons.getFaviconURLForPage(this._uri2, aFaviconURI => { + Assert.ok(aFaviconURI.equals(SMALLPNG_DATA_URI)); + resolve(); + }); + }); }, }); diff --git a/toolkit/components/places/tests/unit/test_bookmarks_html.js b/toolkit/components/places/tests/unit/test_bookmarks_html.js index ec72ee9a9e6a..d65c0fa559ea 100644 --- a/toolkit/components/places/tests/unit/test_bookmarks_html.js +++ b/toolkit/components/places/tests/unit/test_bookmarks_html.js @@ -240,13 +240,21 @@ add_task(async function test_import_chromefavicon() { dataURL ); - let { dataURI: base64Icon } = - await PlacesTestUtils.getFaviconForPage(PAGE_URI); + let data = await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + PAGE_URI, + (uri, dataLen, faviconData) => resolve(faviconData) + ); + }); + + let base64Icon = + "data:image/png;base64," + + base64EncodeString(String.fromCharCode.apply(String, data)); test_bookmarks.unfiled.push({ title: "Test", url: PAGE_URI.spec, - icon: base64Icon.spec, + icon: base64Icon, }); info("Export to html"); @@ -353,10 +361,11 @@ function checkItem(aExpected, aNode) { Assert.equal(aNode.uri, aExpected.url); break; case "icon": { - let { dataURI: base64Icon } = await PlacesTestUtils.getFaviconForPage( - aExpected.url - ); - Assert.ok(base64Icon.spec == aExpected.icon); + let { data } = await getFaviconDataForPage(aExpected.url); + let base64Icon = + "data:image/png;base64," + + base64EncodeString(String.fromCharCode.apply(String, data)); + Assert.ok(base64Icon == aExpected.icon); break; } case "keyword": { diff --git a/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js b/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js index 9d2d63479637..f7b366b309e4 100644 --- a/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js +++ b/toolkit/components/places/tests/unit/test_bookmarks_html_corrupt.js @@ -109,11 +109,18 @@ var database_check = async function () { root.containerOpen = false; // favicons - let favicon = await PlacesTestUtils.getFaviconForPage(TEST_FAVICON_PAGE_URL); - // aURI should never be null when rawData.length > 0. - Assert.notEqual(favicon.uri, null); - // Favicon data is stored in the bookmarks file as a "data:" URI. For - // simplicity, instead of converting the data we receive to a "data:" URI - // and comparing it, we just check the data size. - Assert.equal(TEST_FAVICON_DATA_SIZE, favicon.rawData.length); + await new Promise(resolve => { + PlacesUtils.favicons.getFaviconDataForPage( + uri(TEST_FAVICON_PAGE_URL), + (aURI, aDataLen) => { + // aURI should never be null when aDataLen > 0. + Assert.notEqual(aURI, null); + // Favicon data is stored in the bookmarks file as a "data:" URI. For + // simplicity, instead of converting the data we receive to a "data:" URI + // and comparing it, we just check the data size. + Assert.equal(TEST_FAVICON_DATA_SIZE, aDataLen); + resolve(); + } + ); + }); }; diff --git a/toolkit/components/places/tests/unit/test_bookmarks_json.js b/toolkit/components/places/tests/unit/test_bookmarks_json.js index 1b7973709470..c041cd14239e 100644 --- a/toolkit/components/places/tests/unit/test_bookmarks_json.js +++ b/toolkit/components/places/tests/unit/test_bookmarks_json.js @@ -313,10 +313,11 @@ async function checkItem(aExpected, aNode) { Assert.equal(aNode.uri, aExpected.url); break; case "icon": { - let { dataURI: base64Icon } = await PlacesTestUtils.getFaviconForPage( - aExpected.url - ); - Assert.equal(base64Icon.spec, aExpected.icon); + let { data } = await getFaviconDataForPage(aExpected.url); + let base64Icon = + "data:image/png;base64," + + base64EncodeString(String.fromCharCode.apply(String, data)); + Assert.equal(base64Icon, aExpected.icon); break; } case "keyword": { diff --git a/toolkit/modules/NewTabUtils.sys.mjs b/toolkit/modules/NewTabUtils.sys.mjs index ec71ab82b05e..644a821c9ab2 100644 --- a/toolkit/modules/NewTabUtils.sys.mjs +++ b/toolkit/modules/NewTabUtils.sys.mjs @@ -855,17 +855,17 @@ var ActivityStreamProvider = { async _loadIcons(aUri, preferredFaviconWidth) { let iconData = {}; // Fetch the largest icon available. + let faviconData; try { - let faviconData = await lazy.PlacesUtils.favicons.getFaviconForPage( + faviconData = await lazy.PlacesUtils.promiseFaviconData( aUri, this.THUMB_FAVICON_SIZE ); - let rawData = faviconData.rawData; Object.assign(iconData, { - favicon: rawData, - faviconLength: rawData.length, + favicon: faviconData.data, + faviconLength: faviconData.dataLen, faviconRef: faviconData.uri.ref, - faviconSize: faviconData.width, + faviconSize: faviconData.size, mimeType: faviconData.mimeType, }); } catch (e) { @@ -876,16 +876,15 @@ var ActivityStreamProvider = { // Also fetch a smaller icon. try { - let faviconData = await lazy.PlacesUtils.favicons.getFaviconForPage( + faviconData = await lazy.PlacesUtils.promiseFaviconData( aUri, preferredFaviconWidth ); - let rawData = faviconData.rawData; Object.assign(iconData, { - smallFavicon: rawData, - smallFaviconLength: rawData.length, + smallFavicon: faviconData.data, + smallFaviconLength: faviconData.dataLen, smallFaviconRef: faviconData.uri.ref, - smallFaviconSize: faviconData.width, + smallFaviconSize: faviconData.size, smallFaviconMimeType: faviconData.mimeType, }); } catch (e) { diff --git a/widget/windows/WinUtils.cpp b/widget/windows/WinUtils.cpp index 811d60fb509c..f16a0370e9eb 100644 --- a/widget/windows/WinUtils.cpp +++ b/widget/windows/WinUtils.cpp @@ -48,7 +48,7 @@ #include "nsNetCID.h" #include "prtime.h" #ifdef MOZ_PLACES -# include "mozilla/places/nsFaviconService.h" +# include "nsIFaviconService.h" #endif #include "nsIDownloader.h" #include "nsIChannel.h" @@ -74,6 +74,7 @@ namespace mozilla::widget { #ifdef MOZ_PLACES NS_IMPL_ISUPPORTS(myDownloadObserver, nsIDownloadObserver) +NS_IMPL_ISUPPORTS(AsyncFaviconDataReady, nsIFaviconDataCallback) #endif NS_IMPL_ISUPPORTS(AsyncEncodeAndWriteIcon, nsIRunnable) NS_IMPL_ISUPPORTS(AsyncDeleteAllFaviconsFromDisk, nsIRunnable) @@ -669,6 +670,31 @@ MSG WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd) { } #ifdef MOZ_PLACES +/************************************************************************ + * Constructs as AsyncFaviconDataReady Object + * @param aIOThread : the thread which performs the action + * @param aURLShortcut : Differentiates between (false)Jumplistcache and + * (true)Shortcutcache + * @param aRunnable : Executed in the aIOThread when the favicon cache is + * avaiable + * @param [aPromiseHolder=null]: Optional PromiseHolder that will be forwarded + * to AsyncEncodeAndWriteIcon if getting the + * favicon from the favicon service succeeds. If + * it doesn't succeed, the held MozPromise will + * be rejected. + ************************************************************************/ + +AsyncFaviconDataReady::AsyncFaviconDataReady( + nsIURI* aNewURI, RefPtr aIOThread, + const bool aURLShortcut, already_AddRefed aRunnable, + UniquePtr> + aPromiseHolder) + : mNewURI(aNewURI), + mIOThread(aIOThread), + mRunnable(aRunnable), + mPromiseHolder(std::move(aPromiseHolder)), + mURLShortcut(aURLShortcut) {} + NS_IMETHODIMP myDownloadObserver::OnDownloadComplete(nsIDownloader* downloader, nsIRequest* request, nsresult status, @@ -676,25 +702,28 @@ myDownloadObserver::OnDownloadComplete(nsIDownloader* downloader, return NS_OK; } -static nsresult MaybeDownloadFavicon(nsIURI* aNewURI, const bool aURLShortcut) { - if (!aURLShortcut) { +nsresult AsyncFaviconDataReady::OnFaviconDataNotAvailable(void) { + if (!mURLShortcut) { return NS_OK; } nsCOMPtr icoFile; nsresult rv = - FaviconHelper::GetOutputIconPath(aNewURI, icoFile, aURLShortcut); + FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr mozIconURI; rv = NS_NewURI(getter_AddRefs(mozIconURI), "moz-icon://.html?size=32"); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_FAILED(rv)) { + return rv; + } nsCOMPtr channel; rv = NS_NewChannel(getter_AddRefs(channel), mozIconURI, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, nsIContentPolicy::TYPE_INTERNAL_IMAGE); + NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr downloadObserver = new myDownloadObserver; @@ -705,36 +734,22 @@ static nsresult MaybeDownloadFavicon(nsIURI* aNewURI, const bool aURLShortcut) { return channel->AsyncOpen(listener); } -static nsresult CacheFavicon( - const places::FaviconPromise::ResolveOrRejectValue& aPromiseResult, - nsIURI* aNewURI, RefPtr aIOThread, - const bool aURLShortcut, nsCOMPtr aRunnable, - UniquePtr> - aPromiseHolder) { - nsresult rv = NS_OK; - auto guard = MakeScopeExit([&]() { - if (NS_FAILED(rv) && aPromiseHolder) { - aPromiseHolder->RejectIfExists(rv, __func__); +NS_IMETHODIMP +AsyncFaviconDataReady::OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen, + const uint8_t* aData, + const nsACString& aMimeType, + uint16_t aWidth) { + if (!aDataLen || !aData) { + if (mURLShortcut) { + OnFaviconDataNotAvailable(); } - }); - nsCOMPtr favicon = - aPromiseResult.IsResolve() ? aPromiseResult.ResolveValue() : nullptr; - if (!favicon) { - MaybeDownloadFavicon(std::move(aNewURI), aURLShortcut); - return (rv = NS_ERROR_FAILURE); + return NS_OK; } - // Get favicon content. - nsTArray rawData; - rv = favicon->GetRawData(rawData); - NS_ENSURE_SUCCESS(rv, rv); - nsAutoCString mimeType; - rv = favicon->GetMimeType(mimeType); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr icoFile; - rv = FaviconHelper::GetOutputIconPath(aNewURI, icoFile, aURLShortcut); + nsresult rv = + FaviconHelper::GetOutputIconPath(mNewURI, icoFile, mURLShortcut); NS_ENSURE_SUCCESS(rv, rv); nsAutoString path; @@ -744,9 +759,9 @@ static nsresult CacheFavicon( // Decode the image from the format it was returned to us in (probably PNG) nsCOMPtr container; nsCOMPtr imgtool = do_CreateInstance("@mozilla.org/image/tools;1"); - rv = imgtool->DecodeImageFromBuffer( - reinterpret_cast(rawData.Elements()), rawData.Length(), - mimeType, getter_AddRefs(container)); + rv = imgtool->DecodeImageFromBuffer(reinterpret_cast(aData), + aDataLen, aMimeType, + getter_AddRefs(container)); NS_ENSURE_SUCCESS(rv, rv); RefPtr surface = container->GetFrame( @@ -757,7 +772,7 @@ static nsresult CacheFavicon( RefPtr dataSurface; IntSize size; - if (aURLShortcut && + if (mURLShortcut && (surface->GetSize().width < 48 || surface->GetSize().height < 48)) { // Create a 48x48 surface and paint the icon into the central rect. size.width = std::max(surface->GetSize().width, 48); @@ -775,7 +790,8 @@ static nsresult CacheFavicon( BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride, dataSurface->GetFormat()); if (!dt) { - gfxWarning() << "CreateDrawTargetForData failed in CacheFavicon"; + gfxWarning() << "AsyncFaviconDataReady::OnComplete failed in " + "CreateDrawTargetForData"; return NS_ERROR_OUT_OF_MEMORY; } dt->FillRect(Rect(0, 0, size.width, size.height), @@ -812,13 +828,11 @@ static nsresult CacheFavicon( } int32_t stride = 4 * size.width; - guard.release(); - // AsyncEncodeAndWriteIcon takes ownership of the heap allocated buffer nsCOMPtr event = new AsyncEncodeAndWriteIcon( path, std::move(data), stride, size.width, size.height, - aRunnable.forget(), std::move(aPromiseHolder)); - aIOThread->Dispatch(event, NS_DISPATCH_NORMAL); + mRunnable.forget(), std::move(mPromiseHolder)); + mIOThread->Dispatch(event, NS_DISPATCH_NORMAL); return NS_OK; } @@ -842,18 +856,6 @@ AsyncEncodeAndWriteIcon::AsyncEncodeAndWriteIcon( NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run() { MOZ_ASSERT(!NS_IsMainThread(), "Should not be called on the main thread."); - nsresult rv = NS_OK; - auto guard = MakeScopeExit([&]() { - if (!mPromiseHolder) { - return; - } - if (NS_SUCCEEDED(rv)) { - mPromiseHolder->ResolveIfExists(mIconPath, __func__); - } else { - mPromiseHolder->RejectIfExists(rv, __func__); - } - }); - // Note that since we're off the main thread we can't use // gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget() RefPtr surface = Factory::CreateWrappingDataSourceSurface( @@ -867,27 +869,34 @@ NS_IMETHODIMP AsyncEncodeAndWriteIcon::Run() { MOZ_TRY(NS_NewLocalFile(mIconPath, getter_AddRefs(comFile))); nsCOMPtr dirPath; MOZ_TRY(comFile->GetParent(getter_AddRefs(dirPath))); - rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777); + nsresult rv = dirPath->Create(nsIFile::DIRECTORY_TYPE, 0777); if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) { return rv; } file = _wfopen(mIconPath.get(), L"wb"); if (!file) { - return (rv = NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; } } - rv = gfxUtils::EncodeSourceSurface(surface, ImageType::ICO, u""_ns, - gfxUtils::eBinaryEncode, file); + nsresult rv = gfxUtils::EncodeSourceSurface(surface, ImageType::ICO, u""_ns, + gfxUtils::eBinaryEncode, file); fclose(file); NS_ENSURE_SUCCESS(rv, rv); if (mRunnable) { mRunnable->Run(); } + if (mPromiseHolder) { + mPromiseHolder->ResolveIfExists(mIconPath, __func__); + } return rv; } -AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon() {} +AsyncEncodeAndWriteIcon::~AsyncEncodeAndWriteIcon() { + if (mPromiseHolder) { + mPromiseHolder->RejectIfExists(NS_ERROR_FAILURE, __func__); + } +} AsyncDeleteAllFaviconsFromDisk::AsyncDeleteAllFaviconsFromDisk( bool aIgnoreRecent) @@ -1095,6 +1104,7 @@ auto FaviconHelper::ObtainCachedIconFileAsync( // CacheIconFileFromFaviconURIAsync to request the favicon. RefPtr currentThread = GetCurrentSerialEventTarget(); + return InvokeAsync( GetMainThreadSerialEventTarget(), "ObtainCachedIconFileAsync call to " @@ -1194,17 +1204,16 @@ nsresult FaviconHelper::CacheIconFileFromFaviconURIAsync( nsCOMPtr runnable = aRunnable; #ifdef MOZ_PLACES // Obtain the favicon service and get the favicon for the specified page - auto* favIconSvc = nsFaviconService::GetFaviconService(); + nsCOMPtr favIconSvc( + do_GetService("@mozilla.org/browser/favicon-service;1")); NS_ENSURE_TRUE(favIconSvc, NS_ERROR_FAILURE); - favIconSvc->AsyncGetFaviconForPage(aFaviconPageURI) - ->Then(GetMainThreadSerialEventTarget(), __func__, - [aFaviconPageURI, aIOThread, aURLShortcut, runnable, - promiseHolder = std::move(aPromiseHolder)]( - const places::FaviconPromise::ResolveOrRejectValue& - aResult) mutable { - CacheFavicon(aResult, aFaviconPageURI, aIOThread, aURLShortcut, - runnable, std::move(promiseHolder)); - }); + + nsCOMPtr callback = + new mozilla::widget::AsyncFaviconDataReady( + aFaviconPageURI, aIOThread, aURLShortcut, runnable.forget(), + std::move(aPromiseHolder)); + + favIconSvc->GetFaviconDataForPage(aFaviconPageURI, callback, 0); #endif return NS_OK; } diff --git a/widget/windows/WinUtils.h b/widget/windows/WinUtils.h index cb467449c574..592429172b02 100644 --- a/widget/windows/WinUtils.h +++ b/widget/windows/WinUtils.h @@ -581,6 +581,34 @@ class WinUtils { typedef MozPromise ObtainCachedIconFileAsyncPromise; +#ifdef MOZ_PLACES +class AsyncFaviconDataReady final : public nsIFaviconDataCallback { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIFAVICONDATACALLBACK + + AsyncFaviconDataReady( + nsIURI* aNewURI, RefPtr aIOThread, + const bool aURLShortcut, already_AddRefed aRunnable, + UniquePtr> + aPromiseHolder = nullptr); + nsresult OnFaviconDataNotAvailable(void); + + private: + ~AsyncFaviconDataReady() { + if (mPromiseHolder) { + mPromiseHolder->RejectIfExists(NS_ERROR_FAILURE, __func__); + } + } + + nsCOMPtr mNewURI; + RefPtr mIOThread; + nsCOMPtr mRunnable; + UniquePtr> mPromiseHolder; + const bool mURLShortcut; +}; +#endif + /** * Asynchronously tries add the list to the build */