Skip to content

Commit 10ad019

Browse files
TimothyGuMylesBorins
authored andcommitted
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 #16627, subprocesses and VM contexts will be mixed up, causing confusion. PR-URL: #17720 Refs: #14231 (comment) Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Jon Moss <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent b6f0bf1 commit 10ad019

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
@@ -385,6 +407,15 @@ added: v0.3.1
385407
* `timeout` {number} Specifies the number of milliseconds to execute `code`
386408
before terminating execution. If execution is terminated, an [`Error`][]
387409
will be thrown.
410+
* `contextName` {string} Human-readable name of the newly created context.
411+
**Default:** `'VM Context i'`, where `i` is an ascending numerical index of
412+
the created context.
413+
* `contextOrigin` {string} [Origin][origin] corresponding to the newly
414+
created context for display purposes. The origin should be formatted like a
415+
URL, but with only the scheme, host, and port (if necessary), like the
416+
value of the [`url.origin`][] property of a [`URL`][] object. Most notably,
417+
this string should omit the trailing slash, as that denotes a path.
418+
**Default:** `''`.
388419

389420
The `vm.runInNewContext()` first contextifies the given `sandbox` object (or
390421
creates a new `sandbox` if passed as `undefined`), compiles the `code`, runs it
@@ -510,14 +541,17 @@ associating it with the `sandbox` object is what this document refers to as
510541
"contextifying" the `sandbox`.
511542

512543
[`Error`]: errors.html#errors_class_error
544+
[`URL`]: url.html#url_class_url
513545
[`eval()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
514546
[`script.runInContext()`]: #vm_script_runincontext_contextifiedsandbox_options
515547
[`script.runInThisContext()`]: #vm_script_runinthiscontext_options
516-
[`vm.createContext()`]: #vm_vm_createcontext_sandbox
548+
[`url.origin`]: https://nodejs.org/api/url.html#url_url_origin
549+
[`vm.createContext()`]: #vm_vm_createcontext_sandbox_options
517550
[`vm.runInContext()`]: #vm_vm_runincontext_code_contextifiedsandbox_options
518551
[`vm.runInThisContext()`]: #vm_vm_runinthiscontext_code_options
519552
[V8 Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide#contexts
520553
[command line option]: cli.html
521554
[contextified]: #vm_what_does_it_mean_to_contextify_an_object
522555
[global object]: https://es5.github.io/#x15.1
523556
[indirect `eval()` call]: https://es5.github.io/#x10.4.2
557+
[origin]: https://developer.mozilla.org/en-US/docs/Glossary/Origin

lib/vm.js

+53-12
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const {
3030
runInDebugContext: runInDebugContext_
3131
} = process.binding('contextify');
3232

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

7678
Script.prototype.runInNewContext = function(sandbox, options) {
77-
var context = createContext(sandbox);
79+
const context = createContext(sandbox, getContextOptions(options));
7880
return this.runInContext(context, options);
7981
};
8082

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

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

@@ -140,17 +185,13 @@ function runInContext(code, contextifiedSandbox, options) {
140185
}
141186

142187
function runInNewContext(code, sandbox, options) {
143-
sandbox = createContext(sandbox);
144188
if (typeof options === 'string') {
145-
options = {
146-
filename: options,
147-
[kParsingContext]: sandbox
148-
};
149-
} else {
150-
options = Object.assign({}, options, {
151-
[kParsingContext]: sandbox
152-
});
189+
options = { filename: options };
153190
}
191+
sandbox = createContext(sandbox, getContextOptions(options));
192+
options = Object.assign({}, options, {
193+
[kParsingContext]: sandbox
194+
});
154195
return createScript(code, options).runInNewContext(sandbox, options);
155196
}
156197

src/env-inl.h

+4-3
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,11 @@ inline void Environment::TickInfo::set_index(uint32_t value) {
217217
fields_[kIndex] = value;
218218
}
219219

220-
inline void Environment::AssignToContext(v8::Local<v8::Context> context) {
220+
inline void Environment::AssignToContext(v8::Local<v8::Context> context,
221+
const ContextInfo& info) {
221222
context->SetAlignedPointerInEmbedderData(kContextEmbedderDataIndex, this);
222223
#if HAVE_INSPECTOR
223-
inspector_agent()->ContextCreated(context);
224+
inspector_agent()->ContextCreated(context, info);
224225
#endif // HAVE_INSPECTOR
225226
}
226227

@@ -285,7 +286,7 @@ inline Environment::Environment(IsolateData* isolate_data,
285286

286287
set_module_load_list_array(v8::Array::New(isolate()));
287288

288-
AssignToContext(context);
289+
AssignToContext(context, ContextInfo(""));
289290

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

src/env.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,13 @@ class IsolateData {
359359
DISALLOW_COPY_AND_ASSIGN(IsolateData);
360360
};
361361

362+
struct ContextInfo {
363+
explicit ContextInfo(const std::string& name) : name(name) {}
364+
const std::string name;
365+
std::string origin;
366+
bool is_default = false;
367+
};
368+
362369
class Environment {
363370
public:
364371
class AsyncHooks {
@@ -505,9 +512,11 @@ class Environment {
505512
int exec_argc,
506513
const char* const* exec_argv,
507514
bool start_profiler_idle_notifier);
508-
void AssignToContext(v8::Local<v8::Context> context);
509515
void CleanupHandles();
510516

517+
inline void AssignToContext(v8::Local<v8::Context> context,
518+
const ContextInfo& info);
519+
511520
void StartProfilerIdleNotifier();
512521
void StopProfilerIdleNotifier();
513522

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
@@ -102,8 +102,11 @@ class ContextifyContext {
102102
Persistent<Context> context_;
103103

104104
public:
105-
ContextifyContext(Environment* env, Local<Object> sandbox_obj) : env_(env) {
106-
Local<Context> v8_context = CreateV8Context(env, sandbox_obj);
105+
ContextifyContext(Environment* env,
106+
Local<Object> sandbox_obj,
107+
Local<Object> options_obj)
108+
: env_(env) {
109+
Local<Context> v8_context = CreateV8Context(env, sandbox_obj, options_obj);
107110
context_.Reset(env->isolate(), v8_context);
108111

109112
// Allocation failure or maximum call stack size reached
@@ -156,7 +159,9 @@ class ContextifyContext {
156159
}
157160

158161

159-
Local<Context> CreateV8Context(Environment* env, Local<Object> sandbox_obj) {
162+
Local<Context> CreateV8Context(Environment* env,
163+
Local<Object> sandbox_obj,
164+
Local<Object> options_obj) {
160165
EscapableHandleScope scope(env->isolate());
161166
Local<FunctionTemplate> function_template =
162167
FunctionTemplate::New(env->isolate());
@@ -206,7 +211,25 @@ class ContextifyContext {
206211
env->contextify_global_private_symbol(),
207212
ctx->Global());
208213

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

211234
return scope.Escape(ctx);
212235
}
@@ -268,8 +291,11 @@ class ContextifyContext {
268291
env->context(),
269292
env->contextify_context_private_symbol()).FromJust());
270293

294+
Local<Object> options = args[1].As<Object>();
295+
CHECK(options->IsObject());
296+
271297
TryCatch try_catch(env->isolate());
272-
ContextifyContext* context = new ContextifyContext(env, sandbox);
298+
ContextifyContext* context = new ContextifyContext(env, sandbox, options);
273299

274300
if (try_catch.HasCaught()) {
275301
try_catch.ReThrow();

0 commit comments

Comments
 (0)