Vivin Paliath
Vivin Paliath

Reputation: 95528

Sharing native JavaScript objects across contexts in Nashorn

I have a Nashorn engine in which I evaluate some scripts that expose some common utility functions and objects. I want custom scripts to run in their own contexts and not step over each other, so I create new contexts for them using engine.createBindings():

ScriptContext newContext = new SimpleScriptContext();
newContext.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
newContext.getBindings(ScriptContext.ENGINE_SCOPE).putAll(engine.getBindings(ScriptContext.ENGINE_SCOPE));

Now I have access to everything that was created in the original scope, but this also creates an entirely-new global-object for the new context, which means that instances of native JS objects like Object, Number, etc. are different from corresponding instances in the original context.

This leads to some strange behavior. For example, assume you have the following code that was evaluated in the engine (i.e., the "parent" context"):

function foo(obj) {
    print(JSON.stringify(obj, null, 4));
    print(Object.getPrototypeOf(obj) === Object.prototype);
}

Now let's say your custom script is as follows:

function bar() {
    foo({a: 10, b: 20});
}

I evaluate this against newContext and then invoke the function:

engine.eval(source, newContext);
ScriptObjectMirror foo = newContext.getAttribute("foo", ScriptContext.ENGINE_SCOPE);
foo.call(null);

This returns:

undefined
false

This is expected behavior because objects created in other contexts are treated as foreign objects.

What I'm trying to do is to expose a common library of functions and maintain that within a single script-engine instance. I don't want to keep recreating script-engine instances because I end up losing JIT optimizations (I read this somewhere, but I can't find the link right now). I do like the fact that objects "remember" their originating global-context, but I'd like that not to happen in the case of native JS objects.

Is there a way to create an entirely-new global context, while still sharing JS global-object instances? I've tried manually copying over these instances (enumerating the properties of this), but when I copy them over to the new context, they are ScriptObjectMirror instances and not the unwrapped versions. I assume this is because they were originally created in a different context and therefore are considered to be "foreign".

Upvotes: 2

Views: 568

Answers (1)

Vivin Paliath
Vivin Paliath

Reputation: 95528

It looks like it is not possible to do this unfortunately. I even unwrapped the objects and got back the native objects (I did this from the main thread), and then overwrote the ones in the new context. Unfortunately, this still doesn't work since the Java classes that represent native objects in Java maintain internal references to the original instances that were created (for prototypes of the object).

My workaround for the two cases above was to do this for the object test:

var proto = Object.getPrototypeOf(object);
return (typeof proto.constructor !== "undefined" && (proto.constructor.name === Object.prototype.constructor.name));

For JSON, I overwrote the native JSON object by evaluating Douglas Crockford's JSON library in the main context, and every new context. I did have to make some modifications to get it to work in Nashorn though. The first was to make it redefine the JSON object without check for its existence first (so just remove the if test). The second was to use the hasOwnProperty from the object itself as well, inside stringify. So the following line:

if (Object.prototype.hasOwnProperty.call(value, k))

Changes to:

if (Object.prototype.hasOwnProperty.call(value, k) || (value.hasOwnProperty && value.hasOwnProperty(k)))

With these two changes I was able to get things to work.

Upvotes: 1

Related Questions