diff --git a/src/env-inl.h b/src/env-inl.h index afe15f17aa1f6f..f3ca8882b033cb 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -259,6 +259,9 @@ inline void Environment::TickInfo::set_index(uint32_t value) { inline void Environment::AssignToContext(v8::Local context) { context->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex, this); +#if HAVE_INSPECTOR + inspector_agent()->ContextCreated(context); +#endif // HAVE_INSPECTOR } inline Environment* Environment::GetCurrent(v8::Isolate* isolate) { diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index bedf74f3b02f61..0f9caa32f2a22e 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -12,6 +12,7 @@ #include "libplatform/libplatform.h" #include +#include #include #include @@ -500,6 +501,7 @@ class NodeInspectorClient : public V8InspectorClient { terminated_(false), running_nested_loop_(false) { client_ = V8Inspector::create(env->isolate(), this); + contextCreated(env->context(), "Node.js Main Context"); } void runMessageLoopOnPause(int context_group_id) override { @@ -627,7 +629,8 @@ class NodeInspectorClient : public V8InspectorClient { Agent::Agent(Environment* env) : parent_env_(env), client_(nullptr), platform_(nullptr), - enabled_(false) {} + enabled_(false), + next_context_number_(1) {} // Destructor needs to be defined here in implementation file as the header // does not have full definition of some classes. @@ -641,7 +644,6 @@ bool Agent::Start(v8::Platform* platform, const char* path, client_ = std::unique_ptr( new NodeInspectorClient(parent_env_, platform)); - client_->contextCreated(parent_env_->context(), "Node.js Main Context"); platform_ = platform; CHECK_EQ(0, uv_async_init(uv_default_loop(), &start_io_thread_async, @@ -841,6 +843,14 @@ void Agent::RequestIoThreadStart() { uv_async_send(&start_io_thread_async); } +void Agent::ContextCreated(Local context) { + if (client_ == nullptr) // This happens for a main context + return; + std::ostringstream name; + name << "VM Context " << next_context_number_++; + client_->contextCreated(context, name.str()); +} + } // namespace inspector } // namespace node diff --git a/src/inspector_agent.h b/src/inspector_agent.h index 80967212cd7aef..cf9a8bff8645ec 100644 --- a/src/inspector_agent.h +++ b/src/inspector_agent.h @@ -96,6 +96,7 @@ class Agent { void RequestIoThreadStart(); DebugOptions& options() { return debug_options_; } + void ContextCreated(v8::Local context); private: node::Environment* parent_env_; @@ -105,6 +106,7 @@ class Agent { bool enabled_; std::string path_; DebugOptions debug_options_; + int next_context_number_; }; } // namespace inspector diff --git a/test/inspector/test-contexts.js b/test/inspector/test-contexts.js new file mode 100644 index 00000000000000..54acfab0d5cace --- /dev/null +++ b/test/inspector/test-contexts.js @@ -0,0 +1,63 @@ +'use strict'; + +// Flags: --expose-gc + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const { strictEqual } = require('assert'); +const { runInNewContext } = require('vm'); +const { Session } = require('inspector'); + +const session = new Session(); +session.connect(); + +function notificationPromise(method) { + return new Promise((resolve) => session.once(method, resolve)); +} + +async function testContextCreatedAndDestroyed() { + console.log('Testing context created/destroyed notifications'); + const mainContextPromise = + notificationPromise('Runtime.executionContextCreated'); + + session.post('Runtime.enable'); + let contextCreated = await mainContextPromise; + strictEqual('Node.js Main Context', + contextCreated.params.context.name, + JSON.stringify(contextCreated)); + + const secondContextCreatedPromise = + notificationPromise('Runtime.executionContextCreated'); + + let contextDestroyed = null; + session.once('Runtime.executionContextDestroyed', + (notification) => contextDestroyed = notification); + + runInNewContext('1 + 1', {}); + + contextCreated = await secondContextCreatedPromise; + strictEqual('VM Context 1', + contextCreated.params.context.name, + JSON.stringify(contextCreated)); + + // GC is unpredictable... + while (!contextDestroyed) + global.gc(); + + strictEqual(contextCreated.params.context.id, + contextDestroyed.params.executionContextId, + JSON.stringify(contextDestroyed)); +} + +async function testBreakpointHit() { + console.log('Testing breakpoint is hit in a new context'); + session.post('Debugger.enable'); + + const pausedPromise = notificationPromise('Debugger.paused'); + runInNewContext('debugger', {}); + await pausedPromise; +} + +common.crashOnUnhandledRejection(); +testContextCreatedAndDestroyed().then(testBreakpointHit);