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

151 lines
5.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/. */
/* global Preferences */
"use strict";
const {utils: Cu} = Components;
const {redux} = Cu.import("resource://activity-stream/vendor/Redux.jsm", {});
const {reducers} = Cu.import("resource://activity-stream/common/Reducers.jsm", {});
const {ActivityStreamMessageChannel} = Cu.import("resource://activity-stream/lib/ActivityStreamMessageChannel.jsm", {});
const PREF_PREFIX = "browser.newtabpage.activity-stream.";
Cu.import("resource://gre/modules/Preferences.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.
*/
this.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();
["dispatch", "getState", "subscribe"].forEach(method => {
this[method] = (...args) => {
return this._store[method](...args);
};
});
this.feeds = new Map();
this._feedFactories = null;
this._prefHandlers = new Map();
this._messageChannel = new ActivityStreamMessageChannel({dispatch: this.dispatch});
this._store = redux.createStore(
redux.combineReducers(reducers),
redux.applyMiddleware(this._middleware, this._messageChannel.middleware)
);
}
/**
* _middleware - This is redux middleware consumed by redux.createStore.
* it calls each feed's .onAction method, if one
* is defined.
*/
_middleware(store) {
return next => action => {
next(action);
this.feeds.forEach(s => s.onAction && s.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
*/
initFeed(feedName) {
const feed = this._feedFactories[feedName]();
feed.store = this;
this.feeds.set(feedName, feed);
}
/**
* 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
*/
uninitFeed(feedName) {
const feed = this.feeds.get(feedName);
if (!feed) {
return;
}
if (feed.uninit) {
feed.uninit();
}
this.feeds.delete(feedName);
}
/**
* maybeStartFeedAndListenForPrefChanges - Listen for pref changes that turn a
* feed off/on, and as long as that pref was not explicitly set to
* false, initialize the feed immediately.
*
* @param {string} name The name of a feed, as defined in the object passed
* to Store.init
*/
maybeStartFeedAndListenForPrefChanges(name) {
const prefName = PREF_PREFIX + name;
// If the pref was never set, set it to true by default.
if (!Preferences.has(prefName)) {
Preferences.set(prefName, true);
}
// Create a listener that turns the feed off/on based on changes
// to the pref, and cache it so we can unlisten on shut-down.
const onPrefChanged = isEnabled => (isEnabled ? this.initFeed(name) : this.uninitFeed(name));
this._prefHandlers.set(prefName, onPrefChanged);
Preferences.observe(prefName, onPrefChanged);
// TODO: This should propbably be done in a generic pref manager for Activity Stream.
// If the pref is true, start the feed immediately.
if (Preferences.get(prefName)) {
this.initFeed(name);
}
}
/**
* init - Initializes the ActivityStreamMessageChannel channel, and adds feeds.
*
* @param {array} feeds An array of objects with an optional .onAction method
*/
init(feedConstructors) {
if (feedConstructors) {
this._feedFactories = feedConstructors;
for (const name of Object.keys(feedConstructors)) {
this.maybeStartFeedAndListenForPrefChanges(name);
}
}
this._messageChannel.createChannel();
}
/**
* uninit - Uninitalizes each feed, clears them, and destroys the message
* manager channel.
*
* @return {type} description
*/
uninit() {
this.feeds.forEach(feed => this.uninitFeed(feed));
this._prefHandlers.forEach((handler, pref) => Preferences.ignore(pref, handler));
this._prefHandlers.clear();
this._feedFactories = null;
this.feeds.clear();
this._messageChannel.destroyChannel();
}
};
this.PREF_PREFIX = PREF_PREFIX;
this.EXPORTED_SYMBOLS = ["Store", "PREF_PREFIX"];