Store.jsm 6.1 KB

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