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: "#" 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 * * * * * *
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 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; } });