123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
- // node-inspector version of on webkit-inspector/DebuggerAgent.cpp
- var convert = require('./convert.js'),
- format = require('util').format,
- path = require('path'),
- async = require('async'),
- ScriptFileStorage = require('./ScriptFileStorage').ScriptFileStorage;
- /**
- * @param {{saveLiveEdit,preload}} config
- * @param {FrontendClient} frontendClient
- * @param {DebuggerClient} debuggerClient
- * @param {BreakEventHandler} breakEventHandler
- * @param {ScriptManager} scriptManager
- * @constructor
- */
- function DebuggerAgent(config,
- frontendClient,
- debuggerClient,
- breakEventHandler,
- scriptManager) {
- this._saveLiveEdit = config.saveLiveEdit;
- this._frontendClient = frontendClient;
- this._debuggerClient = debuggerClient;
- this._breakEventHandler = breakEventHandler;
- this._scriptManager = scriptManager;
- this._scriptStorage = new ScriptFileStorage(config, scriptManager);
- }
- DebuggerAgent.prototype = {
- canSetScriptSource: function(params, done) {
- done(null, { result: true });
- },
- enable: function(params, done) {
- var onConnect = function() {
- done();
- this._onDebuggerConnect();
- }.bind(this);
- if (this._debuggerClient.isConnected) {
- process.nextTick(onConnect);
- } else {
- this._debuggerClient.on('connect', onConnect);
- this._debuggerClient.connect();
- }
- },
- _onDebuggerConnect: function() {
- async.waterfall([
- // Remove all existing breakpoints because:
- // 1) front-end inspector cannot restore breakpoints from debugger anyway
- // 2) all breakpoints were disabled when the previous debugger-client
- // disconnected from the debugged application
- this._removeAllBreakpoints.bind(this),
- this._reloadScripts.bind(this),
- this._sendBacktraceIfPaused.bind(this)
- ]);
- },
- _removeAllBreakpoints: function(done) {
- this._debuggerClient.request(
- 'listbreakpoints',
- {},
- function(err, response) {
- if (err) {
- console.log('Warning: cannot remove old breakpoints. %s', err);
- done();
- return;
- }
- function removeOneBreakpoint(bp, next) {
- this._debuggerClient.clearBreakpoint(
- bp.number,
- function(error) {
- if (error)
- console.log(
- 'Warning: cannot remove old breakpoint %d. %s',
- bp.number,
- error
- );
- next();
- }
- );
- }
- async.eachSeries(
- response.breakpoints,
- removeOneBreakpoint.bind(this),
- done
- );
- }.bind(this)
- );
- },
- _reloadScripts: function(done) {
- this._scriptManager.reset();
- this._debuggerClient.request(
- 'scripts',
- {
- includeSource: true,
- types: 4
- },
- function handleScriptsResponse(err, result) {
- if (err) {
- done(err);
- return;
- }
- result.forEach(this._scriptManager.addScript.bind(this._scriptManager));
- done();
- }.bind(this)
- );
- },
- _sendBacktraceIfPaused: function(done) {
- if (!this._debuggerClient.isRunning) {
- this._breakEventHandler.sendBacktraceToFrontend(null);
- }
- done();
- },
- disable: function(params, done) {
- this._debuggerClient.close();
- done();
- },
- resume: function(params, done) {
- this._sendContinue(undefined, done);
- },
- _sendContinue: function(stepAction, done) {
- var args = stepAction ? { stepaction: stepAction } : undefined;
- this._debuggerClient.request('continue', args, function(error, result) {
- done(error);
- if (!error)
- this._frontendClient.sendEvent('Debugger.resumed');
- }.bind(this));
- },
- pause: function(params, done) {
- this._debuggerClient.request('suspend', {}, function(error, result) {
- done(error);
- if (!error) {
- this._breakEventHandler.sendBacktraceToFrontend(null);
- }
- }.bind(this));
- },
- stepOver: function(params, done) {
- this._sendContinue('next', done);
- },
- stepInto: function(params, done) {
- this._sendContinue('in', done);
- },
- stepOut: function(params, done) {
- this._sendContinue('out', done);
- },
- continueToLocation: function(params, done) {
- var requestParams = {
- type: 'scriptId',
- target: convert.inspectorScriptIdToV8Id(params.location.scriptId),
- line: params.location.lineNumber,
- column: params.location.columnNumber
- };
- this._debuggerClient.request('setbreakpoint', requestParams, function(error, response) {
- if (error != null) {
- done(error);
- return;
- }
- this._breakEventHandler.
- continueToLocationBreakpointId = response.breakpoint;
- this._debuggerClient.request('continue', undefined, function(error, response) {
- done(error);
- });
- }.bind(this));
- },
- getScriptSource: function(params, done) {
- this._debuggerClient.getScriptSourceById(
- Number(params.scriptId),
- function(err, source) {
- if (err) return done(err);
- return done(null, { scriptSource: source });
- }
- );
- },
- setScriptSource: function(params, done) {
- this._debuggerClient.request(
- 'changelive',
- {
- script_id: convert.inspectorScriptIdToV8Id(params.scriptId),
- new_source: params.scriptSource,
- preview_only: false
- },
- function(err, response) {
- this._handleChangeLiveOrRestartFrameResponse(done, err, response);
- this._persistScriptChanges(params.scriptId, params.scriptSource);
- }.bind(this)
- );
- },
- _handleChangeLiveOrRestartFrameResponse: function(done, err, response) {
- if (err) {
- done(err);
- return;
- }
- var frontendClient = this._frontendClient;
- var breakEventHandler = this._breakEventHandler;
- function sendResponse(callframes) {
- done(
- null,
- {
- callFrames: callframes || [],
- result: response.result
- }
- );
- }
- function sendResponseWithCallStack() {
- breakEventHandler.fetchCallFrames(function(err, response) {
- var callframes = [];
- if (err) {
- frontendClient.sendLogToConsole(
- 'error',
- 'Cannot update stack trace after a script changed: ' + err);
- } else {
- callframes = response;
- }
- sendResponse(callframes);
- });
- }
- var result = response.result;
- if (result.stack_modified && !result.stack_update_needs_step_in)
- sendResponseWithCallStack();
- else
- sendResponse();
- },
- _persistScriptChanges: function(scriptId, newSource) {
- if (!this._saveLiveEdit) {
- this._warn(
- 'Saving of live-edit changes back to source files is disabled by configuration.\n' +
- 'Change the option "saveLiveEdit" in config.json to enable this feature.'
- );
- return;
- }
- var source = this._scriptManager.findScriptByID(scriptId);
- if (!source) {
- this._warn('Cannot save changes to disk: unknown script id %s', scriptId);
- return;
- }
- var scriptFile = source.v8name;
- if (!scriptFile || scriptFile.indexOf(path.sep) == -1) {
- this._warn(
- 'Cannot save changes to disk: script id %s "%s" was not loaded from a file.',
- scriptId,
- scriptFile || 'null'
- );
- return;
- }
- this._scriptStorage.save(scriptFile, newSource, function(err) {
- if (err) {
- this._warn('Cannot save changes to disk. %s', err);
- }
- }.bind(this));
- },
- _warn: function() {
- this._frontendClient.sendLogToConsole(
- 'warning',
- format.apply(this, arguments)
- );
- },
- setPauseOnExceptions: function(params, done) {
- var args = [
- { type: 'all', enabled: params.state == 'all' },
- { type: 'uncaught', enabled: params.state == 'uncaught' }
- ];
- async.eachSeries(
- args,
- function(arg, next) {
- this._debuggerClient.request('setexceptionbreak', arg, next);
- }.bind(this),
- done);
- },
- setBreakpointByUrl: function(params, done) {
- if (params.urlRegex !== undefined) {
- // DevTools protocol defines urlRegex parameter,
- // but the parameter is not used by the front-end.
- done('Error: setBreakpointByUrl using urlRegex is not implemented.');
- return;
- }
- var requestParams = {
- type: 'script',
- target: convert.inspectorUrlToV8Name(params.url),
- line: params.lineNumber,
- column: params.columnNumber,
- condition: params.condition
- };
- this._debuggerClient.request('setbreakpoint', requestParams, function(error, response) {
- if (error != null) {
- done(error);
- return;
- }
- done(null, {
- breakpointId: response.breakpoint.toString(),
- locations: response.actual_locations.map(convert.v8LocationToInspectorLocation)
- });
- });
- },
- removeBreakpoint: function(params, done) {
- this._debuggerClient.clearBreakpoint(
- params.breakpointId,
- function(error, response) {
- done(error, null);
- }
- );
- },
- setBreakpointsActive: function(params, done) {
- this._debuggerClient.request('listbreakpoints', {}, function(error, response) {
- if (error) {
- done(error);
- return;
- }
- function setBreakpointState(bp, next) {
- var req = { breakpoint: bp.number, enabled: params.active };
- this._debuggerClient.request('changebreakpoint', req, next);
- }
- async.eachSeries(response.breakpoints, setBreakpointState.bind(this), done);
- }.bind(this));
- },
- setOverlayMessage: function(params, done) {
- done();
- },
- evaluateOnCallFrame: function(params, done) {
- var self = this;
- var expression = params.expression;
- var frame = Number(params.callFrameId);
- self._debuggerClient.request(
- 'evaluate',
- {
- expression: params.expression,
- frame: frame
- },
- function(err, result) {
- // Errors from V8 are actually just messages, so we need to fill them out a bit.
- if (err) {
- err = convert.v8ErrorToInspectorError(err);
- }
- done(null, {
- result: err || convert.v8ResultToInspectorResult(result),
- wasThrown: !!err
- });
- }
- );
- },
- getFunctionDetails: function(params, done) {
- var handle = params.functionId;
- this._debuggerClient.request(
- 'lookup',
- {
- handles: [handle],
- includeSource: false
- },
- function(error, responseBody) {
- if (error) {
- done(error);
- } else {
- done(null, convert.v8FunctionLookupToFunctionDetails(responseBody[handle]));
- }
- }.bind(this));
- },
- restartFrame: function(params, done) {
- this._debuggerClient.request(
- 'restartframe',
- {
- frame: Number(params.callFrameId)
- },
- this._handleChangeLiveOrRestartFrameResponse.bind(this, done)
- );
- },
- setVariableValue: function(params, done) {
- this._debuggerClient.evaluateGlobal('process.version', function(err, version) {
- if (!DebuggerAgent.nodeVersionHasSetVariableValue(version)) {
- done(
- 'V8 engine in node version ' + version +
- ' does not support setting variable value from debugger.\n' +
- ' Please upgrade to version v0.10.12 (stable) or v0.11.2 (unstable)' +
- ' or newer.');
- } else {
- this._doSetVariableValue(params, done);
- }
- }.bind(this));
- },
- _doSetVariableValue: function(params, done) {
- var value = convert.inspectorValueToV8Value(params.newValue);
- this._debuggerClient.request(
- 'setVariableValue',
- {
- name: params.variableName,
- scope: {
- number: Number(params.scopeNumber),
- frameNumber: Number(params.callFrameId)
- },
- newValue: value
- },
- function(err, result) {
- done(err, result);
- }
- );
- },
- setSkipAllPauses: function(params, done) {
- if (params.skipped)
- done(new Error('Not implemented.'));
- else
- done();
- }
- };
- DebuggerAgent.nodeVersionHasSetVariableValue = function(version) {
- var match = /^v(\d+)\.(\d+)\.(\d+)$/.exec(version);
- if (!match) return false;
- return match[1] > 0 || // v1+
- (match[2] == 10 && match[3] >= 12) || // v0.10.12+
- (match[2] == 11 && match[3] >= 2) || // v0.11.2+
- (match[2] >= 12); // v0.12+
- };
- exports.DebuggerAgent = DebuggerAgent;
|