Skip to content

Commit 64d56f3

Browse files
franticfacebook-github-bot-7
authored andcommitted
Improve Chrome debugger
Summary: `debugger.html` contained a ton of hacky code that was needed to ensure we have a clean JS runtime every time a client RN app connects. That was needed because we used the page's global environment as runtime. Some time ago WebWorker support was added and now we run RN code inside an isolated WebWorker instance, and we can safely get rid of all these hacks. This has a bunch of nice side-effects: debug reload works faster, `console.log`s are preserved, `debuggerWorker.js` selection doesn't change. Made sure the debugging (breakpoints, etc.) still works as before. Small demo ![](http://g.recordit.co/FPdVHLHPUW.gif) Closes #5715 Reviewed By: svcscm Differential Revision: D2906602 Pulled By: frantic fb-gh-sync-id: 1a6ab9a5655d7c32ddd23619564e59c377b53a35
1 parent 4fd115f commit 64d56f3

File tree

4 files changed

+95
-92
lines changed

4 files changed

+95
-92
lines changed

Libraries/WebSocket/RCTWebSocketExecutor.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ - (void)setUp
5151
if (!_url) {
5252
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
5353
NSInteger port = [standardDefaults integerForKey:@"websocket-executor-port"] ?: 8081;
54-
NSString *URLString = [NSString stringWithFormat:@"http://localhost:%zd/debugger-proxy", port];
54+
NSString *URLString = [NSString stringWithFormat:@"http://localhost:%zd/debugger-proxy?role=client", port];
5555
_url = [RCTConvert NSURL:URLString];
5656
}
5757

ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public class DevServerHelper {
6262
"http://%s/launch-chrome-devtools";
6363
private static final String ONCHANGE_ENDPOINT_URL_FORMAT =
6464
"http://%s/onchange";
65-
private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy";
65+
private static final String WEBSOCKET_PROXY_URL_FORMAT = "ws://%s/debugger-proxy?role=client";
6666
private static final String PACKAGER_STATUS_URL_FORMAT = "http://%s/status";
6767

6868
private static final String PACKAGER_OK_STATUS = "packager-status:running";

local-cli/server/util/debugger.html

Lines changed: 51 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,83 +17,82 @@
1717
<script>
1818
(function() {
1919

20-
var sessionID = window.localStorage.getItem('sessionID');
21-
window.localStorage.removeItem('sessionID');
22-
23-
window.onbeforeunload = function() {
24-
if (sessionID) {
25-
return 'If you reload this page, it is going to break the debugging session. ' +
26-
'You should press ⌘R in simulator to reload.';
27-
}
28-
};
29-
30-
// Alias native implementations needed by the debugger before platform-specific
31-
// implementations are loaded into the global namespace
32-
var debuggerSetTimeout = window.setTimeout;
33-
var DebuggerWebSocket = window.WebSocket;
34-
3520
function setStatus(status) {
3621
document.getElementById('status').innerHTML = status;
3722
}
3823

39-
// This worker will run the application javascript code,
40-
// making sure that it's run in an environment without a global
41-
// document, to make it consistent with the JSC executor environment.
42-
var worker = new Worker('debuggerWorker.js');
24+
var INITIAL_MESSAGE = 'Waiting, press <span class="shortcut">⌘R</span> in simulator to reload and connect.';
4325

44-
var messageHandlers = {
45-
// This method is a bit hacky. Catalyst asks for a new clean JS runtime.
46-
// The easiest way to do this is to reload this page. That also means that
47-
// web socket connection will be lost. To send reply back we need to remember
48-
// message id.
49-
// This message also needs to be handled outside of the worker, since the worker
50-
// doesn't have access to local storage.
51-
'prepareJSRuntime': function(message) {
52-
window.onbeforeunload = undefined;
53-
window.localStorage.setItem('sessionID', message.id);
54-
window.location.reload();
26+
function connectToDebuggerProxy() {
27+
var worker;
28+
var ws = new WebSocket('ws://' + window.location.host + '/debugger-proxy?role=debugger&name=Chrome');
29+
30+
function createJSRuntime() {
31+
// This worker will run the application javascript code,
32+
// making sure that it's run in an environment without a global
33+
// document, to make it consistent with the JSC executor environment.
34+
worker = new Worker('debuggerWorker.js');
35+
worker.onmessage = function(message) {
36+
ws.send(JSON.stringify(message.data));
37+
};
38+
window.onbeforeunload = function() {
39+
return 'If you reload this page, it is going to break the debugging session. ' +
40+
'You should press ⌘R in simulator to reload.';
41+
};
5542
}
56-
};
5743

58-
function connectToDebuggerProxy() {
59-
var ws = new DebuggerWebSocket('ws://' + window.location.host + '/debugger-proxy');
44+
function shutdownJSRuntime() {
45+
if (worker) {
46+
worker.terminate();
47+
worker = null;
48+
window.onbeforeunload = null;
49+
}
50+
}
6051

6152
ws.onopen = function() {
62-
if (sessionID) {
63-
setStatus('Debugger session #' + sessionID + ' active.');
64-
ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)}));
65-
} else {
66-
setStatus('Waiting, press <span class="shortcut">⌘R</span> in simulator to reload and connect.');
67-
}
53+
setStatus(INITIAL_MESSAGE);
6854
};
6955

7056
ws.onmessage = function(message) {
57+
if (!message.data) {
58+
return;
59+
}
7160
var object = JSON.parse(message.data);
61+
62+
if (object.$event === 'client-disconnected') {
63+
shutdownJSRuntime();
64+
setStatus('Waiting, press <span class="shortcut">⌘R</span> in simulator to reload and connect.');
65+
return;
66+
}
67+
7268
if (!object.method) {
7369
return;
7470
}
7571

76-
var handler = messageHandlers[object.method];
77-
if (handler) {
78-
// If we have a local handler, use it.
79-
handler(object);
72+
// Special message that asks for a new JS runtime
73+
if (object.method === 'prepareJSRuntime') {
74+
shutdownJSRuntime();
75+
createJSRuntime();
76+
ws.send(JSON.stringify({replyID: object.id}));
77+
setStatus('Debugger session #' + object.id + ' active.');
78+
} else if (object.method === '$disconnected') {
79+
shutdownJSRuntime();
80+
setStatus(INITIAL_MESSAGE);
8081
} else {
8182
// Otherwise, pass through to the worker.
8283
worker.postMessage(object);
8384
}
8485
};
8586

86-
ws.onclose = function() {
87+
ws.onclose = function(e) {
88+
shutdownJSRuntime();
8789
setStatus('Disconnected from proxy. Attempting reconnection. Is node server running?');
88-
89-
sessionID = null;
90-
window.localStorage.removeItem('sessionID');
91-
debuggerSetTimeout(connectToDebuggerProxy, 100);
90+
if (e.reason) {
91+
setStatus(e.reason);
92+
console.warn(e.reason);
93+
}
94+
setTimeout(connectToDebuggerProxy, 500);
9295
};
93-
94-
worker.onmessage = function(message) {
95-
ws.send(JSON.stringify(message.data));
96-
}
9796
}
9897

9998
connectToDebuggerProxy();

local-cli/server/util/webSocketProxy.js

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,55 +15,59 @@ function attachToServer(server, path) {
1515
server: server,
1616
path: path
1717
});
18-
var clients = [];
18+
var debuggerSocket, clientSocket;
1919

20-
function sendSpecial(message) {
21-
clients.forEach(function (cn) {
22-
try {
23-
cn.send(JSON.stringify(message));
24-
} catch(e) {
25-
// Sometimes this call throws 'not opened'
26-
}
27-
});
20+
function send(dest, message) {
21+
if (!dest) {
22+
return;
23+
}
24+
25+
try {
26+
dest.send(message);
27+
} catch(e) {
28+
console.warn(e);
29+
// Sometimes this call throws 'not opened'
30+
}
2831
}
2932

3033
wss.on('connection', function(ws) {
31-
var id = Math.random().toString(15).slice(10, 20);
32-
sendSpecial({$open: id});
33-
clients.push(ws);
34-
35-
var allClientsExcept = function(ws) {
36-
return clients.filter(function(cn) { return cn !== ws; });
37-
};
34+
const {url} = ws.upgradeReq;
3835

39-
ws.onerror = function() {
40-
clients = allClientsExcept(ws);
41-
sendSpecial({$error: id});
42-
};
43-
44-
ws.onclose = function() {
45-
clients = allClientsExcept(ws);
46-
sendSpecial({$close: id});
47-
};
48-
49-
ws.on('message', function(message) {
50-
allClientsExcept(ws).forEach(function(cn) {
51-
try {
52-
cn.send(message);
53-
} catch(e) {
54-
// Sometimes this call throws 'not opened'
36+
if (url.indexOf('role=debugger') > -1) {
37+
if (debuggerSocket) {
38+
ws.close(1011, 'Another debugger is already connected');
39+
return;
40+
}
41+
debuggerSocket = ws;
42+
debuggerSocket.onerror =
43+
debuggerSocket.onclose = () => {
44+
debuggerSocket = null;
45+
if (clientSocket) {
46+
clientSocket.close(1011, 'Debugger was disconnected');
5547
}
56-
});
57-
});
48+
};
49+
debuggerSocket.onmessage = ({data}) => send(clientSocket, data);
50+
} else if (url.indexOf('role=client') > -1) {
51+
if (clientSocket) {
52+
clientSocket.onerror = clientSocket.onclose = clientSocket.onmessage = null;
53+
clientSocket.close(1011, 'Another client connected');
54+
}
55+
clientSocket = ws;
56+
clientSocket.onerror =
57+
clientSocket.onclose = () => {
58+
clientSocket = null;
59+
send(debuggerSocket, JSON.stringify({method: '$disconnected'}));
60+
};
61+
clientSocket.onmessage = ({data}) => send(debuggerSocket, data);
62+
} else {
63+
ws.close(1011, 'Missing role param');
64+
}
5865
});
5966

6067
return {
6168
server: wss,
6269
isChromeConnected: function() {
63-
return clients
64-
.map(function(ws) { return ws.upgradeReq.headers['user-agent']; })
65-
.filter(Boolean)
66-
.some(function(userAgent) { return userAgent.includes('Chrome'); });
70+
return !!debuggerSocket;
6771
}
6872
};
6973
}

0 commit comments

Comments
 (0)