Quack provides Java (Android and desktop) bindings to JavaScript engines.
Quack supports both the Duktape and QuickJS runtimes.
- Share objects between runtimes seamlessly.
- Javascript Debugging support
- Invocations can be intercepted and coerced both in and out of the JavaScript runtime.
'hello'
Calling QuackContext.evaluate will return a Java Object with the evaluation result.
QuackContext quack = QuackContext.create();
Object result = quack.evaluate(javascriptString);
System.out.println(result);
// prints "hello"
(function () {
return 'hello';
})
QuackContext quack = QuackContext.create();
JavaScriptObject result = quack.evaluateForJavaScriptObject(javascriptString);
System.out.println(result.call());
// prints "hello"
(function (str) {
return str ' world';
})
QuackContext quack = QuackContext.create();
JavaScriptObject result = quack.evaluateForJavaScriptObject(javascriptString);
System.out.println(result.call("hello"));
// prints "hello world"
System.out.println('hello world');
QuackContext quack = QuackContext.create();
quack.getGlobalObject().set("System", System.class);
quack.evaluate(javascriptString);
// prints "hello world"
(function(obj) {
obj.hello();
})
class Foo {
public void hello() {
System.out.println("hello");
}
}
QuackContext quack = QuackContext.create();
JavaScriptObject result = quack.evaluateForJavaScriptObject(javascriptString);
quack.call(new Foo());
// prints "hello world"
(function(func) {
// notice that it is not func.run()
// single method interfaces (lambdas) are automatically coerced into functions!
func();
})
Runnable runnable = () -> {
System.out.println("hello world");
}
QuackContext quack = QuackContext.create();
JavaScriptObject result = quack.evaluateForJavaScriptObject(javascriptString);
result.call(runnable);
// prints "hello world"
return {
hello: function(printer) {
printer('hello world');
}
}
interface Foo {
void hello(Printer printer);
}
interface Printer {
print(String str);
}
QuackContext quack = QuackContext.create();
Foo result = quack.evaluate(javascriptString, Foo.class);
result.hello(str -> System.out.println(str));
// prints "hello world"
var foo = new Foo();
foo.hello('hello world');
class Foo {
public void hello(String str) {
System.out.println(str);
}
}
QuackContext quack = QuackContext.create();
quack.getGlobalObject().set("Foo", Foo.class);
quack.evaluate(javascriptString);
// prints "hello world"
var Foo = JavaClass.forName("com.whatever.Foo");
var foo = new Foo();
foo.hello('hello world');
class Foo {
public void hello(String str) {
System.out.println(str);
}
}
QuackContext quack = QuackContext.create();
quack.getGlobalObject().set("JavaClass", Class.class);
quack.evaluate(javascriptString);
// prints "hello world"
Types need to be marshalled when passing between the runtimes. The class specifier and parameter types are used to determine the behavior when being marshalled. The following builtin types are marshalled as follows:
JavaScript (Input) | Java (Output) |
---|---|
number | Number (Integer or Double) |
Uint8Array | ByteBuffer (direct, deep copy) |
undefined | null |
Java (Input) | JavaScript (Output) |
---|---|
long | string (otherwise precision is lost) |
ByteBuffer (direct or byte array backed) | Uint8Array (deep copy) |
byte, short, int, float, double | number |
null | null |
Types and methods can be coerced between runtimes.
(function(data) {
return data;
})
class Foo {}
QuackContext quack = QuackContext.create();
// all instances of Foo sent to JavaScript get coerced into the String "hello world"
quack.putJavaToJavaScriptCoercion(Foo.class, (clazz, o) -> "hello world");
System.out.println(quack.evaluateForJavaScriptObject.call(new Foo()));
// prints "hello world"
JavaScript runtimes are single threaded. All execution in the JavaScript runtime is gauranteed thread safe, by way of Java synchronization.
When a Java object is passed to the JavaScript runtime, a hard reference is held by the JavaScript proxy counterpart. This reference is removed when the JavaScriptObject is finalized. And same for when a Java object is passed to the JavaScript runtime. JavaScriptObjects sent to the Java runtime will be deduped, so the same proxy instance is always used. JavaObjects sent to JavaScript will marshall a new Proxy object every time.
Install the appropriate QuickJS Debugger or Duktape Debugger for VS Code. QuickJS is the default runtime used by Quack.
System.out.println('set a breakpoint here!');
QuackContext quack = QuackContext.create();
quack.getGlobalObject().set("System", System.class);
quack.waitForDebugger("0.0.0.0:9091")
// attach using VS Code
quack.evaluate(javascriptString);
Quack was initially forked from Square's Duktape Android library. But it has been totally rewritten to suit different needs.