/* 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"); Cu.importGlobalProperties(["fetch"]); XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm"); const ACTIVITY_STREAM_ENABLED_PREF = "browser.newtabpage.activity-stream.enabled"; const BROWSER_READY_NOTIFICATION = "sessionstore-windows-restored"; const PREF_CHANGED_TOPIC = "nsPref:changed"; const REASON_SHUTDOWN_ON_PREF_CHANGE = "PREF_OFF"; const REASON_STARTUP_ON_PREF_CHANGE = "PREF_ON"; const RESOURCE_BASE = "resource://activity-stream"; const ACTIVITY_STREAM_OPTIONS = {newTabURL: "about:newtab"}; let activityStream; let modulesToUnload = new Set(); let startupData; let startupReason; let waitingForBrowserReady = true; // Lazily load ActivityStream then find related modules to unload XPCOMUtils.defineLazyModuleGetter(this, "ActivityStream", "resource://activity-stream/lib/ActivityStream.jsm", null, null, () => { // Helper to fetch a resource directory listing and call back with each item const processListing = async (uri, cb) => (await (await fetch(uri)).text()) .split("\n").slice(2).forEach(line => cb(line.split(" ").slice(1))); // Look for modules one level deeper than the top resource URI processListing(RESOURCE_BASE, ([directory, , , type]) => { if (type === "DIRECTORY") { // Look into this directory for .jsm files const subDir = `${RESOURCE_BASE}/${directory}`; processListing(subDir, ([name]) => { if (name && name.search(/\.jsm$/) !== -1) { modulesToUnload.add(`${subDir}/${name}`); } }); } }); }); /** * init - Initializes an instance of ActivityStream. This could be called by * the startup() function exposed by bootstrap.js, or it could be called * when ACTIVITY_STREAM_ENABLED_PREF is changed from false to true. * * @param {string} reason - Reason for initialization. Could be install, upgrade, or PREF_ON */ function init(reason) { // Don't re-initialize if (activityStream && activityStream.initialized) { return; } const options = Object.assign({}, startupData || {}, ACTIVITY_STREAM_OPTIONS); activityStream = new ActivityStream(options); try { activityStream.init(reason); } catch (e) { Cu.reportError(e); } } /** * uninit - Uninitializes the activityStream instance, if it exsits.This could be * called by the shutdown() function exposed by bootstrap.js, or it could * be called when ACTIVITY_STREAM_ENABLED_PREF is changed from true to false. * * @param {type} reason Reason for uninitialization. Could be uninstall, upgrade, or PREF_OFF */ function uninit(reason) { // Make sure to only uninit once in case both pref change and shutdown happen if (activityStream) { activityStream.uninit(reason); activityStream = null; } } /** * onPrefChanged - handler for changes to ACTIVITY_STREAM_ENABLED_PREF * */ function onPrefChanged() { if (Services.prefs.getBoolPref(ACTIVITY_STREAM_ENABLED_PREF, false)) { init(REASON_STARTUP_ON_PREF_CHANGE); } else { uninit(REASON_SHUTDOWN_ON_PREF_CHANGE); } } /** * Check if an old pref has a custom value to migrate. Clears the pref so that * it's the default after migrating (to avoid future need to migrate). * * @param oldPrefName {string} Pref to check and migrate * @param cbIfNotDefault {function} Callback that gets the current pref value */ function migratePref(oldPrefName, cbIfNotDefault) { // Nothing to do if the user doesn't have a custom value if (!Services.prefs.prefHasUserValue(oldPrefName)) { return; } // Figure out what kind of pref getter to use let prefGetter; switch (Services.prefs.getPrefType(oldPrefName)) { case Services.prefs.PREF_BOOL: prefGetter = "getBoolPref"; break; case Services.prefs.PREF_INT: prefGetter = "getIntPref"; break; case Services.prefs.PREF_STRING: prefGetter = "getStringPref"; break; } // Give the callback the current value then clear the pref cbIfNotDefault(Services.prefs[prefGetter](oldPrefName)); Services.prefs.clearUserPref(oldPrefName); } /** * onBrowserReady - Continues startup of the add-on after browser is ready. */ function onBrowserReady() { waitingForBrowserReady = false; // Listen for changes to the pref that enables Activity Stream Services.prefs.addObserver(ACTIVITY_STREAM_ENABLED_PREF, observe); // eslint-disable-line no-use-before-define // Only initialize if the pref is true if (Services.prefs.getBoolPref(ACTIVITY_STREAM_ENABLED_PREF, false)) { init(startupReason); } // Do a one time migration of Tiles about:newtab prefs that have been modified migratePref("browser.newtabpage.rows", rows => { // Just disable top sites if rows are not desired if (rows <= 0) { Services.prefs.setBoolPref("browser.newtabpage.activity-stream.showTopSites", false); } else { // Assume we want a full row (6 sites per row) Services.prefs.setIntPref("browser.newtabpage.activity-stream.topSitesCount", rows * 6); } }); } /** * observe - nsIObserver callback to handle various browser notifications. */ function observe(subject, topic, data) { switch (topic) { case BROWSER_READY_NOTIFICATION: Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION); // Avoid running synchronously during this event that's used for timing Services.tm.dispatchToMainThread(() => onBrowserReady()); break; case PREF_CHANGED_TOPIC: if (data === ACTIVITY_STREAM_ENABLED_PREF) { onPrefChanged(); } break; } } // The functions below are required by bootstrap.js this.install = function install(data, reason) {}; this.startup = function startup(data, reason) { // Cache startup data which contains stuff like the version number, etc. // so we can use it when we init startupData = data; startupReason = reason; // Only start Activity Stream up when the browser UI is ready if (Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup).startingUp) { Services.obs.addObserver(observe, BROWSER_READY_NOTIFICATION); } else { // Handle manual install or automatic install after manual uninstall onBrowserReady(); } }; this.shutdown = function shutdown(data, reason) { // Uninitialize Activity Stream startupData = null; startupReason = null; uninit(reason); // Stop waiting for browser to be ready if (waitingForBrowserReady) { Services.obs.removeObserver(observe, BROWSER_READY_NOTIFICATION); } else { // Stop listening to the pref that enables Activity Stream Services.prefs.removeObserver(ACTIVITY_STREAM_ENABLED_PREF, observe); } // Unload any add-on modules that might might have been imported modulesToUnload.forEach(Cu.unload); }; this.uninstall = function uninstall(data, reason) { if (activityStream) { activityStream.uninstall(reason); activityStream = null; } };