Skip to content

Commit 128d7b8

Browse files
committed
Merge pull request #72 from zalmoxisus/Pause/resume-the-data-transfer
The extension shouldn't affect the performance when no monitors are opened
2 parents dc45e73 + 6444a0b commit 128d7b8

File tree

8 files changed

+147
-71
lines changed

8 files changed

+147
-71
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"webpack": "^1.12.2"
5454
},
5555
"dependencies": {
56-
"crossmessaging": "^0.2.0",
56+
"crossmessaging": "^0.2.1",
5757
"jsan": "^3.1.1",
5858
"react": "^0.14.6",
5959
"react-dom": "^0.14.6",

src/browser/extension/background/contextMenus.js

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ const menus = [
77
{ id: 'devtools-panel', title: 'Open in a chrome panel (enable in Chrome settings)' },
88
{ id: 'devtools-remote', title: 'Open Remote DevTools' }
99
];
10-
let pageUrl;
11-
let pageTab;
1210

1311
let shortcuts = {};
1412
chrome.commands.getAll(commands => {
@@ -17,24 +15,12 @@ chrome.commands.getAll(commands => {
1715
});
1816
});
1917

20-
export default function createMenu(forUrl, tabId) {
21-
if (typeof tabId !== 'number') return; // It is an extension's background page
22-
chrome.pageAction.show(tabId);
23-
if (tabId === pageTab) return;
24-
25-
let url = forUrl;
26-
let hash = forUrl.indexOf('#');
27-
if (hash !== -1) url = forUrl.substr(0, hash);
28-
if (pageUrl === url) return;
29-
pageUrl = url; pageTab = tabId;
30-
chrome.contextMenus.removeAll();
31-
18+
export default function createMenu() {
3219
menus.forEach(({ id, title }) => {
3320
chrome.contextMenus.create({
3421
id: id,
3522
title: title + (shortcuts[id] ? ' (' + shortcuts[id] + ')' : ''),
3623
contexts: ['all'],
37-
documentUrlPatterns: [url],
3824
onclick: () => { openDevToolsWindow(id); }
3925
});
4026
});

src/browser/extension/background/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import createDevStore from 'remotedev-app/lib/store/createDevStore';
22
import openDevToolsWindow from './openWindow';
33
import { toContentScript } from './messaging';
4+
import createMenu from './contextMenus';
45

56
const store = createDevStore((action) => {
67
toContentScript(action);
@@ -12,3 +13,4 @@ window.store.liftedStore.instances = {};
1213
chrome.commands.onCommand.addListener(shortcut => {
1314
openDevToolsWindow(shortcut);
1415
});
16+
setTimeout(createMenu, 0);

src/browser/extension/background/messaging.js

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,55 @@
11
import { onConnect, onMessage, sendToTab } from 'crossmessaging';
22
import updateState from 'remotedev-app/lib/store/updateState';
33
import syncOptions from '../options/syncOptions';
4-
import createMenu from './contextMenus';
54
import openDevToolsWindow from './openWindow';
6-
let connections = {};
5+
let panelConnections = {};
6+
let tabConnections = {};
77
let catchedErrors = {};
8+
let unsubscribeList = {};
9+
let isMonitored = false;
810

911
window.syncOptions = syncOptions; // Used in the options page
1012

1113
const naMessage = { type: 'NA' };
1214

13-
// Connect to devpanel
14-
onConnect((tabId) => {
15-
if (tabId !== store.id) return naMessage;
16-
return {};
17-
}, {}, connections);
15+
function initPanel(msg, port) {
16+
monitorInstances(true);
17+
panelConnections[msg.tabId] = port;
18+
if (msg.tabId !== store.id) return naMessage;
19+
}
20+
21+
function getId(port) {
22+
return port.sender.tab ? port.sender.tab.id : port.sender.id;
23+
}
24+
25+
function initInstance(msg, port) {
26+
const id = getId(port);
27+
tabConnections[id] = port;
28+
store.liftedStore.instances[id] = msg.instance;
29+
store.id = id;
30+
if (typeof id === 'number') chrome.pageAction.show(id);
31+
if (isMonitored) return { type: 'START' };
32+
}
33+
34+
function disconnect(port) {
35+
if (!port.sender.tab && !port.id) {
36+
monitorInstances(false);
37+
return;
38+
}
39+
const id = getId(port);
40+
delete tabConnections[id];
41+
if (panelConnections[id]) panelConnections[id].postMessage(naMessage);
42+
if (window.store.liftedStore.instances[id]) {
43+
delete window.store.liftedStore.instances[id];
44+
window.store.liftedStore.deleteInstance(id);
45+
}
46+
}
47+
48+
onConnect(undefined, {
49+
INIT_PANEL: initPanel,
50+
INIT_INSTANCE: initInstance,
51+
RELAY: (msg, port) => { messaging(msg.message, port.sender); }
52+
}, panelConnections, disconnect);
1853

1954
function handleInstancesChanged(instance, name) {
2055
window.store.liftedStore.instances[instance] = name || instance;
@@ -24,15 +59,6 @@ function handleInstancesChanged(instance, name) {
2459
function messaging(request, sender, sendResponse) {
2560
const tabId = sender.tab ? sender.tab.id : sender.id;
2661
if (tabId) {
27-
if (request.type === 'PAGE_UNLOADED') {
28-
handleInstancesChanged(tabId, undefined, true);
29-
if (connections[tabId]) connections[tabId].postMessage(naMessage);
30-
if (window.store.liftedStore.instances[tabId]) {
31-
delete window.store.liftedStore.instances[tabId];
32-
window.store.liftedStore.deleteInstance(tabId);
33-
}
34-
return true;
35-
}
3662
if (request.type === 'GET_OPTIONS') {
3763
syncOptions.get(options => {
3864
sendResponse({options: options});
@@ -60,14 +86,9 @@ function messaging(request, sender, sendResponse) {
6086
const payload = updateState(store, request, handleInstancesChanged, store.liftedStore.instance);
6187
if (!payload) return true;
6288

63-
if (request.init) {
64-
store.id = tabId;
65-
createMenu(sender.url, tabId);
66-
}
67-
68-
// Relay the message to the devTools page
69-
if (tabId in connections) {
70-
connections[tabId].postMessage(request);
89+
// Relay the message to the devTools panel
90+
if (tabId in panelConnections) {
91+
panelConnections[tabId].postMessage(request);
7192
}
7293

7394
// Notify when errors occur in the app
@@ -107,9 +128,57 @@ export function toContentScript(action) {
107128
const message = { type: 'DISPATCH', action: action };
108129
let id = store.liftedStore.instance;
109130
if (!id || id === 'auto') id = store.id;
110-
if (id in connections) {
111-
connections[id].postMessage(message);
131+
if (id in panelConnections) {
132+
panelConnections[id].postMessage(message);
112133
} else {
113-
sendToTab(Number(id), message);
134+
tabConnections[id].postMessage(message);
114135
}
115136
}
137+
138+
function monitorInstances(shouldMonitor) {
139+
if (
140+
!shouldMonitor && Object.getOwnPropertyNames(unsubscribeList).length !== 0
141+
|| isMonitored === shouldMonitor
142+
) return;
143+
144+
Object.keys(tabConnections).forEach(id => {
145+
tabConnections[id].postMessage({ type: shouldMonitor ? 'START' : 'STOP' });
146+
});
147+
isMonitored = shouldMonitor;
148+
}
149+
150+
function getTab(cb) {
151+
chrome.tabs.query({
152+
active: true,
153+
windowId: chrome.windows.WINDOW_ID_CURRENT
154+
}, (tab) => {
155+
cb(tab[0].id);
156+
});
157+
}
158+
159+
const unsubscribeMonitor = (tabId) => () => {
160+
if (!unsubscribeList[tabId]) return;
161+
unsubscribeList[tabId]();
162+
delete unsubscribeList[tabId];
163+
if (Object.getOwnPropertyNames(panelConnections).length === 0) {
164+
monitorInstances(false);
165+
}
166+
};
167+
168+
// Expose store to extension's windows (monitors)
169+
window.getStore = (cb) => {
170+
monitorInstances(true);
171+
getTab((tabId) => {
172+
cb({
173+
...store,
174+
liftedStore: {
175+
...store.liftedStore,
176+
subscribe(...args) {
177+
const unsubscribe = store.liftedStore.subscribe(...args);
178+
unsubscribeList[tabId] = unsubscribe;
179+
return unsubscribe;
180+
}
181+
}
182+
}, unsubscribeMonitor(tabId));
183+
});
184+
};

src/browser/extension/devpanel/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ function init(id) {
5656
'source: \'redux-cs\'' +
5757
'}, \'*\');'
5858
);
59-
backgroundPageConnection.postMessage({ name: 'init', tabId: id });
59+
backgroundPageConnection.postMessage({ name: 'INIT_PANEL', tabId: id });
6060
}
6161

6262
if (chrome.devtools.inspectedWindow.tabId) {
Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
1-
import { onMessage, sendToBg } from 'crossmessaging';
21
import { getOptionsFromBg, isAllowed } from '../options/syncOptions';
2+
let bg;
33
let payload;
4-
let sendMessage;
54

65
if (!window.devToolsOptions) getOptionsFromBg();
76

8-
// Relay background script massages to the page script
9-
onMessage((message) => {
10-
if (message.action) {
11-
window.postMessage({
12-
type: 'DISPATCH',
13-
payload: message.action,
14-
source: 'redux-cs'
15-
}, '*');
7+
function connect(instance) {
8+
// Connect to the background script
9+
if (window.devToolsExtensionID) {
10+
bg = chrome.runtime.connect(window.devToolsExtensionID);
11+
} else {
12+
bg = chrome.runtime.connect();
1613
}
17-
});
14+
bg.postMessage({name: 'INIT_INSTANCE', instance});
1815

19-
if (window.devToolsExtensionID) { // Send external messages
20-
sendMessage = function(message) {
21-
chrome.runtime.sendMessage(window.devToolsExtensionID, message);
22-
};
23-
} else {
24-
sendMessage = sendToBg;
16+
// Relay background script massages to the page script
17+
bg.onMessage.addListener((message) => {
18+
if (message.action) {
19+
window.postMessage({
20+
type: 'DISPATCH',
21+
payload: message.action,
22+
source: 'redux-cs'
23+
}, '*');
24+
} else {
25+
window.postMessage({
26+
type: message.type,
27+
source: 'redux-cs'
28+
}, '*');
29+
}
30+
});
2531
}
2632

2733
// Resend messages from the page to the background script
@@ -32,7 +38,9 @@ window.addEventListener('message', function(event) {
3238
if (message.source !== 'redux-page') return;
3339
if (message.payload) payload = message.payload;
3440
try {
35-
sendMessage(message);
41+
if (message.type === 'INIT_INSTANCE') {
42+
connect(message.name);
43+
} else bg.postMessage({ name: 'RELAY', message });
3644
} catch (err) {
3745
if (process.env.NODE_ENV !== 'production') console.error('Failed to send message', err);
3846
}
@@ -42,6 +50,6 @@ if (typeof window.onbeforeunload !== 'undefined') {
4250
// Prevent adding beforeunload listener for Chrome apps
4351
window.onbeforeunload = function() {
4452
if (!isAllowed()) return;
45-
sendMessage({ type: 'PAGE_UNLOADED' });
53+
bg.postMessage({ type: 'PAGE_UNLOADED' });
4654
};
4755
}

src/browser/extension/inject/pageScript.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ window.devToolsExtension = function(config = {}) {
1010
let shouldInit = true;
1111
let lastAction;
1212
let errorOccurred = false;
13+
let isMonitored = false;
1314

1415
function stringify(obj) {
1516
return jsan.stringify(obj, function(key, value) {
@@ -19,7 +20,7 @@ window.devToolsExtension = function(config = {}) {
1920
}
2021

2122
function relaySerialized(message) {
22-
message.payload = stringify(message.payload);
23+
if (message.payload) message.payload = stringify(message.payload);
2324
if (message.action !== '') message.action = stringify(message.action);
2425
window.postMessage(message, '*');
2526
}
@@ -65,6 +66,11 @@ window.devToolsExtension = function(config = {}) {
6566
store.liftedStore.dispatch(message.payload);
6667
} else if (message.type === 'UPDATE') {
6768
relay('STATE', store.liftedStore.getState());
69+
} else if (message.type === 'START') {
70+
isMonitored = true;
71+
relay('STATE', store.liftedStore.getState());
72+
} else if (message.type === 'STOP') {
73+
isMonitored = false;
6874
}
6975
}
7076

@@ -97,7 +103,7 @@ window.devToolsExtension = function(config = {}) {
97103

98104
function init() {
99105
window.addEventListener('message', onMessage, false);
100-
relay('STATE', store.liftedStore.getState());
106+
relay('INIT_INSTANCE');
101107
notifyErrors(() => {
102108
errorOccurred = true;
103109
const state = store.liftedStore.getState();
@@ -110,14 +116,15 @@ window.devToolsExtension = function(config = {}) {
110116

111117
// Detect when the tab is reactivated
112118
document.addEventListener('visibilitychange', function() {
113-
if (document.visibilityState === 'visible') {
119+
if (document.visibilityState === 'visible' && isMonitored) {
114120
shouldInit = true;
115121
relay('STATE', store.liftedStore.getState());
116122
}
117123
}, false);
118124
}
119125

120126
function monitorReducer(state = {}, action) {
127+
if (!isMonitored) return state;
121128
lastAction = action.type;
122129
if (lastAction === '@@redux/INIT' && store.liftedStore) {
123130
// Send new lifted state on hot-reloading
@@ -129,6 +136,7 @@ window.devToolsExtension = function(config = {}) {
129136
}
130137

131138
function handleChange(state, liftedState) {
139+
if (!isMonitored) return;
132140
const nextActionId = liftedState.nextActionId;
133141
const liftedAction = liftedState.actionsById[nextActionId - 1];
134142
const action = liftedAction.action;

src/browser/extension/window/index.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import React from 'react';
22
import { render } from 'react-dom';
33
import DevTools from '../../../app/containers/DevTools';
44

5-
chrome.runtime.getBackgroundPage( background => {
6-
render(
7-
<DevTools store={background.store} />,
8-
document.getElementById('root')
9-
);
5+
chrome.runtime.getBackgroundPage(background => {
6+
background.getStore((store, unsubscribe) => {
7+
render(
8+
<DevTools store={store} />,
9+
document.getElementById('root')
10+
);
11+
addEventListener('unload', unsubscribe, true);
12+
});
1013
});

0 commit comments

Comments
 (0)