Skip to content

Commit f6070a1

Browse files
alexkozyofrobots
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 e40234d commit f6070a1

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
@@ -226,15 +226,52 @@
226226
}
227227

228228
function setupGlobalConsole() {
229+
var inspectorConsole;
230+
var wrapConsoleCall;
231+
if (process.inspector) {
232+
inspectorConsole = global.console;
233+
wrapConsoleCall = process.inspector.wrapConsoleCall;
234+
delete process.inspector;
235+
}
236+
var console;
229237
Object.defineProperty(global, 'console', {
230238
configurable: true,
231239
enumerable: true,
232240
get: function() {
233-
return NativeModule.require('console');
241+
if (!console) {
242+
console = NativeModule.require('console');
243+
installInspectorConsoleIfNeeded(console,
244+
inspectorConsole,
245+
wrapConsoleCall);
246+
}
247+
return console;
234248
}
235249
});
236250
}
237251

252+
function installInspectorConsoleIfNeeded(console,
253+
inspectorConsole,
254+
wrapConsoleCall) {
255+
if (!inspectorConsole)
256+
return;
257+
var config = {};
258+
for (const key of Object.keys(console)) {
259+
if (!inspectorConsole.hasOwnProperty(key))
260+
continue;
261+
// If node console has the same method as inspector console,
262+
// then wrap these two methods into one. Native wrapper will preserve
263+
// the original stack.
264+
console[key] = wrapConsoleCall(inspectorConsole[key],
265+
console[key],
266+
config);
267+
}
268+
for (const key of Object.keys(inspectorConsole)) {
269+
if (console.hasOwnProperty(key))
270+
continue;
271+
console[key] = inspectorConsole[key];
272+
}
273+
}
274+
238275
function setupProcessFatal() {
239276

240277
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)