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 */
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";
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.
22 * For all other actions, it merely calls mainReducer.
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.
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.
34 function mergeStateReducer(mainReducer) {
35 return (prevState, action) => {
36 if (action.type === MERGE_STORE_ACTION) {
37 return { ...prevState, ...action.data };
40 return mainReducer(prevState, action);
45 * messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
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);
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.
67 window.__FROM_STARTUP_CACHE__ &&
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;
84 if (isMergeStoreAction) {
85 getState.didRehydrate = true;
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 }));
95 au.isBroadcastToContent(action) ||
96 au.isSendToOneContent(action) ||
97 au.isSendToPreloaded(action)
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.
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
116 export function initStore(reducers, initialState) {
117 const store = createStore(
118 mergeStateReducer(combineReducers(reducers)),
120 global.RPMAddMessageListener &&
121 applyMiddleware(rehydrationMiddleware, messageMiddleware)
124 if (global.RPMAddMessageListener) {
125 global.RPMAddMessageListener(INCOMING_MESSAGE_NAME, msg => {
127 store.dispatch(msg.data);
129 console.error("Content msg:", msg, "Dispatch error: ", ex);
131 `Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${