c9-core/plugins/c9.ide.run.debug/debuggers/chrome/chromedebugger.js

1376 wiersze
52 KiB
JavaScript

/**
* node debugger Module for the Cloud9
*
* @copyright 2013, Ajax.org B.V.
*/
//https://coderwall.com/p/hkmedw?utm_campaign=weekly_digest&utm_content=2013-04-02+00%3A00%3A00+UTC&utm_medium=email
//https://github.com/johnjbarton/chrome.debugger.remote
//https://github.com/cyrus-and/chrome-remote-interface
//https://github.com/danielconnor/node-devtools/tree/master/lib
//https://github.com/cyrus-and/chrome-remote-interface
//https://developers.google.com/chrome-developer-tools/docs/protocol/1.0/debugger#event-paused
//https://github.com/google/crx2app/issues/1
//https://github.com/google/devtoolsExtended
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 Frame = require("../../data/frame");
var Source = require("../../data/source");
var Breakpoint = require("../../data/breakpoint");
var Variable = require("../../data/variable");
var Scope = require("../../data/scope");
var V8Debugger = require("../v8/lib/V8Debugger");
var V8DebuggerService = require("../v8/lib/StandaloneV8DebuggerService");
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
emit.setMaxListeners(1000);
var stripPrefix = options.basePath || "";
var breakOnExceptions = false;
var breakOnUncaughtExceptions = false;
var breakpointQueue = [];
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 = "v8";
var attached = false;
var v8dbg, v8ds, state, activeFrame, sources, socket;
var scopeTypes = {
"0": "global",
"1": "local",
"2": "with",
"3": "function",
"4": "catch"
};
var hasChildren = {
"regexp": 32,
"error": 16,
"object": 8,
"function": 4
};
var loaded = false;
function load() {
if (loaded) return false;
loaded = true;
debug.registerDebugger(TYPE, plugin);
}
function unload() {
debug.unregisterDebugger(TYPE, plugin);
loaded = false;
}
/***** 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);
getFrames(function(err, frames) {
if (err) return callback(err);
updateBreakpoints(breakpoints, reconnect, function(err, breakpoints) {
if (err) return callback(err);
handleDebugBreak(breakpoints, reconnect, frames[0], function(canAttach) {
attached = canAttach;
emit("attach", { breakpoints: breakpoints });
},
function(isResumed) {
// This check is for when the process is not
// started with debug-brk
if (activeFrame) {
onChangeFrame(activeFrame);
emit("break", {
frame: activeFrame,
frames: frames
});
}
onChangeRunning(null, isResumed);
callback();
});
});
}, true); // The sync backtrace should be silent
});
}
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);
var retries = 0;
listBreakpoints(function handleBps(err, remoteBreakpoints) {
if (err) return callback(err);
// We should always have at least 1 breakpoint
if (!reconnect && !remoteBreakpoints.length && ++retries < 10) {
setTimeout(function() {
if (v8dbg) listBreakpoints(handleBps);
}, 100);
return;
}
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);
});
var i = 0;
function next() {
var bp = list[i++];
if (!bp)
done();
else if (found.indexOf(bp) == -1)
setBreakpoint(bp, next);
else
next();
}
next();
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();
}
var bp = breakpoints[0];
// If there's no breakpoint set
if (!bp) {
attach(reconnect || 0);
// If we reconnect to a break then don't resume.
if (reconnect) {
onChangeFrame(frame);
callback();
}
else
resume(callback.bind(this, true));
return;
}
// Check for a serverOnly breakpoint on line 0
// this bp, is automatically created by v8 to stop on break
if (bp.id === 1 && bp.serverOnly && bp.line === 0) {
// The breakpoint did it's job, now lets remove it
v8dbg.clearbreakpoint(1, wait);
breakpoints.remove(bp);
}
else wait();
function wait() {
// Check if there is a real breakpoint here, so we don't resume
function checkEval(err, variable) {
if (err || isTruthy(variable)) {
onChangeFrame(null);
attach(true);
resume(callback.bind(this, true));
}
else {
onChangeFrame(frame);
attach(true);
callback(false);
}
}
// @todo this is probably a timing issue - probably solved now
if (frame) {
var test = { path: frame.path, line: frame.line };
for (var bpi, i = 0, l = breakpoints.length; i < l; i++) {
if ((bpi = breakpoints[i]).equals(test)) {
// If it's not enabled let's continue
if (!bpi.enabled)
break;
// Check a condition if it has it
if (bpi.condition) {
evaluate(bpi.condition, frame, false, true, checkEval);
}
else {
onChangeFrame(frame);
attach(true);
callback(false);
}
return;
}
}
}
// Resume the process
if (reconnect) {
onChangeFrame(frame);
attach(true);
callback(false);
}
else {
onChangeFrame(null);
attach(true);
resume(callback.bind(this, true));
}
}
}
/**
* Removes the path prefix from a string
*/
function strip(str) {
return str && str.lastIndexOf(stripPrefix, 0) === 0
? str.slice(stripPrefix.length)
: 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) {
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 frameToString(frame) {
var str = [];
var args = frame.arguments;
var argsStr = [];
str.push(frame.func.name || frame.func.inferredName || "anonymous", "(");
for (var i = 0, l = args.length; i < l; i++) {
var arg = args[i];
if (!arg.name)
continue;
argsStr.push(arg.name);
}
str.push(argsStr.join(", "), ")");
return str.join("");
}
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.name || ("-anonymous-" + script.id);
if (stripPrefix == "/") {
if (c9.platform == "win32" && scriptName[1] == ":")
scriptName = "/" + scriptName;
} else if (scriptName.substring(0, stripPrefix.length) == stripPrefix)
scriptName = scriptName.substr(stripPrefix.length);
// windows paths come here independantly from vfs
return scriptName.replace(/\\/g, "/");
}
function createFrame(options, script) {
var frame = new Frame({
index: options.index,
name: apf.escapeXML(frameToString(options)), //dual escape???
column: options.column,
id: getFrameId(options),
line: options.line,
script: strip(script.name),
path: getLocalScriptPath(script),
sourceId: options.func.scriptId
});
var vars = [];
// Arguments
options.arguments.forEach(function(arg) {
vars.push(createVariable(arg, null, "arguments"));
});
// Local variables
options.locals.forEach(function(local) {
if (local.name !== ".arguments")
vars.push(createVariable(local, null, "locals"));
});
// Adding the local object as this
vars.push(createVariable({
name: "this",
value: options.receiver,
kind: "this"
}));
frame.variables = vars;
/*
0: Global
1: Local
2: With
3: Closure
4: Catch >,
if (scope.type > 1) {*/
frame.scopes = options.scopes.filter(function(scope) {
return scope.type != 1;
}).reverse().map(function(scope) {
return new Scope({
index: scope.index,
type: scopeTypes[scope.type],
frameIndex: frame.index
});
});
return frame;
}
function createVariable(options, name, scope, variable) {
var value = options.value || options;
if (variable) {
variable.value = formatType(options);
variable.type = options.type;
}
else {
variable = new Variable({
name: name || options.name,
scope: scope,
value: formatType(value),
type: value.type,
ref: typeof value.ref == "number"
? value.ref
: value.handle,
children: options.children === false
? false : (hasChildren[value.type] ? true : false)
});
}
if (value.prototypeObject)
variable.prototype = new Variable({
tagName: "prototype",
name: "prototype",
type: "object",
ref: value.prototypeObject.ref
});
if (value.protoObject)
variable.proto = new Variable({
tagName: "proto",
name: "proto",
type: "object",
ref: value.protoObject.ref
});
if (value.constructorFunction)
variable.constructorFunction = new Variable({
tagName: "constructor",
name: "constructor",
type: "function",
ref: value.constructorFunction.ref
});
return variable;
}
function updateVariable(variable, body) {
return createVariable(body, null, null, variable);
}
function createSource(options) {
var path = getLocalScriptPath(options);
return new Source({
id: options.id,
name: options.name || "anonymous",
path: path,
text: strip(options.text || "anonymous"),
debug: path.charAt(0) != "/" || path.match(/ \(old\)$/) ? true : false,
lineOffset: options.lineOffset,
customSyntax: "javascript"
});
}
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, isResumed) {
if (!v8dbg) {
state = null;
} else {
state = v8dbg.isRunning() || isResumed ? "running" : "stopped";
}
emit("stateChange", { state: state });
if (state != "stopped")
onChangeFrame(null);
}
function createFrameFromBreak(data) {
// Create a frame from the even information
return new Frame({
index: 0,
name: data.invocationText,
column: data.sourceColumn,
id: String(data.line) + ":" + String(data.sourceColumn),
line: data.sourceLine,
script: strip(data.script.name),
path: getLocalScriptPath(data.script),
sourceId: data.script.id,
istop: true
});
}
function onBreak(e) {
if (!attached) {
if (attached === 0)
attached = true;
return;
}
// @todo update breakpoint text?
var frame = createFrameFromBreak(e.data);
onChangeFrame(frame);
emit("break", {
frame: frame
});
}
function onException(e) {
var frame = createFrameFromBreak(e.data);
var options = e.data.exception;
options.text.match(/^(\w+):(.*)$/);
var name = RegExp.$1 || options.className;
var value = RegExp.$2 || options.text;
options.name = name;
options.value = {
value: value,
type: "error",
handle: options.handle
};
options.children = true;
var variable = createVariable(options);
variable.error = true;
lookup(options.properties, false, function(err, properties) {
variable.properties = properties;
emit("exception", {
frame: frame,
exception: variable
});
});
}
function onAfterCompile(e) {
var queue = breakpointQueue;
breakpointQueue = [];
queue.forEach(function(i) {
setBreakpoint(i[0]);
});
emit("sourcesCompile", { source: createSource(e.data.script) });
}
function onChangeFrame(frame, silent) {
activeFrame = frame;
if (!silent)
emit("frameActivate", { frame: frame });
}
/***** Methods *****/
function attach(s, reconnect, callback) {
if (v8ds)
v8ds.detach();
socket = s;
socket.on("back", function(err) {
sync(emit("getBreakpoints"), true, callback);
}, plugin);
socket.on("error", function(err) {
emit("error", err);
}, plugin);
v8ds = new V8DebuggerService(socket);
v8ds.attach(0, function(err) {
if (err) return callback(err);
v8dbg = new V8Debugger(0, v8ds);
// register event listeners
v8dbg.addEventListener("changeRunning", onChangeRunning);
v8dbg.addEventListener("break", onBreak);
v8dbg.addEventListener("exception", onException);
v8dbg.addEventListener("afterCompile", onAfterCompile);
onChangeFrame(null);
// 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() {
if (!v8ds)
return;
v8ds.detach();
onChangeFrame(null);
onChangeRunning();
if (v8dbg) {
// on detach remove all event listeners
v8dbg.removeEventListener("changeRunning", onChangeRunning);
v8dbg.removeEventListener("break", onBreak);
v8dbg.removeEventListener("exception", onException);
v8dbg.removeEventListener("afterCompile", onAfterCompile);
}
socket.unload();
socket = null;
v8ds = null;
v8dbg = null;
attached = false;
emit("detach");
}
function getSources(callback) {
v8dbg.scripts(4, null, false, function(scripts) {
sources = [];
for (var i = 0, l = scripts.length; i < l; i++) {
var script = scripts[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.scripts(4, [source.id], true, function(scripts) {
if (!scripts.length)
return callback(new Error("File not found : " + source.path));
var source = scripts[0].source
.replace(RE_NODE_PREFIX, "")
.replace(RE_NODE_POSTFIX, "");
callback(null, source);
});
}
function getFrames(callback, silent) {
v8dbg.backtrace(0, 1000, null, true, function(body, refs) {
function ref(id) {
for (var i = 0; i < refs.length; i++) {
if (refs[i].handle == id) {
return refs[i];
}
}
return {};
}
var frames = [];
if (body && body.totalFrames > 0) {
body && body.frames.map(function(frame) {
var script = ref(frame.script.ref);
if (script.name && !/^native /.test(script.name))
frames.push(createFrame(frame, script));
});
var topFrame = frames[0];
if (topFrame)
topFrame.istop = true;
}
emit("getFrames", { frames: frames });
callback(null, frames);
});
}
function getScope(frame, scope, callback) {
v8dbg.scope(scope.index, frame.index, true, function(body, refs, error) {
if (error)
return callback(error);
var variables = body.object.properties.map(function(prop) {
return createVariable(prop);
});
scope.variables = variables;
callback(null, variables, scope, frame);
});
}
function getProperties(variable, callback) {
v8dbg.lookup([variable.ref], false, function(body, refs, err) {
if (err) return callback(err);
var data = body[variable.ref];
data && updateVariable(variable, data);
var props = data.properties || [];
if (props.length > 5000) {
props = [createVariable({
name: "Too many properties",
value: { type: "error", value: "Found more than 5000 properties" },
children: false
})];
variable.properties = props;
callback(null, props, variable);
return;
}
lookup(props, false, function(err, properties) {
variable.properties = properties;
callback(err, properties, variable);
});
});
}
function stepInto(callback) {
v8dbg.continueScript("in", null, callback);
}
function stepOver(callback) {
v8dbg.continueScript("next", null, callback);
}
function stepOut(callback) {
v8dbg.continueScript("out", null, callback);
}
function resume(callback) {
v8dbg.continueScript(null, null, callback);
}
function suspend(callback) {
v8dbg.suspend(function() {
emit("suspend");
callback && callback();
});
}
function lookup(props, includeSource, callback) {
// can happen for numbers. E.g when debugger stops on throw 1
if (!props || !props.length)
return callback(null, []);
v8dbg.lookup(props.map(function(p) { return p.ref; }),
includeSource, function(body) {
if (!body)
return callback(new Error("No body received"));
var properties = props.map(function(prop) {
prop.value = body[prop.ref];
return createVariable(prop);
});
callback(null, properties);
});
}
function setScriptSource(script, newSource, previewOnly, callback) {
newSource = NODE_PREFIX + newSource + NODE_POSTFIX;
v8dbg.changelive(script.id, newSource, previewOnly, function(e) {
var data = e;
function cb() {
emit("setScriptSource", data);
callback(null, data);
}
if (!e)
cb(new Error("Debugger could not update source of saved file."));
else if (e.stepin_recommended)
stepInto(cb);
else if (e.result.stack_modified === false) {
getFrames(function(err, frames) {
if (!activeFrame || !frames.length)
return; // debugger isn't active
onChangeFrame(frames[0]);
emit("break", {
frame: activeFrame,
frames: frames
});
});
cb();
}
else
cb();
});
}
function restartFrame(frame, callback) {
var frameIndex = frame && typeof frame == "object" ? frame.index : frame;
v8dbg.restartframe(frameIndex, function(body) {
if (body.result && body.result.stack_update_needs_step_in) {
stepInto(callback.bind(this, body));
}
else {
callback.apply(this, arguments);
}
});
}
function evaluate(expression, frame, global, disableBreak, callback) {
var frameIndex = frame && typeof frame == "object" ? frame.index : frame;
v8dbg.evaluate(expression, frameIndex, global,
disableBreak, function(body, refs, 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: body
});
if (variable.children) {
lookup(body.properties, false, function(err, properties) {
variable.properties = properties;
callback(null, variable);
});
}
else {
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;
var scriptId = getScriptIdFromPath(path);
if (!scriptId) {
// Wait until source is parsed
breakpointQueue.push([bp, callback]);
callback && callback(new Error("Source not available yet. Queuing request."));
return false;
}
v8dbg.setbreakpoint("scriptId", scriptId, line, column, bp.enabled,
bp.condition, bp.ignoreCount, function(info) {
if (!info)
return callback && callback(new Error());
bp.id = info.breakpoint;
if (info.actual_locations) {
bp.actual = info.actual_locations[0];
emit("breakpointUpdate", { breakpoint: bp });
}
callback && callback(null, bp, info);
});
return true;
}
function changeBreakpoint(bp, callback) {
if (breakpointQueue.some(function(i) {
return i[0] === bp;
})) return;
v8dbg.changebreakpoint(bp.id, bp.enabled,
bp.condition, bp.ignoreCount, function(info) {
callback && callback(null, bp, info);
});
}
function clearBreakpoint(bp, callback) {
if (breakpointQueue.some(function(i, index) {
if (i[0] === bp) {
breakpointQueue.splice(index, 1);
return true;
}
})) return;
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, parents, value, frame, callback) {
// Get variable name
var names = [], scopeNumber, frameIndex = frame.index;
parents.reverse().forEach(function(p) {
// Assuming scopes are accessible
if (p.tagName == "variable")
names.push(p.name.replace(/"/g, '\\"'));
else if (p.tagName == "scope")
scopeNumber = p.index;
});
names.push(variable.name);
function handler(err, body) {
if (err)
return callback(err);
variable.value = formatType(body);
variable.type = body.type;
variable.ref = body.handle;
variable.properties = body.properties || [];
variable.children = (body.properties || "").length ? true : false;
// @todo - and make this consistent with getProperties
// if (body.constructorFunction)
// value.contructor = body.constructorFunction.ref;
// if (body.prototypeObject)
// value.prototype = body.prototypeObject.ref;
if (variable.children) {
lookup(body.properties, false, function(err, properties) {
variable.properties = properties;
callback(null, variable);
});
}
else {
callback(null, variable);
}
}
// If it's a local variable set it directly
if (parents.length == (typeof scopeNumber == "number" ? 1 : 0))
setLocalVariable(variable, value, scopeNumber || 0, frameIndex, handler);
// Otherwise set a variable or property
else
setAnyVariable(variable, parents[0], value, handler);
}
function setLocalVariable(variable, value, scopeNumber, frameIndex, callback) {
v8dbg.simpleevaluate(value, null, true, [], function(body, refs, error) {
if (error) {
var err = new Error(error.message);
err.name = error.name;
err.stack = error.stack;
return callback(err);
}
v8dbg.setvariablevalue(variable.name, body, scopeNumber, frameIndex,
function(body, refs, error) {
// lookup([variable.ref], false, function(err, properties) {
// variable.properties = properties;
// callback(null, variable);
// });
if (error) {
var err = new Error(error.message);
err.name = error.name;
err.stack = error.stack;
return callback(err);
}
callback(null, body.newValue);
});
});
}
function setAnyVariable(variable, parent, value, callback) {
var expression = "(function(a, b) { this[a] = b; })"
+ ".call(__cloud9_debugger_self__, \""
+ variable.name + "\", " + value + ")";
v8dbg.simpleevaluate(expression, null, true, [{
name: "__cloud9_debugger_self__",
handle: parent.ref
}], function(body, refs, error) {
if (error) {
var err = new Error(error.message);
err.name = error.name;
err.stack = error.stack;
return callback(err);
}
callback(null, body);
});
}
function serializeVariable(variable, callback) {
var expr = "(function(fn){ return fn.toString() })"
+ "(__cloud9_debugger_self__)";
v8dbg.simpleevaluate(expr, null, true, [{
name: "__cloud9_debugger_self__",
handle: variable.ref
}], function(body, refs, error) {
callback(body.value);
});
}
function setBreakBehavior(type, enabled, callback) {
breakOnExceptions = enabled ? type == "all" : false;
breakOnUncaughtExceptions = enabled ? type == "uncaught" : false;
v8dbg.setexceptionbreak(enabled ? type : "all", enabled, callback);
}
/***** Lifecycle *****/
plugin.on("load", function() {
load();
});
plugin.on("enable", function() {
});
plugin.on("disable", function() {
});
plugin.on("unload", function() {
unload();
});
/***** 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({
/**
* 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 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,
/**
*
*/
restartFrame: restartFrame,
/**
*
*/
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
});
register(null, {
chromedebugger: plugin
});
}
});