From d07a60aa484de3fbf70aeb178563ddf74bc35437 Mon Sep 17 00:00:00 2001 From: nightwing Date: Tue, 6 Feb 2018 22:22:25 +0400 Subject: [PATCH] address review comments --- .../debuggers/chrome/Debugger.js | 178 ++++++++++++++ .../debuggers/chrome/DevtoolsProtocol.js | 42 ++-- .../debuggers/chrome/MessageReader.js | 2 +- .../chrome/chrome-debug-proxy-launcher.js | 12 +- .../debuggers/chrome/chrome-debug-proxy.js | 217 ++---------------- 5 files changed, 230 insertions(+), 221 deletions(-) create mode 100644 plugins/c9.ide.run.debug/debuggers/chrome/Debugger.js diff --git a/plugins/c9.ide.run.debug/debuggers/chrome/Debugger.js b/plugins/c9.ide.run.debug/debuggers/chrome/Debugger.js new file mode 100644 index 00000000..8b755d41 --- /dev/null +++ b/plugins/c9.ide.run.debug/debuggers/chrome/Debugger.js @@ -0,0 +1,178 @@ +var net = require("net"); +var WebSocket = require("ws/index"); +var MessageReader = require("./MessageReader"); +var EventEmitter = require("events").EventEmitter; + +var RETRY_INTERVAL = 300; +var MAX_RETRIES = 100; + + +function Debugger(options) { + var clients = this.clients = []; + + this.broadcast = function(message) { + if (typeof message !== "string") + message = JSON.stringify(message); + clients.forEach(function(c) { + c.write(message + "\0"); + }); + }; +} + +(function() { + this.__proto__ = EventEmitter.prototype; + + this.addClient = function(client) { + this.clients.push(client); + // client.send({$: 1}); + client.debugger = this; + }; + this.removeClient = function(client) { + var i = this.clients.indexOf(client); + if (i != -1) + this.clients.splice(i, 1); + client.debugger = null; + }; + this.handleMessage = function(message) { + if (this.ws) + this.ws.send(JSON.stringify(message)); + else if (this.v8Socket) + this.v8Socket.send(message); + else + console.error("recieved message when debugger is not ready", message); + }; + + this.connect = function(options) { + getDebuggerData(options.port, function(err, res) { + if (err) { + this.broadcast({ $: "error", message: err.message }); + this.disconnect(); + return console.log(err); + } + var tabs = res; + + if (!tabs) { + this.connectToV8(options); + return; + } + + if (tabs.length > 1) + console.log("connecting to first tab from " + tabs.length); + + if (tabs[0] && tabs[0].webSocketDebuggerUrl) { + this.connectToWebsocket(tabs[0].webSocketDebuggerUrl); + } + }.bind(this)); + }; + + this.connectToWebsocket = function(url) { + var broadcast = this.broadcast; + var self = this; + var ws = new WebSocket(url); + ws.on("open", function open() { + console.log("connected"); + broadcast({ $: "connected" }); + }); + ws.on("close", function close() { + console.log("disconnected"); + self.disconnect(); + }); + ws.on("message", function incoming(data) { + // console.log("<<" + data); + broadcast(data); + }); + ws.on("error", function(e) { + console.log("error", e); + broadcast({ $: "error", err: e }); + self.disconnect(); + }); + this.ws = ws; + }; + + this.connectToV8 = function(options) { + var broadcast = this.broadcast; + var self = this; + + var connection = net.connect(options.port, options.host); + connection.on("connect", function() { + console.log("netproxy connected to debugger"); + broadcast({ $: "connected", mode: "v8" }); + }); + connection.on("error", function(e) { + console.log("error in v8 connection", e); + self.disconnect(); + }); + connection.on("close", function(e) { + console.log("v8 connection closed", e); + self.disconnect(); + }); + new MessageReader(connection, function(response) { + broadcast(response.toString("utf8")); + }); + connection.send = function(msg) { + if (msg.arguments && !msg.arguments.maxStringLength) + msg.arguments.maxStringLength = 10000; + var data = new Buffer(JSON.stringify(msg)); + + connection.write(new Buffer("Content-Length:" + data.length + "\r\n\r\n")); + connection.write(data); + }; + this.v8Socket = connection; + }; + + this.disconnect = function() { + this.emit("disconnect"); + this.clients.forEach(function(client) { + client.end(); + }); + if (this.ws) + this.ws.close(); + if (this.v8Socket) + this.v8Socket.close(); + }; + +}).call(Debugger.prototype); + + +function getDebuggerData(port, callback, retries) { + console.log("Connecting to port", port, retries); + if (retries == null) retries = MAX_RETRIES; + request({ + host: "127.0.0.1", + port: port, + path: "/json/list", + }, function(err, res) { + if (err && retries > 0) { + return setTimeout(function() { + getDebuggerData(port, callback, retries - 1); + }, RETRY_INTERVAL); + } + console.log(res); + callback(err, res); + }); +} + +function request(options, callback) { + var socket = new net.Socket(); + new MessageReader(socket, function(response) { + console.log("Initial connection response:", response); + socket.end(); + if (response) { + try { + response = JSON.parse(response); + } catch (e) {} + } + callback(null, response); + }); + socket.on("error", function(e) { + console.log("Initial connection error", options, e); + socket.end(); + callback(e); + }); + socket.connect(options.port, options.host); + socket.on("connect", function() { + socket.write("GET " + options.path + " HTTP/1.1\r\nConnection: close\r\n\r\n"); + }); +} + +module.exports = Debugger; diff --git a/plugins/c9.ide.run.debug/debuggers/chrome/DevtoolsProtocol.js b/plugins/c9.ide.run.debug/debuggers/chrome/DevtoolsProtocol.js index c253745b..49b7ce0f 100644 --- a/plugins/c9.ide.run.debug/debuggers/chrome/DevtoolsProtocol.js +++ b/plugins/c9.ide.run.debug/debuggers/chrome/DevtoolsProtocol.js @@ -84,6 +84,7 @@ var DevtoolsProtocol = module.exports = function(socket) { this.$send("Profiler.enable"); this.$send("Runtime.enable"); this.$send("Debugger.enable"); + // TODO add support for these to debugger ui // this.$send("Debugger.setPauseOnExceptions", {"state":"uncaught"}); // this.$send("Debugger.setBlackboxPatterns", {"patterns":[]}); this.$send("Debugger.setAsyncCallStackDepth", { maxDepth: 32 }); @@ -172,7 +173,7 @@ var DevtoolsProtocol = module.exports = function(socket) { if (err) return callback(err); if (variable.parent.index != null) { - this.$send("Debugger.setVariableValue", { + return this.$send("Debugger.setVariableValue", { scopeNumber: variable.parent.index, variableName: variable.name, newValue: data.result, @@ -181,19 +182,18 @@ var DevtoolsProtocol = module.exports = function(socket) { callback(err); }); } - else { - this.$send("Runtime.callFunctionOn", { - "objectId": variable.parent.ref || variable.parent.id, - "functionDeclaration": "function(a, b) { this[a] = b; }", - "arguments": [ - { "value": variable.name }, - data.result - ], - "silent": true - }, function(data, err) { - callback(err); - }); - } + + this.$send("Runtime.callFunctionOn", { + "objectId": variable.parent.ref || variable.parent.id, + "functionDeclaration": "function(a, b) { this[a] = b; }", + "arguments": [ + { "value": variable.name }, + data.result + ], + "silent": true + }, function(data, err) { + callback(err); + }); }.bind(this)); }; @@ -213,12 +213,10 @@ var DevtoolsProtocol = module.exports = function(socket) { this.$send("Debugger.setBreakpointByUrl", { lineNumber: line, url: target, - // urlRegex: + // TODO add support for urlRegex: columnNumber: column || 0, condition: condition - }, function(info) { - callback(info); - }); + }, callback); }; this.clearbreakpoint = function(breakpointId, callback) { @@ -235,22 +233,22 @@ var DevtoolsProtocol = module.exports = function(socket) { this.changelive = function(scriptId, newSource, previewOnly, callback, $retry) { - var that = this; that.$send("Debugger.setScriptSource", { scriptId: scriptId, scriptSource: newSource, dryRun: !!previewOnly }, function(result, error) { if (error && error.code == -32000 && !$retry) { - return that.changelive(scriptId, newSource, previewOnly, callback, true); + // errors with code == -32000 usually go away after second try + return this.changelive(scriptId, newSource, previewOnly, callback, true); } if (result && result.stackChanged) { - return that.stepInto(function() { + return this.stepInto(function() { callback({}, null); }); } callback(result, error); - }); + }.bind(this)); }; this.restartframe = function(frameId, callback) { diff --git a/plugins/c9.ide.run.debug/debuggers/chrome/MessageReader.js b/plugins/c9.ide.run.debug/debuggers/chrome/MessageReader.js index 4ace15e4..00d9d7b5 100644 --- a/plugins/c9.ide.run.debug/debuggers/chrome/MessageReader.js +++ b/plugins/c9.ide.run.debug/debuggers/chrome/MessageReader.js @@ -1,4 +1,4 @@ - +// TODO use Buffer instead of this function readBytes(str, start, bytes) { // returns the byte length of an utf8 string var consumed = 0; diff --git a/plugins/c9.ide.run.debug/debuggers/chrome/chrome-debug-proxy-launcher.js b/plugins/c9.ide.run.debug/debuggers/chrome/chrome-debug-proxy-launcher.js index c46a95e9..e2be4829 100644 --- a/plugins/c9.ide.run.debug/debuggers/chrome/chrome-debug-proxy-launcher.js +++ b/plugins/c9.ide.run.debug/debuggers/chrome/chrome-debug-proxy-launcher.js @@ -49,9 +49,10 @@ define(function(require, exports, module) { code: code.replace("{EXTPATH}", extPath), redefine: true }, function(err, remote) { - if (err) console.log(err); - if (remote && remote.api && remote.api.launch) { - require(["text!" + debugProxy.srcUrl], function(code) { + if (err) console.error("Error loading vfs extension", err); + // try connecting after error, in case there is proxy already launched + if (remote && remote.api.launch) { + return require(["text!" + debugProxy.srcUrl], function(code) { fs.writeFile(util.normalizePath(extPath), code, function() { remote.api.launch(function() { tryConnect(30); @@ -59,9 +60,8 @@ define(function(require, exports, module) { }); }); } - else { - tryConnect(30); - } + + tryConnect(30); }); function tryConnect(retries) { diff --git a/plugins/c9.ide.run.debug/debuggers/chrome/chrome-debug-proxy.js b/plugins/c9.ide.run.debug/debuggers/chrome/chrome-debug-proxy.js index f8d5ac35..367cf564 100644 --- a/plugins/c9.ide.run.debug/debuggers/chrome/chrome-debug-proxy.js +++ b/plugins/c9.ide.run.debug/debuggers/chrome/chrome-debug-proxy.js @@ -14,16 +14,16 @@ node-process1 node-process2 ... debuggers var fs = require("fs"); var net = require("net"); -var WebSocket = require("ws/index"); +var Debugger = require("./Debugger"); var MessageReader = require("./MessageReader"); -var EventEmitter = require("events").EventEmitter; var startT = Date.now(); +var IS_WINDOWS = process.platform == "win32"; /*** connect to cloud9 ***/ var socketPath = process.env.HOME + "/.c9/chrome.sock"; -if (process.platform == "win32") +if (IS_WINDOWS) socketPath = "\\\\.\\pipe\\" + socketPath.replace(/\//g, "\\"); console.log("Using socket", socketPath); @@ -44,18 +44,20 @@ function checkServer(id) { }); client.on("error", function(err) { - if (!id && err && (err.code === "ECONNREFUSED" || err.code === "ENOENT" || err.code === "EAGAIN")) { - createServer(); - } - else { - process.exit(1); + if (!id && err) { + var code = err.code; + if (code == "ECONNREFUSED" || code === "ENOENT" || code === "EAGAIN") + return createServer(); } + + process.exit(1); }); } var $id = 0; var server; var ideClients = {}; +var debuggers = {}; function createServer() { server = net.createServer(function(client) { var isClosed = false; @@ -125,28 +127,32 @@ function createServer() { client.send({ ping: process.pid }); }); - server.on("error", function(e) { - console.log("server error", e); - process.exit(1); + server.on("error", function(err) { + console.error("server error", err); + throw err; }); server.on("close", function (e) { console.log("server closed", e); process.exit(1); }); - if (process.platform !== "win32") { - try { - fs.unlinkSync(socketPath); - } catch (e) { - if (e.code != "ENOENT") - console.log(e); - } - } + + removeStaleSocket(); server.listen(socketPath, function() { console.log("server listening on ", socketPath); checkServer(process.pid); }); } +function removeStaleSocket() { + if (IS_WINDOWS) + return; + try { + fs.unlinkSync(socketPath); + } catch (e) { + if (e.code != "ENOENT") + console.error(e); + } +} var actions = { exit: function(message, client) { @@ -175,179 +181,6 @@ var actions = { }, }; -/*** connect to node ***/ - -function Debugger(options) { - var clients = this.clients = []; - - this.broadcast = function(message) { - if (typeof message !== "string") - message = JSON.stringify(message); - clients.forEach(function(c) { - c.write(message + "\0"); - }); - }; -} - -(function() { - this.__proto__ = EventEmitter.prototype; - - this.addClient = function(client) { - this.clients.push(client); - // client.send({$: 1}); - client.debugger = this; - }; - this.removeClient = function(client) { - var i = this.clients.indexOf(client); - if (i != -1) - this.clients.splice(i, 1); - client.debugger = null; - }; - this.handleMessage = function(message) { - if (this.ws) - this.ws.send(JSON.stringify(message)); - else if (this.v8Socket) - this.v8Socket.send(message); - else - console.error("recieved message when debugger is not ready", message); - }; - - this.connect = function(options) { - getDebuggerData(options.port, function(err, res) { - if (err) { - this.broadcast({ $: "error", message: err.message }); - this.emit("disconnect"); - return console.log(err); - } - var tabs = res; - - if (!tabs) { - this.connectToV8(options); - return; - } - - if (tabs.length > 1) - console.log("connecting to first tab from " + tabs.length); - - if (tabs[0] && tabs[0].webSocketDebuggerUrl) { - this.connectToWebsocket(tabs[0].webSocketDebuggerUrl); - } - }.bind(this)); - }; - - this.connectToWebsocket = function(url) { - var broadcast = this.broadcast; - var self = this; - var ws = new WebSocket(url); - ws.on("open", function open() { - console.log("connected"); - broadcast({ $: "connected" }); - }); - ws.on("close", function close() { - console.log("disconnected"); - self.emit("disconnect"); - }); - ws.on("message", function incoming(data) { - // console.log("<<" + data); - broadcast(data); - }); - ws.on("error", function(e) { - console.log("error", e); - broadcast({ $: "error", err: e }); - self.emit("disconnect"); - }); - this.ws = ws; - }; - - this.connectToV8 = function(options) { - var broadcast = this.broadcast; - var self = this; - - var connection = net.connect(options.port, options.host); - connection.on("connect", function() { - console.log("netproxy connected to debugger"); - broadcast({ $: "connected", mode: "v8" }); - }); - connection.on("error", function(e) { - console.log("error in v8 connection", e); - self.emit("disconnect"); - }); - connection.on("close", function(e) { - console.log("v8 connection closed", e); - self.emit("disconnect"); - }); - new MessageReader(connection, function(response) { - broadcast(response.toString("utf8")); - }); - connection.send = function(msg) { - if (msg.arguments && !msg.arguments.maxStringLength) - msg.arguments.maxStringLength = 10000; - var data = new Buffer(JSON.stringify(msg)); - - connection.write(new Buffer("Content-Length:" + data.length + "\r\n\r\n")); - connection.write(data); - }; - this.v8Socket = connection; - }; - - this.disconnect = function() { - this.clients.forEach(function(client) { - client.end(); - }); - if (this.ws) - this.ws.close(); - if (this.v8Socket) - this.v8Socket.close(); - }; - -}).call(Debugger.prototype); - - -var RETRY_INTERVAL = 300; -var MAX_RETRIES = 100; -var debuggers = {}; - - -function getDebuggerData(port, callback, retries) { - console.log("Connecting to port", port, retries); - if (retries == null) retries = MAX_RETRIES; - request({ - host: "127.0.0.1", - port: port, - path: "/json/list", - }, function(err, res) { - if (err && retries > 0) { - return setTimeout(function() { - getDebuggerData(port, callback, retries - 1); - }, RETRY_INTERVAL); - } - console.log(res); - callback(err, res); - }); -} - -function request(options, callback) { - var socket = new net.Socket(); - new MessageReader(socket, function(response) { - console.log("Initial connection response:", response); - socket.end(); - if (response) { - try { - response = JSON.parse(response); - } catch (e) {} - } - callback(null, response); - }); - socket.on("error", function(e) { - console.log("Initial connection error", options, e); - socket.end(); - callback(e); - }); - socket.connect(options.port, options.host); - socket.on("connect", function() { - socket.write("GET " + options.path + " HTTP/1.1\r\nConnection: close\r\n\r\n"); - }); -} /*** =============== ***/ var idle = 0;