Making JSNI Calls

JavaScript Native Interface (JSNI)

Mix handwritten JavaScript into your Java classes to access low-level browser functionality.

Contents

Overview

The GWT compiler translates Java source into JavaScript. Sometimes it's very useful to mix handwritten JavaScript into your Java source code. For example, the lowest-level functionality of certain core GWT classes are handwritten in JavaScript. GWT borrows from the Java Native Interface (JNI) concept to implement JavaScript Native Interface (JSNI).

Writing JSNI methods is a powerful technique, but should be used sparingly. JSNI code is less portable across browsers, more likely to leak memory, less amenable to Java tools, and hard for the compiler to optimize.

We think of JSNI as the web equivalent of inline assembly code. You can:

  • Implement a Java method directly in JavaScript
  • Wrap type-safe Java method signatures around existing JavaScript
  • Call from JavaScript into Java code and vice-versa
  • Throw exceptions across Java/JavaScript boundaries
  • Read and write Java fields from JavaScript
  • Use hosted mode to debug both Java source (with a Java debugger) and JavaScript (with a script debugger, only in Windows right now)
Tip
When accessing the browser's window and document objects from JSNI, you must reference them as $wnd and $doc, respectively. Your compiled script runs in a nested frame, and $wnd and $doc are automatically initialized to correctly refer to the host page's window and document.

Writing Native JavaScript Methods

JSNI methods are declared native and contain JavaScript code in a specially formatted comment block between the end of the parameter list and the trailing semicolon. A JSNI comment block begins with the exact token /*-{ and ends with the exact token }-*/. JSNI methods are be called just like any normal Java method. They can be static or instance methods.

Example

public static native void alert(String msg) /*-{
$wnd.alert(msg);
}-*/;
Tip
In hosted mode, you can set a breakpoint on the source line containing the opening brace of a JSNI method, allowing you to see invocation arguments.

Accessing Java Methods and Fields from JavaScript

It can be very useful to manipulate Java objects from within the JavaScript implementation of a JSNI method. There is a special syntax for this.

Invoking Java methods from JavaScript

Calling Java methods from JavaScript is somewhat similar to calling Java methods from C code in JNI. In particular, JSNI borrows the JNI mangled method signature approach to distinguish among overloaded methods.

JavaScript calls into Java methods are of the form

[instance-expr.]@class-name::method-name(param-signature)(arguments)
where
[instance-expr.]
must be present when calling an instance method and must be absent when calling a static method
class-name
is the fully-qualified name of the class in which the method is declared (or a subclass thereof)
param-signature
is the internal Java method signature as specified here but without the trailing signature of the method return type since it isn't needed to choose the overload
arguments
the actual argument list to pass to the called method

Accessing Java fields from JavaScript

Static and instance fields can be accessed from handwritten JavaScript. Field references are of the form
[instance-expr.]@class-name::field-name

Example

public class JSNIExample {

String myInstanceField;
static int myStaticField;

void instanceFoo(String s) {
// use s
}

static void staticFoo(String s) {
// use s
}

public native void bar(JSNIExample x, String s) /*-{
// Call instance method instanceFoo() on this
this.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);

// Call instance method instanceFoo() on x
x.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);

// Call static method staticFoo()
@com.google.gwt.examples.JSNIExample::staticFoo(Ljava/lang/String;)(s);

// Read instance field on this
var val = this.@com.google.gwt.examples.JSNIExample::myInstanceField;

// Write instance field on x
x.@com.google.gwt.examples.JSNIExample::myInstanceField = val + " and stuff";

// Read static field (no qualifier)
@com.google.gwt.examples.JSNIExample::myStaticField = val + " and stuff";
}-*/;

}
Tip
When writing JSNI code, it's helpful to occasionally run in web mode. The JavaScript compiler checks your JSNI code and can flag errors at compile time that you wouldn't catch until runtime in hosted mode.

Sharing objects between Java source and JavaScript

Parameters and return types in JSNI methods are declared as Java types. There are very specific rules for how values passing in and out of JavaScript code must be treated. These rules must be followed whether the values enter and leave through normal method call semantics, or through the special syntax.

Passing Java values into JavaScript

Incoming Java typeHow it appears to JavaScript code
a Java numeric primitivea JavaScript numeric value, as in var x = 42;
String a JavaScript string, as in var s = "my string";
booleana JavaScript boolean value, as in var b = true;
JavaScriptObject (see notes)a JavaScriptObject that must have originated from JavaScript code, typically as the return value of some other JSNI method
Java array an opaque value that can only be passed back into Java code
any other Java Object an opaque value accessible through special syntax

Passing JavaScript values into Java code

Outgoing Java typeWhat must be passed
a Java numeric primitivea JavaScript numeric value, as in return 19;
Stringa JavaScript string, as in return "boo";
boolean a JavaScript boolean value, as in return false;
JavaScriptObject (see notes) a native JavaScript object, as in return document.createElement("div")
any other Java Object (including arrays)a Java Object of the correct type that must have originated in Java code; Java objects cannot be constructed from "thin air" in JavaScript

Important Notes

  • A Java numeric primitive is one of byte, short, char, int, long, float, or double. You must ensure the value is appropriate for the declared type. Returning 3.7 when the declared type is int will cause unpredictable behavior.
  • Java null and JavaScript null are identical and always legal values for any non-primitive Java type. JavaScript undefined is not identical to null; never return undefined from a JSNI method or unpredictable behavior will occur.
  • Violating any of these marshaling rules in hosted mode will generate a com.google.gwt.dev.shell.HostedModeException detailing the problem. This exception is not translatable and never thrown in web mode.
  • JavaScriptObject is a magical type that gets special treatment from the GWT compiler and hosted browser. Its purpose is to provide an opaque representation of native JavaScript objects to Java code.
Tip
When returning a possibly undefined value from a JSNI method, we suggest using the idiom return (value == null) ? null : value; to avoid returning undefined.

Exceptions and JSNI

Exceptions can originate both in Java code and in handwritten JavaScript code.

An exception that originates in a JSNI method and escapes into Java code can be caught as a JavaScriptException. Relying on this behavior is discouraged because JavaScript exceptions are not usefully typed. The recommended practice is to handle JavaScript exceptions in JavaScript code and Java exceptions in Java code.

When a JSNI method invokes a Java method, a more complex call chain results. An exception thrown from the inner Java method can safely pass through the sandwiched JSNI method back to the original Java call site, retaining type fidelity. It can be caught as expected. For example,

  1. Java method foo() calls JSNI method bar()
  2. JavaScript method bar() calls Java method baz()
  3. Java method baz() throws an exception
The exception thrown out of baz() will propagate through bar() and can be caught in foo().

0 comments: