forked from mirrors/gecko-dev
Differential Revision: https://phabricator.services.mozilla.com/D33089 --HG-- extra : moz-landing-system : lando
138 lines
4.8 KiB
JavaScript
138 lines
4.8 KiB
JavaScript
/* eslint-env mozilla/frame-script */
|
|
|
|
import {actionCreators as ac, actionTypes as at, actionUtils as au} from "common/Actions.jsm";
|
|
import {applyMiddleware, combineReducers, createStore} from "redux";
|
|
|
|
export const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
|
|
export const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
|
|
export const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
|
|
export const EARLY_QUEUED_ACTIONS = [at.SAVE_SESSION_PERF_DATA];
|
|
|
|
/**
|
|
* A higher-order function which returns a reducer that, on MERGE_STORE action,
|
|
* will return the action.data object merged into the previous state.
|
|
*
|
|
* For all other actions, it merely calls mainReducer.
|
|
*
|
|
* Because we want this to merge the entire state object, it's written as a
|
|
* higher order function which takes the main reducer (itself often a call to
|
|
* combineReducers) as a parameter.
|
|
*
|
|
* @param {function} mainReducer reducer to call if action != MERGE_STORE_ACTION
|
|
* @return {function} a reducer that, on MERGE_STORE_ACTION action,
|
|
* will return the action.data object merged
|
|
* into the previous state, and the result
|
|
* of calling mainReducer otherwise.
|
|
*/
|
|
function mergeStateReducer(mainReducer) {
|
|
return (prevState, action) => {
|
|
if (action.type === MERGE_STORE_ACTION) {
|
|
return {...prevState, ...action.data};
|
|
}
|
|
|
|
return mainReducer(prevState, action);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
|
|
*/
|
|
const messageMiddleware = store => next => action => {
|
|
const skipLocal = action.meta && action.meta.skipLocal;
|
|
if (au.isSendToMain(action)) {
|
|
RPMSendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
|
|
}
|
|
if (!skipLocal) {
|
|
next(action);
|
|
}
|
|
};
|
|
|
|
export const rehydrationMiddleware = store => next => action => {
|
|
if (store._didRehydrate) {
|
|
return next(action);
|
|
}
|
|
|
|
const isMergeStoreAction = action.type === MERGE_STORE_ACTION;
|
|
const isRehydrationRequest = action.type === at.NEW_TAB_STATE_REQUEST;
|
|
|
|
if (isRehydrationRequest) {
|
|
store._didRequestInitialState = true;
|
|
return next(action);
|
|
}
|
|
|
|
if (isMergeStoreAction) {
|
|
store._didRehydrate = true;
|
|
return next(action);
|
|
}
|
|
|
|
// If init happened after our request was made, we need to re-request
|
|
if (store._didRequestInitialState && action.type === at.INIT) {
|
|
return next(ac.AlsoToMain({type: at.NEW_TAB_STATE_REQUEST}));
|
|
}
|
|
|
|
if (au.isBroadcastToContent(action) || au.isSendToOneContent(action) || au.isSendToPreloaded(action)) {
|
|
// Note that actions received before didRehydrate will not be dispatched
|
|
// because this could negatively affect preloading and the the state
|
|
// will be replaced by rehydration anyway.
|
|
return null;
|
|
}
|
|
|
|
return next(action);
|
|
};
|
|
|
|
/**
|
|
* This middleware queues up all the EARLY_QUEUED_ACTIONS until it receives
|
|
* the first action from main. This is useful for those actions for main which
|
|
* require higher reliability, i.e. the action will not be lost in the case
|
|
* that it gets sent before the main is ready to receive it. Conversely, any
|
|
* actions allowed early are accepted to be ignorable or re-sendable.
|
|
*/
|
|
export const queueEarlyMessageMiddleware = store => next => action => {
|
|
if (store._receivedFromMain) {
|
|
next(action);
|
|
} else if (au.isFromMain(action)) {
|
|
next(action);
|
|
store._receivedFromMain = true;
|
|
// Sending out all the early actions as main is ready now
|
|
if (store._earlyActionQueue) {
|
|
store._earlyActionQueue.forEach(next);
|
|
store._earlyActionQueue = [];
|
|
}
|
|
} else if (EARLY_QUEUED_ACTIONS.includes(action.type)) {
|
|
store._earlyActionQueue = store._earlyActionQueue || [];
|
|
store._earlyActionQueue.push(action);
|
|
} else {
|
|
// Let any other type of action go through
|
|
next(action);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* initStore - Create a store and listen for incoming actions
|
|
*
|
|
* @param {object} reducers An object containing Redux reducers
|
|
* @param {object} intialState (optional) The initial state of the store, if desired
|
|
* @return {object} A redux store
|
|
*/
|
|
export function initStore(reducers) {
|
|
const store = createStore(
|
|
mergeStateReducer(combineReducers(reducers)),
|
|
global.RPMAddMessageListener && applyMiddleware(rehydrationMiddleware, queueEarlyMessageMiddleware, messageMiddleware)
|
|
);
|
|
|
|
store._didRehydrate = false;
|
|
store._didRequestInitialState = false;
|
|
|
|
if (global.RPMAddMessageListener) {
|
|
global.RPMAddMessageListener(INCOMING_MESSAGE_NAME, msg => {
|
|
try {
|
|
store.dispatch(msg.data);
|
|
} catch (ex) {
|
|
console.error("Content msg:", msg, "Dispatch error: ", ex); // eslint-disable-line no-console
|
|
dump(`Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${ex.stack}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
return store;
|
|
}
|