267 lines
8.2 KiB
JavaScript

define(function(require, exports, module) {
"use strict";
var oop = require("ace/lib/oop");
var EventEmitter = require("ace/lib/event_emitter").EventEmitter;
var DevtoolsProtocol = module.exports = function(socket) {
this.callbacks = {};
this.$scripts = {};
this.socket = socket;
};
(function() {
oop.implement(this, EventEmitter);
this.events = [
"changeRunning",
"break",
"exception",
"afterCompile"
];
this.$seq = 0;
this.$send = function(method, params, callback) {
this.$seq++;
if (callback)
this.callbacks[this.$seq] = callback;
this.ws.send({
id: this.$seq,
method: method,
params: params || undefined,
});
};
this.handleMessage = function(message) {
if (message.id) {
if (this.callbacks[message.id])
return this.callbacks[message.id](message.result, message.error);
} else {
var params = message.params;
switch (message.method) {
case "Debugger.scriptParsed":
this.$scripts[params.scriptId] = params;
this._signal("afterCompile", params);
break;
case "Runtime.executionContextCreated":
// TODO add support for threads
break;
case "Runtime.executionContextDestroyed":
this.detachDebugger();
break;
case "Debugger.resumed":
this.$callstack = null;
this._signal("changeRunning", params);
break;
case "Debugger.paused":
this.$callstack = params;
this._signal("changeRunning", params);
if (params.reason == "exception")
this._signal("exception", params);
else
this._signal("break", params);
break;
case "Runtime.consoleAPICalled":
break;
}
}
};
this.detachDebugger = function() {
if (this.ws)
this.ws.send({ $: "detach" });
}
this.attach = function(port, cb) {
this.ws = this.socket;
this.ws.on("message", this.handleMessage.bind(this));
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 });
this.$send("Runtime.runIfWaitingForDebugger");
cb();
};
this.detach = function() {
if (this.ws)
this.ws.close();
};
this.isRunning = function() {
return !this.$callstack;
};
this.stepInto = function(callback) {
this.$send("Debugger.stepInto", null, callback);
};
this.stepOver = function(callback) {
this.$send("Debugger.stepOver", null, callback);
};
this.stepOut = function(callback) {
this.$send("Debugger.stepOut", null, callback);
};
this.resume = function(callback) {
this.$send("Debugger.resume", null, callback);
};
this.suspend = function(callback) {
this.$send("Debugger.pause", null, callback);
};
this.backtrace = function(callback) {
callback(this.$callstack || []);
};
this.getProperties = function(params, callback) {
this.$send("Runtime.getProperties", params, callback);
};
this.scripts = function(callback) {
callback(this.$scripts);
};
this.getScriptSource = function(id, callback) {
this.$send("Debugger.getScriptSource", { scriptId: id }, callback);
};
this.evaluate = function(expression, frame, global, disableBreak, callback) {
if (frame) {
this.$send("Debugger.evaluateOnCallFrame", {
expression: expression,
callFrameId: frame.id,
objectGroup: "popover",
includeCommandLineAPI: false,
silent: true,
returnByValue: false,
generatePreview: false,
}, callback);
} else {
this.$send("Runtime.evaluate", {
expression: expression,
objectGroup: "console",
includeCommandLineAPI: true,
silent: false,
contextId: 1,
returnByValue: false,
generatePreview: true,
userGesture: true,
awaitPromise: false,
}, callback);
}
};
this.setexceptionbreak = function(state, callback) {
this.$send("Debugger.setPauseOnExceptions", {
state: state
}, callback);
};
this.setvariablevalue = function(variable, value, frame, callback) {
if (!variable.parent)
return;
this.evaluate("(" + value + ")", frame, null, true, function(data, err) {
if (err)
return callback(err);
if (variable.parent.index != null) {
return this.$send("Debugger.setVariableValue", {
scopeNumber: variable.parent.index,
variableName: variable.name,
newValue: data.result,
callFrameId: frame.id,
}, 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));
};
this.setbreakpoint = function(target, line, column, enabled, condition, callback) {
// lineNumber| columnNumber
// url | urlRegex | scriptHash
// condition
var breakpointId = target + ":" + line + ":" + column;
this.$send("Debugger.removeBreakpoint", {
breakpointId: breakpointId
}, function() {
if (!enabled) callback(null, {});
});
if (!enabled) return;
this.$send("Debugger.setBreakpointByUrl", {
lineNumber: line,
url: target,
// TODO add support for urlRegex:
columnNumber: column || 0,
condition: condition
}, callback);
};
this.clearbreakpoint = function(breakpointId, callback) {
this.$send("Debugger.removeBreakpoint", {
breakpointId: breakpointId
}, callback);
};
this.listbreakpoints = function(callback) {
callback({
breakpoints: []
});
};
this.changelive = function(scriptId, newSource, previewOnly, callback, $retry) {
this.$send("Debugger.setScriptSource", {
scriptId: scriptId,
scriptSource: newSource,
dryRun: !!previewOnly
}, function(result, error) {
if (error && error.code == -32000 && !$retry) {
// errors with code == -32000 usually go away after second try
return this.changelive(scriptId, newSource, previewOnly, callback, true);
}
if (result && result.stackChanged) {
return this.stepInto(function() {
callback({}, null);
});
}
callback(result, error);
}.bind(this));
};
this.restartframe = function(frameId, callback) {
this.$send("Debugger.restartFrame", {
callFrameId: frameId
}, callback);
};
// TODO add support for this in debugger
// this.disableBreak = function() {
// "Debugger.setBreakpointsActive"
// "Debugger.setSkipAllPauses"
// };
}).call(DevtoolsProtocol.prototype);
});