Skip to content
Prev Previous commit
vm: support binding a script to a context
  • Loading branch information
TimothyGu committed Jul 17, 2017
commit 15a8ff8f52a8e01f41e773ea1f4e970976625aac
5 changes: 5 additions & 0 deletions doc/api/vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ changes:
`cachedData` property of the returned `vm.Script` instance.
The `cachedDataProduced` value will be set to either `true` or `false`
depending on whether code cache data is produced successfully.
* `contextifiedSandbox` {Object} A [contextified][] sandbox to associate with
the current script. This does not bind the created script to this context
(i.e. `runInContext` still works with all contexts), but merely informs V8
(and V8 inspector) that the script is associated with the context. Defaults
to the current context.

Creating a new `vm.Script` object compiles `code` but does not run it. The
compiled `vm.Script` can be run later multiple times. It is important to note
Expand Down
22 changes: 21 additions & 1 deletion lib/vm.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,32 @@ function runInDebugContext(code) {
}

function runInContext(code, contextifiedSandbox, options) {
options = Object.assign({}, options, {
contextifiedSandbox
});
return createScript(code, options)
.runInContext(contextifiedSandbox, options);
}

function runInNewContext(code, sandbox, options) {
return createScript(code, options).runInNewContext(sandbox, options);
sandbox = createContext(sandbox);
const needToAttach = contextAttached &&
!(options && options.doNotInformInspector) &&
!contextAttached(sandbox);
if (needToAttach) {
attachContext(sandbox);
}
try {
options = Object.assign({}, options, {
contextifiedSandbox: sandbox,
doNotInformInspector: true
});
return createScript(code, options).runInNewContext(sandbox, options);
} finally {
if (needToAttach) {
detachContext(sandbox);
}
}
}

function runInThisContext(code, options) {
Expand Down
43 changes: 43 additions & 0 deletions src/node_contextify.cc
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ class ContextifyScript : public BaseObject {
Maybe<bool> maybe_display_errors = GetDisplayErrorsArg(env, options);
MaybeLocal<Uint8Array> cached_data_buf = GetCachedData(env, options);
Maybe<bool> maybe_produce_cached_data = GetProduceCachedData(env, options);
MaybeLocal<Context> maybe_context = GetContext(env, options);
if (try_catch.HasCaught()) {
try_catch.ReThrow();
return;
Expand Down Expand Up @@ -565,6 +566,11 @@ class ContextifyScript : public BaseObject {
else if (produce_cached_data)
compile_options = ScriptCompiler::kProduceCodeCache;

Context::Scope scope(
maybe_context.IsEmpty() ?
env->context() :
maybe_context.ToLocalChecked());

MaybeLocal<UnboundScript> v8_script = ScriptCompiler::CompileUnboundScript(
env->isolate(),
&source,
Expand Down Expand Up @@ -917,6 +923,43 @@ class ContextifyScript : public BaseObject {
return value->ToInteger(env->context());
}

static MaybeLocal<Context> GetContext(Environment* env,
Local<Value> options) {
if (!options->IsObject())
return MaybeLocal<Context>();

MaybeLocal<Value> maybe_value =
options.As<Object>()->Get(
env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "contextifiedSandbox"));
if (maybe_value.IsEmpty())
return MaybeLocal<Context>();

Local<Value> value = maybe_value.ToLocalChecked();
if (!value->IsObject()) {
if (!value->IsNullOrUndefined()) {
env->ThrowTypeError(
"contextifiedSandbox property must be an object.");
}
return MaybeLocal<Context>();
}

ContextifyContext* sandbox =
ContextifyContext::ContextFromContextifiedSandbox(
env, value.As<Object>());
if (!sandbox) {
env->ThrowTypeError(
"contextifiedSandbox property must have been converted to a "
"context.");
return MaybeLocal<Context>();
}

Local<Context> context = sandbox->context();
if (context.IsEmpty())
return MaybeLocal<Context>();
return context;
}


static bool EvalMachine(Environment* env,
const int64_t timeout,
Expand Down
104 changes: 104 additions & 0 deletions test/parallel/test-vm-inspector-scriptparsed-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
'use strict';
const common = require('../common');
const { fires, neverFires } = common;
common.skipIfInspectorDisabled();
const assert = require('assert');
const {
Session,
attachContext,
detachContext
} = require('inspector');
const { promisify } = require('util');
const {
Script,
createContext,
runInContext,
runInNewContext,
runInThisContext
} = require('vm');

common.crashOnUnhandledRejection();

const session = new Session();
const once = common.cancellableOnce.bind(session);
const post = promisify(session.post).bind(session);

async function main() {
session.connect();

post('Debugger.enable');

// Get info about default context.
let topContext;
{
const eventPromise = fires(once('Runtime.executionContextCreated'));
post('Runtime.enable');
topContext = (await eventPromise).params.context;
assert.strictEqual(topContext.auxData.isDefault, true);
}

// Create sandbox for use later.
const sandbox = createContext();
let vmContext;
{
const eventPromise = fires(once('Runtime.executionContextCreated'));
attachContext(sandbox);
vmContext = (await eventPromise).params.context;
}

// Script
{
// Bind to current context by default
{
const eventPromise = fires(once('Debugger.scriptParsed'));
new Script('Array;');
const { params } = await eventPromise;
assert.strictEqual(params.executionContextId, topContext.id);
}

// Bind to selected context
{
const eventPromise = fires(once('Debugger.scriptParsed'));
new Script('Array;', {
contextifiedSandbox: sandbox
});
const { params } = await eventPromise;
assert.strictEqual(params.executionContextId, vmContext.id);
}
}

// runInContext: Bind script to specified context.
{
const eventPromise = fires(once('Debugger.scriptParsed'));
runInContext('Array;', sandbox);
const { params } = await eventPromise;
assert.strictEqual(params.executionContextId, vmContext.id);
}

// runInNewContext: Bind script to new context.
{
const createdPromise =
fires(once('Runtime.executionContextCreated'));
const destroyedPromise =
fires(once('Runtime.executionContextDestroyed'));
const eventPromise = fires(once('Debugger.scriptParsed'));
runInNewContext('Array;', {});
await createdPromise;
const { params } = await eventPromise;
assert.notStrictEqual(params.executionContextId, topContext.id);
assert.notStrictEqual(params.executionContextId, vmContext.id);
await destroyedPromise;
}

// runInThisContext: Bind script to current context.
{
const eventPromise = fires(once('Debugger.scriptParsed'));
runInThisContext('Array;');
const { params } = await eventPromise;
assert.strictEqual(params.executionContextId, topContext.id);
}

session.disconnect();
}

main();