1/* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4"use strict"; 5 6const { ActivityStreamMessageChannel } = ChromeUtils.import( 7 "resource://activity-stream/lib/ActivityStreamMessageChannel.jsm" 8); 9const { ActivityStreamStorage } = ChromeUtils.import( 10 "resource://activity-stream/lib/ActivityStreamStorage.jsm" 11); 12const { Prefs } = ChromeUtils.import( 13 "resource://activity-stream/lib/ActivityStreamPrefs.jsm" 14); 15const { reducers } = ChromeUtils.import( 16 "resource://activity-stream/common/Reducers.jsm" 17); 18const { redux } = ChromeUtils.import( 19 "resource://activity-stream/vendor/Redux.jsm" 20); 21 22/** 23 * Store - This has a similar structure to a redux store, but includes some extra 24 * functionality to allow for routing of actions between the Main processes 25 * and child processes via a ActivityStreamMessageChannel. 26 * It also accepts an array of "Feeds" on inititalization, which 27 * can listen for any action that is dispatched through the store. 28 */ 29this.Store = class Store { 30 /** 31 * constructor - The redux store and message manager are created here, 32 * but no listeners are added until "init" is called. 33 */ 34 constructor() { 35 this._middleware = this._middleware.bind(this); 36 // Bind each redux method so we can call it directly from the Store. E.g., 37 // store.dispatch() will call store._store.dispatch(); 38 for (const method of ["dispatch", "getState", "subscribe"]) { 39 this[method] = (...args) => this._store[method](...args); 40 } 41 this.feeds = new Map(); 42 this._prefs = new Prefs(); 43 this._messageChannel = new ActivityStreamMessageChannel({ 44 dispatch: this.dispatch, 45 }); 46 this._store = redux.createStore( 47 redux.combineReducers(reducers), 48 redux.applyMiddleware(this._middleware, this._messageChannel.middleware) 49 ); 50 this.storage = null; 51 } 52 53 /** 54 * _middleware - This is redux middleware consumed by redux.createStore. 55 * it calls each feed's .onAction method, if one 56 * is defined. 57 */ 58 _middleware() { 59 return next => action => { 60 next(action); 61 for (const store of this.feeds.values()) { 62 if (store.onAction) { 63 store.onAction(action); 64 } 65 } 66 }; 67 } 68 69 /** 70 * initFeed - Initializes a feed by calling its constructor function 71 * 72 * @param {string} feedName The name of a feed, as defined in the object 73 * passed to Store.init 74 * @param {Action} initAction An optional action to initialize the feed 75 */ 76 initFeed(feedName, initAction) { 77 const feed = this._feedFactories.get(feedName)(); 78 feed.store = this; 79 this.feeds.set(feedName, feed); 80 if (initAction && feed.onAction) { 81 feed.onAction(initAction); 82 } 83 } 84 85 /** 86 * uninitFeed - Removes a feed and calls its uninit function if defined 87 * 88 * @param {string} feedName The name of a feed, as defined in the object 89 * passed to Store.init 90 * @param {Action} uninitAction An optional action to uninitialize the feed 91 */ 92 uninitFeed(feedName, uninitAction) { 93 const feed = this.feeds.get(feedName); 94 if (!feed) { 95 return; 96 } 97 if (uninitAction && feed.onAction) { 98 feed.onAction(uninitAction); 99 } 100 this.feeds.delete(feedName); 101 } 102 103 /** 104 * onPrefChanged - Listener for handling feed changes. 105 */ 106 onPrefChanged(name, value) { 107 if (this._feedFactories.has(name)) { 108 if (value) { 109 this.initFeed(name, this._initAction); 110 } else { 111 this.uninitFeed(name, this._uninitAction); 112 } 113 } 114 } 115 116 /** 117 * init - Initializes the ActivityStreamMessageChannel channel, and adds feeds. 118 * 119 * Note that it intentionally initializes the TelemetryFeed first so that the 120 * addon is able to report the init errors from other feeds. 121 * 122 * @param {Map} feedFactories A Map of feeds with the name of the pref for 123 * the feed as the key and a function that 124 * constructs an instance of the feed. 125 * @param {Action} initAction An optional action that will be dispatched 126 * to feeds when they're created. 127 * @param {Action} uninitAction An optional action for when feeds uninit. 128 */ 129 async init(feedFactories, initAction, uninitAction) { 130 this._feedFactories = feedFactories; 131 this._initAction = initAction; 132 this._uninitAction = uninitAction; 133 134 const telemetryKey = "feeds.telemetry"; 135 if (feedFactories.has(telemetryKey) && this._prefs.get(telemetryKey)) { 136 this.initFeed(telemetryKey); 137 } 138 139 await this._initIndexedDB(telemetryKey); 140 141 for (const pref of feedFactories.keys()) { 142 if (pref !== telemetryKey && this._prefs.get(pref)) { 143 this.initFeed(pref); 144 } 145 } 146 147 this._prefs.observeBranch(this); 148 this._messageChannel.createChannel(); 149 150 // Dispatch an initial action after all enabled feeds are ready 151 if (initAction) { 152 this.dispatch(initAction); 153 } 154 155 // Dispatch NEW_TAB_INIT/NEW_TAB_LOAD events after INIT event. 156 this._messageChannel.simulateMessagesForExistingTabs(); 157 } 158 159 async _initIndexedDB(telemetryKey) { 160 this.dbStorage = new ActivityStreamStorage({ 161 storeNames: ["sectionPrefs", "snippets"], 162 }); 163 // Accessing the db causes the object stores to be created / migrated. 164 // This needs to happen before other instances try to access the db, which 165 // would update only a subset of the stores to the latest version. 166 try { 167 await this.dbStorage.db; // eslint-disable-line no-unused-expressions 168 } catch (e) { 169 this.dbStorage.telemetry = null; 170 } 171 } 172 173 /** 174 * uninit - Uninitalizes each feed, clears them, and destroys the message 175 * manager channel. 176 * 177 * @return {type} description 178 */ 179 uninit() { 180 if (this._uninitAction) { 181 this.dispatch(this._uninitAction); 182 } 183 this._prefs.ignoreBranch(this); 184 this.feeds.clear(); 185 this._feedFactories = null; 186 this._messageChannel.destroyChannel(); 187 } 188}; 189 190const EXPORTED_SYMBOLS = ["Store"]; 191