Files
tubestation/browser/extensions/activity-stream/lib/ActivityStream.jsm

340 lines
12 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";
ChromeUtils.import("resource://gre/modules/Services.jsm");
// NB: Eagerly load modules that will be loaded/constructed/initialized in the
// common case to avoid the overhead of wrapping and detecting lazy loading.
const {actionCreators: ac, actionTypes: at} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm", {});
const {DefaultPrefs} = ChromeUtils.import("resource://activity-stream/lib/ActivityStreamPrefs.jsm", {});
const {ManualMigration} = ChromeUtils.import("resource://activity-stream/lib/ManualMigration.jsm", {});
const {NewTabInit} = ChromeUtils.import("resource://activity-stream/lib/NewTabInit.jsm", {});
const {SectionsFeed} = ChromeUtils.import("resource://activity-stream/lib/SectionsManager.jsm", {});
const {PlacesFeed} = ChromeUtils.import("resource://activity-stream/lib/PlacesFeed.jsm", {});
const {PrefsFeed} = ChromeUtils.import("resource://activity-stream/lib/PrefsFeed.jsm", {});
const {Store} = ChromeUtils.import("resource://activity-stream/lib/Store.jsm", {});
const {SnippetsFeed} = ChromeUtils.import("resource://activity-stream/lib/SnippetsFeed.jsm", {});
const {SystemTickFeed} = ChromeUtils.import("resource://activity-stream/lib/SystemTickFeed.jsm", {});
const {TelemetryFeed} = ChromeUtils.import("resource://activity-stream/lib/TelemetryFeed.jsm", {});
const {FaviconFeed} = ChromeUtils.import("resource://activity-stream/lib/FaviconFeed.jsm", {});
const {TopSitesFeed} = ChromeUtils.import("resource://activity-stream/lib/TopSitesFeed.jsm", {});
const {TopStoriesFeed} = ChromeUtils.import("resource://activity-stream/lib/TopStoriesFeed.jsm", {});
const {HighlightsFeed} = ChromeUtils.import("resource://activity-stream/lib/HighlightsFeed.jsm", {});
const DEFAULT_SITES = new Map([
// This first item is the global list fallback for any unexpected geos
["", "https://www.youtube.com/,https://www.facebook.com/,https://www.wikipedia.org/,https://www.reddit.com/,https://www.amazon.com/,https://twitter.com/"],
["US", "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://twitter.com/"],
["CA", "https://www.youtube.com/,https://www.facebook.com/,https://www.reddit.com/,https://www.wikipedia.org/,https://www.amazon.ca/,https://twitter.com/"],
["DE", "https://www.youtube.com/,https://www.facebook.com/,https://www.amazon.de/,https://www.ebay.de/,https://www.wikipedia.org/,https://www.reddit.com/"],
["PL", "https://www.youtube.com/,https://www.facebook.com/,https://allegro.pl/,https://www.wikipedia.org/,https://www.olx.pl/,https://www.wykop.pl/"],
["RU", "https://vk.com/,https://www.youtube.com/,https://ok.ru/,https://www.avito.ru/,https://www.aliexpress.com/,https://www.wikipedia.org/"],
["GB", "https://www.youtube.com/,https://www.facebook.com/,https://www.reddit.com/,https://www.amazon.co.uk/,https://www.bbc.co.uk/,https://www.ebay.co.uk/"],
["FR", "https://www.youtube.com/,https://www.facebook.com/,https://www.wikipedia.org/,https://www.amazon.fr/,https://www.leboncoin.fr/,https://twitter.com/"]
]);
const GEO_PREF = "browser.search.region";
const REASON_ADDON_UNINSTALL = 6;
// Configure default Activity Stream prefs with a plain `value` or a `getValue`
// that computes a value. A `value_local_dev` is used for development defaults.
const PREFS_CONFIG = new Map([
["default.sites", {
title: "Comma-separated list of default top sites to fill in behind visited sites",
getValue: ({geo}) => DEFAULT_SITES.get(DEFAULT_SITES.has(geo) ? geo : "")
}],
["feeds.section.topstories.options", {
title: "Configuration options for top stories feed",
// This is a dynamic pref as it depends on the feed being shown or not
getValue: args => JSON.stringify({
api_key_pref: "extensions.pocket.oAuthConsumerKey",
// Use the opposite value as what default value the feed would have used
hidden: !PREFS_CONFIG.get("feeds.section.topstories").getValue(args),
provider_header: "pocket_feedback_header",
provider_description: "pocket_description",
provider_icon: "pocket",
provider_name: "Pocket",
read_more_endpoint: "https://getpocket.com/explore/trending?src=fx_new_tab",
stories_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/global-recs?version=2&consumer_key=$apiKey&locale_lang=${args.locale}`,
stories_referrer: "https://getpocket.com/recommendations",
info_link: "https://www.mozilla.org/privacy/firefox/#pocketstories",
disclaimer_link: "https://getpocket.com/firefox/new_tab_learn_more",
topics_endpoint: `https://getpocket.cdn.mozilla.net/v3/firefox/trending-topics?version=2&consumer_key=$apiKey&locale_lang=${args.locale}`,
show_spocs: false,
personalized: true
})
}],
["showSponsored", {
title: "Show sponsored cards in spoc experiment (show_spocs in topstories.options has to be set to true as well)",
value: true
}],
["filterAdult", {
title: "Remove adult pages from sites, highlights, etc.",
value: true
}],
["migrationExpired", {
title: "Boolean flag that decides whether to show the migration message or not.",
value: false
}],
["migrationLastShownDate", {
title: "Timestamp when migration message was last shown. In seconds.",
value: 0
}],
["migrationRemainingDays", {
title: "Number of days to show the manual migration message",
value: 4
}],
["prerender", {
title: "Use the prerendered version of activity-stream.html. This is set automatically by PrefsFeed.jsm.",
value: true
}],
["showSearch", {
title: "Show the Search bar",
value: true
}],
["disableSnippets", {
title: "Disable snippets on activity stream",
value: false
}],
["showTopSites", {
title: "Show the Top Sites section",
value: true
}],
["collapseTopSites", {
title: "Collapse the Top Sites section",
value: false
}],
["topSitesRows", {
title: "Number of rows of Top Sites to display",
value: 1
}],
["telemetry", {
title: "Enable system error and usage data collection",
value: true,
value_local_dev: false
}],
["telemetry.ping.endpoint", {
title: "Telemetry server endpoint",
value: "https://tiles.services.mozilla.com/v4/links/activity-stream"
}],
["section.highlights.collapsed", {
title: "Collapse the Highlights section",
value: false
}],
["section.topstories.collapsed", {
title: "Collapse the Top Stories section",
value: false
}],
["section.topstories.showDisclaimer", {
title: "Boolean flag that decides whether or not to show the topstories disclaimer.",
value: true
}],
["tippyTop.service.endpoint", {
title: "Tippy Top service manifest url",
value: "https://activity-stream-icons.services.mozilla.com/v1/icons.json.br"
}],
["enableWideLayout", {
title: "Enable the wider layout (8 topsites per row and larger pocket+highlight cards)",
value: true
}]
]);
// Array of each feed's FEEDS_CONFIG factory and values to add to PREFS_CONFIG
const FEEDS_DATA = [
{
name: "migration",
factory: () => new ManualMigration(),
title: "Manual migration wizard",
value: true
},
{
name: "newtabinit",
factory: () => new NewTabInit(),
title: "Sends a copy of the state to each new tab that is opened",
value: true
},
{
name: "places",
factory: () => new PlacesFeed(),
title: "Listens for and relays various Places-related events",
value: true
},
{
name: "prefs",
factory: () => new PrefsFeed(PREFS_CONFIG),
title: "Preferences",
value: true
},
{
name: "sections",
factory: () => new SectionsFeed(),
title: "Manages sections",
value: true
},
{
name: "section.highlights",
factory: () => new HighlightsFeed(),
title: "Fetches content recommendations from places db",
value: true
},
{
name: "section.topstories",
factory: () => new TopStoriesFeed(),
title: "Fetches content recommendations from a configurable content provider",
// Dynamically determine if Pocket should be shown for a geo / locale
getValue: ({geo, locale}) => {
const locales = ({
"US": ["en-US", "en-GB", "en-ZA"],
"CA": ["en-US", "en-GB", "en-ZA"],
"DE": ["de", "de-DE", "de-AT", "de-CH"]
})[geo];
return !!locales && locales.includes(locale);
}
},
{
name: "snippets",
factory: () => new SnippetsFeed(),
title: "Gets snippets data",
value: true
},
{
name: "systemtick",
factory: () => new SystemTickFeed(),
title: "Produces system tick events to periodically check for data expiry",
value: true
},
{
name: "telemetry",
factory: () => new TelemetryFeed(),
title: "Relays telemetry-related actions to PingCentre",
value: true
},
{
name: "favicon",
factory: () => new FaviconFeed(),
title: "Fetches tippy top manifests from remote service",
value: true
},
{
name: "topsites",
factory: () => new TopSitesFeed(),
title: "Queries places and gets metadata for Top Sites section",
value: true
}
];
const FEEDS_CONFIG = new Map();
for (const config of FEEDS_DATA) {
const pref = `feeds.${config.name}`;
FEEDS_CONFIG.set(pref, config.factory);
PREFS_CONFIG.set(pref, config);
}
var ActivityStream = class ActivityStream {
/**
* constructor - Initializes an instance of ActivityStream
*
* @param {object} options Options for the ActivityStream instance
* @param {string} options.id Add-on ID. e.g. "activity-stream@mozilla.org".
* @param {string} options.version Version of the add-on. e.g. "0.1.0"
* @param {string} options.newTabURL URL of New Tab page on which A.S. is displayed. e.g. "about:newtab"
*/
constructor(options = {}) {
this.initialized = false;
this.options = options;
this.store = new Store();
this.feeds = FEEDS_CONFIG;
this._defaultPrefs = new DefaultPrefs(PREFS_CONFIG);
}
init() {
try {
this._updateDynamicPrefs();
this._defaultPrefs.init();
// Hook up the store and let all feeds and pages initialize
this.store.init(this.feeds, ac.BroadcastToContent({
type: at.INIT,
data: {version: this.options.version}
}), {type: at.UNINIT});
this.initialized = true;
} catch (e) {
// TelemetryFeed could be unavailable if the telemetry is disabled, or
// the telemetry feed is not yet initialized.
const telemetryFeed = this.store.feeds.get("feeds.telemetry");
if (telemetryFeed) {
telemetryFeed.handleUndesiredEvent({data: {event: "ADDON_INIT_FAILED"}});
}
throw e;
}
}
uninit() {
if (this.geo === "") {
Services.prefs.removeObserver(GEO_PREF, this);
}
this.store.uninit();
this.initialized = false;
}
uninstall(reason) {
if (reason === REASON_ADDON_UNINSTALL) {
// This resets all prefs in the config to their default values,
// so we DON'T want to do this on an upgrade/downgrade, only on a
// real uninstall
this._defaultPrefs.reset();
}
}
_updateDynamicPrefs() {
// Save the geo pref if we have it
if (Services.prefs.prefHasUserValue(GEO_PREF)) {
this.geo = Services.prefs.getStringPref(GEO_PREF);
} else if (this.geo !== "") {
// Watch for geo changes and use a dummy value for now
Services.prefs.addObserver(GEO_PREF, this);
this.geo = "";
}
this.locale = Services.locale.getAppLocaleAsLangTag();
// Update the pref config of those with dynamic values
for (const pref of PREFS_CONFIG.keys()) {
const prefConfig = PREFS_CONFIG.get(pref);
if (!prefConfig.getValue) {
continue;
}
const newValue = prefConfig.getValue({
geo: this.geo,
locale: this.locale
});
// If there's an existing value and it has changed, that means we need to
// overwrite the default with the new value.
if (prefConfig.value !== undefined && prefConfig.value !== newValue) {
this._defaultPrefs.setDefaultPref(pref, newValue);
}
prefConfig.value = newValue;
}
}
observe(subject, topic, data) {
switch (topic) {
case "nsPref:changed":
// We should only expect one geo change, so update and stop observing
if (data === GEO_PREF) {
this._updateDynamicPrefs();
Services.prefs.removeObserver(GEO_PREF, this);
}
break;
}
}
};
var EXPORTED_SYMBOLS = ["ActivityStream", "PREFS_CONFIG"];