Skip to content

Commit 2cb2145

Browse files
committed
vm: allow modifying context name in inspector
The `auxData` field is not exposed to JavaScript, as DevTools uses it for its `isDefault` parameter, which is implemented faithfully, contributing to the nice indentation in the context selection panel. Without the indentation, when `Target` domain gets implemented (along with a single Inspector for cluster) in nodejs#16627, subprocesses and VM contexts will be mixed up, causing confusion. PR-URL: nodejs#17720 Refs: nodejs#14231 (comment) Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Jon Moss <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent c339931 commit 2cb2145

File tree

8 files changed

+264
-59
lines changed

8 files changed

+264
-59
lines changed

doc/api/vm.md

+36-2
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,15 @@ added: v0.3.1
175175
* `timeout` {number} Specifies the number of milliseconds to execute `code`
176176
before terminating execution. If execution is terminated, an [`Error`][]
177177
will be thrown.
178+
* `contextName` {string} Human-readable name of the newly created context.
179+
**Default:** `'VM Context i'`, where `i` is an ascending numerical index of
180+
the created context.
181+
* `contextOrigin` {string} [Origin][origin] corresponding to the newly
182+
created context for display purposes. The origin should be formatted like a
183+
URL, but with only the scheme, host, and port (if necessary), like the
184+
value of the [`url.origin`][] property of a [`URL`][] object. Most notably,
185+
this string should omit the trailing slash, as that denotes a path.
186+
**Default:** `''`.
178187

179188
First contextifies the given `sandbox`, runs the compiled code contained by
180189
the `vm.Script` object within the created sandbox, and returns the result.
@@ -242,12 +251,22 @@ console.log(globalVar);
242251
// 1000
243252
```
244253

245-
## vm.createContext([sandbox])
254+
## vm.createContext([sandbox[, options]])
246255
<!-- YAML
247256
added: v0.3.1
248257
-->
249258

250259
* `sandbox` {Object}
260+
* `options` {Object}
261+
* `name` {string} Human-readable name of the newly created context.
262+
**Default:** `'VM Context i'`, where `i` is an ascending numerical index of
263+
the created context.
264+
* `origin` {string} [Origin][origin] corresponding to the newly created
265+
context for display purposes. The origin should be formatted like a URL,
266+
but with only the scheme, host, and port (if necessary), like the value of
267+
the [`url.origin`][] property of a [`URL`][] object. Most notably, this
268+
string should omit the trailing slash, as that denotes a path.
269+
**Default:** `''`.
251270

252271
If given a `sandbox` object, the `vm.createContext()` method will [prepare
253272
that sandbox][contextified] so that it can be used in calls to
@@ -282,6 +301,9 @@ web browser, the method can be used to create a single sandbox representing a
282301
window's global object, then run all `<script>` tags together within the context
283302
of that sandbox.
284303

304+
The provided `name` and `origin` of the context are made visible through the
305+
Inspector API.
306+
285307
## vm.isContext(sandbox)
286308
<!-- YAML
287309
added: v0.11.7
@@ -355,6 +377,15 @@ added: v0.3.1
355377
* `timeout` {number} Specifies the number of milliseconds to execute `code`
356378
before terminating execution. If execution is terminated, an [`Error`][]
357379
will be thrown.
380+
* `contextName` {string} Human-readable name of the newly created context.
381+
**Default:** `'VM Context i'`, where `i` is an ascending numerical index of
382+
the created context.
383+
* `contextOrigin` {string} [Origin][origin] corresponding to the newly
384+
created context for display purposes. The origin should be formatted like a
385+
URL, but with only the scheme, host, and port (if necessary), like the
386+
value of the [`url.origin`][] property of a [`URL`][] object. Most notably,
387+
this string should omit the trailing slash, as that denotes a path.
388+
**Default:** `''`.
358389

359390
The `vm.runInNewContext()` first contextifies the given `sandbox` object (or
360391
creates a new `sandbox` if passed as `undefined`), compiles the `code`, runs it
@@ -480,13 +511,16 @@ associating it with the `sandbox` object is what this document refers to as
480511
"contextifying" the `sandbox`.
481512

482513
[`Error`]: errors.html#errors_class_error
514+
[`URL`]: url.html#url_class_url
483515
[`eval()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
484516
[`script.runInContext()`]: #vm_script_runincontext_contextifiedsandbox_options
485517
[`script.runInThisContext()`]: #vm_script_runinthiscontext_options
486-
[`vm.createContext()`]: #vm_vm_createcontext_sandbox
518+
[`url.origin`]: https://nodejs.org/api/url.html#url_url_origin
519+
[`vm.createContext()`]: #vm_vm_createcontext_sandbox_options
487520
[`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options
488521
[`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options
489522
[V8 Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide#contexts
490523
[contextified]: #vm_what_does_it_mean_to_contextify_an_object
491524
[global object]: https://es5.github.io/#x15.1
492525
[indirect `eval()` call]: https://es5.github.io/#x10.4.2
526+
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin

lib/vm.js

+53-12
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const {
2929
isContext,
3030
} = process.binding('contextify');
3131

32+
const errors = require('internal/errors');
33+
3234
// The binding provides a few useful primitives:
3335
// - Script(code, { filename = "evalmachine.anonymous",
3436
// displayErrors = true } = {})
@@ -73,18 +75,61 @@ Script.prototype.runInContext = function(contextifiedSandbox, options) {
7375
};
7476

7577
Script.prototype.runInNewContext = function(sandbox, options) {
76-
var context = createContext(sandbox);
78+
const context = createContext(sandbox, getContextOptions(options));
7779
return this.runInContext(context, options);
7880
};
7981

80-
function createContext(sandbox) {
82+
function getContextOptions(options) {
83+
const contextOptions = options ? {
84+
name: options.contextName,
85+
origin: options.contextOrigin
86+
} : {};
87+
if (contextOptions.name !== undefined &&
88+
typeof contextOptions.name !== 'string') {
89+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options.contextName',
90+
'string', contextOptions.name);
91+
}
92+
if (contextOptions.origin !== undefined &&
93+
typeof contextOptions.origin !== 'string') {
94+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options.contextOrigin',
95+
'string', contextOptions.origin);
96+
}
97+
return contextOptions;
98+
}
99+
100+
let defaultContextNameIndex = 1;
101+
function createContext(sandbox, options) {
81102
if (sandbox === undefined) {
82103
sandbox = {};
83104
} else if (isContext(sandbox)) {
84105
return sandbox;
85106
}
86107

87-
makeContext(sandbox);
108+
if (options !== undefined) {
109+
if (typeof options !== 'object' || options === null) {
110+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options',
111+
'object', options);
112+
}
113+
options = {
114+
name: options.name,
115+
origin: options.origin
116+
};
117+
if (options.name === undefined) {
118+
options.name = `VM Context ${defaultContextNameIndex++}`;
119+
} else if (typeof options.name !== 'string') {
120+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options.name',
121+
'string', options.name);
122+
}
123+
if (options.origin !== undefined && typeof options.origin !== 'string') {
124+
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options.origin',
125+
'string', options.origin);
126+
}
127+
} else {
128+
options = {
129+
name: `VM Context ${defaultContextNameIndex++}`
130+
};
131+
}
132+
makeContext(sandbox, options);
88133
return sandbox;
89134
}
90135

@@ -126,17 +171,13 @@ function runInContext(code, contextifiedSandbox, options) {
126171
}
127172

128173
function runInNewContext(code, sandbox, options) {
129-
sandbox = createContext(sandbox);
130174
if (typeof options === 'string') {
131-
options = {
132-
filename: options,
133-
[kParsingContext]: sandbox
134-
};
135-
} else {
136-
options = Object.assign({}, options, {
137-
[kParsingContext]: sandbox
138-
});
175+
options = { filename: options };
139176
}
177+
sandbox = createContext(sandbox, getContextOptions(options));
178+
options = Object.assign({}, options, {
179+
[kParsingContext]: sandbox
180+
});
140181
return createScript(code, options).runInNewContext(sandbox, options);
141182
}
142183

src/env-inl.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,11 @@ inline void Environment::TickInfo::set_index(uint32_t value) {
227227
fields_[kIndex] = value;
228228
}
229229

230-
inline void Environment::AssignToContext(v8::Local<v8::Context> context) {
230+
inline void Environment::AssignToContext(v8::Local<v8::Context> context,
231+
const ContextInfo& info) {
231232
context->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex, this);
232233
#if HAVE_INSPECTOR
233-
inspector_agent()->ContextCreated(context);
234+
inspector_agent()->ContextCreated(context, info);
234235
#endif // HAVE_INSPECTOR
235236
}
236237

@@ -295,7 +296,7 @@ inline Environment::Environment(IsolateData* isolate_data,
295296

296297
set_module_load_list_array(v8::Array::New(isolate()));
297298

298-
AssignToContext(context);
299+
AssignToContext(context, ContextInfo(""));
299300

300301
destroy_async_id_list_.reserve(512);
301302
performance_state_ = Calloc<performance::performance_state>(1);

src/env.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,13 @@ class IsolateData {
361361
DISALLOW_COPY_AND_ASSIGN(IsolateData);
362362
};
363363

364+
struct ContextInfo {
365+
explicit ContextInfo(const std::string& name) : name(name) {}
366+
const std::string name;
367+
std::string origin;
368+
bool is_default = false;
369+
};
370+
364371
class Environment {
365372
public:
366373
class AsyncHooks {
@@ -508,9 +515,11 @@ class Environment {
508515
int exec_argc,
509516
const char* const* exec_argv,
510517
bool start_profiler_idle_notifier);
511-
void AssignToContext(v8::Local<v8::Context> context);
512518
void CleanupHandles();
513519

520+
inline void AssignToContext(v8::Local<v8::Context> context,
521+
const ContextInfo& info);
522+
514523
void StartProfilerIdleNotifier();
515524
void StopProfilerIdleNotifier();
516525

src/inspector_agent.cc

+23-11
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ using v8::Isolate;
3131
using v8::Local;
3232
using v8::Object;
3333
using v8::Persistent;
34+
using v8::String;
3435
using v8::Value;
3536

3637
using v8_inspector::StringBuffer;
@@ -304,7 +305,9 @@ class NodeInspectorClient : public V8InspectorClient {
304305
running_nested_loop_(false) {
305306
client_ = V8Inspector::create(env->isolate(), this);
306307
// TODO(bnoordhuis) Make name configurable from src/node.cc.
307-
contextCreated(env->context(), GetHumanReadableProcessName());
308+
ContextInfo info(GetHumanReadableProcessName());
309+
info.is_default = true;
310+
contextCreated(env->context(), info);
308311
}
309312

310313
void runMessageLoopOnPause(int context_group_id) override {
@@ -334,11 +337,23 @@ class NodeInspectorClient : public V8InspectorClient {
334337
}
335338
}
336339

337-
void contextCreated(Local<Context> context, const std::string& name) {
338-
std::unique_ptr<StringBuffer> name_buffer = Utf8ToStringView(name);
339-
v8_inspector::V8ContextInfo info(context, CONTEXT_GROUP_ID,
340-
name_buffer->string());
341-
client_->contextCreated(info);
340+
void contextCreated(Local<Context> context, const ContextInfo& info) {
341+
auto name_buffer = Utf8ToStringView(info.name);
342+
auto origin_buffer = Utf8ToStringView(info.origin);
343+
std::unique_ptr<StringBuffer> aux_data_buffer;
344+
345+
v8_inspector::V8ContextInfo v8info(
346+
context, CONTEXT_GROUP_ID, name_buffer->string());
347+
v8info.origin = origin_buffer->string();
348+
349+
if (info.is_default) {
350+
aux_data_buffer = Utf8ToStringView("{\"isDefault\":true}");
351+
} else {
352+
aux_data_buffer = Utf8ToStringView("{\"isDefault\":false}");
353+
}
354+
v8info.auxData = aux_data_buffer->string();
355+
356+
client_->contextCreated(v8info);
342357
}
343358

344359
void contextDestroyed(Local<Context> context) {
@@ -464,7 +479,6 @@ Agent::Agent(Environment* env) : parent_env_(env),
464479
client_(nullptr),
465480
platform_(nullptr),
466481
enabled_(false),
467-
next_context_number_(1),
468482
pending_enable_async_hook_(false),
469483
pending_disable_async_hook_(false) {}
470484

@@ -676,12 +690,10 @@ void Agent::RequestIoThreadStart() {
676690
uv_async_send(&start_io_thread_async);
677691
}
678692

679-
void Agent::ContextCreated(Local<Context> context) {
693+
void Agent::ContextCreated(Local<Context> context, const ContextInfo& info) {
680694
if (client_ == nullptr) // This happens for a main context
681695
return;
682-
std::ostringstream name;
683-
name << "VM Context " << next_context_number_++;
684-
client_->contextCreated(context, name.str());
696+
client_->contextCreated(context, info);
685697
}
686698

687699
bool Agent::IsWaitingForConnect() {

src/inspector_agent.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class StringView;
2020
namespace node {
2121
// Forward declaration to break recursive dependency chain with src/env.h.
2222
class Environment;
23+
struct ContextInfo;
2324

2425
namespace inspector {
2526

@@ -89,7 +90,7 @@ class Agent {
8990
void RequestIoThreadStart();
9091

9192
DebugOptions& options() { return debug_options_; }
92-
void ContextCreated(v8::Local<v8::Context> context);
93+
void ContextCreated(v8::Local<v8::Context> context, const ContextInfo& info);
9394

9495
void EnableAsyncHook();
9596
void DisableAsyncHook();
@@ -105,7 +106,6 @@ class Agent {
105106
bool enabled_;
106107
std::string path_;
107108
DebugOptions debug_options_;
108-
int next_context_number_;
109109

110110
bool pending_enable_async_hook_;
111111
bool pending_disable_async_hook_;

src/node_contextify.cc

+31-5
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,11 @@ class ContextifyContext {
100100
Persistent<Context> context_;
101101

102102
public:
103-
ContextifyContext(Environment* env, Local<Object> sandbox_obj) : env_(env) {
104-
Local<Context> v8_context = CreateV8Context(env, sandbox_obj);
103+
ContextifyContext(Environment* env,
104+
Local<Object> sandbox_obj,
105+
Local<Object> options_obj)
106+
: env_(env) {
107+
Local<Context> v8_context = CreateV8Context(env, sandbox_obj, options_obj);
105108
context_.Reset(env->isolate(), v8_context);
106109

107110
// Allocation failure or maximum call stack size reached
@@ -154,7 +157,9 @@ class ContextifyContext {
154157
}
155158

156159

157-
Local<Context> CreateV8Context(Environment* env, Local<Object> sandbox_obj) {
160+
Local<Context> CreateV8Context(Environment* env,
161+
Local<Object> sandbox_obj,
162+
Local<Object> options_obj) {
158163
EscapableHandleScope scope(env->isolate());
159164
Local<FunctionTemplate> function_template =
160165
FunctionTemplate::New(env->isolate());
@@ -204,7 +209,25 @@ class ContextifyContext {
204209
env->contextify_global_private_symbol(),
205210
ctx->Global());
206211

207-
env->AssignToContext(ctx);
212+
Local<Value> name =
213+
options_obj->Get(env->context(), env->name_string())
214+
.ToLocalChecked();
215+
CHECK(name->IsString());
216+
Utf8Value name_val(env->isolate(), name);
217+
218+
ContextInfo info(*name_val);
219+
220+
Local<Value> origin =
221+
options_obj->Get(env->context(),
222+
FIXED_ONE_BYTE_STRING(env->isolate(), "origin"))
223+
.ToLocalChecked();
224+
if (!origin->IsUndefined()) {
225+
CHECK(origin->IsString());
226+
Utf8Value origin_val(env->isolate(), origin);
227+
info.origin = *origin_val;
228+
}
229+
230+
env->AssignToContext(ctx, info);
208231

209232
return scope.Escape(ctx);
210233
}
@@ -235,8 +258,11 @@ class ContextifyContext {
235258
env->context(),
236259
env->contextify_context_private_symbol()).FromJust());
237260

261+
Local<Object> options = args[1].As<Object>();
262+
CHECK(options->IsObject());
263+
238264
TryCatch try_catch(env->isolate());
239-
ContextifyContext* context = new ContextifyContext(env, sandbox);
265+
ContextifyContext* context = new ContextifyContext(env, sandbox, options);
240266

241267
if (try_catch.HasCaught()) {
242268
try_catch.ReThrow();

0 commit comments

Comments
 (0)