mirror of https://github.com/lukechilds/node.git
Browse Source
PR-URL: https://github.com/nodejs/node/pull/12263 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Sam Roberts <vieuxtech@gmail.com> Reviewed-By: Timothy Gu <timothygu99@gmail.com> Reviewed-By: Josh Gavant <josh.gavant@outlook.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>v6
Eugene Ostroukhov
8 years ago
committed by
Anna Henningsen
8 changed files with 463 additions and 1 deletions
@ -0,0 +1,114 @@ |
|||
# Inspector |
|||
|
|||
> Stability: 1 - Experimental |
|||
|
|||
The `inspector` module provides an API for interacting with the V8 inspector. |
|||
|
|||
It can be accessed using: |
|||
|
|||
```js |
|||
const inspector = require('inspector'); |
|||
``` |
|||
|
|||
## Class: inspector.Session |
|||
|
|||
The `inspector.Session` is used for dispatching messages to the V8 inspector |
|||
back-end and receiving message responses and notifications. |
|||
|
|||
### Constructor: new inspector.Session() |
|||
<!-- YAML |
|||
added: REPLACEME |
|||
--> |
|||
|
|||
Create a new instance of the `inspector.Session` class. The inspector session |
|||
needs to be connected through [`session.connect()`][] before the messages |
|||
can be dispatched to the inspector backend. |
|||
|
|||
`inspector.Session` is an [`EventEmitter`][] with the following events: |
|||
|
|||
### Event: 'inspectorNotification' |
|||
<!-- YAML |
|||
added: REPLACEME |
|||
--> |
|||
|
|||
* {Object} The notification message object |
|||
|
|||
Emitted when any notification from the V8 Inspector is received. |
|||
|
|||
```js |
|||
session.on('inspectorNotification', (message) => console.log(message.method)); |
|||
// Debugger.paused |
|||
// Debugger.resumed |
|||
``` |
|||
|
|||
It is also possible to subscribe only to notifications with specific method: |
|||
|
|||
### Event: <inspector-protocol-method> |
|||
<!-- YAML |
|||
added: REPLACEME |
|||
--> |
|||
|
|||
* {Object} The notification message object |
|||
|
|||
Emitted when an inspector notification is received that has its method field set |
|||
to the `<inspector-protocol-method>` value. |
|||
|
|||
The following snippet installs a listener on the [`Debugger.paused`][] |
|||
event, and prints the reason for program suspension whenever program |
|||
execution is suspended (through breakpoints, for example): |
|||
|
|||
```js |
|||
session.on('Debugger.paused', ({params}) => console.log(params.hitBreakpoints)); |
|||
// [ '/node/test/inspector/test-bindings.js:11:0' ] |
|||
``` |
|||
|
|||
### session.connect() |
|||
<!-- YAML |
|||
added: REPLACEME |
|||
--> |
|||
|
|||
Connects a session to the inspector back-end. An exception will be thrown |
|||
if there is already a connected session established either through the API or by |
|||
a front-end connected to the Inspector WebSocket port. |
|||
|
|||
### session.post(method[, params][, callback]) |
|||
<!-- YAML |
|||
added: REPLACEME |
|||
--> |
|||
|
|||
* method {string} |
|||
* params {Object} |
|||
* callback {Function} |
|||
|
|||
Posts a message to the inspector back-end. `callback` will be notified when |
|||
a response is received. `callback` is a function that accepts two optional |
|||
arguments - error and message-specific result. |
|||
|
|||
```js |
|||
session.post('Runtime.evaluate', {'expression': '2 + 2'}, |
|||
(error, {result}) => console.log(result.value)); |
|||
// Output: { type: 'number', value: 4, description: '4' } |
|||
``` |
|||
|
|||
The latest version of the V8 inspector protocol is published on the |
|||
[Chrome DevTools Protocol Viewer][]. |
|||
|
|||
Node inspector supports all the Chrome DevTools Protocol domains declared |
|||
by V8. Chrome DevTools Protocol domain provides an interface for interacting |
|||
with one of the runtime agents used to inspect the application state and listen |
|||
to the run-time events. |
|||
|
|||
### session.disconnect() |
|||
<!-- YAML |
|||
added: REPLACEME |
|||
--> |
|||
|
|||
Immediately close the session. All pending message callbacks will be called |
|||
with an error. [`session.connect()`] will need to be called to be able to send |
|||
messages again. Reconnected session will lose all inspector state, such as |
|||
enabled agents or configured breakpoints. |
|||
|
|||
[`session.connect()`]: #sessionconnect |
|||
[`Debugger.paused`]: https://chromedevtools.github.io/devtools-protocol/v8/Debugger/#event-paused |
|||
[`EventEmitter`]: events.html#events_class_eventemitter |
|||
[Chrome DevTools Protocol Viewer]: https://chromedevtools.github.io/devtools-protocol/v8/ |
@ -0,0 +1,87 @@ |
|||
'use strict'; |
|||
|
|||
const connect = process.binding('inspector').connect; |
|||
const EventEmitter = require('events'); |
|||
const util = require('util'); |
|||
|
|||
if (!connect) |
|||
throw new Error('Inspector is not available'); |
|||
|
|||
const connectionSymbol = Symbol('connectionProperty'); |
|||
const messageCallbacksSymbol = Symbol('messageCallbacks'); |
|||
const nextIdSymbol = Symbol('nextId'); |
|||
const onMessageSymbol = Symbol('onMessage'); |
|||
|
|||
class Session extends EventEmitter { |
|||
constructor() { |
|||
super(); |
|||
this[connectionSymbol] = null; |
|||
this[nextIdSymbol] = 1; |
|||
this[messageCallbacksSymbol] = new Map(); |
|||
} |
|||
|
|||
connect() { |
|||
if (this[connectionSymbol]) |
|||
throw new Error('Already connected'); |
|||
this[connectionSymbol] = |
|||
connect((message) => this[onMessageSymbol](message)); |
|||
} |
|||
|
|||
[onMessageSymbol](message) { |
|||
const parsed = JSON.parse(message); |
|||
if (parsed.id) { |
|||
const callback = this[messageCallbacksSymbol].get(parsed.id); |
|||
this[messageCallbacksSymbol].delete(parsed.id); |
|||
if (callback) |
|||
callback(parsed.error || null, parsed.result || null); |
|||
} else { |
|||
this.emit(parsed.method, parsed); |
|||
this.emit('inspectorNotification', parsed); |
|||
} |
|||
} |
|||
|
|||
post(method, params, callback) { |
|||
if (typeof method !== 'string') |
|||
throw new TypeError( |
|||
`"method" must be a string, got ${typeof method} instead`); |
|||
if (!callback && util.isFunction(params)) { |
|||
callback = params; |
|||
params = null; |
|||
} |
|||
if (params && typeof params !== 'object') |
|||
throw new TypeError( |
|||
`"params" must be an object, got ${typeof params} instead`); |
|||
if (callback && typeof callback !== 'function') |
|||
throw new TypeError( |
|||
`"callback" must be a function, got ${typeof callback} instead`); |
|||
|
|||
if (!this[connectionSymbol]) |
|||
throw new Error('Session is not connected'); |
|||
const id = this[nextIdSymbol]++; |
|||
const message = {id, method}; |
|||
if (params) { |
|||
message['params'] = params; |
|||
} |
|||
if (callback) { |
|||
this[messageCallbacksSymbol].set(id, callback); |
|||
} |
|||
this[connectionSymbol].dispatch(JSON.stringify(message)); |
|||
} |
|||
|
|||
disconnect() { |
|||
if (!this[connectionSymbol]) |
|||
return; |
|||
this[connectionSymbol].disconnect(); |
|||
this[connectionSymbol] = null; |
|||
const remainingCallbacks = this[messageCallbacksSymbol].values(); |
|||
for (const callback of remainingCallbacks) { |
|||
process.nextTick(callback, new Error('Session was closed')); |
|||
} |
|||
this[messageCallbacksSymbol].clear(); |
|||
this[nextIdSymbol] = 1; |
|||
} |
|||
} |
|||
|
|||
module.exports = { |
|||
Session |
|||
}; |
@ -0,0 +1,105 @@ |
|||
'use strict'; |
|||
require('../common'); |
|||
const assert = require('assert'); |
|||
const inspector = require('inspector'); |
|||
const path = require('path'); |
|||
|
|||
// This test case will set a breakpoint 4 lines below
|
|||
function debuggedFunction() { |
|||
let i; |
|||
let accum = 0; |
|||
for (i = 0; i < 5; i++) { |
|||
accum += i; |
|||
} |
|||
return accum; |
|||
} |
|||
|
|||
let scopeCallback = null; |
|||
|
|||
function checkScope(session, scopeId) { |
|||
session.post('Runtime.getProperties', { |
|||
'objectId': scopeId, |
|||
'ownProperties': false, |
|||
'accessorPropertiesOnly': false, |
|||
'generatePreview': true |
|||
}, scopeCallback); |
|||
} |
|||
|
|||
function debuggerPausedCallback(session, notification) { |
|||
const params = notification['params']; |
|||
const callFrame = params['callFrames'][0]; |
|||
const scopeId = callFrame['scopeChain'][0]['object']['objectId']; |
|||
checkScope(session, scopeId); |
|||
} |
|||
|
|||
function testNoCrashWithExceptionInCallback() { |
|||
// There is a deliberate exception in the callback
|
|||
const session = new inspector.Session(); |
|||
session.connect(); |
|||
const error = new Error('We expect this'); |
|||
assert.throws(() => { |
|||
session.post('Console.enable', () => { throw error; }); |
|||
}, (e) => e === error); |
|||
session.disconnect(); |
|||
} |
|||
|
|||
function testSampleDebugSession() { |
|||
let cur = 0; |
|||
const failures = []; |
|||
const expects = { |
|||
i: [0, 1, 2, 3, 4], |
|||
accum: [0, 0, 1, 3, 6] |
|||
}; |
|||
scopeCallback = function(error, result) { |
|||
const i = cur++; |
|||
let v, actual, expected; |
|||
for (v of result['result']) { |
|||
actual = v['value']['value']; |
|||
expected = expects[v['name']][i]; |
|||
if (actual !== expected) { |
|||
failures.push('Iteration ' + i + ' variable: ' + v['name'] + |
|||
' expected: ' + expected + ' actual: ' + actual); |
|||
} |
|||
} |
|||
}; |
|||
const session = new inspector.Session(); |
|||
session.connect(); |
|||
let secondSessionOpened = false; |
|||
const secondSession = new inspector.Session(); |
|||
try { |
|||
secondSession.connect(); |
|||
secondSessionOpened = true; |
|||
} catch (error) { |
|||
// expected as the session already exists
|
|||
} |
|||
assert.strictEqual(secondSessionOpened, false); |
|||
session.on('Debugger.paused', |
|||
(notification) => debuggerPausedCallback(session, notification)); |
|||
let cbAsSecondArgCalled = false; |
|||
assert.throws(() => { |
|||
session.post('Debugger.enable', function() {}, function() {}); |
|||
}, TypeError); |
|||
session.post('Debugger.enable', () => cbAsSecondArgCalled = true); |
|||
session.post('Debugger.setBreakpointByUrl', { |
|||
'lineNumber': 11, |
|||
'url': path.resolve(__dirname, __filename), |
|||
'columnNumber': 0, |
|||
'condition': '' |
|||
}); |
|||
|
|||
debuggedFunction(); |
|||
assert.deepStrictEqual(cbAsSecondArgCalled, true); |
|||
assert.deepStrictEqual(failures, []); |
|||
assert.strictEqual(cur, 5); |
|||
scopeCallback = null; |
|||
session.disconnect(); |
|||
assert.throws(() => session.post('Debugger.enable'), (e) => !!e); |
|||
} |
|||
|
|||
testNoCrashWithExceptionInCallback(); |
|||
testSampleDebugSession(); |
|||
let breakpointHit = false; |
|||
scopeCallback = () => (breakpointHit = true); |
|||
debuggedFunction(); |
|||
assert.strictEqual(breakpointHit, false); |
|||
testSampleDebugSession(); |
Loading…
Reference in new issue