/** * 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: "#" 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 * * * * * *
Value Description
null process doesn't exist
"stopped" paused on breakpoint
"running" process is running
* @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 }); } });