Bug 1764275 - Add faviconDataUrl and imagePageUrl to Snapshot Groups. r=daleharvey

Add faviconDataUrl and imagePageUrl to each Snapshot Group so the companion can
directly use these instead of a polyfill for faviconImage and url.

Also change favicon service APIs to respect mDefaultIconURIPreferredSize instead
of always returning the largest icon. This allows to use those APIs more easily
from jsm modules without having to pass around window handles.

Differential Revision: https://phabricator.services.mozilla.com/D143462
This commit is contained in:
Marco Bonardo 2022-04-14 13:10:54 +00:00
parent 4e7c111145
commit 59338c231d
4 changed files with 85 additions and 5 deletions

View File

@ -49,6 +49,10 @@ XPCOMUtils.defineLazyPreferenceGetter(
* This title should only be used if `title` is not present.
* @property {string} imageUrl
* The image url to use for the group.
* @property {string} faviconDataUrl
* The data url to use for the favicon, null if not available.
* @property {string} imagePageUrl
* The url of the snapshot used to get the image and favicon urls.
* @property {number} lastAccessed
* The last access time of the most recently accessed snapshot.
* Stored as the number of milliseconds since the epoch.
@ -372,7 +376,7 @@ const SnapshotGroups = new (class SnapshotGroups {
params
);
return rows.map(row => this.#translateSnapshotGroupRow(row));
return Promise.all(rows.map(row => this.#translateSnapshotGroupRow(row)));
}
/**
@ -499,7 +503,7 @@ const SnapshotGroups = new (class SnapshotGroups {
* The database row to translate.
* @returns {SnapshotGroup}
*/
#translateSnapshotGroupRow(row) {
async #translateSnapshotGroupRow(row) {
// Group image selection should be done in this order:
// 1. Oldest view in group featured image
// 2. Second Oldest View in group featured image
@ -512,7 +516,7 @@ const SnapshotGroups = new (class SnapshotGroups {
// available.
// The query returns featured1|featured2|url1|url2
let imageUrls = row.getResultByName("image_urls")?.split("|");
let imageUrl = null;
let imageUrl, faviconDataUrl, imagePageUrl;
if (imageUrls) {
imageUrl = imageUrls[0] || imageUrls[1];
if (!imageUrl && imageUrls[2]) {
@ -524,11 +528,41 @@ const SnapshotGroups = new (class SnapshotGroups {
imageUrl = PageThumbs.getThumbnailURL(imageUrl);
}
}
// The favicon is for the same page we return a preview image for.
imagePageUrl =
imageUrls[2] && !imageUrls[0] && imageUrls[1]
? imageUrls[3]
: imageUrls[2];
if (imagePageUrl) {
faviconDataUrl = await new Promise(resolve => {
PlacesUtils.favicons.getFaviconDataForPage(
Services.io.newURI(imagePageUrl),
(uri, dataLength, data, mimeType) => {
if (dataLength) {
// NOTE: honestly this is awkward and inefficient. We build a string
// with String.fromCharCode and then btoa that. It's a Uint8Array
// under the hood, and we should probably just expose something in
// ChromeUtils to Base64 encode a Uint8Array directly, but this is
// fine for now.
let b64 = btoa(
data.reduce((d, byte) => d + String.fromCharCode(byte), "")
);
resolve(`data:${mimeType};base64,${b64}`);
return;
}
resolve(undefined);
}
);
});
}
}
let snapshotGroup = {
id: row.getResultByName("id"),
faviconDataUrl,
imageUrl,
imagePageUrl,
title: row.getResultByName("title") || "",
hidden: row.getResultByName("hidden") == 1,
builder: row.getResultByName("builder"),

View File

@ -5,6 +5,10 @@
* Tests for snapshot groups addition, update, and removal.
*/
const FAVICON_DATAURL1 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
const FAVICON_DATAURL2 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVBBBBCklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
const TEST_URL1 = "https://example.com/";
const TEST_IMAGE_URL1 = "https://example.com/preview1.png";
const TEST_URL2 = "https://example.com/12345";
@ -21,6 +25,26 @@ async function delete_all_groups() {
}
}
async function setIcon(pageUrl, iconUrl, dataUrl) {
PlacesUtils.favicons.replaceFaviconDataFromDataURL(
Services.io.newURI(iconUrl),
dataUrl,
(Date.now() + 8640000) * 1000,
Services.scriptSecurityManager.getSystemPrincipal()
);
await new Promise(resolve => {
PlacesUtils.favicons.setAndFetchFaviconForPage(
Services.io.newURI(pageUrl),
Services.io.newURI(iconUrl),
false,
PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
resolve,
Services.scriptSecurityManager.getSystemPrincipal()
);
});
}
/**
* Adds interactions and snapshots for the supplied data.
*
@ -83,6 +107,10 @@ add_task(async function test_add_and_query() {
data.map(d => d.url)
);
info("add a favicon for both urls");
await setIcon(TEST_URL1, TEST_URL1 + "favicon.ico", FAVICON_DATAURL1);
await setIcon(TEST_URL2, TEST_URL2 + "/favicon.ico", FAVICON_DATAURL2);
let groups = await SnapshotGroups.query({ skipMinimum: true });
Assert.equal(groups.length, 1, "Should return 1 SnapshotGroup");
assertSnapshotGroup(groups[0], {
@ -93,6 +121,7 @@ add_task(async function test_add_and_query() {
snapshotCount: data.length,
lastAccessed: now - 10000,
imageUrl: getPageThumbURL(TEST_URL1),
imagePageUrl: TEST_URL1,
});
info("add a featured image for second oldest snapshot");
@ -110,6 +139,8 @@ add_task(async function test_add_and_query() {
snapshotCount: data.length,
lastAccessed: now - 10000,
imageUrl: previewImageURL,
faviconDataUrl: FAVICON_DATAURL2,
imagePageUrl: TEST_URL2,
});
info("add a featured image for the oldest snapshot");
@ -127,6 +158,8 @@ add_task(async function test_add_and_query() {
snapshotCount: data.length,
lastAccessed: now - 10000,
imageUrl: previewImageURL,
faviconDataUrl: FAVICON_DATAURL1,
imagePageUrl: TEST_URL1,
});
});
@ -543,6 +576,7 @@ add_task(async function test_snapshot_image_with_hidden() {
// The images were set on TEST_URL1/TEST_URL2 in an earlier test.
assertSnapshotGroup(groups[0], {
imageUrl: TEST_IMAGE_URL1,
imagePageUrl: TEST_URL1,
});
await SnapshotGroups.setUrlHidden(groups[0].id, TEST_URL1, true);
@ -551,6 +585,7 @@ add_task(async function test_snapshot_image_with_hidden() {
info("Should use the oldest non-hidden image");
assertSnapshotGroup(groups[0], {
imageUrl: TEST_IMAGE_URL2,
imagePageUrl: TEST_URL2,
});
});

View File

@ -537,6 +537,10 @@ nsFaviconService::GetFaviconURLForPage(nsIURI* aPageURI,
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;
}
nsAutoCString pageSpec;
nsresult rv = aPageURI->GetSpec(pageSpec);
@ -562,6 +566,10 @@ nsFaviconService::GetFaviconDataForPage(nsIURI* aPageURI,
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;
}
nsAutoCString pageSpec;
nsresult rv = aPageURI->GetSpec(pageSpec);
@ -772,6 +780,7 @@ nsFaviconService::SetDefaultIconURIPreferredSize(uint16_t aDefaultSize) {
NS_IMETHODIMP
nsFaviconService::PreferredSizeFromURI(nsIURI* aURI, uint16_t* _size) {
NS_ENSURE_ARG(aURI);
*_size = mDefaultIconURIPreferredSize;
nsAutoCString ref;
// Check for a ref first.

View File

@ -221,7 +221,8 @@ interface nsIFaviconService : nsISupports
* aMimeType will be an empty string, regardless of whether a favicon
* was found.
* @param [optional] aPreferredWidth
* The preferred icon width, 0 for the biggest available.
* 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.
@ -248,7 +249,8 @@ interface nsIFaviconService : nsISupports
* 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, 0 for the biggest available.
* 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.
*