176 lines
6.1 KiB
JavaScript
176 lines
6.1 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
"use strict";
|
|
|
|
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {});
|
|
|
|
const {shortURL} = Cu.import("resource://activity-stream/lib/ShortURL.jsm", {});
|
|
const {SectionsManager} = Cu.import("resource://activity-stream/lib/SectionsManager.jsm", {});
|
|
const {TOP_SITES_SHOWMORE_LENGTH} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
|
|
const {Dedupe} = Cu.import("resource://activity-stream/common/Dedupe.jsm", {});
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "filterAdult",
|
|
"resource://activity-stream/lib/FilterAdult.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "LinksCache",
|
|
"resource://activity-stream/lib/LinksCache.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
|
|
"resource://gre/modules/NewTabUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Screenshots",
|
|
"resource://activity-stream/lib/Screenshots.jsm");
|
|
|
|
const HIGHLIGHTS_MAX_LENGTH = 9;
|
|
const HIGHLIGHTS_UPDATE_TIME = 15 * 60 * 1000; // 15 minutes
|
|
const MANY_EXTRA_LENGTH = HIGHLIGHTS_MAX_LENGTH * 5 + TOP_SITES_SHOWMORE_LENGTH;
|
|
const SECTION_ID = "highlights";
|
|
|
|
this.HighlightsFeed = class HighlightsFeed {
|
|
constructor() {
|
|
this.highlightsLastUpdated = 0;
|
|
this.highlightsLength = 0;
|
|
this.dedupe = new Dedupe(this._dedupeKey);
|
|
this.linksCache = new LinksCache(NewTabUtils.activityStreamLinks,
|
|
"getHighlights", ["image"]);
|
|
}
|
|
|
|
_dedupeKey(site) {
|
|
// Treat bookmarks as un-dedupable, otherwise show one of a url
|
|
return site && (site.type === "bookmark" ? {} : site.url);
|
|
}
|
|
|
|
init() {
|
|
SectionsManager.onceInitialized(this.postInit.bind(this));
|
|
}
|
|
|
|
postInit() {
|
|
SectionsManager.enableSection(SECTION_ID);
|
|
this.fetchHighlights(true);
|
|
}
|
|
|
|
uninit() {
|
|
SectionsManager.disableSection(SECTION_ID);
|
|
}
|
|
|
|
async fetchHighlights(broadcast = false) {
|
|
// We broadcast when we want to force an update, so get fresh links
|
|
if (broadcast) {
|
|
this.linksCache.expire();
|
|
}
|
|
|
|
// We need TopSites to have been initialised for deduping
|
|
if (!this.store.getState().TopSites.initialized) {
|
|
await new Promise(resolve => {
|
|
const unsubscribe = this.store.subscribe(() => {
|
|
if (this.store.getState().TopSites.initialized) {
|
|
unsubscribe();
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Request more than the expected length to allow for items being removed by
|
|
// deduping against Top Sites or multiple history from the same domain, etc.
|
|
const manyPages = await this.linksCache.request({numItems: MANY_EXTRA_LENGTH});
|
|
|
|
// Remove adult highlights if we need to
|
|
const checkedAdult = this.store.getState().Prefs.values.filterAdult ?
|
|
filterAdult(manyPages) : manyPages;
|
|
|
|
// Remove any Highlights that are in Top Sites already
|
|
const [, deduped] = this.dedupe.group(this.store.getState().TopSites.rows, checkedAdult);
|
|
|
|
// Keep all "bookmark"s and at most one (most recent) "history" per host
|
|
const highlights = [];
|
|
const hosts = new Set();
|
|
for (const page of deduped) {
|
|
const hostname = shortURL(page);
|
|
// Skip this history page if we already something from the same host
|
|
if (page.type === "history" && hosts.has(hostname)) {
|
|
continue;
|
|
}
|
|
|
|
// If we already have the image for the card, use that immediately. Else
|
|
// asynchronously fetch the image.
|
|
if (!page.image) {
|
|
this.fetchImage(page);
|
|
}
|
|
|
|
// We want the page, so update various fields for UI
|
|
Object.assign(page, {
|
|
hasImage: true, // We always have an image - fall back to a screenshot
|
|
hostname,
|
|
type: page.bookmarkGuid ? "bookmark" : page.type
|
|
});
|
|
|
|
// Add the "bookmark" or not-skipped "history"
|
|
highlights.push(page);
|
|
hosts.add(hostname);
|
|
|
|
// Remove internal properties that might be updated after dispatch
|
|
delete page.__sharedCache;
|
|
|
|
// Skip the rest if we have enough items
|
|
if (highlights.length === HIGHLIGHTS_MAX_LENGTH) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
SectionsManager.updateSection(SECTION_ID, {rows: highlights}, broadcast);
|
|
this.highlightsLastUpdated = Date.now();
|
|
this.highlightsLength = highlights.length;
|
|
}
|
|
|
|
/**
|
|
* Fetch an image for a given highlight and update the card with it. If no
|
|
* image is available then fallback to fetching a screenshot.
|
|
*/
|
|
async fetchImage(page) {
|
|
// Request a screenshot if we don't already have one pending
|
|
const {preview_image_url: imageUrl, url} = page;
|
|
Screenshots.maybeCacheScreenshot(page, imageUrl || url, "image", image => {
|
|
SectionsManager.updateSectionCard(SECTION_ID, url, {image}, true);
|
|
});
|
|
}
|
|
|
|
onAction(action) {
|
|
switch (action.type) {
|
|
case at.INIT:
|
|
this.init();
|
|
break;
|
|
case at.NEW_TAB_LOAD:
|
|
if (this.highlightsLength < HIGHLIGHTS_MAX_LENGTH) {
|
|
// If we haven't filled the highlights grid yet, fetch again.
|
|
this.fetchHighlights(true);
|
|
} else if (Date.now() - this.highlightsLastUpdated >= HIGHLIGHTS_UPDATE_TIME) {
|
|
// If the last time we refreshed the data is greater than 15 minutes, fetch again.
|
|
this.fetchHighlights(false);
|
|
}
|
|
break;
|
|
case at.MIGRATION_COMPLETED:
|
|
case at.PLACES_HISTORY_CLEARED:
|
|
case at.PLACES_LINKS_DELETED:
|
|
case at.PLACES_LINK_BLOCKED:
|
|
this.fetchHighlights(true);
|
|
break;
|
|
case at.PLACES_BOOKMARK_ADDED:
|
|
case at.PLACES_BOOKMARK_REMOVED:
|
|
this.linksCache.expire();
|
|
this.fetchHighlights(false);
|
|
break;
|
|
case at.TOP_SITES_UPDATED:
|
|
this.fetchHighlights(false);
|
|
break;
|
|
case at.UNINIT:
|
|
this.uninit();
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.HIGHLIGHTS_UPDATE_TIME = HIGHLIGHTS_UPDATE_TIME;
|
|
this.EXPORTED_SYMBOLS = ["HighlightsFeed", "HIGHLIGHTS_UPDATE_TIME", "SECTION_ID"];
|