Skip to content

Commit 16401b4

Browse files
committed
src: expose node::RegisterContext to make a node managed context
Signed-off-by: Chengzhong Wu <cwu631@bloomberg.net>
1 parent 3b19867 commit 16401b4

7 files changed

Lines changed: 182 additions & 7 deletions

File tree

src/api/environment.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,16 @@ Maybe<bool> InitializeContext(Local<Context> context) {
10531053
return Just(true);
10541054
}
10551055

1056+
void RegisterContext(Environment* env,
1057+
v8::Local<v8::Context> context,
1058+
ContextInfo info) {
1059+
env->AssignToContext(context, nullptr, info);
1060+
}
1061+
1062+
void UnregisterContext(Environment* env, v8::Local<v8::Context> context) {
1063+
env->UnassignFromContext(context);
1064+
}
1065+
10561066
uv_loop_t* GetCurrentEventLoop(Isolate* isolate) {
10571067
HandleScope handle_scope(isolate);
10581068
Local<Context> context = isolate->GetCurrentContext();

src/env.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,6 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
255255
wrapper_data_map_;
256256
};
257257

258-
struct ContextInfo {
259-
explicit ContextInfo(const std::string& name) : name(name) {}
260-
const std::string name;
261-
std::string origin;
262-
bool is_default = false;
263-
};
264-
265258
class EnabledDebugList;
266259

267260
namespace per_process {

src/node.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,8 @@ NODE_EXTERN v8::Isolate* NewIsolate(
563563
const IsolateSettings& settings = {});
564564

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

577+
struct ContextInfo {
578+
explicit ContextInfo(const std::string& name) : name(name) {}
579+
ContextInfo(const std::string& name, const std::string& origin)
580+
: name(name), origin(origin) {}
581+
582+
const std::string name;
583+
std::string origin;
584+
bool is_default = false;
585+
};
586+
587+
// Associate the context with the given Environment. This registers the context
588+
// as known to Node.js, make it available to the inspector. This registers
589+
// Node.js promise hooks on the context.
590+
NODE_EXTERN void RegisterContext(Environment* env,
591+
v8::Local<v8::Context> context,
592+
ContextInfo info);
593+
// Unregister the context. Call this when embedder finished all work with this
594+
// context.
595+
NODE_EXTERN void UnregisterContext(Environment* env,
596+
v8::Local<v8::Context> context);
597+
575598
// If `platform` is passed, it will be used to register new Worker instances.
576599
// It can be `nullptr`, in which case creating new Workers inside of
577600
// Environments that use this `IsolateData` will not work.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#include <node.h>
2+
#include <v8.h>
3+
4+
namespace {
5+
6+
using v8::Context;
7+
using v8::FunctionCallbackInfo;
8+
using v8::HandleScope;
9+
using v8::Isolate;
10+
using v8::Local;
11+
using v8::Object;
12+
using v8::Script;
13+
using v8::String;
14+
using v8::Value;
15+
16+
void MakeContext(const FunctionCallbackInfo<Value>& args) {
17+
Isolate* isolate = Isolate::GetCurrent();
18+
HandleScope handle_scope(isolate);
19+
Local<Context> context = isolate->GetCurrentContext();
20+
node::Environment* env = node::GetCurrentEnvironment(context);
21+
assert(env);
22+
23+
// Create a new context with Node.js-specific setup.
24+
v8::MaybeLocal<Context> maybe_context = node::NewContext(isolate);
25+
v8::Local<Context> new_context;
26+
if (!maybe_context.ToLocal(&new_context)) {
27+
return;
28+
}
29+
node::ContextInfo context_info("Addon Context", "addon://about");
30+
node::RegisterContext(env, new_context, context_info);
31+
32+
// Return the global proxy object.
33+
args.GetReturnValue().Set(new_context->Global());
34+
}
35+
36+
void RunInContext(const FunctionCallbackInfo<Value>& args) {
37+
Isolate* isolate = Isolate::GetCurrent();
38+
HandleScope handle_scope(isolate);
39+
assert(args.Length() == 2);
40+
41+
assert(args[0]->IsObject());
42+
Local<Object> global_proxy = args[0].As<Object>();
43+
v8::MaybeLocal<Context> maybe_context = global_proxy->GetCreationContext();
44+
v8::Local<Context> new_context;
45+
if (!maybe_context.ToLocal(&new_context)) {
46+
return;
47+
}
48+
Context::Scope context_scope(new_context);
49+
50+
assert(args[1]->IsString());
51+
Local<String> source = args[1].As<String>();
52+
Local<Script> script;
53+
Local<Value> result;
54+
55+
if (Script::Compile(new_context, source).ToLocal(&script) &&
56+
script->Run(new_context).ToLocal(&result)) {
57+
args.GetReturnValue().Set(result);
58+
}
59+
}
60+
61+
void Initialize(Local<Object> exports,
62+
Local<Value> module,
63+
Local<Context> context) {
64+
NODE_SET_METHOD(exports, "makeContext", MakeContext);
65+
NODE_SET_METHOD(exports, "runInContext", RunInContext);
66+
}
67+
68+
} // anonymous namespace
69+
70+
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': ['binding.cc'],
6+
'includes': ['../common.gypi'],
7+
},
8+
]
9+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
common.skipIfInspectorDisabled();
5+
6+
const assert = require('node:assert');
7+
const { once } = require('node:events');
8+
const { Session } = require('node:inspector');
9+
10+
const binding = require(`./build/${common.buildType}/binding`);
11+
12+
const session = new Session();
13+
session.connect();
14+
15+
(async function() {
16+
const mainContextPromise =
17+
once(session, 'Runtime.executionContextCreated');
18+
session.post('Runtime.enable', assert.ifError);
19+
await mainContextPromise;
20+
21+
// Addon-created context should be reported to the inspector.
22+
{
23+
const addonContextPromise =
24+
once(session, 'Runtime.executionContextCreated');
25+
26+
const ctx = binding.makeContext();
27+
const result = binding.runInContext(ctx, '1 + 1');
28+
assert.strictEqual(result, 2);
29+
30+
const { 0: contextCreated } = await addonContextPromise;
31+
const { name, origin, auxData } = contextCreated.params.context;
32+
assert.strictEqual(name, 'Addon Context',
33+
JSON.stringify(contextCreated));
34+
assert.strictEqual(origin, 'addon://about',
35+
JSON.stringify(contextCreated));
36+
assert.strictEqual(auxData.isDefault, false,
37+
JSON.stringify(contextCreated));
38+
}
39+
40+
// `debugger` statement should pause in addon-created context.
41+
{
42+
session.post('Debugger.enable', assert.ifError);
43+
44+
const pausedPromise = once(session, 'Debugger.paused');
45+
const ctx = binding.makeContext();
46+
binding.runInContext(ctx, 'debugger');
47+
await pausedPromise;
48+
49+
session.post('Debugger.resume');
50+
}
51+
})().then(common.mustCall());
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
const common = require('../../common');
4+
const assert = require('assert');
5+
const vm = require('vm');
6+
7+
const binding = require(`./build/${common.buildType}/binding`);
8+
9+
// This verifies that the addon-created context has an independent
10+
// global object.
11+
{
12+
const ctx = binding.makeContext();
13+
const result = binding.runInContext(ctx, `
14+
globalThis.foo = 'bar';
15+
foo;
16+
`);
17+
assert.strictEqual(result, 'bar');
18+
assert.strictEqual(globalThis.foo, undefined);
19+
}

0 commit comments

Comments
 (0)