I'm trying to preload some libraries into a global scope (things like chai.js). This changes the prototype of some objects and I realised that this works for ENGINE_SCOPE but not for GLOBAL_SCOPE.
Minimal example:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
Bindings globalBindings = engine.createBindings();
engine.eval("Object.prototype.test = function(arg){print(arg);}", globalBindings);
//works as expected, printing "hello"
engine.getContext().setBindings(globalBindings, ScriptContext.ENGINE_SCOPE);
engine.eval("var x = {}; x.test('hello');");
//throws TypeError: null is not a function in <eval> at line number 1
engine.getContext().setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
engine.getContext().setBindings(globalBindings, ScriptContext.GLOBAL_SCOPE);
engine.eval("var x = {}; x.test('hello');");
Is there a workaround to make it work as expected, i.e. changes are propagated correctly from global to engine scope?
The global scope can only be used for simple variable mapping. For example:
ScriptContext defCtx = engine.getContext();
defCtx.getBindings(ScriptContext.GLOBAL_SCOPE).put("foo", "hello");
Object
lives in the engine scope and thus the global scope is not even searched for any mappings related to it (Object.prototype.test
in your case).
An excerpt from the documentation:
The default context's ENGINE_SCOPE is a wrapped instance of ECMAScript "global" object - which is the "this" in top level script expressions. So, you can access ECMAScript top-level objects like "Object", "Math", "RegExp", "undefined" from this scope object. Nashorn Global scope object is represented by an internal implementation class called jdk.nashorn.internal.objects.Global. Instance of this class is wrapped as a jdk.nashorn.api.scripting.ScriptObjectMirror instance. ScriptObjectMirror class implements javax.script.Bindings interface. Please note that the context's GLOBAL_SCOPE Bindings and nashorn global object are different. Nashorn's global object is associated with ENGINE_SCOPE and not with GLOBAL_SCOPE. GLOBAL_SCOPE object of default script context is a javax.script.SimpleBindings instance.The user can fill it with name, value pairs from the java code.
https://wiki.openjdk.java.net/display/Nashorn/Nashorn+jsr223+engine+notes
--global-per-engine option
by specifying -Dnashorn.args=--global-per-engine
in your Java command line. Then Nashorn will use a single instance of the global object for all script evaluations regardless of ScriptContext passed.ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
SimpleScriptContext context = new SimpleScriptContext();
engine.eval("Object.prototype.test = function(arg){print(arg);}", context);
engine.eval("var x = {}; x.test('hello');", context);
Every time you need a new context with libraries simply create it:
public static void main(String[] args) throws ScriptException {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
SimpleScriptContext context1 = createContextWithLibraries(engine);
//works as expected, printing "hello"
engine.eval("var x = {}; x.test('hello'); var y = 'world';", context1);
SimpleScriptContext context2 = createContextWithLibraries(engine);
//works as expected, printing "hello"
engine.eval("var x = {}; x.test('hello');", context2);
//works as expected, printing "world"
engine.eval("print(y);", context1);
//fails with exception since there is no "y" variable in context2
engine.eval("print(y);", context2);
}
private static SimpleScriptContext createContextWithLibraries(ScriptEngine engine) throws ScriptException {
SimpleScriptContext context = new SimpleScriptContext();
engine.eval("Object.prototype.test = function(arg){print(arg);}", context);
return context;
}