Skip to content

Commit b431a49

Browse files
TimothyGuevanlucas
authored andcommitted
inspector: reimplement JS binding
- Group all relevant methods/states into a C++ class. - Uses internal fields instead of the less efficient v8::External for storing the pointer to the C++ object. - Use AsyncWrap to allow instrumenting callback states. Backport-PR-URL: #16071 PR-URL: #15643 Refs: #13503 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Eugene Ostroukhov <eostroukhov@google.com> Reviewed-By: Trevor Norris <trev.norris@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 25c9803 commit b431a49

File tree

5 files changed

+123
-124
lines changed

5 files changed

+123
-124
lines changed

lib/inspector.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
const EventEmitter = require('events');
44
const util = require('util');
5-
const { connect, open, url } = process.binding('inspector');
5+
const { Connection, open, url } = process.binding('inspector');
66

7-
if (!connect)
7+
if (!Connection)
88
throw new Error('Inspector is not available');
99

1010
const connectionSymbol = Symbol('connectionProperty');
@@ -24,7 +24,7 @@ class Session extends EventEmitter {
2424
if (this[connectionSymbol])
2525
throw new Error('Already connected');
2626
this[connectionSymbol] =
27-
connect((message) => this[onMessageSymbol](message));
27+
new Connection((message) => this[onMessageSymbol](message));
2828
}
2929

3030
[onMessageSymbol](message) {

src/async-wrap.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,17 @@ namespace node {
7272
#define NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V)
7373
#endif // HAVE_OPENSSL
7474

75+
#if HAVE_INSPECTOR
76+
#define NODE_ASYNC_INSPECTOR_PROVIDER_TYPES(V) \
77+
V(INSPECTORJSBINDING)
78+
#else
79+
#define NODE_ASYNC_INSPECTOR_PROVIDER_TYPES(V)
80+
#endif // HAVE_INSPECTOR
81+
7582
#define NODE_ASYNC_PROVIDER_TYPES(V) \
7683
NODE_ASYNC_NON_CRYPTO_PROVIDER_TYPES(V) \
77-
NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V)
84+
NODE_ASYNC_CRYPTO_PROVIDER_TYPES(V) \
85+
NODE_ASYNC_INSPECTOR_PROVIDER_TYPES(V)
7886

7987
class Environment;
8088

src/env.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ class ModuleWrap;
8989
V(arrow_message_private_symbol, "node:arrowMessage") \
9090
V(contextify_context_private_symbol, "node:contextify:context") \
9191
V(contextify_global_private_symbol, "node:contextify:global") \
92-
V(inspector_delegate_private_symbol, "node:inspector:delegate") \
9392
V(decorated_private_symbol, "node:decorated") \
9493
V(npn_buffer_private_symbol, "node:npnBuffer") \
9594
V(processed_private_symbol, "node:processed") \

src/inspector_agent.cc

Lines changed: 104 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "inspector_agent.h"
22

33
#include "inspector_io.h"
4+
#include "base-object.h"
5+
#include "base-object-inl.h"
46
#include "node_internals.h"
57
#include "v8-inspector.h"
68
#include "v8-platform.h"
@@ -26,9 +28,9 @@ using node::FatalError;
2628
using v8::Array;
2729
using v8::Boolean;
2830
using v8::Context;
29-
using v8::External;
3031
using v8::Function;
3132
using v8::FunctionCallbackInfo;
33+
using v8::FunctionTemplate;
3234
using v8::HandleScope;
3335
using v8::Integer;
3436
using v8::Isolate;
@@ -191,143 +193,117 @@ static int StartDebugSignalHandler() {
191193
}
192194
#endif // _WIN32
193195

194-
class JsBindingsSessionDelegate : public InspectorSessionDelegate {
196+
class JSBindingsConnection : public AsyncWrap {
195197
public:
196-
JsBindingsSessionDelegate(Environment* env,
197-
Local<Object> session,
198-
Local<Object> receiver,
199-
Local<Function> callback)
200-
: env_(env),
201-
session_(env->isolate(), session),
202-
receiver_(env->isolate(), receiver),
203-
callback_(env->isolate(), callback) {
204-
session_.SetWeak(this, JsBindingsSessionDelegate::Release,
205-
v8::WeakCallbackType::kParameter);
206-
}
207-
208-
~JsBindingsSessionDelegate() override {
209-
session_.Reset();
210-
receiver_.Reset();
211-
callback_.Reset();
212-
}
198+
class JSBindingsSessionDelegate : public InspectorSessionDelegate {
199+
public:
200+
JSBindingsSessionDelegate(Environment* env,
201+
JSBindingsConnection* connection)
202+
: env_(env),
203+
connection_(connection) {
204+
}
213205

214-
bool WaitForFrontendMessageWhilePaused() override {
215-
return false;
216-
}
206+
bool WaitForFrontendMessageWhilePaused() override {
207+
return false;
208+
}
217209

218-
void SendMessageToFrontend(const v8_inspector::StringView& message) override {
219-
Isolate* isolate = env_->isolate();
220-
v8::HandleScope handle_scope(isolate);
221-
Context::Scope context_scope(env_->context());
222-
MaybeLocal<String> v8string =
223-
String::NewFromTwoByte(isolate, message.characters16(),
224-
NewStringType::kNormal, message.length());
225-
Local<Value> argument = v8string.ToLocalChecked().As<Value>();
226-
Local<Function> callback = callback_.Get(isolate);
227-
Local<Object> receiver = receiver_.Get(isolate);
228-
callback->Call(env_->context(), receiver, 1, &argument)
229-
.FromMaybe(Local<Value>());
230-
}
210+
void SendMessageToFrontend(const v8_inspector::StringView& message)
211+
override {
212+
Isolate* isolate = env_->isolate();
213+
HandleScope handle_scope(isolate);
214+
Context::Scope context_scope(env_->context());
215+
MaybeLocal<String> v8string =
216+
String::NewFromTwoByte(isolate, message.characters16(),
217+
NewStringType::kNormal, message.length());
218+
Local<Value> argument = v8string.ToLocalChecked().As<Value>();
219+
connection_->OnMessage(argument);
220+
}
231221

232-
void Disconnect() {
233-
Agent* agent = env_->inspector_agent();
234-
if (agent->delegate() == this)
235-
agent->Disconnect();
236-
}
222+
void Disconnect() {
223+
Agent* agent = env_->inspector_agent();
224+
if (agent->delegate() == this)
225+
agent->Disconnect();
226+
}
237227

238-
private:
239-
static void Release(
240-
const v8::WeakCallbackInfo<JsBindingsSessionDelegate>& info) {
241-
info.SetSecondPassCallback(ReleaseSecondPass);
242-
info.GetParameter()->session_.Reset();
228+
private:
229+
Environment* env_;
230+
JSBindingsConnection* connection_;
231+
};
232+
233+
JSBindingsConnection(Environment* env,
234+
Local<Object> wrap,
235+
Local<Function> callback)
236+
: AsyncWrap(env, wrap, PROVIDER_INSPECTORJSBINDING),
237+
delegate_(env, this),
238+
callback_(env->isolate(), callback) {
239+
Wrap(wrap, this);
240+
241+
Agent* inspector = env->inspector_agent();
242+
if (inspector->delegate() != nullptr) {
243+
env->ThrowTypeError("Session is already attached");
244+
return;
245+
}
246+
inspector->Connect(&delegate_);
243247
}
244248

245-
static void ReleaseSecondPass(
246-
const v8::WeakCallbackInfo<JsBindingsSessionDelegate>& info) {
247-
JsBindingsSessionDelegate* delegate = info.GetParameter();
248-
delegate->Disconnect();
249-
delete delegate;
249+
~JSBindingsConnection() override {
250+
callback_.Reset();
250251
}
251252

252-
Environment* env_;
253-
Persistent<Object> session_;
254-
Persistent<Object> receiver_;
255-
Persistent<Function> callback_;
256-
};
253+
void OnMessage(Local<Value> value) {
254+
MakeCallback(callback_.Get(env()->isolate()), 1, &value);
255+
}
257256

258-
void SetDelegate(Environment* env, Local<Object> inspector,
259-
JsBindingsSessionDelegate* delegate) {
260-
inspector->SetPrivate(env->context(),
261-
env->inspector_delegate_private_symbol(),
262-
v8::External::New(env->isolate(), delegate));
263-
}
257+
void CheckIsCurrent() {
258+
Agent* inspector = env()->inspector_agent();
259+
CHECK_EQ(&delegate_, inspector->delegate());
260+
}
264261

265-
Maybe<JsBindingsSessionDelegate*> GetDelegate(
266-
const FunctionCallbackInfo<Value>& info) {
267-
Environment* env = Environment::GetCurrent(info);
268-
Local<Value> delegate;
269-
MaybeLocal<Value> maybe_delegate =
270-
info.This()->GetPrivate(env->context(),
271-
env->inspector_delegate_private_symbol());
272-
273-
if (maybe_delegate.ToLocal(&delegate)) {
274-
CHECK(delegate->IsExternal());
275-
void* value = delegate.As<External>()->Value();
276-
if (value != nullptr) {
277-
return v8::Just(static_cast<JsBindingsSessionDelegate*>(value));
262+
static void New(const FunctionCallbackInfo<Value>& info) {
263+
Environment* env = Environment::GetCurrent(info);
264+
if (!info[0]->IsFunction()) {
265+
env->ThrowTypeError("Message callback is required");
266+
return;
278267
}
268+
Local<Function> callback = info[0].As<Function>();
269+
new JSBindingsConnection(env, info.This(), callback);
279270
}
280-
env->ThrowError("Inspector is not connected");
281-
return v8::Nothing<JsBindingsSessionDelegate*>();
282-
}
283271

284-
void Dispatch(const FunctionCallbackInfo<Value>& info) {
285-
Environment* env = Environment::GetCurrent(info);
286-
if (!info[0]->IsString()) {
287-
env->ThrowError("Inspector message must be a string");
288-
return;
272+
void Disconnect() {
273+
delegate_.Disconnect();
274+
if (!persistent().IsEmpty()) {
275+
ClearWrap(object());
276+
persistent().Reset();
277+
}
278+
delete this;
289279
}
290-
Maybe<JsBindingsSessionDelegate*> maybe_delegate = GetDelegate(info);
291-
if (maybe_delegate.IsNothing())
292-
return;
293-
Agent* inspector = env->inspector_agent();
294-
CHECK_EQ(maybe_delegate.ToChecked(), inspector->delegate());
295-
inspector->Dispatch(ToProtocolString(env->isolate(), info[0])->string());
296-
}
297280

298-
void Disconnect(const FunctionCallbackInfo<Value>& info) {
299-
Environment* env = Environment::GetCurrent(info);
300-
Maybe<JsBindingsSessionDelegate*> delegate = GetDelegate(info);
301-
if (delegate.IsNothing()) {
302-
return;
281+
static void Disconnect(const FunctionCallbackInfo<Value>& info) {
282+
JSBindingsConnection* session;
283+
ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
284+
session->Disconnect();
303285
}
304-
delegate.ToChecked()->Disconnect();
305-
SetDelegate(env, info.This(), nullptr);
306-
delete delegate.ToChecked();
307-
}
308286

309-
void ConnectJSBindingsSession(const FunctionCallbackInfo<Value>& info) {
310-
Environment* env = Environment::GetCurrent(info);
311-
if (!info[0]->IsFunction()) {
312-
env->ThrowError("Message callback is required");
313-
return;
314-
}
315-
Agent* inspector = env->inspector_agent();
316-
if (inspector->delegate() != nullptr) {
317-
env->ThrowError("Session is already attached");
318-
return;
287+
static void Dispatch(const FunctionCallbackInfo<Value>& info) {
288+
Environment* env = Environment::GetCurrent(info);
289+
JSBindingsConnection* session;
290+
ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder());
291+
if (!info[0]->IsString()) {
292+
env->ThrowTypeError("Inspector message must be a string");
293+
return;
294+
}
295+
296+
session->CheckIsCurrent();
297+
Agent* inspector = env->inspector_agent();
298+
inspector->Dispatch(ToProtocolString(env->isolate(), info[0])->string());
319299
}
320-
Local<Object> session = Object::New(env->isolate());
321-
env->SetMethod(session, "dispatch", Dispatch);
322-
env->SetMethod(session, "disconnect", Disconnect);
323-
info.GetReturnValue().Set(session);
324300

325-
JsBindingsSessionDelegate* delegate =
326-
new JsBindingsSessionDelegate(env, session, info.Holder(),
327-
info[0].As<Function>());
328-
inspector->Connect(delegate);
329-
SetDelegate(env, session, delegate);
330-
}
301+
size_t self_size() const override { return sizeof(*this); }
302+
303+
private:
304+
JSBindingsSessionDelegate delegate_;
305+
Persistent<Function> callback_;
306+
};
331307

332308
void InspectorConsoleCall(const v8::FunctionCallbackInfo<Value>& info) {
333309
Isolate* isolate = info.GetIsolate();
@@ -973,7 +949,6 @@ void Agent::InitInspector(Local<Object> target, Local<Value> unused,
973949
env->SetMethod(target, "addCommandLineAPI", AddCommandLineAPI);
974950
if (agent->debug_options_.wait_for_connect())
975951
env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart);
976-
env->SetMethod(target, "connect", ConnectJSBindingsSession);
977952
env->SetMethod(target, "open", Open);
978953
env->SetMethod(target, "url", Url);
979954

@@ -987,6 +962,16 @@ void Agent::InitInspector(Local<Object> target, Local<Value> unused,
987962

988963
env->SetMethod(target, "registerAsyncHook", RegisterAsyncHookWrapper);
989964
env->SetMethod(target, "isEnabled", IsEnabled);
965+
966+
auto conn_str = FIXED_ONE_BYTE_STRING(env->isolate(), "Connection");
967+
Local<FunctionTemplate> tmpl =
968+
env->NewFunctionTemplate(JSBindingsConnection::New);
969+
tmpl->InstanceTemplate()->SetInternalFieldCount(1);
970+
tmpl->SetClassName(conn_str);
971+
AsyncWrap::AddWrapMethods(env, tmpl);
972+
env->SetProtoMethod(tmpl, "dispatch", JSBindingsConnection::Dispatch);
973+
env->SetProtoMethod(tmpl, "disconnect", JSBindingsConnection::Disconnect);
974+
target->Set(env->context(), conn_str, tmpl->GetFunction()).ToChecked();
990975
}
991976

992977
void Agent::RequestIoThreadStart() {

test/sequential/test-async-wrap-getasyncid.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,10 @@ if (common.hasCrypto) { // eslint-disable-line crypto-check
273273
handle.send(req, [Buffer.alloc(1)], 1, req.port, req.address, true);
274274
testInitialized(req, 'SendWrap');
275275
}
276+
277+
if (process.config.variables.v8_enable_inspector !== 0) {
278+
const binding = process.binding('inspector');
279+
const handle = new binding.Connection(() => {});
280+
testInitialized(handle, 'Connection');
281+
handle.disconnect();
282+
}

0 commit comments

Comments
 (0)