1 |
|
|
#include "async_wrap-inl.h" |
2 |
|
|
#include "base_object-inl.h" |
3 |
|
|
#include "inspector_agent.h" |
4 |
|
|
#include "inspector_io.h" |
5 |
|
|
#include "memory_tracker-inl.h" |
6 |
|
|
#include "node_external_reference.h" |
7 |
|
|
#include "util-inl.h" |
8 |
|
|
#include "v8-inspector.h" |
9 |
|
|
#include "v8.h" |
10 |
|
|
|
11 |
|
|
#include <memory> |
12 |
|
|
|
13 |
|
|
namespace node { |
14 |
|
|
namespace inspector { |
15 |
|
|
namespace { |
16 |
|
|
|
17 |
|
|
using v8::Context; |
18 |
|
|
using v8::Function; |
19 |
|
|
using v8::FunctionCallbackInfo; |
20 |
|
|
using v8::FunctionTemplate; |
21 |
|
|
using v8::Global; |
22 |
|
|
using v8::HandleScope; |
23 |
|
|
using v8::Isolate; |
24 |
|
|
using v8::Local; |
25 |
|
|
using v8::MaybeLocal; |
26 |
|
|
using v8::NewStringType; |
27 |
|
|
using v8::Object; |
28 |
|
|
using v8::String; |
29 |
|
|
using v8::Uint32; |
30 |
|
|
using v8::Value; |
31 |
|
|
|
32 |
|
|
using v8_inspector::StringBuffer; |
33 |
|
|
using v8_inspector::StringView; |
34 |
|
|
|
35 |
|
1241 |
std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate, |
36 |
|
|
Local<Value> value) { |
37 |
|
1241 |
TwoByteValue buffer(isolate, value); |
38 |
|
1241 |
return StringBuffer::create(StringView(*buffer, buffer.length())); |
39 |
|
|
} |
40 |
|
|
|
41 |
|
|
struct LocalConnection { |
42 |
|
924 |
static std::unique_ptr<InspectorSession> Connect( |
43 |
|
|
Agent* inspector, std::unique_ptr<InspectorSessionDelegate> delegate) { |
44 |
|
924 |
return inspector->Connect(std::move(delegate), false); |
45 |
|
|
} |
46 |
|
|
|
47 |
|
827 |
static Local<String> GetClassName(Environment* env) { |
48 |
|
827 |
return FIXED_ONE_BYTE_STRING(env->isolate(), "Connection"); |
49 |
|
|
} |
50 |
|
|
}; |
51 |
|
|
|
52 |
|
|
struct MainThreadConnection { |
53 |
|
2 |
static std::unique_ptr<InspectorSession> Connect( |
54 |
|
|
Agent* inspector, std::unique_ptr<InspectorSessionDelegate> delegate) { |
55 |
|
2 |
return inspector->ConnectToMainThread(std::move(delegate), true); |
56 |
|
|
} |
57 |
|
|
|
58 |
|
827 |
static Local<String> GetClassName(Environment* env) { |
59 |
|
827 |
return FIXED_ONE_BYTE_STRING(env->isolate(), "MainThreadConnection"); |
60 |
|
|
} |
61 |
|
|
}; |
62 |
|
|
|
63 |
|
|
template <typename ConnectionType> |
64 |
|
|
class JSBindingsConnection : public AsyncWrap { |
65 |
|
|
public: |
66 |
|
|
class JSBindingsSessionDelegate : public InspectorSessionDelegate { |
67 |
|
|
public: |
68 |
|
1852 |
JSBindingsSessionDelegate(Environment* env, |
69 |
|
|
JSBindingsConnection* connection) |
70 |
|
|
: env_(env), |
71 |
|
1852 |
connection_(connection) { |
72 |
|
1852 |
} |
73 |
|
|
|
74 |
|
13732 |
void SendMessageToFrontend(const v8_inspector::StringView& message) |
75 |
|
|
override { |
76 |
|
13732 |
Isolate* isolate = env_->isolate(); |
77 |
|
13732 |
HandleScope handle_scope(isolate); |
78 |
|
27464 |
Context::Scope context_scope(env_->context()); |
79 |
|
|
Local<Value> argument; |
80 |
|
13732 |
if (!String::NewFromTwoByte(isolate, message.characters16(), |
81 |
|
|
NewStringType::kNormal, |
82 |
✗✓ |
27464 |
message.length()).ToLocal(&argument)) return; |
83 |
|
13732 |
connection_->OnMessage(argument); |
84 |
|
|
} |
85 |
|
|
|
86 |
|
|
private: |
87 |
|
|
Environment* env_; |
88 |
|
|
BaseObjectPtr<JSBindingsConnection> connection_; |
89 |
|
|
}; |
90 |
|
|
|
91 |
|
1852 |
JSBindingsConnection(Environment* env, |
92 |
|
|
Local<Object> wrap, |
93 |
|
|
Local<Function> callback) |
94 |
|
|
: AsyncWrap(env, wrap, PROVIDER_INSPECTORJSBINDING), |
95 |
|
1852 |
callback_(env->isolate(), callback) { |
96 |
|
1852 |
Agent* inspector = env->inspector_agent(); |
97 |
|
1852 |
session_ = ConnectionType::Connect( |
98 |
|
|
inspector, std::make_unique<JSBindingsSessionDelegate>(env, this)); |
99 |
|
1852 |
} |
100 |
|
|
|
101 |
|
13732 |
void OnMessage(Local<Value> value) { |
102 |
|
27464 |
MakeCallback(callback_.Get(env()->isolate()), 1, &value); |
103 |
|
13732 |
} |
104 |
|
|
|
105 |
|
3308 |
static void Bind(Environment* env, Local<Object> target) { |
106 |
|
3308 |
Isolate* isolate = env->isolate(); |
107 |
|
|
Local<FunctionTemplate> tmpl = |
108 |
|
3308 |
NewFunctionTemplate(isolate, JSBindingsConnection::New); |
109 |
|
6616 |
tmpl->InstanceTemplate()->SetInternalFieldCount( |
110 |
|
|
JSBindingsConnection::kInternalFieldCount); |
111 |
|
3308 |
tmpl->Inherit(AsyncWrap::GetConstructorTemplate(env)); |
112 |
|
3308 |
SetProtoMethod(isolate, tmpl, "dispatch", JSBindingsConnection::Dispatch); |
113 |
|
3308 |
SetProtoMethod( |
114 |
|
|
isolate, tmpl, "disconnect", JSBindingsConnection::Disconnect); |
115 |
|
3308 |
SetConstructorFunction( |
116 |
|
|
env->context(), target, ConnectionType::GetClassName(env), tmpl); |
117 |
|
3308 |
} |
118 |
|
|
|
119 |
|
1852 |
static void New(const FunctionCallbackInfo<Value>& info) { |
120 |
|
1852 |
Environment* env = Environment::GetCurrent(info); |
121 |
✗✓ |
1852 |
CHECK(info[0]->IsFunction()); |
122 |
|
3704 |
Local<Function> callback = info[0].As<Function>(); |
123 |
|
1852 |
new JSBindingsConnection(env, info.This(), callback); |
124 |
|
1852 |
} |
125 |
|
|
|
126 |
|
1822 |
void Disconnect() { |
127 |
|
1822 |
session_.reset(); |
128 |
✓✗ |
1822 |
delete this; |
129 |
|
|
} |
130 |
|
|
|
131 |
|
1822 |
static void Disconnect(const FunctionCallbackInfo<Value>& info) { |
132 |
|
|
JSBindingsConnection* session; |
133 |
✗✓ |
1822 |
ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder()); |
134 |
|
1822 |
session->Disconnect(); |
135 |
|
|
} |
136 |
|
|
|
137 |
|
2482 |
static void Dispatch(const FunctionCallbackInfo<Value>& info) { |
138 |
|
2482 |
Environment* env = Environment::GetCurrent(info); |
139 |
|
|
JSBindingsConnection* session; |
140 |
✗✓ |
2482 |
ASSIGN_OR_RETURN_UNWRAP(&session, info.Holder()); |
141 |
✗✓ |
4964 |
CHECK(info[0]->IsString()); |
142 |
|
|
|
143 |
✓✗ |
2482 |
if (session->session_) { |
144 |
✓✗ |
4964 |
session->session_->Dispatch( |
145 |
|
4964 |
ToProtocolString(env->isolate(), info[0])->string()); |
146 |
|
|
} |
147 |
|
|
} |
148 |
|
|
|
149 |
|
4 |
void MemoryInfo(MemoryTracker* tracker) const override { |
150 |
|
4 |
tracker->TrackField("callback", callback_); |
151 |
|
4 |
tracker->TrackFieldWithSize( |
152 |
|
|
"session", sizeof(*session_), "InspectorSession"); |
153 |
|
|
} |
154 |
|
|
|
155 |
|
4 |
SET_MEMORY_INFO_NAME(JSBindingsConnection) |
156 |
|
2 |
SET_SELF_SIZE(JSBindingsConnection) |
157 |
|
|
|
158 |
|
|
bool IsNotIndicativeOfMemoryLeakAtExit() const override { |
159 |
|
|
return true; // Binding connections emit events on their own. |
160 |
|
|
} |
161 |
|
|
|
162 |
|
|
private: |
163 |
|
|
std::unique_ptr<InspectorSession> session_; |
164 |
|
|
Global<Function> callback_; |
165 |
|
|
}; |
166 |
|
|
|
167 |
|
74699 |
static bool InspectorEnabled(Environment* env) { |
168 |
|
74699 |
Agent* agent = env->inspector_agent(); |
169 |
|
74699 |
return agent->IsActive(); |
170 |
|
|
} |
171 |
|
|
|
172 |
|
827 |
void SetConsoleExtensionInstaller(const FunctionCallbackInfo<Value>& info) { |
173 |
|
827 |
auto env = Environment::GetCurrent(info); |
174 |
|
|
|
175 |
✗✓ |
827 |
CHECK_EQ(info.Length(), 1); |
176 |
✗✓ |
827 |
CHECK(info[0]->IsFunction()); |
177 |
|
|
|
178 |
|
1654 |
env->set_inspector_console_extension_installer(info[0].As<Function>()); |
179 |
|
827 |
} |
180 |
|
|
|
181 |
|
8 |
void CallAndPauseOnStart(const FunctionCallbackInfo<v8::Value>& args) { |
182 |
|
8 |
Environment* env = Environment::GetCurrent(args); |
183 |
✗✓ |
8 |
CHECK_GT(args.Length(), 1); |
184 |
✗✓ |
8 |
CHECK(args[0]->IsFunction()); |
185 |
|
14 |
SlicedArguments call_args(args, /* start */ 2); |
186 |
✓✗ |
8 |
env->inspector_agent()->PauseOnNextJavascriptStatement("Break on start"); |
187 |
|
|
v8::MaybeLocal<v8::Value> retval = |
188 |
|
22 |
args[0].As<v8::Function>()->Call(env->context(), args[1], |
189 |
✓✗ |
16 |
call_args.length(), call_args.out()); |
190 |
✓✓ |
6 |
if (!retval.IsEmpty()) { |
191 |
✗✓ |
10 |
args.GetReturnValue().Set(retval.ToLocalChecked()); |
192 |
|
|
} |
193 |
|
6 |
} |
194 |
|
|
|
195 |
|
74691 |
void InspectorConsoleCall(const FunctionCallbackInfo<Value>& info) { |
196 |
|
74691 |
Environment* env = Environment::GetCurrent(info); |
197 |
|
74691 |
Isolate* isolate = env->isolate(); |
198 |
|
74691 |
Local<Context> context = isolate->GetCurrentContext(); |
199 |
✗✓ |
74691 |
CHECK_GE(info.Length(), 2); |
200 |
|
74691 |
SlicedArguments call_args(info, /* start */ 2); |
201 |
✓✓ |
74691 |
if (InspectorEnabled(env)) { |
202 |
|
74690 |
Local<Value> inspector_method = info[0]; |
203 |
✗✓ |
74690 |
CHECK(inspector_method->IsFunction()); |
204 |
✓✓ |
74690 |
if (!env->is_in_inspector_console_call()) { |
205 |
|
74689 |
env->set_is_in_inspector_console_call(true); |
206 |
|
|
MaybeLocal<Value> ret = |
207 |
|
74689 |
inspector_method.As<Function>()->Call(context, |
208 |
|
|
info.Holder(), |
209 |
|
74689 |
call_args.length(), |
210 |
|
224067 |
call_args.out()); |
211 |
|
74689 |
env->set_is_in_inspector_console_call(false); |
212 |
✓✓ |
74689 |
if (ret.IsEmpty()) |
213 |
|
9 |
return; |
214 |
|
|
} |
215 |
|
|
} |
216 |
|
|
|
217 |
|
74682 |
Local<Value> node_method = info[1]; |
218 |
✗✓ |
74682 |
CHECK(node_method->IsFunction()); |
219 |
|
149364 |
USE(node_method.As<Function>()->Call(context, |
220 |
|
|
info.Holder(), |
221 |
|
74682 |
call_args.length(), |
222 |
|
298728 |
call_args.out())); |
223 |
|
|
} |
224 |
|
|
|
225 |
|
25 |
static void* GetAsyncTask(int64_t asyncId) { |
226 |
|
|
// The inspector assumes that when other clients use its asyncTask* API, |
227 |
|
|
// they use real pointers, or at least something aligned like real pointer. |
228 |
|
|
// In general it means that our task_id should always be even. |
229 |
|
|
// |
230 |
|
|
// On 32bit platforms, the 64bit asyncId would get truncated when converted |
231 |
|
|
// to a 32bit pointer. However, the javascript part will never enable |
232 |
|
|
// the async_hook on 32bit platforms, therefore the truncation will never |
233 |
|
|
// happen in practice. |
234 |
|
25 |
return reinterpret_cast<void*>(asyncId << 1); |
235 |
|
|
} |
236 |
|
|
|
237 |
|
|
template <void (Agent::*asyncTaskFn)(void*)> |
238 |
|
44 |
static void InvokeAsyncTaskFnWithId(const FunctionCallbackInfo<Value>& args) { |
239 |
|
44 |
Environment* env = Environment::GetCurrent(args); |
240 |
✗✓ |
44 |
CHECK(args[0]->IsNumber()); |
241 |
|
44 |
int64_t task_id = args[0]->IntegerValue(env->context()).FromJust(); |
242 |
|
44 |
(env->inspector_agent()->*asyncTaskFn)(GetAsyncTask(task_id)); |
243 |
|
44 |
} |
244 |
|
|
|
245 |
|
3 |
static void AsyncTaskScheduledWrapper(const FunctionCallbackInfo<Value>& args) { |
246 |
|
3 |
Environment* env = Environment::GetCurrent(args); |
247 |
|
|
|
248 |
✗✓ |
6 |
CHECK(args[0]->IsString()); |
249 |
|
6 |
Local<String> task_name = args[0].As<String>(); |
250 |
|
6 |
String::Value task_name_value(args.GetIsolate(), task_name); |
251 |
|
3 |
StringView task_name_view(*task_name_value, task_name_value.length()); |
252 |
|
|
|
253 |
✗✓ |
3 |
CHECK(args[1]->IsNumber()); |
254 |
|
3 |
int64_t task_id = args[1]->IntegerValue(env->context()).FromJust(); |
255 |
|
3 |
void* task = GetAsyncTask(task_id); |
256 |
|
|
|
257 |
✗✓ |
3 |
CHECK(args[2]->IsBoolean()); |
258 |
|
3 |
bool recurring = args[2]->BooleanValue(args.GetIsolate()); |
259 |
|
|
|
260 |
|
3 |
env->inspector_agent()->AsyncTaskScheduled(task_name_view, task, recurring); |
261 |
|
3 |
} |
262 |
|
|
|
263 |
|
6497 |
static void RegisterAsyncHookWrapper(const FunctionCallbackInfo<Value>& args) { |
264 |
|
6497 |
Environment* env = Environment::GetCurrent(args); |
265 |
|
|
|
266 |
✗✓ |
6497 |
CHECK(args[0]->IsFunction()); |
267 |
✓✗ |
12994 |
Local<Function> enable_function = args[0].As<Function>(); |
268 |
✗✓ |
6497 |
CHECK(args[1]->IsFunction()); |
269 |
|
6497 |
Local<Function> disable_function = args[1].As<Function>(); |
270 |
|
6497 |
env->inspector_agent()->RegisterAsyncHook(env->isolate(), |
271 |
|
|
enable_function, disable_function); |
272 |
|
6497 |
} |
273 |
|
|
|
274 |
|
8 |
void IsEnabled(const FunctionCallbackInfo<Value>& args) { |
275 |
|
8 |
Environment* env = Environment::GetCurrent(args); |
276 |
✓✓ |
8 |
args.GetReturnValue().Set(InspectorEnabled(env)); |
277 |
|
8 |
} |
278 |
|
|
|
279 |
|
|
void Open(const FunctionCallbackInfo<Value>& args) { |
280 |
|
|
Environment* env = Environment::GetCurrent(args); |
281 |
|
|
Agent* agent = env->inspector_agent(); |
282 |
|
|
|
283 |
|
|
if (args.Length() > 0 && args[0]->IsUint32()) { |
284 |
|
|
uint32_t port = args[0].As<Uint32>()->Value(); |
285 |
|
|
CHECK_LE(port, std::numeric_limits<uint16_t>::max()); |
286 |
|
|
ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port()); |
287 |
|
|
host_port->set_port(static_cast<int>(port)); |
288 |
|
|
} |
289 |
|
|
|
290 |
|
|
if (args.Length() > 1 && args[1]->IsString()) { |
291 |
|
|
Utf8Value host(env->isolate(), args[1].As<String>()); |
292 |
|
|
ExclusiveAccess<HostPort>::Scoped host_port(agent->host_port()); |
293 |
|
|
host_port->set_host(*host); |
294 |
|
|
} |
295 |
|
|
|
296 |
|
|
agent->StartIoThread(); |
297 |
|
|
} |
298 |
|
|
|
299 |
|
|
void WaitForDebugger(const FunctionCallbackInfo<Value>& args) { |
300 |
|
|
Environment* env = Environment::GetCurrent(args); |
301 |
|
|
Agent* agent = env->inspector_agent(); |
302 |
|
|
if (agent->IsActive()) |
303 |
|
|
agent->WaitForConnect(); |
304 |
|
|
args.GetReturnValue().Set(agent->IsActive()); |
305 |
|
|
} |
306 |
|
|
|
307 |
|
6 |
void Url(const FunctionCallbackInfo<Value>& args) { |
308 |
|
6 |
Environment* env = Environment::GetCurrent(args); |
309 |
|
6 |
std::string url = env->inspector_agent()->GetWsUrl(); |
310 |
✓✓ |
6 |
if (url.empty()) { |
311 |
|
1 |
return; |
312 |
|
|
} |
313 |
|
10 |
args.GetReturnValue().Set(OneByteString(env->isolate(), url.c_str())); |
314 |
|
|
} |
315 |
|
|
|
316 |
|
827 |
void Initialize(Local<Object> target, Local<Value> unused, |
317 |
|
|
Local<Context> context, void* priv) { |
318 |
|
827 |
Environment* env = Environment::GetCurrent(context); |
319 |
|
827 |
Isolate* isolate = env->isolate(); |
320 |
|
|
|
321 |
|
|
v8::Local<v8::Function> consoleCallFunc = |
322 |
|
827 |
NewFunctionTemplate(isolate, |
323 |
|
|
InspectorConsoleCall, |
324 |
|
|
v8::Local<v8::Signature>(), |
325 |
|
|
v8::ConstructorBehavior::kThrow, |
326 |
|
827 |
v8::SideEffectType::kHasSideEffect) |
327 |
|
827 |
->GetFunction(context) |
328 |
|
827 |
.ToLocalChecked(); |
329 |
|
827 |
auto name_string = FIXED_ONE_BYTE_STRING(isolate, "consoleCall"); |
330 |
|
1654 |
target->Set(context, name_string, consoleCallFunc).Check(); |
331 |
|
827 |
consoleCallFunc->SetName(name_string); |
332 |
|
|
|
333 |
|
827 |
SetMethod(context, |
334 |
|
|
target, |
335 |
|
|
"setConsoleExtensionInstaller", |
336 |
|
|
SetConsoleExtensionInstaller); |
337 |
|
827 |
SetMethod(context, target, "callAndPauseOnStart", CallAndPauseOnStart); |
338 |
|
827 |
SetMethod(context, target, "open", Open); |
339 |
|
827 |
SetMethodNoSideEffect(context, target, "url", Url); |
340 |
|
827 |
SetMethod(context, target, "waitForDebugger", WaitForDebugger); |
341 |
|
|
|
342 |
|
827 |
SetMethod(context, target, "asyncTaskScheduled", AsyncTaskScheduledWrapper); |
343 |
|
827 |
SetMethod(context, |
344 |
|
|
target, |
345 |
|
|
"asyncTaskCanceled", |
346 |
|
|
InvokeAsyncTaskFnWithId<&Agent::AsyncTaskCanceled>); |
347 |
|
827 |
SetMethod(context, |
348 |
|
|
target, |
349 |
|
|
"asyncTaskStarted", |
350 |
|
|
InvokeAsyncTaskFnWithId<&Agent::AsyncTaskStarted>); |
351 |
|
827 |
SetMethod(context, |
352 |
|
|
target, |
353 |
|
|
"asyncTaskFinished", |
354 |
|
|
InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>); |
355 |
|
|
|
356 |
|
827 |
SetMethod(context, target, "registerAsyncHook", RegisterAsyncHookWrapper); |
357 |
|
827 |
SetMethodNoSideEffect(context, target, "isEnabled", IsEnabled); |
358 |
|
|
|
359 |
|
827 |
Local<String> console_string = FIXED_ONE_BYTE_STRING(isolate, "console"); |
360 |
|
|
|
361 |
|
|
// Grab the console from the binding object and expose those to our binding |
362 |
|
|
// layer. |
363 |
|
827 |
Local<Object> binding = context->GetExtrasBindingObject(); |
364 |
|
|
target |
365 |
|
827 |
->Set(context, |
366 |
|
|
console_string, |
367 |
|
1654 |
binding->Get(context, console_string).ToLocalChecked()) |
368 |
|
|
.Check(); |
369 |
|
|
|
370 |
|
827 |
JSBindingsConnection<LocalConnection>::Bind(env, target); |
371 |
|
827 |
JSBindingsConnection<MainThreadConnection>::Bind(env, target); |
372 |
|
827 |
} |
373 |
|
|
|
374 |
|
|
} // namespace |
375 |
|
|
|
376 |
|
5718 |
void RegisterExternalReferences(ExternalReferenceRegistry* registry) { |
377 |
|
5718 |
registry->Register(InspectorConsoleCall); |
378 |
|
5718 |
registry->Register(SetConsoleExtensionInstaller); |
379 |
|
5718 |
registry->Register(CallAndPauseOnStart); |
380 |
|
5718 |
registry->Register(Open); |
381 |
|
5718 |
registry->Register(Url); |
382 |
|
5718 |
registry->Register(WaitForDebugger); |
383 |
|
|
|
384 |
|
5718 |
registry->Register(AsyncTaskScheduledWrapper); |
385 |
|
5718 |
registry->Register(InvokeAsyncTaskFnWithId<&Agent::AsyncTaskCanceled>); |
386 |
|
5718 |
registry->Register(InvokeAsyncTaskFnWithId<&Agent::AsyncTaskStarted>); |
387 |
|
5718 |
registry->Register(InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>); |
388 |
|
|
|
389 |
|
5718 |
registry->Register(RegisterAsyncHookWrapper); |
390 |
|
5718 |
registry->Register(IsEnabled); |
391 |
|
|
|
392 |
|
5718 |
registry->Register(JSBindingsConnection<LocalConnection>::New); |
393 |
|
5718 |
registry->Register(JSBindingsConnection<LocalConnection>::Dispatch); |
394 |
|
5718 |
registry->Register(JSBindingsConnection<LocalConnection>::Disconnect); |
395 |
|
5718 |
registry->Register(JSBindingsConnection<MainThreadConnection>::New); |
396 |
|
5718 |
registry->Register(JSBindingsConnection<MainThreadConnection>::Dispatch); |
397 |
|
5718 |
registry->Register(JSBindingsConnection<MainThreadConnection>::Disconnect); |
398 |
|
5718 |
} |
399 |
|
|
|
400 |
|
|
} // namespace inspector |
401 |
|
|
} // namespace node |
402 |
|
|
|
403 |
|
5789 |
NODE_BINDING_CONTEXT_AWARE_INTERNAL(inspector, node::inspector::Initialize) |
404 |
|
5718 |
NODE_BINDING_EXTERNAL_REFERENCE(inspector, |
405 |
|
|
node::inspector::RegisterExternalReferences) |