diff options
Diffstat (limited to 'browser/components/newtab/lib/Store.jsm')
-rw-r--r-- | browser/components/newtab/lib/Store.jsm | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/browser/components/newtab/lib/Store.jsm b/browser/components/newtab/lib/Store.jsm new file mode 100644 index 0000000000..12574e196a --- /dev/null +++ b/browser/components/newtab/lib/Store.jsm @@ -0,0 +1,190 @@ +/* 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 { ActivityStreamMessageChannel } = ChromeUtils.import( + "resource://activity-stream/lib/ActivityStreamMessageChannel.jsm" +); +const { ActivityStreamStorage } = ChromeUtils.import( + "resource://activity-stream/lib/ActivityStreamStorage.jsm" +); +const { Prefs } = ChromeUtils.import( + "resource://activity-stream/lib/ActivityStreamPrefs.jsm" +); +const { reducers } = ChromeUtils.import( + "resource://activity-stream/common/Reducers.jsm" +); +const { redux } = ChromeUtils.import( + "resource://activity-stream/vendor/Redux.jsm" +); + +/** + * Store - This has a similar structure to a redux store, but includes some extra + * functionality to allow for routing of actions between the Main processes + * and child processes via a ActivityStreamMessageChannel. + * It also accepts an array of "Feeds" on inititalization, which + * can listen for any action that is dispatched through the store. + */ +class Store { + /** + * constructor - The redux store and message manager are created here, + * but no listeners are added until "init" is called. + */ + constructor() { + this._middleware = this._middleware.bind(this); + // Bind each redux method so we can call it directly from the Store. E.g., + // store.dispatch() will call store._store.dispatch(); + for (const method of ["dispatch", "getState", "subscribe"]) { + this[method] = (...args) => this._store[method](...args); + } + this.feeds = new Map(); + this._prefs = new Prefs(); + this._messageChannel = new ActivityStreamMessageChannel({ + dispatch: this.dispatch, + }); + this._store = redux.createStore( + redux.combineReducers(reducers), + redux.applyMiddleware(this._middleware, this._messageChannel.middleware) + ); + this.storage = null; + } + + /** + * _middleware - This is redux middleware consumed by redux.createStore. + * it calls each feed's .onAction method, if one + * is defined. + */ + _middleware() { + return next => action => { + next(action); + for (const store of this.feeds.values()) { + if (store.onAction) { + store.onAction(action); + } + } + }; + } + + /** + * initFeed - Initializes a feed by calling its constructor function + * + * @param {string} feedName The name of a feed, as defined in the object + * passed to Store.init + * @param {Action} initAction An optional action to initialize the feed + */ + initFeed(feedName, initAction) { + const feed = this._feedFactories.get(feedName)(); + feed.store = this; + this.feeds.set(feedName, feed); + if (initAction && feed.onAction) { + feed.onAction(initAction); + } + } + + /** + * uninitFeed - Removes a feed and calls its uninit function if defined + * + * @param {string} feedName The name of a feed, as defined in the object + * passed to Store.init + * @param {Action} uninitAction An optional action to uninitialize the feed + */ + uninitFeed(feedName, uninitAction) { + const feed = this.feeds.get(feedName); + if (!feed) { + return; + } + if (uninitAction && feed.onAction) { + feed.onAction(uninitAction); + } + this.feeds.delete(feedName); + } + + /** + * onPrefChanged - Listener for handling feed changes. + */ + onPrefChanged(name, value) { + if (this._feedFactories.has(name)) { + if (value) { + this.initFeed(name, this._initAction); + } else { + this.uninitFeed(name, this._uninitAction); + } + } + } + + /** + * init - Initializes the ActivityStreamMessageChannel channel, and adds feeds. + * + * Note that it intentionally initializes the TelemetryFeed first so that the + * addon is able to report the init errors from other feeds. + * + * @param {Map} feedFactories A Map of feeds with the name of the pref for + * the feed as the key and a function that + * constructs an instance of the feed. + * @param {Action} initAction An optional action that will be dispatched + * to feeds when they're created. + * @param {Action} uninitAction An optional action for when feeds uninit. + */ + async init(feedFactories, initAction, uninitAction) { + this._feedFactories = feedFactories; + this._initAction = initAction; + this._uninitAction = uninitAction; + + const telemetryKey = "feeds.telemetry"; + if (feedFactories.has(telemetryKey) && this._prefs.get(telemetryKey)) { + this.initFeed(telemetryKey); + } + + await this._initIndexedDB(telemetryKey); + + for (const pref of feedFactories.keys()) { + if (pref !== telemetryKey && this._prefs.get(pref)) { + this.initFeed(pref); + } + } + + this._prefs.observeBranch(this); + this._messageChannel.createChannel(); + + // Dispatch an initial action after all enabled feeds are ready + if (initAction) { + this.dispatch(initAction); + } + + // Dispatch NEW_TAB_INIT/NEW_TAB_LOAD events after INIT event. + this._messageChannel.simulateMessagesForExistingTabs(); + } + + async _initIndexedDB(telemetryKey) { + this.dbStorage = new ActivityStreamStorage({ + storeNames: ["sectionPrefs", "snippets"], + }); + // Accessing the db causes the object stores to be created / migrated. + // This needs to happen before other instances try to access the db, which + // would update only a subset of the stores to the latest version. + try { + await this.dbStorage.db; // eslint-disable-line no-unused-expressions + } catch (e) { + this.dbStorage.telemetry = null; + } + } + + /** + * uninit - Uninitalizes each feed, clears them, and destroys the message + * manager channel. + * + * @return {type} description + */ + uninit() { + if (this._uninitAction) { + this.dispatch(this._uninitAction); + } + this._prefs.ignoreBranch(this); + this.feeds.clear(); + this._feedFactories = null; + this._messageChannel.destroyChannel(); + } +} + +const EXPORTED_SYMBOLS = ["Store"]; |