Backed out 2 changesets (bug 1864896) for causing node failures. CLOSED TREE
[gecko.git] / browser / components / newtab / content-src / lib / init-store.js
blob20fcedc6c00067a9feefbeb88d361b593a9e41ae
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 file,
3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /* eslint-env mozilla/remote-page */
7 import {
8   actionCreators as ac,
9   actionTypes as at,
10   actionUtils as au,
11 } from "common/Actions.sys.mjs";
12 import { applyMiddleware, combineReducers, createStore } from "redux";
14 export const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
15 export const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
16 export const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
18 /**
19  * A higher-order function which returns a reducer that, on MERGE_STORE action,
20  * will return the action.data object merged into the previous state.
21  *
22  * For all other actions, it merely calls mainReducer.
23  *
24  * Because we want this to merge the entire state object, it's written as a
25  * higher order function which takes the main reducer (itself often a call to
26  * combineReducers) as a parameter.
27  *
28  * @param  {function} mainReducer reducer to call if action != MERGE_STORE_ACTION
29  * @return {function}             a reducer that, on MERGE_STORE_ACTION action,
30  *                                will return the action.data object merged
31  *                                into the previous state, and the result
32  *                                of calling mainReducer otherwise.
33  */
34 function mergeStateReducer(mainReducer) {
35   return (prevState, action) => {
36     if (action.type === MERGE_STORE_ACTION) {
37       return { ...prevState, ...action.data };
38     }
40     return mainReducer(prevState, action);
41   };
44 /**
45  * messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
46  */
47 const messageMiddleware = store => next => action => {
48   const skipLocal = action.meta && action.meta.skipLocal;
49   if (au.isSendToMain(action)) {
50     RPMSendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
51   }
52   if (!skipLocal) {
53     next(action);
54   }
57 export const rehydrationMiddleware = ({ getState }) => {
58   // NB: The parameter here is MiddlewareAPI which looks like a Store and shares
59   // the same getState, so attached properties are accessible from the store.
60   getState.didRehydrate = false;
61   getState.didRequestInitialState = false;
62   return next => action => {
63     if (getState.didRehydrate || window.__FROM_STARTUP_CACHE__) {
64       // Startup messages can be safely ignored by the about:home document
65       // stored in the startup cache.
66       if (
67         window.__FROM_STARTUP_CACHE__ &&
68         action.meta &&
69         action.meta.isStartup
70       ) {
71         return null;
72       }
73       return next(action);
74     }
76     const isMergeStoreAction = action.type === MERGE_STORE_ACTION;
77     const isRehydrationRequest = action.type === at.NEW_TAB_STATE_REQUEST;
79     if (isRehydrationRequest) {
80       getState.didRequestInitialState = true;
81       return next(action);
82     }
84     if (isMergeStoreAction) {
85       getState.didRehydrate = true;
86       return next(action);
87     }
89     // If init happened after our request was made, we need to re-request
90     if (getState.didRequestInitialState && action.type === at.INIT) {
91       return next(ac.AlsoToMain({ type: at.NEW_TAB_STATE_REQUEST }));
92     }
94     if (
95       au.isBroadcastToContent(action) ||
96       au.isSendToOneContent(action) ||
97       au.isSendToPreloaded(action)
98     ) {
99       // Note that actions received before didRehydrate will not be dispatched
100       // because this could negatively affect preloading and the the state
101       // will be replaced by rehydration anyway.
102       return null;
103     }
105     return next(action);
106   };
110  * initStore - Create a store and listen for incoming actions
112  * @param  {object} reducers An object containing Redux reducers
113  * @param  {object} intialState (optional) The initial state of the store, if desired
114  * @return {object}          A redux store
115  */
116 export function initStore(reducers, initialState) {
117   const store = createStore(
118     mergeStateReducer(combineReducers(reducers)),
119     initialState,
120     global.RPMAddMessageListener &&
121       applyMiddleware(rehydrationMiddleware, messageMiddleware)
122   );
124   if (global.RPMAddMessageListener) {
125     global.RPMAddMessageListener(INCOMING_MESSAGE_NAME, msg => {
126       try {
127         store.dispatch(msg.data);
128       } catch (ex) {
129         console.error("Content msg:", msg, "Dispatch error: ", ex);
130         dump(
131           `Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${
132             ex.stack
133           }`
134         );
135       }
136     });
137   }
139   return store;