diff --git a/src/browser/extension/background/messaging.js b/src/browser/extension/background/messaging.js index 5373e194..ddda4358 100644 --- a/src/browser/extension/background/messaging.js +++ b/src/browser/extension/background/messaging.js @@ -1,6 +1,6 @@ import { onConnect, onMessage, sendToTab } from 'crossmessaging'; import { stringify } from 'circular-json'; -import parseJSON from '../utils/parseJSON'; +import updateState from '../utils/updateState'; import syncOptions from '../options/syncOptions'; import createMenu from './contextMenus'; import openDevToolsWindow from './openWindow'; @@ -9,21 +9,15 @@ let catchedErrors = {}; window.syncOptions = syncOptions; // Used in the options page -const naMessage = { - na: true, - source: 'redux-page' -}; +const naMessage = { na: true }; // Connect to devpanel onConnect((tabId) => { if (tabId !== store.id) return naMessage; - return { - payload: stringify(window.store.liftedStore.getState()), - source: 'redux-page' - }; + return {}; }, {}, connections); -// Receive message from content script and relay to the devTools page +// Receive message from content script function messaging(request, sender, sendResponse) { const tabId = sender.tab ? sender.tab.id : sender.id; if (tabId) { @@ -55,15 +49,17 @@ function messaging(request, sender, sendResponse) { return true; } - const payload = parseJSON(request.payload); + const payload = updateState(store, request); if (!payload) return true; - store.liftedStore.setState(payload); + if (request.init) { store.id = tabId; createMenu(sender.url, tabId); } + + // Relay the message to the devTools page if (tabId in connections) { - connections[tabId].postMessage({ payload: request.payload }); + connections[tabId].postMessage(request); } // Notify when errors occur in the app diff --git a/src/browser/extension/devpanel/index.js b/src/browser/extension/devpanel/index.js index b9027cb8..4b7f0c17 100644 --- a/src/browser/extension/devpanel/index.js +++ b/src/browser/extension/devpanel/index.js @@ -3,7 +3,7 @@ import React from 'react'; import { render } from 'react-dom'; import DevTools from '../../../app/containers/DevTools'; import createDevStore from '../../../app/store/createDevStore'; -import parseJSON from '../utils/parseJSON'; +import updateState from '../utils/updateState'; const backgroundPageConnection = connect(); @@ -39,11 +39,8 @@ backgroundPageConnection.onMessage.addListener((message) => { document.getElementById('root') ); rendered = false; - } else if (message.payload) { - const payload = parseJSON(message.payload); - if (!payload) return; - store.liftedStore.setState(payload); - showDevTools(); + } else if (message.type) { + if (updateState(store, message)) showDevTools(); } else if (message.action) { dispatch(message.action); } diff --git a/src/browser/extension/inject/pageScript.js b/src/browser/extension/inject/pageScript.js index 8da76747..065be00e 100644 --- a/src/browser/extension/inject/pageScript.js +++ b/src/browser/extension/inject/pageScript.js @@ -6,15 +6,19 @@ import { isAllowed } from '../options/syncOptions'; window.devToolsExtension = function(next) { let store = {}; if (!window.devToolsOptions) window.devToolsOptions = {}; - let filtered = { last: null, post: false, skip: false }; let shouldSerialize = false; + let shouldInit = true; + let actionsCount = 0; - function relayChanges(state, init) { + function relay(type, state, action) { const message = { payload: state, + action: action || '', + type: type, source: 'redux-page', - init: init || false + init: shouldInit }; + if (shouldInit) shouldInit = false; if (shouldSerialize) { message.payload = stringify(state); window.postMessage(message, '*'); @@ -29,44 +33,6 @@ window.devToolsExtension = function(next) { } } - function checkState() { - filtered.post = true; - const state = store.liftedStore.getState(); - if (window.devToolsOptions.filter) { - if (filtered.skip) filtered.skip = false; - else { - const actionType = state.actionsById[state.nextActionId - 1].action.type; - const { whitelist, blacklist } = window.devToolsOptions; - if ( - whitelist && whitelist.indexOf(actionType) === -1 || - blacklist && blacklist.indexOf(actionType) !== -1 - ) filtered.post = false; - } - } - return state; - } - - function onChange(init) { - const state = checkState(); - - if (!filtered.post) return; - filtered.post = false; - - if (window.devToolsOptions.limit && state.currentStateIndex > window.devToolsOptions.limit) { - store.liftedStore.dispatch({type: COMMIT, timestamp: Date.now()}); - return; - } - - if (window.devToolsOptions.filter) { - const { whitelist, blacklist } = window.devToolsOptions; - state.filter = { whitelist, blacklist }; - } - - relayChanges(state, init); - - window.devToolsExtension.notifyErrors(); - } - function onMessage(event) { if (!event || event.source !== window) { return; @@ -79,26 +45,56 @@ window.devToolsExtension = function(next) { } if (message.type === ACTION) { - filtered.skip = true; store.liftedStore.dispatch(message.payload); } else if (message.type === UPDATE) { - onChange(); + relay('STATE', store.liftedStore.getState()); } + } + function isFiltered(action) { + if (!window.devToolsOptions.filter) return false; + const { whitelist, blacklist } = window.devToolsOptions; + return ( + whitelist && whitelist.indexOf(action.type) === -1 || + blacklist && blacklist.indexOf(action.type) !== -1 + ); } - function devToolsInit() { - store.liftedStore.subscribe(onChange); - window.addEventListener('message', onMessage, false); + function isLimit() { + if (window.devToolsOptions.limit && actionsCount > window.devToolsOptions.limit) { + store.liftedStore.dispatch({type: COMMIT, timestamp: Date.now()}); + return true; + } + actionsCount++; + return false; + } - onChange(true); + function subscriber(state = {}, action) { + if (action && action.type) { + setTimeout(() => { + if (action.type === 'PERFORM_ACTION') { + if (isLimit() || isFiltered(action.action)) return state; + relay('ACTION', store.getState(), action); + } else { + const liftedState = store.liftedStore.getState(); + relay('STATE', liftedState); + actionsCount = liftedState.nextActionId; + } + }, 0); + } + return state; + } + + function init() { + window.addEventListener('message', onMessage, false); + window.devToolsExtension.notifyErrors(); } if (next) { console.warn('Please use \'window.devToolsExtension()\' instead of \'window.devToolsExtension\' as store enhancer. The latter will not be supported.'); return (reducer, initialState) => { - const store = configureStore(next)(reducer, initialState); - devToolsInit(); + store = configureStore(next, subscriber)(reducer, initialState); + init(); return store; }; } @@ -106,8 +102,8 @@ window.devToolsExtension = function(next) { return (reducer, initialState) => { if (!isAllowed(window.devToolsOptions)) return next(reducer, initialState); - const store = configureStore(next)(reducer, initialState); - devToolsInit(); + store = configureStore(next, subscriber)(reducer, initialState); + init(); return store; }; }; diff --git a/src/browser/extension/utils/updateState.js b/src/browser/extension/utils/updateState.js new file mode 100644 index 00000000..d60060d2 --- /dev/null +++ b/src/browser/extension/utils/updateState.js @@ -0,0 +1,26 @@ +import parseJSON from '../utils/parseJSON'; + +function recompute(previousLiftedState, storeState, action) { + let liftedState = { ...previousLiftedState }; + liftedState.stagedActionIds.push(liftedState.nextActionId); + liftedState.actionsById[liftedState.nextActionId] = action; + liftedState.nextActionId++; + liftedState.computedStates.push({ state: storeState }); + liftedState.currentStateIndex++; + return liftedState; +} + +export default function updateState(store, request) { + const payload = parseJSON(request.payload); + if (!payload) return null; + + if (request.type === 'ACTION') { + store.liftedStore.setState( + recompute(store.liftedStore.getState(), payload, request.action) + ); + } else if (request.type === 'STATE') { + store.liftedStore.setState(payload); + } + + return payload; +}