In Java 7 (1.7), I could access a Java method from JavaScript by running this:
ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
jse.eval("importClass(net.apocalypselabs.symat.Functions);");
jse.eval("SyMAT_Functions = new net.apocalypselabs.symat.Functions();");
String input = "notify(\"Foo\");"; // This is user input
jse.eval("with(SyMAT_Functions){ "+input+" }");
Which would run the notify() function from the Functions java class:
public class Functions {
private Object someObjectThatCannotBeStatic;
public void notify(Object message) {
JOptionPane.showMessageDialog(null, message.toString());
}
/* Lots more functions in here, several working with the same non-static variable */
}
How do I access the Functions class in Java 1.8 with the Nashorn engine? My goal is to run different code for the first snippet if the user has Java 1.8, while still allowing people with 1.7 to use the app.
I've tried http://www.doublecloud.org/2014/04/java-8-new-features-nashorn-javascript-engine/ , https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/api.html , and How to instantiate a Java class in JavaScript using Nashorn? without luck. None of them seem to allow me the same thing as Java 1.7 did, instead assuming I only want to access static functions and objects.
The most common error I get:
I start with...
ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
jse.eval("var SyMAT_Functions;with (new JavaImporter(Packages.net.apocalypselabs.symat)) {"
+ "SyMAT_Functions = new Functions();}");
...then...
jse.eval("with(SyMAT_Functions){ "+input+" }");
...spits out...
TypeError: Cannot apply "with" to non script object in <eval> at line number 1
I was able to reproduce. First of all, Nashorn doesn't try to make it difficult to use Java objects (non-static or otherwise) in general. I have used it in other projects and not had any major issue converting from Rhino in Java 7 beyond what is covered in the migration guide. However, the issue here appears to deal with the use of the with statement which is "not recommended" and is even disallowed in strict mode of ECMAScript 5.1, both according to MDN.
Meanwhile, I found a thread on the Nashorn-dev mailing list discussing a similar case. The relevant part of the response was:
Nashorn allows only script objects (i.e., objects created by a JS
constructor or JS object literal expression) as scope expression for
"with" statement. Arbitrary objects . . . can not be used as 'scope' expression for
'with'.
In jdk9, support has been added to support script objects mirror other
script engines or other globals (which are instances of ScriptObjectMirror).
It's not the most elegant solution but, without using JDK 9, I was able to get your intended use of with to function by writing a proxy object inside the Javascript to mirror the public API of the Java class:
package com.example;
import javax.script.*;
public class StackOverflow27120811
{
public static void main(String... args) throws Exception {
ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
jse.eval(
"var real = new Packages.com.example.StackOverflow27120811(); " +
"var proxy = { doSomething: function(str) { return real.doSomething(str); } }; "
);
jse.eval("with (proxy) { doSomething(\"hello, world\"); } ");
}
public void doSomething(String foo) {
System.out.println(foo);
}
}
Attila Szegedi pointed out the non-standard Nashorn Object.bindProperties function. While it can't be expected to work with anything but the Nashorn engine, it does eliminate the complexity of re-declaring all of the public API inside the proxy object. Using this approach, the first jse.eval(...) call can be replaced by:
jse.eval(
"var real = new Packages.com.example.StackOverflow27120811(); " +
"var proxy = { }; " +
"Object.bindProperties(proxy, real); " // Nashorn-only feature
);
I decided to compile and bundle the "old" Rhino interpreter with my application instead of using Nashorn.
https://wiki.openjdk.java.net/display/Nashorn/Using+Rhino+JSR-223+engine+with+JDK8
Related
I would like to get a clear answer on how to Sandbox execution Nashorn within a Java Application.
I have seen 'similar questions' (which I will refer to) but ultimately none of the answer seem to address my concerns.
Let me start with definitions.
Assume we start with this:
ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
engine.put("map",new HashMap());
engine.eval(jsCode); // jsCode can access 'map' only.
By "Sandboxing" I mean ensure that the JavaScript must not access any java object except the one added in the scope.
so the following evals should be fine.
engine.eval("map.toString()");
engine.eval("map.size()");
engine.eval("map.put('name','jeff'); ");
engine.eval("map.getClass()");
But the following evals will not:
engine.eval("var m = new java.util.HashMap();"); // <-- stop accessing Java
engine.eval("map.getClass().forName('java.io.File'); "); // stop. it's trying to be sneaky
Finally, I am not concerned about this:
engine.eval("while(1) {;}"); // this is impossible to detect. Maybe it's possible for this simple case... but sneaky users could make it impossible to detect... anyway this is not what I am asking. I am only concerned on accessing java objects.
So by sandboxing I intend to prevent jsCode to access java objects that I don't define.
I saw that this might be a potential solution:
jdk.nashorn.api.scripting.NashornScriptEngineFactory factory = new jdk.nashorn.api.scripting.NashornScriptEngineFactory();
ScriptEngine engine = factory.getScriptEngine(new jdk.nashorn.api.scripting.ClassFilter() {
public boolean exposeToScripts(String s) {
return false;
}
});
but is it 'safe' to access a package beginning with jdk.* directly ?
Another approach I saw is even more mysterious:
final ScriptEngine engine =
new NashornScriptEngineFactory().getScriptEngine(new String[] { "--no-java" });
I saw that one here:
Safely re-using sandboxed Nashorn containers
Can somebody let me know ?
You can use jdk.nashorn.api.scripting.* API if that would help in your application. javadoc for the same is here -> https://docs.oracle.com/javase/8/docs/jdk/api/nashorn/
And yes, --no-java is the option for preventing java package access from script code.
My application uses a ScriptEngine to offer plugin-ability to my end-users.
ScriptEngineManager engineManager = new ScriptEngineManager();
ScriptEngine engine = engineManager.getEngineByName("nashorn");
Whenever a user makes changes to his script, then the application replaces the engine instane by a new instance.
String newScript = ...;
engine = engineManager.getEngineByName("nashorn");
engine.eval(newScript);
Two closely related questions:
Should I try to reuse engines and perform some kind of clear() on them ?
If I just replace my engine with a new instance, should I dispose the previous instance in some way, to avoid memory leaks ? (e.g. I can imagine that the user could manage to create a script that starts a thread.)
The problem is, I cannot find any method that looks like a clear() or a dispose(). Does that mean that my current approach is correct ?
You can use a single engine instance but use separate Bindings objects. Bindings acts as a top-level program environment, so if you want to evaluate a script into what is basically a "new global scope" then you could do that. Look into javax.script API docs on how to do this. You can either use ScriptEngine.eval that takes a Bindings as second argument or the one that takes ScriptContext as second argument.
Even if there's no script code surviving from the previous evaluation, you'll save some initialization time as the script engine will already have predefined various JavaScript data-holder classes and property maps ("hidden classes").
Also: yes, everything is garbage collected. There's no need for an explicit "disposal" API.
I just wanted to share what I tested myself. It makes perfect sense, but for those still in doubt: Created threads do continue to run if you just replace engine instances:
public static void main(String[] args) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
String script =
"new java.lang.Thread(function() {\n" +
" for(;;) {" +
" print('Here\\'s Johnny !');" +
" java.lang.Thread.sleep(1000);" +
" }\n" +
"}).start();";
ScriptEngine engine = manager.getEngineByName("nashorn");
try {
engine.eval(script);
} catch (ScriptException e) {
e.printStackTrace();
}
// replace engine
engine = manager.getEngineByName("nashorn");
engine.eval("print('please, make it stop!!!');");
// please collect !!!
System.gc();
}
Output:
Here's Johnny !
please, make it stop!!!
Here's Johnny !
Here's Johnny !
Here's Johnny !
...
I guess that the garbage collector can clean the scripts, but not their actions outside their context. I think created threads are not even linked to the scripts in any way (i.e. outside their scope). So, I think it's just impossible for the jvm to detect or decide that these threads are linked to a replaced script and may or may not be stopped.
But this leads us too far for one stackoverflow question. Let's just focus on the ability to dispose/clear the bindings (i.e. ScriptContext).
Block java threads in nashorn scripts:
A possible solution, is to narrow down the available functionality. Here follow a couple of ways to avoid the creation of threads:
The following disables all java functionality:
// the option -nj is short for --no-java
ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine("-nj");
But you can also disable specific classes, using a ClassFilter.
ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine((className) -> {
if ("java.lang.Thread".equals(className)) return false;
if ("java.lang.Runnable".equals(className)) return false;
if ("java.util.Timer".equals(className)) return false;
if (className.startsWith("java.util.concurrency")) return false;
if (className.startsWith("javafx")) return false;
if (className.startsWith("javax.swing")) return false;
if (className.startsWith("java.awt")) return false;
return true;
});
Note: as soon as you define a ClassFilter also reflection classes are blocked automatically. So, you don't have to block those packages explicitly.
Problem Description
A somewhat contrived example to illustrate my question. Imagine we have some library of javascript functions that is already maintained and updated daily by an army of frontend devs. To be specific, imagine one such function looks like this:
function employeesForStore(store) {
var dictionary = {
"downtown": ["Joe", "Mary", "Steve"],
"uptown": ["Jules", "Vincent", "Matt"],
// and so on for hundreds of locations
};
return dictionary[store];
}
NOTE: Please ignore the details of this function's implementation. The actual function may be far more complex than simple JSON dictionary lookups, and assume we don't know any implementation details about the js function. All we know is it takes a String argument and returns and array of Strings.
Now we would like to take advantage of this function in our Java code. That is, in our Java code, we'd like to "load" this function, and then be able to call it multiple times, passing it String args and receiving String[] or ArrayList<String> results.
From searching SO and google so far, I understand that this will involve using:
javax.script.ScriptEngineManager
javax.script.ScriptEngine
and perhaps scriptEngine.getContext() for passing values into the function and receiving results.
I am a bit hazy on the details of the above, especially since most examples I've found involve running javascript code a single time, rather than making javascript function available to Java.
Example Code I'd Like To See
Assuming the js function is in the file "my_functions.js", load that file into Java so all of its functions will be available for use.
Call employeesForStore("downtown") and store its results in a native java String[] or List<String> in a variable called downtownResults.
Same as 2, but call employeesForStore("uptown") and store in variable uptownResults
Create an interface to act as a facade to your JavaScript code.
Here is an example using the Rhino implementation embedded in Oracle's Java 1.7 implementation:
package demo;
import java.io.*; import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import javax.script.*;
public class StoreData {
public static interface Stores {
public String[] employees(String store);
}
public static Stores stores() throws IOException, ScriptException {
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName("JavaScript");
AtomicReference<Stores> ref = new AtomicReference<>();
engine.put("ref", ref);
String adapt = "ref.set("
+ "new Packages.demo.StoreData.Stores({employees:employeesForStore})"
+ ");";
try (Reader myFns = new FileReader("my_functions.js")) { // TODO encoding
engine.eval(myFns);
engine.eval(adapt);
return ref.get();
}
}
public static void main(String[] args) throws IOException, ScriptException {
List<String> employees = Arrays.asList(stores().employees("uptown"));
System.out.println(employees);
}
}
By specifying an interface we let Rhino coerce the JavaScript types to Java types (String, String[], etc.)
The JRE spec makes no guarantees about what scripting engines should be provided so it may be wise to rely on an external engine. I don't know if Nashorn will change this.
You can use Rhino API to execute JS code in java
This tutorial covers the examples requested.
I have code which uses ScriptEngineManager, ScriptEngine class for executing JavaScript code using Java. But it works fine in Java SE, and doesn't work in Android - SDK show error of missing classes. Is it possible to execute JS code in Android? Thank you.
AndroidJSCore is a great one. And here is another little library I wrote for evaluating JavaScript:
https://github.com/evgenyneu/js-evaluator-for-android
jsEvaluator.evaluate("function hello(){ return 'Hello world!'; } hello();", new JsCallback() {
#Override
public void onResult(final String result) {
// get result here (optional)
}
});
It creates a WebView behind the scenes. Works on Android version 3 and newer.
You can use Webview which inherits View class. Make an XML tag and use findViewById() function to use in the activity. But to use the JavaScript, you can make a HTML file containing the JavaScript code. The example blelow might help.
Webview browser=(Webview) findViewById(R.main.browser); //if you gave the id as browser
browser.getSettings().setJavaScriptEnabled(true); //Yes you have to do it
browser.loadUrl("file:///android_asset/JsPage.html"); //If you put the HTML file in asset folder of android
Remember that the JS will run on WebView, not in native environment, thus you might experience a lag or slow FPS in emulator. However when using on an actual phone, the code may run fast, depending on how fast is your phone.
http://divineprogrammer.blogspot.com/2009/11/javascript-rhino-on-android.html will get you started. ScriptEngine is a java thing. Android doesn't have a JVM but a DalvikVM which is not identical but similar.
UPDATE 2018: AndroidJSCore has been superseded by LiquidCore, which is based on V8. Not only does it include the V8 engine, but all of Node.js is available as well.
Original answer:
AndroidJSCore is an Android Java JNI wrapper around Webkit's JavaScriptCore C library. It is inspired by the Objective-C JavaScriptCore Framework included natively in iOS 7. Being able to natively use JavaScript in an app without requiring the use of JavaScript injection on a bloated, slow, security-constrained WebView is very useful for many types of apps, such as games or platforms that support plugins. However, its use is artificially limited because the framework is only supported on iOS. Most developers want to use technologies that will scale across both major mobile operating systems. AndroidJSCore was designed to support that requirement.
For example, you can share Java objects and make async calls:
public interface IAsyncObj {
public void callMeMaybe(Integer ms, JSValue callback) throws JSException;
}
public class AsyncObj extends JSObject implements IAsyncObj {
public AsyncObj(JSContext ctx) throws JSException { super(ctx,IAsyncObj.class); }
#Override
public void callMeMaybe(Integer ms, JSValue callback) throws JSException {
new CallMeLater(ms).execute(callback.toObject());
}
private class CallMeLater extends AsyncTask<JSObject, Void, JSObject> {
public CallMeLater(Integer ms) {
this.ms = ms;
}
private final Integer ms;
#Override
protected JSObject doInBackground(JSObject... params) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
Thread.interrupted();
}
return params[0];
}
#Override
protected void onPostExecute(JSObject callback) {
JSValue args [] = { new JSValue(context,
"This is a delayed message from Java!") };
try {
callback.callAsFunction(null, args);
} catch (JSException e) {
System.out.println(e);
}
}
}
}
public void run() throws JSException {
AsyncObj async = new AsyncObj(context);
context.property("async",async);
context.evaluateScript(
"log('Please call me back in 5 seconds');\n" +
"async.callMeMaybe(5000, function(msg) {\n" +
" alert(msg);\n" +
" log('Whoomp. There it is.');\n" +
"});\n" +
"log('async.callMeMaybe() has returned, but wait for it ...');\n"
);
}
I was also looking for a way to run javascript on Android and came across j2v8 library. This is a java wrapper for Google's v8 engine.
To use it add a dependency:
compile 'com.eclipsesource.j2v8:j2v8_android:3.0.5#aar'
It has pretty simple api, but I haven't found any docs online apart from javadoc in maven repository. The articles on their blog are also useful.
Code sample from this article:
public static void main(String[] args) {
V8 runtime = V8.createV8Runtime();
int result = runtime.executeIntegerScript(""
+ "var hello = 'hello, ';\n"
+ "var world = 'world!';\n"
+ "hello.concat(world).length;\n");
System.out.println(result);
runtime.release();
}
The javax.script package is not part of the Android SDK. You can execute JavaScript in a WebView, as described here. You perhaps can use Rhino, as described here. You might also take a look at the Scripting Layer for Android project.
You can use Rhino library to execute JavaScript without WebView.
Download Rhino first, unzip it, put the js.jar file under libs folder. It is very small, so you don't need to worry your apk file will be ridiculously large because of this one external jar.
Here is some simple code to execute JavaScript code.
Object[] params = new Object[] { "javaScriptParam" };
// Every Rhino VM begins with the enter()
// This Context is not Android's Context
Context rhino = Context.enter();
// Turn off optimization to make Rhino Android compatible
rhino.setOptimizationLevel(-1);
try {
Scriptable scope = rhino.initStandardObjects();
// Note the forth argument is 1, which means the JavaScript source has
// been compressed to only one line using something like YUI
rhino.evaluateString(scope, javaScriptCode, "JavaScript", 1, null);
// Get the functionName defined in JavaScriptCode
Object obj = scope.get(functionNameInJavaScriptCode, scope);
if (obj instanceof Function) {
Function jsFunction = (Function) obj;
// Call the function with params
Object jsResult = jsFunction.call(rhino, scope, scope, params);
// Parse the jsResult object to a String
String result = Context.toString(jsResult);
}
} finally {
Context.exit();
}
You can see more details at my post.
Given that ScriptEngineManager and ScriptEngine are part of the JDK and Android SDK is not the same thing as the JDK I would say that you can't use these classes to work with JavaScript under Android.
You can check the Android SDK's reference documentation/package index to see what classes are included (what can you work on Android out of the box) and which of them are missing.
I just found the App JavaScript for Android, which is the Rhino JavaScript engine for Java. It can use all Java-classes, so it has BIG potential. The problem is it might be slow, since it is not really optimized (heavy CPU load). There is another JavaScript engine named Nashorn, but that unfortunately doesn't works on Google's DalvikVM Java engine (does not support the optimizations of Oracle Java engine). I hope Google keeps up with that, I would just love it!
If you want to run some javascript code on chrome browser as per the question copy this code and paste it into address bar:
data:text/html, <html contenteditable> <title> Notepad </title> <script> alert('Abhasker Alert Test on Mobile'); </script> </html>
I am embedding some javascript in a Java application using Rhino. I am following the example on the Rhino website, executing a script by calling the Context's evaluateString method and passing the actual script in as a String.
I have a whole bunch of existing javascript code that I would like to make use of. I don't want to concatenate it all into an enormous String and pass it in to evaluateString. I would rather be able to load the code in so that I can call it from the code that I do pass into evaluateString (kind of like the AddCode method works in Microsoft's scripting control). I would like to add code like I can currently add variables by using the ScriptableObject.putProperty method.
Is there a way to do this? Can someone provide a code snippet or a link to the documentation. Thanks!
From the documentation and examples it looks like references to previously evaluated objects are controlled by scopes.
Context context = Context.enter();
try {
ScriptableObject scope = context.initStandardObjects();
Object out = Context.javaToJS(System.out, scope);
ScriptableObject.putProperty(scope, "out", out);
context.evaluateString(scope,
"function foo() { out.println('Hello, World!'); }", "<1>", 1, null);
context
.evaluateString(scope, "function bar() { foo(); }", "<2>", 1, null);
context.evaluateString(scope, "bar();", "<3>", 1, null);
} finally {
Context.exit();
}
(Rhino 1.7 release 2)
I know some people use Rhino directly to get the latest version, but the Java 6 implementation can evaluate scripts like this:
ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
engine.eval("function foo() { println('Hello, World!'); }");
engine.eval("function bar() { foo(); }");
engine.eval("bar();");
In my code I had that need (utility scripts and such), and I just simply concatenated them together in a giant StringBuilder and evaled it (Java 6). Its the only way since javascript can't do (without Java wrapper objects) otherJSScript.someUsefulFunction().