inspector: JavaScript bindings for the inspector
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>
This commit is contained in:
parent
c0d858f8bb
commit
60390cd7fb
@ -24,6 +24,7 @@
|
|||||||
* [Globals](globals.html)
|
* [Globals](globals.html)
|
||||||
* [HTTP](http.html)
|
* [HTTP](http.html)
|
||||||
* [HTTPS](https.html)
|
* [HTTPS](https.html)
|
||||||
|
* [Inspector](inspector.html)
|
||||||
* [Modules](modules.html)
|
* [Modules](modules.html)
|
||||||
* [Net](net.html)
|
* [Net](net.html)
|
||||||
* [OS](os.html)
|
* [OS](os.html)
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
@include globals
|
@include globals
|
||||||
@include http
|
@include http
|
||||||
@include https
|
@include https
|
||||||
|
@include inspector
|
||||||
@include modules
|
@include modules
|
||||||
@include net
|
@include net
|
||||||
@include os
|
@include os
|
||||||
|
114
doc/api/inspector.md
Normal file
114
doc/api/inspector.md
Normal file
@ -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/
|
87
lib/inspector.js
Normal file
87
lib/inspector.js
Normal file
@ -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
|
||||||
|
};
|
1
node.gyp
1
node.gyp
@ -43,6 +43,7 @@
|
|||||||
'lib/_http_outgoing.js',
|
'lib/_http_outgoing.js',
|
||||||
'lib/_http_server.js',
|
'lib/_http_server.js',
|
||||||
'lib/https.js',
|
'lib/https.js',
|
||||||
|
'lib/inspector.js',
|
||||||
'lib/module.js',
|
'lib/module.js',
|
||||||
'lib/net.js',
|
'lib/net.js',
|
||||||
'lib/os.js',
|
'lib/os.js',
|
||||||
|
@ -22,12 +22,17 @@ namespace node {
|
|||||||
namespace inspector {
|
namespace inspector {
|
||||||
namespace {
|
namespace {
|
||||||
using v8::Context;
|
using v8::Context;
|
||||||
|
using v8::External;
|
||||||
using v8::Function;
|
using v8::Function;
|
||||||
using v8::FunctionCallbackInfo;
|
using v8::FunctionCallbackInfo;
|
||||||
using v8::HandleScope;
|
using v8::HandleScope;
|
||||||
using v8::Isolate;
|
using v8::Isolate;
|
||||||
using v8::Local;
|
using v8::Local;
|
||||||
|
using v8::Maybe;
|
||||||
|
using v8::MaybeLocal;
|
||||||
|
using v8::NewStringType;
|
||||||
using v8::Object;
|
using v8::Object;
|
||||||
|
using v8::Persistent;
|
||||||
using v8::String;
|
using v8::String;
|
||||||
using v8::Value;
|
using v8::Value;
|
||||||
|
|
||||||
@ -176,6 +181,143 @@ static int RegisterDebugSignalHandler() {
|
|||||||
}
|
}
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
|
class JsBindingsSessionDelegate : public InspectorSessionDelegate {
|
||||||
|
public:
|
||||||
|
JsBindingsSessionDelegate(Environment* env,
|
||||||
|
Local<Object> session,
|
||||||
|
Local<Object> receiver,
|
||||||
|
Local<Function> callback)
|
||||||
|
: env_(env),
|
||||||
|
session_(env->isolate(), session),
|
||||||
|
receiver_(env->isolate(), receiver),
|
||||||
|
callback_(env->isolate(), callback) {
|
||||||
|
session_.SetWeak(this, JsBindingsSessionDelegate::Release,
|
||||||
|
v8::WeakCallbackType::kParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~JsBindingsSessionDelegate() {
|
||||||
|
session_.Reset();
|
||||||
|
receiver_.Reset();
|
||||||
|
callback_.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WaitForFrontendMessage() override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnMessage(const v8_inspector::StringView& message) override {
|
||||||
|
Isolate* isolate = env_->isolate();
|
||||||
|
v8::HandleScope handle_scope(isolate);
|
||||||
|
Context::Scope context_scope(env_->context());
|
||||||
|
MaybeLocal<String> v8string =
|
||||||
|
String::NewFromTwoByte(isolate, message.characters16(),
|
||||||
|
NewStringType::kNormal, message.length());
|
||||||
|
Local<Value> argument = v8string.ToLocalChecked().As<Value>();
|
||||||
|
Local<Function> callback = callback_.Get(isolate);
|
||||||
|
Local<Object> receiver = receiver_.Get(isolate);
|
||||||
|
callback->Call(env_->context(), receiver, 1, &argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disconnect() {
|
||||||
|
Agent* agent = env_->inspector_agent();
|
||||||
|
if (agent->delegate() == this)
|
||||||
|
agent->Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void Release(
|
||||||
|
const v8::WeakCallbackInfo<JsBindingsSessionDelegate>& info) {
|
||||||
|
info.SetSecondPassCallback(ReleaseSecondPass);
|
||||||
|
info.GetParameter()->session_.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReleaseSecondPass(
|
||||||
|
const v8::WeakCallbackInfo<JsBindingsSessionDelegate>& info) {
|
||||||
|
JsBindingsSessionDelegate* delegate = info.GetParameter();
|
||||||
|
delegate->Disconnect();
|
||||||
|
delete delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment* env_;
|
||||||
|
Persistent<Object> session_;
|
||||||
|
Persistent<Object> receiver_;
|
||||||
|
Persistent<Function> callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void SetDelegate(Environment* env, Local<Object> inspector,
|
||||||
|
JsBindingsSessionDelegate* delegate) {
|
||||||
|
inspector->SetPrivate(env->context(),
|
||||||
|
env->inspector_delegate_private_symbol(),
|
||||||
|
v8::External::New(env->isolate(), delegate));
|
||||||
|
}
|
||||||
|
|
||||||
|
Maybe<JsBindingsSessionDelegate*> GetDelegate(
|
||||||
|
const FunctionCallbackInfo<Value>& info) {
|
||||||
|
Environment* env = Environment::GetCurrent(info);
|
||||||
|
Local<Value> delegate;
|
||||||
|
MaybeLocal<Value> maybe_delegate =
|
||||||
|
info.This()->GetPrivate(env->context(),
|
||||||
|
env->inspector_delegate_private_symbol());
|
||||||
|
|
||||||
|
if (maybe_delegate.ToLocal(&delegate)) {
|
||||||
|
CHECK(delegate->IsExternal());
|
||||||
|
void* value = delegate.As<External>()->Value();
|
||||||
|
if (value != nullptr) {
|
||||||
|
return v8::Just(static_cast<JsBindingsSessionDelegate*>(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env->ThrowError("Inspector is not connected");
|
||||||
|
return v8::Nothing<JsBindingsSessionDelegate*>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dispatch(const FunctionCallbackInfo<Value>& info) {
|
||||||
|
Environment* env = Environment::GetCurrent(info);
|
||||||
|
if (!info[0]->IsString()) {
|
||||||
|
env->ThrowError("Inspector message must be a string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Maybe<JsBindingsSessionDelegate*> maybe_delegate = GetDelegate(info);
|
||||||
|
if (maybe_delegate.IsNothing())
|
||||||
|
return;
|
||||||
|
Agent* inspector = env->inspector_agent();
|
||||||
|
CHECK_EQ(maybe_delegate.ToChecked(), inspector->delegate());
|
||||||
|
inspector->Dispatch(ToProtocolString(env->isolate(), info[0])->string());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disconnect(const FunctionCallbackInfo<Value>& info) {
|
||||||
|
Environment* env = Environment::GetCurrent(info);
|
||||||
|
Maybe<JsBindingsSessionDelegate*> delegate = GetDelegate(info);
|
||||||
|
if (delegate.IsNothing()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delegate.ToChecked()->Disconnect();
|
||||||
|
SetDelegate(env, info.This(), nullptr);
|
||||||
|
delete delegate.ToChecked();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConnectJSBindingsSession(const FunctionCallbackInfo<Value>& info) {
|
||||||
|
Environment* env = Environment::GetCurrent(info);
|
||||||
|
if (!info[0]->IsFunction()) {
|
||||||
|
env->ThrowError("Message callback is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Agent* inspector = env->inspector_agent();
|
||||||
|
if (inspector->delegate() != nullptr) {
|
||||||
|
env->ThrowError("Session is already attached");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Local<Object> session = Object::New(env->isolate());
|
||||||
|
env->SetMethod(session, "dispatch", Dispatch);
|
||||||
|
env->SetMethod(session, "disconnect", Disconnect);
|
||||||
|
info.GetReturnValue().Set(session);
|
||||||
|
|
||||||
|
JsBindingsSessionDelegate* delegate =
|
||||||
|
new JsBindingsSessionDelegate(env, session, info.Holder(),
|
||||||
|
info[0].As<Function>());
|
||||||
|
inspector->Connect(delegate);
|
||||||
|
SetDelegate(env, session, delegate);
|
||||||
|
}
|
||||||
|
|
||||||
void InspectorConsoleCall(const v8::FunctionCallbackInfo<Value>& info) {
|
void InspectorConsoleCall(const v8::FunctionCallbackInfo<Value>& info) {
|
||||||
Isolate* isolate = info.GetIsolate();
|
Isolate* isolate = info.GetIsolate();
|
||||||
HandleScope handle_scope(isolate);
|
HandleScope handle_scope(isolate);
|
||||||
@ -229,7 +371,6 @@ void CallAndPauseOnStart(
|
|||||||
call_args.size(), call_args.data());
|
call_args.size(), call_args.data());
|
||||||
args.GetReturnValue().Set(retval.ToLocalChecked());
|
args.GetReturnValue().Set(retval.ToLocalChecked());
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// Used in NodeInspectorClient::currentTimeMS() below.
|
// Used in NodeInspectorClient::currentTimeMS() below.
|
||||||
const int NANOS_PER_MSEC = 1000000;
|
const int NANOS_PER_MSEC = 1000000;
|
||||||
@ -284,6 +425,8 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
|
|||||||
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
|
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
class NodeInspectorClient : public v8_inspector::V8InspectorClient {
|
class NodeInspectorClient : public v8_inspector::V8InspectorClient {
|
||||||
public:
|
public:
|
||||||
NodeInspectorClient(node::Environment* env,
|
NodeInspectorClient(node::Environment* env,
|
||||||
@ -510,6 +653,14 @@ void Agent::RunMessageLoop() {
|
|||||||
inspector_->runMessageLoopOnPause(CONTEXT_GROUP_ID);
|
inspector_->runMessageLoopOnPause(CONTEXT_GROUP_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InspectorSessionDelegate* Agent::delegate() {
|
||||||
|
CHECK_NE(inspector_, nullptr);
|
||||||
|
ChannelImpl* channel = inspector_->channel();
|
||||||
|
if (channel == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
return channel->delegate();
|
||||||
|
}
|
||||||
|
|
||||||
void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
|
void Agent::PauseOnNextJavascriptStatement(const std::string& reason) {
|
||||||
ChannelImpl* channel = inspector_->channel();
|
ChannelImpl* channel = inspector_->channel();
|
||||||
if (channel != nullptr)
|
if (channel != nullptr)
|
||||||
@ -524,6 +675,7 @@ void Agent::InitJSBindings(Local<Object> target, Local<Value> unused,
|
|||||||
env->SetMethod(target, "consoleCall", InspectorConsoleCall);
|
env->SetMethod(target, "consoleCall", InspectorConsoleCall);
|
||||||
if (agent->debug_options_.wait_for_connect())
|
if (agent->debug_options_.wait_for_connect())
|
||||||
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
|
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
|
||||||
|
env->SetMethod(target, "connect", ConnectJSBindingsSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Agent::RequestIoStart() {
|
void Agent::RequestIoStart() {
|
||||||
|
@ -59,6 +59,7 @@ class Agent {
|
|||||||
void FatalException(v8::Local<v8::Value> error,
|
void FatalException(v8::Local<v8::Value> error,
|
||||||
v8::Local<v8::Message> message);
|
v8::Local<v8::Message> message);
|
||||||
void Connect(InspectorSessionDelegate* delegate);
|
void Connect(InspectorSessionDelegate* delegate);
|
||||||
|
InspectorSessionDelegate* delegate();
|
||||||
void Disconnect();
|
void Disconnect();
|
||||||
void Dispatch(const v8_inspector::StringView& message);
|
void Dispatch(const v8_inspector::StringView& message);
|
||||||
void RunMessageLoop();
|
void RunMessageLoop();
|
||||||
|
105
test/inspector/test-bindings.js
Normal file
105
test/inspector/test-bindings.js
Normal file
@ -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…
x
Reference in New Issue
Block a user