kopia lustrzana https://github.com/c9/core
1133 wiersze
44 KiB
JavaScript
1133 wiersze
44 KiB
JavaScript
define(function(require, exports, module) {
|
|
main.consumes = ["Plugin", "debugger", "util", "c9"];
|
|
main.provides = ["chromedebugger"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var Plugin = imports.Plugin;
|
|
var util = imports.util;
|
|
var debug = imports["debugger"];
|
|
var c9 = imports.c9;
|
|
var async = require("async");
|
|
|
|
var Frame = debug.Frame;
|
|
var Source = debug.Source;
|
|
var Breakpoint = debug.Breakpoint;
|
|
var Variable = debug.Variable;
|
|
var Scope = debug.Scope;
|
|
|
|
var DevtoolsProtocol = require("./DevtoolsProtocol");
|
|
|
|
/***** Initialization *****/
|
|
|
|
var plugin = new Plugin("Ajax.org", main.consumes);
|
|
var emit = plugin.getEmitter();
|
|
emit.setMaxListeners(1000);
|
|
|
|
var stripPrefix = c9.toInternalPath((options.basePath || "").replace(/[\/\\]$/, ""));
|
|
var breakOnExceptions = false;
|
|
var breakOnUncaughtExceptions = false;
|
|
|
|
var NODE_PREFIX = "(function (exports, require, module, __filename, __dirname) { ";
|
|
var NODE_POSTFIX = "\n});";
|
|
|
|
var RE_NODE_PREFIX = new RegExp("^" + util.escapeRegExp(NODE_PREFIX));
|
|
var RE_NODE_POSTFIX = new RegExp(util.escapeRegExp(NODE_POSTFIX) + "$");
|
|
|
|
var TYPE = "chrome";
|
|
|
|
var attached = false;
|
|
var v8dbg, state, activeFrame, sources, socket, pathMap;
|
|
|
|
var hasChildren = {
|
|
"regexp": 32,
|
|
"error": 16,
|
|
"object": 8,
|
|
"function": 4
|
|
};
|
|
|
|
/***** Helper Functions *****/
|
|
|
|
/**
|
|
* Syncs the debug state to the client
|
|
*/
|
|
function sync(breakpoints, reconnect, callback) {
|
|
if (!v8dbg)
|
|
return console.error("Sync called without v8dbg");
|
|
getSources(function(err, sources) {
|
|
if (err) return callback(err);
|
|
|
|
updateBreakpoints(breakpoints, reconnect, function(err, breakpoints) {
|
|
if (err) return callback(err);
|
|
|
|
handleDebugBreak(breakpoints, reconnect, null, function(canAttach) {
|
|
attached = canAttach;
|
|
emit("attach", { breakpoints: breakpoints });
|
|
},
|
|
function(isResumed) {
|
|
callback();
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function updateBreakpoints(breakpoints, reconnect, callback) {
|
|
function find(bp) {
|
|
for (var i = 0, l = breakpoints.length; i < l; i++) {
|
|
if (breakpoints[i].equals(bp))
|
|
return breakpoints[i];
|
|
}
|
|
}
|
|
|
|
var list = breakpoints.slice(0);
|
|
if (list.length == 0) {
|
|
// node v9.5 segfaults when there are no breakpoints
|
|
list.push({
|
|
path: "_c9_node_segfault_workaround_",
|
|
line: 100,
|
|
enabled: true,
|
|
});
|
|
}
|
|
|
|
listBreakpoints(function handleBps(err, remoteBreakpoints) {
|
|
if (err) return callback(err);
|
|
|
|
var found = [];
|
|
var notfound = [];
|
|
|
|
remoteBreakpoints.forEach(function(rbp) {
|
|
var bp;
|
|
if ((bp = find(rbp))) {
|
|
if (rbp.enabled == bp.enabled)
|
|
found.push(bp);
|
|
}
|
|
else
|
|
notfound.push(rbp);
|
|
});
|
|
|
|
async.each(list, function(bp, next) {
|
|
if (found.indexOf(bp) == -1)
|
|
setBreakpoint(bp, next);
|
|
else
|
|
next();
|
|
}, done);
|
|
|
|
function done() {
|
|
notfound.forEach(function(bp) {
|
|
bp.serverOnly = true;
|
|
list.push(bp);
|
|
});
|
|
|
|
list.sort(function(a, b) {
|
|
if (!a.id && !b.id) return 0;
|
|
if (!a.id && b.id) return 1;
|
|
if (a.id && !b.id) return -1;
|
|
return a.id - b.id;
|
|
});
|
|
|
|
callback(null, list);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Detects a break on a frame or a known breakpoint, otherwise resumes
|
|
*/
|
|
function handleDebugBreak(breakpoints, reconnect, frame, attach, callback) {
|
|
if (!v8dbg) {
|
|
console.error("No debugger is set");
|
|
attach();
|
|
return callback();
|
|
}
|
|
|
|
wait();
|
|
reconnect = true;
|
|
|
|
function wait() {
|
|
// Check if there is a real breakpoint here, so we don't resume
|
|
function resumeIfNeeded(callback) {
|
|
if (!activeFrame) {
|
|
return v8dbg.$waitForBreak = function(frame) {
|
|
activeFrame = frame;
|
|
v8dbg.$waitForBreak = null;
|
|
resumeIfNeeded(function() {
|
|
callback();
|
|
});
|
|
return true;
|
|
};
|
|
}
|
|
var onBreakpoint = breakpoints.some(function(bp) {
|
|
return bp.enabled && activeFrame.path === bp.path && bp.line == activeFrame.line;
|
|
});
|
|
if (!onBreakpoint) {
|
|
resume(callback);
|
|
} else {
|
|
onChangeRunning();
|
|
onBreak({frame: activeFrame});
|
|
}
|
|
}
|
|
|
|
// Resume the process
|
|
if (reconnect) {
|
|
onChangeFrame(frame);
|
|
attach(true);
|
|
callback(false);
|
|
}
|
|
else {
|
|
// onChangeFrame(null);
|
|
attach(true);
|
|
resumeIfNeeded(callback.bind(this, true));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes the path prefix from a string
|
|
*/
|
|
function strip(str) {
|
|
if (!str) return "";
|
|
str = c9.toInternalPath(str);
|
|
str = applyPathMap(str, "toInternal");
|
|
return str && str.lastIndexOf(stripPrefix, 0) === 0
|
|
? util.normalizePath(str.slice(stripPrefix.length))
|
|
: util.normalizePath(str || "");
|
|
}
|
|
|
|
/**
|
|
* Returns the unique id of a frame
|
|
*/
|
|
function getFrameId(frame) {
|
|
return frame.func.name + ":" + frame.func.inferredName
|
|
+ ":" + frame.func.scriptId + ":"
|
|
+ (frame.received && frame.received.ref || "")
|
|
+ frame.arguments.map(function(a) {return a.value.ref;}).join("-");
|
|
|
|
//return (frame.func.name || frame.func.inferredName || (frame.line + frame.position));
|
|
}
|
|
|
|
function formatType(value) {
|
|
if (value.description)
|
|
return value.description;
|
|
switch (value.type) {
|
|
case "undefined":
|
|
case "null":
|
|
return value.type;
|
|
|
|
case "error":
|
|
return value.value || "[Error]";
|
|
|
|
case "regexp":
|
|
return value.text;
|
|
|
|
case "boolean":
|
|
case "number":
|
|
return value.value + "";
|
|
|
|
case "string":
|
|
return JSON.stringify(value.value);
|
|
|
|
case "object":
|
|
// text: "#<Student>"
|
|
var name = value.className || (value.text
|
|
? value.text.replace(/#<(.*)>/, "$1")
|
|
: "Object");
|
|
return "[" + name + "]";
|
|
|
|
case "function":
|
|
return "function " + value.inferredName + "()";
|
|
|
|
default:
|
|
return value.type;
|
|
}
|
|
}
|
|
|
|
function isTruthy(variable) {
|
|
if ("undefined|null".indexOf(variable.type) > -1)
|
|
return false;
|
|
if ("false|NaN|\"\"".indexOf(variable.value) > -1)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
function getPathFromScriptId(scriptId) {
|
|
for (var i = 0; i < sources.length; i++) {
|
|
if (sources[i].id == scriptId)
|
|
return sources[i].path;
|
|
}
|
|
}
|
|
|
|
function getScriptIdFromPath(path) {
|
|
for (var i = 0; i < sources.length; i++) {
|
|
if (sources[i].path == path)
|
|
return sources[i].id;
|
|
}
|
|
}
|
|
|
|
function getLocalScriptPath(script) {
|
|
var scriptName = script.url || ("-anonymous-" + script.scriptId);
|
|
scriptName = c9.toExternalPath(scriptName);
|
|
scriptName = strip(scriptName);
|
|
return scriptName;
|
|
}
|
|
|
|
function createFrame(frameData, index) {
|
|
var options = frameData[index];
|
|
var frame = options._frame || new Frame({
|
|
istop: index == 0,
|
|
index: index,
|
|
name: options.functionName || "(anonymous)",
|
|
line: options.location.lineNumber,
|
|
column: options.location.columnNumber,
|
|
id: options.callFrameId,
|
|
script: options.location.scriptId,
|
|
path: getLocalScriptPath(v8dbg.$scripts[options.location.scriptId]),
|
|
sourceId: options.location.scriptId
|
|
});
|
|
options._frame = frame;
|
|
|
|
var vars = [];
|
|
|
|
frame.variables = frame.variables || vars;
|
|
|
|
/*
|
|
0: Global
|
|
1: Local
|
|
2: With
|
|
3: Closure
|
|
4: Catch >,
|
|
if (scope.type > 1) {*/
|
|
|
|
frame.scopes = options.scopeChain.map(function(scope, i) {
|
|
return new Scope({
|
|
id: scope.object.objectId,
|
|
type: scope.type,
|
|
frame: frame,
|
|
index: i
|
|
});
|
|
});
|
|
if (frame.scopes[0])
|
|
frame.scopes[0].isOpen = true;
|
|
|
|
return frame;
|
|
}
|
|
|
|
function createVariable(options, name, scope, variable) {
|
|
var value = options.value || options;
|
|
|
|
if (!variable) {
|
|
if (value.subtype == "null")
|
|
value.type = value.subtype;
|
|
variable = new Variable({
|
|
name: name || options.name,
|
|
scope: scope,
|
|
value: formatType(value),
|
|
type: value.type,
|
|
ref: value.objectId,
|
|
children: options.children === false
|
|
? false : (hasChildren[value.type] ? true : false),
|
|
options: options,
|
|
});
|
|
}
|
|
return variable;
|
|
}
|
|
|
|
function createSource(options) {
|
|
var path = getLocalScriptPath(options);
|
|
// isLiveEdit: false,
|
|
// sourceMapURL: "",
|
|
// hasSourceURL: false,
|
|
// isModule: false,
|
|
return new Source({
|
|
id: options.scriptId,
|
|
name: options.url || "anonymous",
|
|
path: path,
|
|
text: strip(options.text || "anonymous"),
|
|
debug: path.charAt(0) != "/" || path.match(/ \(old\)$/) ? true : false,
|
|
lineOffset: options.lineOffset,
|
|
customSyntax: "javascript",
|
|
threadId: options.executionContextId,
|
|
});
|
|
}
|
|
|
|
function createBreakpoint(options, serverOnly) {
|
|
return new Breakpoint({
|
|
id: options.number,
|
|
path: getPathFromScriptId(options.script_id),
|
|
line: options.line,
|
|
column: options.column,
|
|
condition: options.condition,
|
|
enabled: options.active,
|
|
ignoreCount: options.ignoreCount,
|
|
serverOnly: serverOnly || false
|
|
});
|
|
}
|
|
|
|
/***** Event Handler *****/
|
|
|
|
function onChangeRunning(e) {
|
|
if (!v8dbg) {
|
|
state = null;
|
|
} else {
|
|
state = v8dbg.isRunning() ? "running" : "stopped";
|
|
}
|
|
|
|
if (attached) {
|
|
emit("stateChange", { state: state });
|
|
|
|
if (state != "stopped")
|
|
onChangeFrame(null);
|
|
}
|
|
}
|
|
|
|
function onBreak(e) {
|
|
var frame = e.frame || createFrame(e.callFrames, 0);
|
|
if (v8dbg.$waitForBreak && v8dbg.$waitForBreak(frame)) {
|
|
return;
|
|
}
|
|
onChangeFrame(frame, !attached);
|
|
if (attached)
|
|
emit("break", { frame: frame });
|
|
}
|
|
|
|
function onException(e) {
|
|
var frame = createFrame(e.callFrames, 0);
|
|
|
|
var options = e.data;
|
|
var m = /^(\w+):(.*)$/m.exec(options.description);
|
|
var name = m && m[1] || options.className;
|
|
var value = m && m[2] || options.description;
|
|
|
|
options.name = name;
|
|
options.value = {
|
|
value: value,
|
|
type: "error",
|
|
objectId: options.objectId,
|
|
};
|
|
options.children = true;
|
|
|
|
var variable = createVariable(options);
|
|
variable.error = true;
|
|
|
|
emit("exception", {
|
|
frame: frame,
|
|
exception: variable
|
|
});
|
|
}
|
|
|
|
function onAfterCompile(e) {
|
|
emit("sourcesCompile", { source: createSource(e) });
|
|
}
|
|
|
|
function onChangeFrame(frame, silent) {
|
|
activeFrame = frame;
|
|
if (!silent)
|
|
emit("frameActivate", { frame: frame });
|
|
}
|
|
|
|
/***** Methods *****/
|
|
|
|
function getProxySource(_process) {
|
|
return false;
|
|
}
|
|
|
|
function attach(s, reconnect, callback) {
|
|
if (v8dbg)
|
|
v8dbg.detach();
|
|
|
|
socket = s;
|
|
|
|
// socket.connect();
|
|
|
|
socket.on("back", function(err) {
|
|
sync(emit("getBreakpoints"), true, callback);
|
|
}, plugin);
|
|
socket.on("error", function(err) {
|
|
emit("error", err);
|
|
}, plugin);
|
|
|
|
v8dbg = new DevtoolsProtocol(socket);
|
|
attached = false;
|
|
v8dbg.attach(s, function(err, msg) {
|
|
if (err) return callback(err);
|
|
|
|
// register event listeners
|
|
v8dbg.on("changeRunning", onChangeRunning);
|
|
v8dbg.on("break", onBreak);
|
|
v8dbg.on("exception", onException);
|
|
v8dbg.on("afterCompile", onAfterCompile);
|
|
|
|
onChangeFrame(null);
|
|
|
|
if (msg && msg.type == "connect")
|
|
reconnect = false;
|
|
// This fixes reconnecting. I dont understand why, but without
|
|
// this timeout during reconnect the getSources() call never
|
|
// returns
|
|
setTimeout(function() {
|
|
sync(emit("getBreakpoints"), reconnect, callback);
|
|
});
|
|
});
|
|
}
|
|
|
|
function detach() {
|
|
onChangeFrame(null);
|
|
onChangeRunning();
|
|
|
|
if (v8dbg) {
|
|
v8dbg.detach();
|
|
// on detach remove all event listeners
|
|
v8dbg.off("changeRunning", onChangeRunning);
|
|
v8dbg.off("break", onBreak);
|
|
v8dbg.off("exception", onException);
|
|
v8dbg.off("afterCompile", onAfterCompile);
|
|
}
|
|
|
|
socket = null;
|
|
v8dbg = null;
|
|
attached = false;
|
|
|
|
emit("detach");
|
|
}
|
|
|
|
function getSources(callback) {
|
|
v8dbg.scripts(function(scriptMap) {
|
|
sources = [];
|
|
for (var i in scriptMap) {
|
|
var script = scriptMap[i];
|
|
if ((script.name || "").indexOf("chrome-extension://") === 0)
|
|
continue;
|
|
sources.push(createSource(script));
|
|
}
|
|
callback(null, sources);
|
|
|
|
emit("sources", { sources: sources });
|
|
});
|
|
}
|
|
|
|
function getSource(source, callback) {
|
|
v8dbg.getScriptSource(source.id, function(script) {
|
|
if (!script || !script.scriptSource)
|
|
return callback(new Error("File not found : " + source.path));
|
|
|
|
var source = script.scriptSource
|
|
.replace(RE_NODE_PREFIX, "")
|
|
.replace(RE_NODE_POSTFIX, "");
|
|
|
|
callback(null, source);
|
|
});
|
|
}
|
|
|
|
function getFrames(callback, silent) {
|
|
v8dbg.backtrace(function(data) {
|
|
var frames = data.callFrames.map(function(frameData, index) {
|
|
return createFrame(data.callFrames, index);
|
|
});
|
|
|
|
emit("getFrames", { frames: frames });
|
|
callback(null, frames);
|
|
});
|
|
}
|
|
|
|
function getScope(frame, scope, callback) {
|
|
v8dbg.getProperties({
|
|
objectId: scope.id,
|
|
ownProperties: false,
|
|
accessorPropertiesOnly: false,
|
|
generatePreview: true
|
|
}, function(body, error) {
|
|
if (error)
|
|
return callback(error);
|
|
|
|
var variables = (body.result || []).map(function(prop) {
|
|
return createVariable(prop);
|
|
});
|
|
|
|
scope.variables = variables;
|
|
|
|
callback(null, variables, scope, frame);
|
|
});
|
|
}
|
|
|
|
function getProperties(variable, callback) {
|
|
if (!variable.ref)
|
|
return callback(null, []);
|
|
v8dbg.getProperties({
|
|
objectId: variable.ref,
|
|
ownProperties: true,
|
|
accessorPropertiesOnly: false,
|
|
generatePreview: true
|
|
}, function(body, error) {
|
|
if (error)
|
|
return callback(error);
|
|
|
|
var properties = (body.result || []).map(function(prop) {
|
|
return createVariable(prop);
|
|
});
|
|
|
|
variable.properties = properties;
|
|
|
|
callback(null, properties);
|
|
});
|
|
}
|
|
|
|
function stepInto(callback) {
|
|
v8dbg.stepInto(callback);
|
|
}
|
|
|
|
function stepOver(callback) {
|
|
v8dbg.stepOver(callback);
|
|
}
|
|
|
|
function stepOut(callback) {
|
|
v8dbg.stepOut(callback);
|
|
}
|
|
|
|
function resume(callback) {
|
|
v8dbg.resume(callback);
|
|
}
|
|
|
|
function suspend(callback) {
|
|
v8dbg.suspend(function() {
|
|
emit("suspend");
|
|
callback && callback();
|
|
});
|
|
}
|
|
|
|
function setScriptSource(script, newSource, previewOnly, callback) {
|
|
newSource = NODE_PREFIX + newSource.replace(/^#!.*/, "") + NODE_POSTFIX;
|
|
|
|
v8dbg.changelive(script.id, newSource, previewOnly, function(data, error) {
|
|
var errorMessage = "Debugger could not update source of saved file."
|
|
if (error)
|
|
return callback(new Error(errorMessage));
|
|
if (data && data.exceptionDetails)
|
|
return callback(new Error(errorMessage + " " + data.exceptionDetails.text));
|
|
|
|
emit("setScriptSource", data);
|
|
callback(null, data);
|
|
});
|
|
}
|
|
|
|
function restartFrame(frame, callback) {
|
|
v8dbg.restartframe(frame.id, function(result, error) {
|
|
stepInto(callback);
|
|
});
|
|
}
|
|
|
|
function evaluate(expression, frame, global, disableBreak, callback) {
|
|
v8dbg.evaluate(expression, frame, global, disableBreak, function(data) {
|
|
var value = data.result;
|
|
var error = data.error;
|
|
var name = expression.trim();
|
|
if (error) {
|
|
var err = new Error(error.message);
|
|
err.name = name;
|
|
err.stack = error.stack;
|
|
return callback(err);
|
|
}
|
|
|
|
var variable = createVariable({
|
|
name: name,
|
|
value: value,
|
|
});
|
|
|
|
callback(null, variable);
|
|
});
|
|
}
|
|
|
|
function setBreakpoint(bp, callback) {
|
|
var sm = bp.sourcemap || {};
|
|
var path = sm.source || bp.path;
|
|
var line = sm.line || bp.line;
|
|
var column = sm.column || bp.column;
|
|
|
|
if (!path) {
|
|
// TODO find out why this happens
|
|
callback && callback(new Error("Ignoring breakpoint with invalid path."));
|
|
return false;
|
|
}
|
|
|
|
path = applyPathMap(path, "toExternal");
|
|
|
|
if (path[0] == "/")
|
|
path = stripPrefix + path;
|
|
else if (path[0] == "~")
|
|
path = c9.home + path.substr(1);
|
|
|
|
path = c9.toExternalPath(path);
|
|
|
|
v8dbg.setbreakpoint(path, line, column, bp.enabled, bp.condition, function(info) {
|
|
if (!info)
|
|
return callback && callback(new Error());
|
|
|
|
bp.id = info.breakpointId;
|
|
if (info.locations) {
|
|
var loc = info.locations[0];
|
|
bp.actual = loc && {
|
|
line: loc.lineNumber,
|
|
column: loc.columnNumber,
|
|
scriptId: loc.scriptId,
|
|
};
|
|
emit("breakpointUpdate", { breakpoint: bp });
|
|
}
|
|
callback && callback(null, bp, info);
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
function changeBreakpoint(bp, callback) {
|
|
setBreakpoint(bp, callback);
|
|
}
|
|
|
|
function clearBreakpoint(bp, callback) {
|
|
v8dbg.clearbreakpoint(bp.id, callback);
|
|
}
|
|
|
|
function listBreakpoints(callback) {
|
|
v8dbg.listbreakpoints(function(data) {
|
|
if (!data) return callback(new Error("Not Connected"));
|
|
|
|
breakOnExceptions = data.breakOnExceptions;
|
|
breakOnUncaughtExceptions = data.breakOnUncaughtExceptions;
|
|
|
|
callback(null, data.breakpoints.map(function(bp) {
|
|
return createBreakpoint(bp);
|
|
}));
|
|
});
|
|
}
|
|
|
|
function setVariable(variable, value, frame, callback) {
|
|
v8dbg.setvariablevalue(variable, value, frame, callback);
|
|
}
|
|
|
|
function serializeVariable(variable, callback) {
|
|
return callback(variable.value);
|
|
}
|
|
|
|
function setBreakBehavior(type, enabled, callback) {
|
|
breakOnExceptions = enabled ? type == "all" : false;
|
|
breakOnUncaughtExceptions = enabled ? type == "uncaught" : false;
|
|
|
|
v8dbg.setexceptionbreak(enabled ? type : "none", callback);
|
|
}
|
|
|
|
function setPathMap(v) {
|
|
if (!Array.isArray(v)) v = null;
|
|
pathMap = v && v.map(function(x) {
|
|
if (!x.toInternal || !x.toExternal) return;
|
|
var map = {
|
|
toInternal: {},
|
|
toExternal: {}
|
|
};
|
|
if (typeof x.toInternal.regex == "string")
|
|
map.toInternal.regex = new RegExp(x.toInternal.regex, "g");
|
|
map.toInternal.replacement = x.toInternal.replacement;
|
|
if (typeof x.toExternal.regex == "string")
|
|
map.toExternal.regex = new RegExp(x.toExternal.regex, "g");
|
|
map.toExternal.replacement = x.toExternal.replacement;
|
|
return map;
|
|
}).filter(Boolean);
|
|
}
|
|
|
|
function applyPathMap(path, dir) {
|
|
if (!pathMap)
|
|
return path;
|
|
pathMap.forEach(function(record) {
|
|
var mapping = record[dir];
|
|
path = path.replace(mapping.regex, mapping.replacement);
|
|
});
|
|
return path;
|
|
}
|
|
|
|
/***** Lifecycle *****/
|
|
|
|
plugin.on("load", function() {
|
|
});
|
|
plugin.on("unload", function() {
|
|
breakOnExceptions = null;
|
|
breakOnUncaughtExceptions = null;
|
|
attached = false;
|
|
v8dbg = null;
|
|
state = null;
|
|
activeFrame = null;
|
|
sources = null;
|
|
socket = null;
|
|
pathMap = null;
|
|
});
|
|
|
|
/***** Register and define API *****/
|
|
|
|
/**
|
|
* Debugger implementation for Cloud9. When you are implementing a
|
|
* custom debugger, implement this API. If you are looking for the
|
|
* debugger interface of Cloud9, check out the {@link debugger}.
|
|
*
|
|
* This interface is defined to be as stateless as possible. By
|
|
* implementing these methods and events you'll be able to hook your
|
|
* debugger seamlessly into the Cloud9 debugger UI.
|
|
*
|
|
* See also {@link debugger#registerDebugger}.
|
|
*
|
|
* @class debugger.implementation
|
|
*/
|
|
plugin.freezePublicAPI({
|
|
/**
|
|
* Specifies the features that this debugger implementation supports
|
|
* @property {Object} features
|
|
* @property {Boolean} features.scripts Able to download code (disable the scripts button)
|
|
* @property {Boolean} features.conditionalBreakpoints Able to have conditional breakpoints (disable menu item)
|
|
* @property {Boolean} features.liveUpdate Able to update code live (don't do anything when saving)
|
|
* @property {Boolean} features.updateWatchedVariables Able to edit variables in watches (don't show editor)
|
|
* @property {Boolean} features.updateScopeVariables Able to edit variables in variables panel (don't show editor)
|
|
* @property {Boolean} features.setBreakBehavior Able to configure break behavior (disable break behavior button)
|
|
* @property {Boolean} features.executeCode Able to execute code (disable REPL)
|
|
*/
|
|
features: {
|
|
scripts: true,
|
|
conditionalBreakpoints: true,
|
|
liveUpdate: true,
|
|
updateWatchedVariables: true,
|
|
updateScopeVariables: true,
|
|
setBreakBehavior: true,
|
|
executeCode: true
|
|
},
|
|
/**
|
|
* The type of the debugger implementation. This is the identifier
|
|
* with which the runner selects the debugger implementation.
|
|
* @property {String} type
|
|
* @readonly
|
|
*/
|
|
type: TYPE,
|
|
/**
|
|
* @property {null|"running"|"stopped"} state The state of the debugger process
|
|
* <table>
|
|
* <tr><td>Value</td><td> Description</td></tr>
|
|
* <tr><td>null</td><td> process doesn't exist</td></tr>
|
|
* <tr><td>"stopped"</td><td> paused on breakpoint</td></tr>
|
|
* <tr><td>"running"</td><td> process is running</td></tr>
|
|
* </table>
|
|
* @readonly
|
|
*/
|
|
get state() { return state; },
|
|
/**
|
|
*
|
|
*/
|
|
get attached() { return attached; },
|
|
/**
|
|
* Whether the debugger will break when it encounters any exception.
|
|
* This includes exceptions in try/catch blocks.
|
|
* @property {Boolean} breakOnExceptions
|
|
* @readonly
|
|
*/
|
|
get breakOnExceptions() { return breakOnExceptions; },
|
|
/**
|
|
* Whether the debugger will break when it encounters an uncaught
|
|
* exception.
|
|
* @property {Boolean} breakOnUncaughtExceptions
|
|
* @readonly
|
|
*/
|
|
get breakOnUncaughtExceptions() { return breakOnUncaughtExceptions; },
|
|
|
|
_events: [
|
|
/**
|
|
* Fires when the debugger is attached.
|
|
* @event attach
|
|
* @param {Object} e
|
|
* @param {debugger.Breakpoint[]} e.breakpoints A list of breakpoints that is set in the running process
|
|
*/
|
|
"attach",
|
|
/**
|
|
* Fires when the debugger is detached.
|
|
* @event detach
|
|
*/
|
|
"detach",
|
|
/**
|
|
* Fires when execution is suspended (paused)
|
|
* @event suspend
|
|
*/
|
|
"suspend",
|
|
/**
|
|
* Fires when the source of a file is updated
|
|
* @event setScriptSource
|
|
* @param {Object} e
|
|
*/
|
|
"setScriptSource",
|
|
/**
|
|
* Fires when the socket experiences an error
|
|
* @event error
|
|
*/
|
|
"error",
|
|
/**
|
|
* Fires when the current list of breakpoints is needed
|
|
* @event getBreakpoints
|
|
*/
|
|
"getBreakpoints",
|
|
/**
|
|
* Fires when a breakpoint is updated. This can happen when it
|
|
* is set at a location which is not an expression. Certain
|
|
* debuggers (such as v8) will move the breakpoint location to
|
|
* the first expression that's next in source order.
|
|
* @event breakpointUpdate
|
|
* @param {Object} e
|
|
* @param {debugger.Breakpoint} e.breakpoint
|
|
*/
|
|
"breakpointUpdate",
|
|
/**
|
|
* Fires when the debugger hits a breakpoint.
|
|
* @event break
|
|
* @param {Object} e
|
|
* @param {debugger.Frame} e.frame The frame where the debugger has breaked at.
|
|
* @param {debugger.Frame[]} [e.frames] The callstack frames.
|
|
*/
|
|
"break",
|
|
/**
|
|
* Fires when the {@link #state} property changes
|
|
* @event stateChange
|
|
* @param {Object} e
|
|
* @param {debugger.Frame} e.state The new value of the state property.
|
|
*/
|
|
"stateChange",
|
|
/**
|
|
* Fires when the debugger hits an exception.
|
|
* @event exception
|
|
* @param {Object} e
|
|
* @param {debugger.Frame} e.frame The frame where the debugger has breaked at.
|
|
* @param {Error} e.exception The exception that the debugger breaked at.
|
|
*/
|
|
"exception",
|
|
/**
|
|
* Fires when a frame becomes active. This happens when the debugger
|
|
* hits a breakpoint, or when it starts running again.
|
|
* @event frameActivate
|
|
* @param {Object} e
|
|
* @param {debugger.Frame/null} e.frame The current frame or null if there is no active frame.
|
|
*/
|
|
"frameActivate",
|
|
/**
|
|
* Fires when the result of the {@link #method-getFrames} call comes in.
|
|
* @event getFrames
|
|
* @param {Object} e
|
|
* @param {debugger.Frame[]} e.frames The frames that were retrieved.
|
|
*/
|
|
"getFrames",
|
|
/**
|
|
* Fires when the result of the {@link #getSources} call comes in.
|
|
* @event sources
|
|
* @param {Object} e
|
|
* @param {debugger.Source[]} e.sources The sources that were retrieved.
|
|
*/
|
|
"sources",
|
|
/**
|
|
* Fires when a source file is (re-)compiled. In your event
|
|
* handler, make sure you check against the sources you already
|
|
* have collected to see if you need to update or add your source.
|
|
* @event sourcesCompile
|
|
* @param {Object} e
|
|
* @param {debugger.Source} e.file the source file that is compiled.
|
|
**/
|
|
"sourcesCompile"
|
|
],
|
|
|
|
/**
|
|
* Attaches the debugger to the started process.
|
|
* @param {Object} runner A runner as specified by {@link run#run}.
|
|
* @param {debugger.Breakpoint[]} breakpoints The set of breakpoints that should be set from the start
|
|
*/
|
|
attach: attach,
|
|
|
|
/**
|
|
* Detaches the debugger from the started process.
|
|
*/
|
|
detach: detach,
|
|
|
|
/**
|
|
* Loads all the active sources from the process
|
|
*
|
|
* @param {Function} callback Called when the sources are retrieved.
|
|
* @param {Error} callback.err The error object if an error occured.
|
|
* @param {debugger.Source[]} callback.sources A list of the active sources.
|
|
* @fires sources
|
|
*/
|
|
getSources: getSources,
|
|
|
|
/**
|
|
* Retrieves the contents of a source file
|
|
* @param {debugger.Source} source The source to retrieve the contents for
|
|
* @param {Function} callback Called when the contents is retrieved
|
|
* @param {Error} callback.err The error object if an error occured.
|
|
* @param {String} callback.contents The contents of the source file
|
|
*/
|
|
getSource: getSource,
|
|
|
|
/**
|
|
* Retrieves the current stack of frames (aka "the call stack")
|
|
* from the debugger.
|
|
* @param {Function} callback Called when the frame are retrieved.
|
|
* @param {Error} callback.err The error object if an error occured.
|
|
* @param {debugger.Frame[]} callback.frames A list of frames, where index 0 is the frame where the debugger has breaked in.
|
|
* @fires getFrames
|
|
*/
|
|
getFrames: getFrames,
|
|
|
|
/**
|
|
* Retrieves the variables from a scope.
|
|
* @param {debugger.Frame} frame The frame to which the scope is related.
|
|
* @param {debugger.Scope} scope The scope from which to load the variables.
|
|
* @param {Function} callback Called when the variables are loaded
|
|
* @param {Error} callback.err The error object if an error occured.
|
|
* @param {debugger.Variable[]} callback.variables A list of variables defined in the `scope`.
|
|
* @param {debugger.Scope} callback.scope The scope to which these variables belong
|
|
* @param {debugger.Frame} callback.frame The frame related to the scope.
|
|
*/
|
|
getScope: getScope,
|
|
|
|
/**
|
|
* Retrieves and sets the properties of a variable.
|
|
* @param {debugger.Variable} variable The variable for which to retrieve the properties.
|
|
* @param {Function} callback Called when the properties are loaded
|
|
* @param {Error} callback.err The error object if an error occured.
|
|
* @param {debugger.Variable[]} callback.properties A list of properties of the variable.
|
|
* @param {debugger.Variable} callback.variable The variable to which the properties belong.
|
|
*/
|
|
getProperties: getProperties,
|
|
|
|
/**
|
|
* Step into the next statement.
|
|
*/
|
|
stepInto: stepInto,
|
|
|
|
/**
|
|
* Step over the next statement.
|
|
*/
|
|
stepOver: stepOver,
|
|
|
|
/**
|
|
* Step out of the current statement.
|
|
*/
|
|
stepOut: stepOut,
|
|
|
|
/**
|
|
* Continues execution of a process after it has hit a breakpoint.
|
|
*/
|
|
resume: resume,
|
|
|
|
/**
|
|
* Pauses the execution of a process at the next statement.
|
|
*/
|
|
suspend: suspend,
|
|
|
|
/**
|
|
* Evaluates an expression in a frame or in global space.
|
|
* @param {String} expression The expression.
|
|
* @param {debugger.Frame} frame The stack frame which serves as the contenxt of the expression.
|
|
* @param {Boolean} global Specifies whether to execute the expression in global space.
|
|
* @param {Boolean} disableBreak Specifies whether to disabled breaking when executing this expression.
|
|
* @param {Function} callback Called after the expression has executed.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
* @param {debugger.Variable} callback.variable The result of the expression.
|
|
*/
|
|
evaluate: evaluate,
|
|
|
|
/**
|
|
* Change a live running source to the latest code state
|
|
* @param {debugger.Source} source The source file to update.
|
|
* @param {String} value The new contents of the source file.
|
|
* @param {Boolean} previewOnly
|
|
* @param {Function} callback Called after the expression has executed.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
*/
|
|
setScriptSource: setScriptSource,
|
|
|
|
/**
|
|
* Adds a breakpoint to a line in a source file.
|
|
* @param {debugger.Breakpoint} breakpoint The breakpoint to add.
|
|
* @param {Function} callback Called after the expression has executed.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
* @param {debugger.Breakpoint} callback.breakpoint The added breakpoint
|
|
* @param {Object} callback.data Additional debugger specific information.
|
|
*/
|
|
setBreakpoint: setBreakpoint,
|
|
|
|
/**
|
|
* Updates properties of a breakpoint
|
|
* @param {debugger.Breakpoint} breakpoint The breakpoint to update.
|
|
* @param {Function} callback Called after the expression has executed.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
* @param {debugger.Breakpoint} callback.breakpoint The updated breakpoint
|
|
*/
|
|
changeBreakpoint: changeBreakpoint,
|
|
|
|
/**
|
|
* Removes a breakpoint from a line in a source file.
|
|
* @param {debugger.Breakpoint} breakpoint The breakpoint to remove.
|
|
* @param {Function} callback Called after the expression has executed.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
* @param {debugger.Breakpoint} callback.breakpoint The removed breakpoint
|
|
*/
|
|
clearBreakpoint: clearBreakpoint,
|
|
|
|
/**
|
|
* Retrieves a list of all the breakpoints that are set in the
|
|
* debugger.
|
|
* @param {Function} callback Called when the breakpoints are retrieved.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
* @param {debugger.Breakpoint[]} callback.breakpoints A list of breakpoints
|
|
*/
|
|
listBreakpoints: listBreakpoints,
|
|
|
|
/**
|
|
* Sets the value of a variable.
|
|
* @param {debugger.Variable} variable The variable to set the value of.
|
|
* @param {debugger.Variable[]} parents The parent variables (i.e. the objects of which the variable is the property).
|
|
* @param {Mixed} value The new value of the variable.
|
|
* @param {debugger.Frame} frame The frame to which the variable belongs.
|
|
* @param {Function} callback
|
|
* @param {Function} callback Called when the breakpoints are retrieved.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
* @param {Object} callback.data Additional debugger specific information.
|
|
*/
|
|
setVariable: setVariable,
|
|
|
|
/**
|
|
* Starts a frame (usually a function) from the first expression in that frame.
|
|
* @param {debugger.Frame} frame The frame to restart.
|
|
* @param {Function} callback
|
|
* @param {Function} callback Called when the frame is restarted.
|
|
*/
|
|
restartFrame: restartFrame,
|
|
|
|
/**
|
|
* Retrieve the value of a variable
|
|
* @param {debugger.Variable} variable The variable for which to retrieve the value
|
|
* @param {Function} callback
|
|
* @param {Function} callback Called when the value is retrieved
|
|
* @param {String} callback.value The value of the variable
|
|
*/
|
|
serializeVariable: serializeVariable,
|
|
|
|
/**
|
|
* Defines how the debugger deals with exceptions.
|
|
* @param {"all"/"uncaught"} type Specifies which errors to break on.
|
|
* @param {Boolean} enabled Specifies whether to enable breaking on exceptions.
|
|
* @param {Function} callback Called after the setting is changed.
|
|
* @param {Error} callback.err The error if any error occured.
|
|
*/
|
|
setBreakBehavior: setBreakBehavior,
|
|
|
|
/**
|
|
* Returns the source of the proxy
|
|
*/
|
|
getProxySource: getProxySource,
|
|
|
|
/**
|
|
* @ignore
|
|
* Experimental method for meteor runner
|
|
*/
|
|
setPathMap: setPathMap
|
|
});
|
|
|
|
return plugin;
|
|
}
|
|
}); |