JavaScript and Java applets
Saturday, October 11th, 2008I know, Applets are almost dead, but we actually had use for them recently in a project. We needed to upload multiple images at once, and since no one on our team knows Flash, we decided to use an Applet. Anyway, the trouble we came across was the Java to JavaScript bridge is flat-out broken. What you are supposed to be able to do is well documented at Sun’s java_js page and Mozilla’s LiveConnect page. The trouble is, that they are wrong when it comes to calling a Java method from JavaScript. They also fail to mention the security implications of that altogether.
Signed applets with unsigned JavaScript
The first challenge we came to (which is actually the easiest to solve) was that we were getting AccessControlExceptions, even though we went through the trouble of signing the Applet jar. As it turns out, the permission context used is that of the JavaScript, so you need to elevate the permission to your Applets context, using AccessController, and PrivilegedAction.
Java:
public void javascriptCallsMe() {
AccessController.doPrivileged(new PrivilegedAction() {
public Void run() {
// We can now
readOrWriteFilesOrWhatever();
return null;
}
});
}
That solved that problem.
Passing JavaScript objects to Java
The next problem, which plagued me for a week, was that getting a JSObject in Java seems to be broken. There are two ways to get an instance of the netscape.javascript.JSObject class. The first way, and this always seems to work, is the JSObject.getWindow(Applet applet). This will get the JSObject wrapping the “window” browser object. This is useful if you know the “path” to the JavaScript object your code cares about. It is akin to using a static reference, and isn’t good design. The other way, is to have a Java method that takes an Object or JSObject reference: In Java:
public class MyApplet extends JApplet {
public void doStuff(JSObject params) {
System.out.println(params.getMember("foo"));
}
}
Then in JavaScript you should be able to do this: documents.applets[0].doStuff({foo: "bar"}); Unfortunately, what really happens is you get a “broken” instance of JSObject. Debugging the Applet, I found that the JSObject instance has a field called nac, which has a value for the JSObject.getWindow(…), but is null for values passed in from JavaScript. So, what solutions and work arounds have people come up with? None that I could find. I searched high and low. Plenty of people have discovered this bug, but none of come up with a solution. Until now! I thought about it and realized, JSObjects I get from the “window” JSObject all work, so maybe I can put my broken object into a working object, and pull it back out to get a working object. A little experiment proved that it worked (at least on FireFox, I’ll guess it works on IE too, anyone want to verify?). So, I decided to go ahead and create a JSObject resolver, that will fix any possibly broken object:
public class MyApplet extends JApplet {
private JSObject appletTmp;
public JSObject resolveObject(Object o) {
final int hashCode = System.identityHashCode(o);
appletTmp.setMember("toResolve" + hashCode, o);
return (JSObject) appletTmp.getMember("toResolve" + hashCode);
}
public void init() {
if (appletTmp == null) {
final JSObject window = JSObject.getWindow(this);
final String tmpName = "_AppletTmp" + System.identityHashCode(this);
window.eval("var "+ tmpName +" = {}");
appletTmp = (JSObject)window.getMember(tmpName);
}
}
}
Granted, this code doesn’t clean up after itself, so if you use it for a long time or on a lot of JS objects, you will need to add some clean up code to it. With that, I was finally able to fully use the Java Applet the way I wanted to: As a “service provider” to the JavaScript on our existing page.
