Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/api/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#if HAVE_OPENSSL
#include "crypto/crypto_util.h"
#endif // HAVE_OPENSSL
#include "env.h"
#include "env_properties.h"
#include "node.h"
#include "node_builtins.h"
Expand Down Expand Up @@ -1053,6 +1054,18 @@ Maybe<bool> InitializeContext(Local<Context> context) {
return Just(true);
}

void RegisterContext(Environment* env,
v8::Local<v8::Context> context,
std::string_view name,
std::string_view origin) {
ContextInfo info{std::string(name), std::string(origin)};
env->AssignToContext(context, nullptr, info);
}

void UnregisterContext(Environment* env, v8::Local<v8::Context> context) {
env->UnassignFromContext(context);
}

uv_loop_t* GetCurrentEventLoop(Isolate* isolate) {
HandleScope handle_scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
Expand Down
2 changes: 2 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {

struct ContextInfo {
explicit ContextInfo(const std::string& name) : name(name) {}
ContextInfo(const std::string& name, const std::string& origin)
: name(name), origin(origin) {}
const std::string name;
std::string origin;
bool is_default = false;
Expand Down
14 changes: 14 additions & 0 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,8 @@ NODE_EXTERN v8::Isolate* NewIsolate(
const IsolateSettings& settings = {});

// Creates a new context with Node.js-specific tweaks.
// Call `RegisterContext` after the context been created to register
// the context with Node.js specific setups like the inspector.
NODE_EXTERN v8::Local<v8::Context> NewContext(
v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> object_template =
Expand All @@ -572,6 +574,18 @@ NODE_EXTERN v8::Local<v8::Context> NewContext(
// Return value indicates success of operation
NODE_EXTERN v8::Maybe<bool> InitializeContext(v8::Local<v8::Context> context);

// Associate the context with the given Environment. This registers the context
// as known to Node.js, makes it available to the inspector. This also registers
// Node.js promise hooks on the context.
NODE_EXTERN void RegisterContext(Environment* env,
v8::Local<v8::Context> context,
std::string_view name = "",
std::string_view origin = "");
// Unregister the context. Call this when the embedder finished all work with
// this context.
NODE_EXTERN void UnregisterContext(Environment* env,
v8::Local<v8::Context> context);

// If `platform` is passed, it will be used to register new Worker instances.
// It can be `nullptr`, in which case creating new Workers inside of
// Environments that use this `IsolateData` will not work.
Expand Down
69 changes: 69 additions & 0 deletions test/addons/new-context-inspector/binding.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <node.h>
#include <v8.h>

namespace {

using v8::Context;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::Script;
using v8::String;
using v8::Value;

void MakeContext(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope handle_scope(isolate);
Local<Context> context = isolate->GetCurrentContext();
node::Environment* env = node::GetCurrentEnvironment(context);
assert(env);

// Create a new context with Node.js-specific setup.
v8::MaybeLocal<Context> maybe_context = node::NewContext(isolate);
v8::Local<Context> new_context;
if (!maybe_context.ToLocal(&new_context)) {
return;
}
node::RegisterContext(env, new_context, "Addon Context", "addon://about");

// Return the global proxy object.
args.GetReturnValue().Set(new_context->Global());
}

void RunInContext(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope handle_scope(isolate);
assert(args.Length() == 2);

assert(args[0]->IsObject());
Local<Object> global_proxy = args[0].As<Object>();
v8::MaybeLocal<Context> maybe_context = global_proxy->GetCreationContext();
v8::Local<Context> new_context;
if (!maybe_context.ToLocal(&new_context)) {
return;
}
Context::Scope context_scope(new_context);

assert(args[1]->IsString());
Local<String> source = args[1].As<String>();
Local<Script> script;
Local<Value> result;

if (Script::Compile(new_context, source).ToLocal(&script) &&
script->Run(new_context).ToLocal(&result)) {
args.GetReturnValue().Set(result);
}
}

void Initialize(Local<Object> exports,
Local<Value> module,
Local<Context> context) {
NODE_SET_METHOD(exports, "makeContext", MakeContext);
NODE_SET_METHOD(exports, "runInContext", RunInContext);
}

} // anonymous namespace

NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)
9 changes: 9 additions & 0 deletions test/addons/new-context-inspector/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
'targets': [
{
'target_name': 'binding',
'sources': ['binding.cc'],
'includes': ['../common.gypi'],
},
]
}
51 changes: 51 additions & 0 deletions test/addons/new-context-inspector/test-inspector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
'use strict';

const common = require('../../common');
common.skipIfInspectorDisabled();

const assert = require('node:assert');
const { once } = require('node:events');
const { Session } = require('node:inspector');

const binding = require(`./build/${common.buildType}/binding`);

const session = new Session();
session.connect();

(async function() {
const mainContextPromise =
once(session, 'Runtime.executionContextCreated');
session.post('Runtime.enable', assert.ifError);
await mainContextPromise;

// Addon-created context should be reported to the inspector.
{
const addonContextPromise =
once(session, 'Runtime.executionContextCreated');

const ctx = binding.makeContext();
const result = binding.runInContext(ctx, '1 + 1');
assert.strictEqual(result, 2);

const { 0: contextCreated } = await addonContextPromise;
const { name, origin, auxData } = contextCreated.params.context;
assert.strictEqual(name, 'Addon Context',
JSON.stringify(contextCreated));
assert.strictEqual(origin, 'addon://about',
JSON.stringify(contextCreated));
assert.strictEqual(auxData.isDefault, false,
JSON.stringify(contextCreated));
}

// `debugger` statement should pause in addon-created context.
{
session.post('Debugger.enable', assert.ifError);

const pausedPromise = once(session, 'Debugger.paused');
const ctx = binding.makeContext();
binding.runInContext(ctx, 'debugger');
await pausedPromise;

session.post('Debugger.resume');
}
})().then(common.mustCall());
18 changes: 18 additions & 0 deletions test/addons/new-context-inspector/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

const common = require('../../common');
const assert = require('assert');

const binding = require(`./build/${common.buildType}/binding`);

// This verifies that the addon-created context has an independent
// global object.
{
const ctx = binding.makeContext();
const result = binding.runInContext(ctx, `
globalThis.foo = 'bar';
foo;
`);
assert.strictEqual(result, 'bar');
assert.strictEqual(globalThis.foo, undefined);
}
Loading