Skip to content

Commit 60d6e04

Browse files
alexkozycjihrig
authored andcommitted
deps: v8_inspector: console support
When node is running with --inspect flag, default console.log, console.warn and other methods call inspector console methods in addition to current behaviour (dump formatted message to stderr and stdout). Inspector console methods forward message to DevTools and show up in DevTools Console with DevTools formatters. Inspector console methods not present on Node console will be added into it. Only own methods on global.console object will be changed while in a debugging session. User are still able to redefine it, use console.Console or change original methods on Console.prototype. PR-URL: #7988 Reviewed-By: bnoordhuis - Ben Noordhuis <[email protected]> Reviewed-By: jasnell - James M Snell <[email protected]> Reviewed-By: ofrobots - Ali Ijaz Sheikh <[email protected]>
1 parent a9fe85e commit 60d6e04

File tree

2 files changed

+130
-1
lines changed

2 files changed

+130
-1
lines changed

lib/internal/bootstrap_node.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,15 +238,52 @@
238238
}
239239

240240
function setupGlobalConsole() {
241+
var inspectorConsole;
242+
var wrapConsoleCall;
243+
if (process.inspector) {
244+
inspectorConsole = global.console;
245+
wrapConsoleCall = process.inspector.wrapConsoleCall;
246+
delete process.inspector;
247+
}
248+
var console;
241249
Object.defineProperty(global, 'console', {
242250
configurable: true,
243251
enumerable: true,
244252
get: function() {
245-
return NativeModule.require('console');
253+
if (!console) {
254+
console = NativeModule.require('console');
255+
installInspectorConsoleIfNeeded(console,
256+
inspectorConsole,
257+
wrapConsoleCall);
258+
}
259+
return console;
246260
}
247261
});
248262
}
249263

264+
function installInspectorConsoleIfNeeded(console,
265+
inspectorConsole,
266+
wrapConsoleCall) {
267+
if (!inspectorConsole)
268+
return;
269+
var config = {};
270+
for (const key of Object.keys(console)) {
271+
if (!inspectorConsole.hasOwnProperty(key))
272+
continue;
273+
// If node console has the same method as inspector console,
274+
// then wrap these two methods into one. Native wrapper will preserve
275+
// the original stack.
276+
console[key] = wrapConsoleCall(inspectorConsole[key],
277+
console[key],
278+
config);
279+
}
280+
for (const key of Object.keys(inspectorConsole)) {
281+
if (console.hasOwnProperty(key))
282+
continue;
283+
console[key] = inspectorConsole[key];
284+
}
285+
}
286+
250287
function setupProcessFatal() {
251288

252289
process._fatalException = function(er) {

src/inspector_agent.cc

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ class AgentImpl {
186186
const std::string& path);
187187
static void WriteCbIO(uv_async_t* async);
188188

189+
void InstallInspectorOnProcess();
190+
189191
void WorkerRunIO();
190192
void OnInspectorConnectionIO(inspector_socket_t* socket);
191193
void OnRemoteDataIO(inspector_socket_t* stream, ssize_t read,
@@ -276,6 +278,9 @@ class ChannelImpl final : public blink::protocol::FrontendChannel {
276278
AgentImpl* const agent_;
277279
};
278280

281+
// Used in V8NodeInspector::currentTimeMS() below.
282+
#define NANOS_PER_MSEC 1000000
283+
279284
class V8NodeInspector : public blink::V8InspectorClient {
280285
public:
281286
V8NodeInspector(AgentImpl* agent, node::Environment* env,
@@ -308,6 +313,10 @@ class V8NodeInspector : public blink::V8InspectorClient {
308313
running_nested_loop_ = false;
309314
}
310315

316+
double currentTimeMS() override {
317+
return uv_hrtime() * 1.0 / NANOS_PER_MSEC;
318+
}
319+
311320
void quitMessageLoopOnPause() override {
312321
terminated_ = true;
313322
}
@@ -361,11 +370,78 @@ AgentImpl::~AgentImpl() {
361370
data_written_ = nullptr;
362371
}
363372

373+
void InspectorConsoleCall(const v8::FunctionCallbackInfo<v8::Value>& info) {
374+
v8::Isolate* isolate = info.GetIsolate();
375+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
376+
377+
CHECK(info.Data()->IsArray());
378+
v8::Local<v8::Array> args = info.Data().As<v8::Array>();
379+
CHECK_EQ(args->Length(), 3);
380+
381+
v8::Local<v8::Value> inspector_method =
382+
args->Get(context, 0).ToLocalChecked();
383+
CHECK(inspector_method->IsFunction());
384+
v8::Local<v8::Value> node_method =
385+
args->Get(context, 1).ToLocalChecked();
386+
CHECK(node_method->IsFunction());
387+
v8::Local<v8::Value> config_value =
388+
args->Get(context, 2).ToLocalChecked();
389+
CHECK(config_value->IsObject());
390+
v8::Local<v8::Object> config_object = config_value.As<v8::Object>();
391+
392+
std::vector<v8::Local<v8::Value>> call_args(info.Length());
393+
for (int i = 0; i < info.Length(); ++i) {
394+
call_args[i] = info[i];
395+
}
396+
397+
v8::Local<v8::String> in_call_key = OneByteString(isolate, "in_call");
398+
bool in_call = config_object->Has(context, in_call_key).FromMaybe(false);
399+
if (!in_call) {
400+
CHECK(config_object->Set(context,
401+
in_call_key,
402+
v8::True(isolate)).FromJust());
403+
CHECK(!inspector_method.As<v8::Function>()->Call(
404+
context,
405+
info.Holder(),
406+
call_args.size(),
407+
call_args.data()).IsEmpty());
408+
}
409+
410+
v8::TryCatch try_catch(info.GetIsolate());
411+
node_method.As<v8::Function>()->Call(context,
412+
info.Holder(),
413+
call_args.size(),
414+
call_args.data());
415+
CHECK(config_object->Delete(context, in_call_key).FromJust());
416+
if (try_catch.HasCaught())
417+
try_catch.ReThrow();
418+
}
419+
420+
void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo<v8::Value>& args) {
421+
Environment* env = Environment::GetCurrent(args);
422+
423+
if (args.Length() != 3 || !args[0]->IsFunction() ||
424+
!args[1]->IsFunction() || !args[2]->IsObject()) {
425+
return env->ThrowError("inspector.wrapConsoleCall takes exactly 3 "
426+
"arguments: two functions and an object.");
427+
}
428+
429+
v8::Local<v8::Array> array = v8::Array::New(env->isolate(), args.Length());
430+
CHECK(array->Set(env->context(), 0, args[0]).FromJust());
431+
CHECK(array->Set(env->context(), 1, args[1]).FromJust());
432+
CHECK(array->Set(env->context(), 2, args[2]).FromJust());
433+
args.GetReturnValue().Set(v8::Function::New(env->context(),
434+
InspectorConsoleCall,
435+
array).ToLocalChecked());
436+
}
437+
364438
bool AgentImpl::Start(v8::Platform* platform, int port, bool wait) {
365439
auto env = parent_env_;
366440
inspector_ = new V8NodeInspector(this, env, platform);
367441
platform_ = platform;
368442

443+
InstallInspectorOnProcess();
444+
369445
int err = uv_loop_init(&child_loop_);
370446
CHECK_EQ(err, 0);
371447

@@ -403,6 +479,22 @@ void AgentImpl::WaitForDisconnect() {
403479
inspector_->runMessageLoopOnPause(0);
404480
}
405481

482+
#define READONLY_PROPERTY(obj, str, var) \
483+
do { \
484+
obj->DefineOwnProperty(env->context(), \
485+
OneByteString(env->isolate(), str), \
486+
var, \
487+
v8::ReadOnly).FromJust(); \
488+
} while (0)
489+
490+
void AgentImpl::InstallInspectorOnProcess() {
491+
auto env = parent_env_;
492+
v8::Local<v8::Object> process = env->process_object();
493+
v8::Local<v8::Object> inspector = v8::Object::New(env->isolate());
494+
READONLY_PROPERTY(process, "inspector", inspector);
495+
env->SetMethod(inspector, "wrapConsoleCall", InspectorWrapConsoleCall);
496+
}
497+
406498
// static
407499
void AgentImpl::ThreadCbIO(void* agent) {
408500
static_cast<AgentImpl*>(agent)->WorkerRunIO();

0 commit comments

Comments
 (0)