From 60d6e048f0f912882c80a088aeb5828a9ecf639b Mon Sep 17 00:00:00 2001 From: Aleksei Koziatinskii Date: Fri, 5 Aug 2016 13:45:08 -0700 Subject: [PATCH] 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: https://github.com/nodejs/node/pull/7988 Reviewed-By: bnoordhuis - Ben Noordhuis Reviewed-By: jasnell - James M Snell Reviewed-By: ofrobots - Ali Ijaz Sheikh --- lib/internal/bootstrap_node.js | 39 +++++++++++++- src/inspector_agent.cc | 92 ++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index 22c72bc8120cf5..140816aac08cc1 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -238,15 +238,52 @@ } function setupGlobalConsole() { + var inspectorConsole; + var wrapConsoleCall; + if (process.inspector) { + inspectorConsole = global.console; + wrapConsoleCall = process.inspector.wrapConsoleCall; + delete process.inspector; + } + var console; Object.defineProperty(global, 'console', { configurable: true, enumerable: true, get: function() { - return NativeModule.require('console'); + if (!console) { + console = NativeModule.require('console'); + installInspectorConsoleIfNeeded(console, + inspectorConsole, + wrapConsoleCall); + } + return console; } }); } + function installInspectorConsoleIfNeeded(console, + inspectorConsole, + wrapConsoleCall) { + if (!inspectorConsole) + return; + var config = {}; + for (const key of Object.keys(console)) { + if (!inspectorConsole.hasOwnProperty(key)) + continue; + // If node console has the same method as inspector console, + // then wrap these two methods into one. Native wrapper will preserve + // the original stack. + console[key] = wrapConsoleCall(inspectorConsole[key], + console[key], + config); + } + for (const key of Object.keys(inspectorConsole)) { + if (console.hasOwnProperty(key)) + continue; + console[key] = inspectorConsole[key]; + } + } + function setupProcessFatal() { process._fatalException = function(er) { diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 6bcc195e77535c..78fc3719a6972f 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -186,6 +186,8 @@ class AgentImpl { const std::string& path); static void WriteCbIO(uv_async_t* async); + void InstallInspectorOnProcess(); + void WorkerRunIO(); void OnInspectorConnectionIO(inspector_socket_t* socket); void OnRemoteDataIO(inspector_socket_t* stream, ssize_t read, @@ -276,6 +278,9 @@ class ChannelImpl final : public blink::protocol::FrontendChannel { AgentImpl* const agent_; }; +// Used in V8NodeInspector::currentTimeMS() below. +#define NANOS_PER_MSEC 1000000 + class V8NodeInspector : public blink::V8InspectorClient { public: V8NodeInspector(AgentImpl* agent, node::Environment* env, @@ -308,6 +313,10 @@ class V8NodeInspector : public blink::V8InspectorClient { running_nested_loop_ = false; } + double currentTimeMS() override { + return uv_hrtime() * 1.0 / NANOS_PER_MSEC; + } + void quitMessageLoopOnPause() override { terminated_ = true; } @@ -361,11 +370,78 @@ AgentImpl::~AgentImpl() { data_written_ = nullptr; } +void InspectorConsoleCall(const v8::FunctionCallbackInfo& info) { + v8::Isolate* isolate = info.GetIsolate(); + v8::Local context = isolate->GetCurrentContext(); + + CHECK(info.Data()->IsArray()); + v8::Local args = info.Data().As(); + CHECK_EQ(args->Length(), 3); + + v8::Local inspector_method = + args->Get(context, 0).ToLocalChecked(); + CHECK(inspector_method->IsFunction()); + v8::Local node_method = + args->Get(context, 1).ToLocalChecked(); + CHECK(node_method->IsFunction()); + v8::Local config_value = + args->Get(context, 2).ToLocalChecked(); + CHECK(config_value->IsObject()); + v8::Local config_object = config_value.As(); + + std::vector> call_args(info.Length()); + for (int i = 0; i < info.Length(); ++i) { + call_args[i] = info[i]; + } + + v8::Local in_call_key = OneByteString(isolate, "in_call"); + bool in_call = config_object->Has(context, in_call_key).FromMaybe(false); + if (!in_call) { + CHECK(config_object->Set(context, + in_call_key, + v8::True(isolate)).FromJust()); + CHECK(!inspector_method.As()->Call( + context, + info.Holder(), + call_args.size(), + call_args.data()).IsEmpty()); + } + + v8::TryCatch try_catch(info.GetIsolate()); + node_method.As()->Call(context, + info.Holder(), + call_args.size(), + call_args.data()); + CHECK(config_object->Delete(context, in_call_key).FromJust()); + if (try_catch.HasCaught()) + try_catch.ReThrow(); +} + +void InspectorWrapConsoleCall(const v8::FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.Length() != 3 || !args[0]->IsFunction() || + !args[1]->IsFunction() || !args[2]->IsObject()) { + return env->ThrowError("inspector.wrapConsoleCall takes exactly 3 " + "arguments: two functions and an object."); + } + + v8::Local array = v8::Array::New(env->isolate(), args.Length()); + CHECK(array->Set(env->context(), 0, args[0]).FromJust()); + CHECK(array->Set(env->context(), 1, args[1]).FromJust()); + CHECK(array->Set(env->context(), 2, args[2]).FromJust()); + args.GetReturnValue().Set(v8::Function::New(env->context(), + InspectorConsoleCall, + array).ToLocalChecked()); +} + bool AgentImpl::Start(v8::Platform* platform, int port, bool wait) { auto env = parent_env_; inspector_ = new V8NodeInspector(this, env, platform); platform_ = platform; + InstallInspectorOnProcess(); + int err = uv_loop_init(&child_loop_); CHECK_EQ(err, 0); @@ -403,6 +479,22 @@ void AgentImpl::WaitForDisconnect() { inspector_->runMessageLoopOnPause(0); } +#define READONLY_PROPERTY(obj, str, var) \ + do { \ + obj->DefineOwnProperty(env->context(), \ + OneByteString(env->isolate(), str), \ + var, \ + v8::ReadOnly).FromJust(); \ + } while (0) + +void AgentImpl::InstallInspectorOnProcess() { + auto env = parent_env_; + v8::Local process = env->process_object(); + v8::Local inspector = v8::Object::New(env->isolate()); + READONLY_PROPERTY(process, "inspector", inspector); + env->SetMethod(inspector, "wrapConsoleCall", InspectorWrapConsoleCall); +} + // static void AgentImpl::ThreadCbIO(void* agent) { static_cast(agent)->WorkerRunIO();