Bug 1738957 - Get page image from snapshot data. r=mossop

Add a way to get an image that best represents a snapshot. The current
priority is:
 1) meta data image
 2) thumbnail of page

Differential Revision: https://phabricator.services.mozilla.com/D130204
This commit is contained in:
Brendan Dahl 2021-11-05 09:25:48 +00:00
Родитель 1aeac93d23
Коммит d870cdcfe5
3 изменённых файлов: 157 добавлений и 0 удалений

Просмотреть файл

@ -13,10 +13,13 @@ const { XPCOMUtils } = ChromeUtils.import(
const VERSION_PREF = "browser.places.snapshots.version";
XPCOMUtils.defineLazyModuleGetters(this, {
BackgroundPageThumbs: "resource://gre/modules/BackgroundPageThumbs.jsm",
CommonNames: "resource:///modules/CommonNames.jsm",
Interactions: "resource:///modules/Interactions.jsm",
PageDataCollector: "resource:///modules/pagedata/PageDataCollector.jsm",
PageDataService: "resource:///modules/pagedata/PageDataService.jsm",
PageThumbs: "resource://gre/modules/PageThumbs.jsm",
PageThumbsStorage: "resource://gre/modules/PageThumbs.jsm",
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
});
@ -117,6 +120,8 @@ const Snapshots = new (class Snapshots {
// want to accumulate changes and update on idle, plus store a cache of the
// last notified pages to avoid hitting the same page continuously.
// PageDataService.on("page-data", this.#onPageData);
PageThumbs.addExpirationFilter(this);
}
/**
@ -137,6 +142,20 @@ const Snapshots = new (class Snapshots {
Services.obs.notifyObservers(null, topic, JSON.stringify(urls));
}
/**
* This is called by PageThumbs to see what thumbnails should be kept alive.
* Currently, the last 100 snapshots are kept alive.
* @param {function} callback
*/
async filterForThumbnailExpiration(callback) {
let snapshots = await this.query();
let urls = [];
for (let snapshot of snapshots) {
urls.push(snapshot.url);
}
callback(urls);
}
/**
* Fetches page data for the given urls and stores it with snapshots.
* @param {Array<Objects>} urls Array of {placeId, url} tuples.
@ -147,6 +166,7 @@ const Snapshots = new (class Snapshots {
let bindings = {};
for (let { placeId, url } of urls) {
let pageData = PageDataService.getCached(url);
let imageURL = null;
if (pageData?.data.length) {
for (let data of pageData.data) {
if (Object.values(PageDataCollector.DATA_TYPE).includes(data.type)) {
@ -158,6 +178,14 @@ const Snapshots = new (class Snapshots {
bindings[`data${index}`] = JSON.stringify(data);
values.push(`(:id${index}, :type${index}, :data${index})`);
index++;
if (data.type === PageDataCollector.DATA_TYPE.GENERAL) {
let websiteData = data.data.find(
element => element.type === "website"
);
if (websiteData?.image) {
imageURL = websiteData.image;
}
}
}
}
} else {
@ -165,6 +193,7 @@ const Snapshots = new (class Snapshots {
// was found, but we're not yet handling that, see the constructor.
PageDataService.queueFetch(url).catch(console.error);
}
this.#downloadPageImage(url, imageURL);
}
logConsole.debug(
@ -190,6 +219,24 @@ const Snapshots = new (class Snapshots {
);
}
/**
* TODO: Store the downloaded and rescaled page image. For now this
* only kicks off the page thumbnail if there isn't a meta data image.
*
* @param {string} url The URL of the page. This is only used if there isn't
* a `metaDataImageURL`.
* @param {string} metaDataImageURL The URL of the meta data page image. If
* this exists, it is prioritized over the page thumbnail.
*/
#downloadPageImage(url, metaDataImageURL = null) {
if (!metaDataImageURL) {
// No metadata image was found, start the process to capture a thumbnail
// so it will be ready when needed. Ignore any errors since we can
// fallback to a favicon.
BackgroundPageThumbs.captureIfMissing(url).catch(console.error);
}
}
/**
* Whether a given URL can be added to snapshots.
* The rules are defined in this.urlRequirements.
@ -486,6 +533,32 @@ const Snapshots = new (class Snapshots {
return snapshot;
}
/**
* Get the image that should represent the snapshot. The image URL
* returned comes from the following priority:
* 1) meta data page image
* 2) thumbnail of the page
* @param {Snapshot} snapshot
*
* @returns {string?}
*/
async getSnapshotImageURL(snapshot) {
const generalPageData = snapshot.pageData
.get(PageDataCollector.DATA_TYPE.GENERAL)
?.find(element => element.type === "website");
if (generalPageData) {
return generalPageData.image;
}
const url = snapshot.url;
await BackgroundPageThumbs.captureIfMissing(url).catch(console.error);
const exists = await PageThumbsStorage.fileExistsForURL(url);
if (exists) {
return PageThumbs.getThumbnailURL(url);
}
return null;
}
/**
* Translates a date value from the database.
*

Просмотреть файл

@ -0,0 +1,83 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests that adding a snapshot also adds related page data.
*/
XPCOMUtils.defineLazyModuleGetters(this, {
PageDataCollector: "resource:///modules/pagedata/PageDataCollector.jsm",
PageDataService: "resource:///modules/pagedata/PageDataService.jsm",
});
const TEST_URL1 = "https://example.com/";
const TEST_URL2 = "https://example.com/12345";
const TEST_IMAGE_URL = "https://example.com/dummy.png";
add_task(async function pageImage() {
// Register some page data.
PageDataService.pageDataDiscovered(TEST_URL1, [
{
type: PageDataCollector.DATA_TYPE.GENERAL,
data: [
{
type: "website",
image: TEST_IMAGE_URL,
},
],
},
]);
let now = Date.now();
// Make snapshots for any page with a typing time greater than 30 seconds
// in any one visit.
Services.prefs.setCharPref(
"browser.places.interactions.snapshotCriteria",
JSON.stringify([
{
property: "total_view_time",
aggregator: "max",
cutoff: 30000,
},
])
);
// Page thumbnails are broken in xpcshell tests.
Services.prefs.setBoolPref("browser.pagethumbnails.capturing_disabled", true);
await addInteractions([
{
url: TEST_URL1,
totalViewTime: 40000,
created_at: now - 1000,
},
{
url: TEST_URL2,
totalViewTime: 20000,
created_at: now - 2000,
},
]);
await assertSnapshots([
{
url: TEST_URL1,
userPersisted: false,
documentType: Interactions.DOCUMENT_TYPE.GENERIC,
},
]);
let snap = await Snapshots.get(TEST_URL1);
let imageUrl = await Snapshots.getSnapshotImageURL(snap);
Assert.equal(snap.pageData.size, 1, "Should have some page data.");
Assert.equal(imageUrl, TEST_IMAGE_URL, "Should have a page image.");
// In the browser a snapshot would usually have a thumbnail generated, but the
// thumbnail service does not work in xpcshell tests.
await Snapshots.add({ url: TEST_URL2 });
snap = await Snapshots.get(TEST_URL2);
imageUrl = await Snapshots.getSnapshotImageURL(snap);
Assert.equal(imageUrl, null, "Should not have a page image.");
await reset();
});

Просмотреть файл

@ -14,6 +14,7 @@ skip-if = toolkit == 'android' # bug 1730213
[test_snapshots_create_allow_protocols.js]
[test_snapshots_create_criteria.js]
[test_snapshots_pagedata.js]
[test_snapshots_page_image.js]
[test_snapshots_queries.js]
[test_snapshotselection_adult.js]
[test_snapshotselection_recent.js]