Thursday, July 24, 2008

Cross Domain FormPanel submissions in GWT

While working on Timelord, our next generation charting and analytics tool, I found the need to do cross-domain REST requests because Timelord may be hosted anywhere, but the server-side services are running on a fixed domain (hosted by Google App Engine). Recently, abuse cool hacks using the window.name field have become en vogue, much like the earlier activity around fragment identifiers, and one particularly cool usage is the recent Dojo proposal to use window.name for cross-domain cross-frame communication.

After a little experimenting, and some snooping on the Dojo patch, I implemented a quick patch to GWT using Deferred Binding, here's an example:


public class WindowNameFormPanelImpl extends FormPanelImpl {

/**
* Gets the response html from the loaded iframe's name property
*
* @param iframe the iframe from which the response html is to be extracted
* @return the response html
*/
public native String getContents(Element iframe) /*-{
try {
// Make sure the iframe's window & document are loaded.
if (!iframe.contentWindow || !iframe.contentWindow.document)
return null;

// Get the response from window.name
return iframe.contentWindow.name;
} catch (e) {
return null;
}
}-*/;

/**
* Hooks the iframe's onLoad event and the form's onSubmit event.
*
* @param iframe the iframe whose onLoad event is to be hooked
* @param form the form whose onSubmit event is to be hooked
* @param listener the listener to receive notification
*/
public native void hookEvents(Element iframe, Element form,
FormPanelImplHost listener) /*-{
if (iframe) {
iframe.onload = function() {
// If there is no __formAction yet, this is a spurious onload
// generated when the iframe is first added to the DOM.
if (!iframe.__formAction)
return;

if(!iframe.__restoreSameDomain) {
iframe.__restoreSameDomain = true;
// restore same domain property of iframe to read window.name property
iframe.contentWindow.location =
@com.google.gwt.core.client.GWT::getModuleBaseURL()() +
"clear.cache.gif";
return;
}
listener.@com.google.gwt.user.client.ui.impl.FormPanelImplHost::onFrameLoad()();
};
}

form.onsubmit = function() {
// Hang on to the form's action url, needed in the
// onload/onreadystatechange handler.
if (iframe) {
iframe.__formAction = form.action;
iframe.__restoreSameDomain = false;
}
return listener.@com.google.gwt.user.client.ui.impl.FormPanelImplHost::onFormSubmit()();
};
}-*/;
}


And in your GWT module

<replace-with class="org.timepedia.timelord.client.impl.WindowNameFormPanelImpl">
<when-type-is class="com.google.gwt.user.client.ui.impl.FormPanelImpl"/>
</replace-with>


With this code, you simply submit a FormPanel to a service with an additional parameter, like "windowname=true" which indicates you want window.name transport. The resulting output of a POST should contain something like "<script>window.name = '....result string...'</script>. When the FormPanel.submit() method is invoked, this result will be passed to the FormHandler.onSubmitComplete(event), and you can retrieve the result via event.getResult().

-Ray
p.s. there are separate implementations for IE

Friday, July 4, 2008

Chronoscope GViz API and Gadget released

Hey guys, a beta of Chronoscope 0.86 (in the trunk) has been packaged up as a GViz API and Google Gadget (with Spreadsheets integration). Check it out at Timepedia.org to see the new features.

-Ray

Wednesday, June 11, 2008

Google I/O GWT Extreme! Presentation video now online

or, how I managed to render a million datapoints in Javascript in real time without crashing my browser. :)

For those who missed it, you can now catch my GWT Extreme! presentation on YouTube courtesy of Google.



You can also view the slides directly here:



I would highly recommend watching the following presentations first before watching mine:

Surprisingly Rockin Javascript and DOM Programming
Faster Than Possible Code: Deferred Binding
Resource Bundles and Linkers

If you're interested in just seeing the demo of Chronoscope zooming and rendering over 1 million datapoints, skip to about 27 minutes into my video.

-Ray

Preliminary GWT Export 2.0 release

For 1.5 users, I made two quick changes to the GWT Exporter to implement the first two features I mentioned last time: Zero-overhead Deferred Binding and 1.5 annotations.

Here's the change log:
* @gwt.export,@gwt.noexport,@gwt.exportClosure,@gwt.exportPackage now full fledged annotations instead of old 1.4 comment style
* Replacements are @Export, @NoExport, @ExportClosure, and @ExportPackage
* New deferred binding yields zero-overhead when exporting turned off
** By default, exports turned off, use <set-property name="export" value="yes"/> in your module to compile with them on

Build from the repository, or download the jar file at http://code.google.com/p/gwt-exporter

-Ray

Thursday, June 5, 2008

GWT Exporter:The Next Generation

Now that GWT 1.5 is out, it's time to look at revising my GWT Exporter library to take advantage of new features, as well as improve it speed/performance. Here are some of the features I'm looking at including in GWT Exporter 2.0

1) Support for 1.5 annotations (place @Export in front of a method to export it)
2) Export as Deferred Binding controlled by property (e.g. add ?export to URL or use <set-property) to avoid the overhead when you don't need it. For example, today Chronoscope always exports methods, even if you are including it inside of 100% GWT app. There should be zero overhead/cost when not being used.
3) Eliminate wrapper classes and bridge methods. Today, GWT Exporter generates Javascript classes which wrap GWT classes and expose methods. In 2.0, it should achieve exporting by adding non-obfuscated method aliases to GWT objects themselves.
4) Export as pure-JS libraries (no bootstrap script) with JSDoc annotations. Ideally, I copy could Javadoc from Java to JSDoc, but it's problematic since Generators don't get comments exposed to them (except for metadata)

If you have any feature suggestions for the Exporter, leave them here in the comments.

-Ray

How I lost 3 hours of my life to dynamic typing

Today, while working on integrating Chronoscope into Google's GViz API, I ran across a bizarre problem, the depths of the problem would flush 3 hours of work time down the toilet.

In order to integrate Chronoscope with GViz, I decided to provide some lightweight GWT 1.5 overlays on top of the API. To use the API, it is recommended to use the Google AJAX Loader. Quickly reading the docs, it seemed very straightforward. You start out by


<script src="http://www.google.com/jsapi?callback=handler">

After the google.load API is available, you write

function handler() {
google.load("visualization", "1", function() { alert("visualization api loaded!"); })
}

or do you?

Here comes the pain


So, if you're already familiar with the AJAX Loader API, you'll recognize I made a typo. Between the time I read the documentation, and the time I wrote the code above, the types of the parameters needed for this call morphed in my head. The third parameter can include a callback for asynchronous loading, however, the callback isn't passed as a function, but as a object, like this

google.load("visualization", "1", { callback: function() { alert("visualization api loaded!") } });

and I would have discovered this problem 3 hours earlier if it hadn't been for one fact: the wrong code locks up Firebug and generates an infinite "Loading..." in the browser tab.

If you run the wrong code inside a regular HTML page, it won't have this effect. But I was running this through an Apache Shindig IFRAME, which had loaded script containing this code via _IG_GetCachedURL, and somehow the combination of the IFRAME, the proxy loading of the script, and the cross-domain included JSAPI combined to kill Firefox. I couldn't use Firebug to set breakpoints, to inspect the network to see what stage of loading caused the failure, to see which script might be the culprit, or even to inspect the HTML DOM. I tried FF3 and installing Firebug 1.2beta, no dice. I tried Safari debugging, no dice.

Eventually, I came back to read the docs two or more times again, and discovered how I had screwed up the type of the third argument. In many other AJAX libraries, like Dojo or jQuery, they are more forgiving, and probably would have checked if the third argument was typeof(o) == "function" or "object" and acted accordingly.

I know, you're saying "Serves you right! You should RTFM more carefully!" Unfortunately, these kinds of errors are bound to be made by mere mortals, even if they do RTFM, especially with the size and complexity of todays AJAX apis.

Why do I endure using Java by using GWT my Javascript Ninja Brethren? When a simple mistake, or typo can waste half your day, you'll understand. And now that I've written a strongly typed GWT GViz/AJAX Loader wrapper, it's unlikely any GWT user will ever have to suffer the same fate.

-Ray

Wednesday, June 4, 2008

Design Patterns vs GWT Compiler: Round 1, Fight!

I've spent a lot of time maximizing performance of GWT code, and in doing so, I've tended to shy away from using too much OOP and Design Patterns, but the question is, is this fear justified? You've heard the GWT team continually tell you that you can have your abstractions without much performance loss, but is it true? It turns out, in many cases, the answer is a resounding yes.

Take for example, the Iterator pattern. I've crafted GwtQuery to iterate over native arrays, because I feared the overhead of iterators. But if you're careful in how you use the type system, the overhead turns out to be approximately, zero.

For example:


public class MyArray implements Iterable {
private String[] items = {"foo", "bar", "baz"};

public Iterator iterator() {
return new StringArrayIterator(items);
}

private class StringArrayIterator implements Iterator {

private String[] items;

private int index;

public StringArrayIterator(String[] items) {
this.items = items;
this.index = 0;
}

public boolean hasNext() {
return index < items.length;
}

public String next() {
return items[index++];
}

public void remove() {
throw new UnsupportedOperationException();
}
}
}

used as

MyArray m = new MyArray();
for(String s : m)
Window.alert(s);

The generated Javascript is

var m, s, s$iterator;
m = $MyArray(new MyArray());
for (s$iterator = $MyArray$StringArrayIterator(new MyArray$StringArrayIterator(), m.items);
s$iterator.index < s$iterator.items.length;) {
s = s$iterator.items[s$iterator.index++];
$wnd.alert(s);
}

As long as GWT can type-tighten the Iterable interface to the concrete implementing class, this output will usually hold true. The following will break the optimization and impose polymorphic dispatches:

public void onModuleLoad() {
MyArray m = new MyArray();
MyArray2 m2 = new MyArray2();
iterate(m);
iterate(m2);
}

public void iterate(Iterable m) {
for(String s : m)
Window.alert(s);
}

Here we introduce a new type, MyArray2, and we use an iterate() method which invokes the Iterable interface. Since GWT now has two classes in existence which implement Iterable<String>, it can't decide whether it has a MyArray or MyArray2, and therefore can't elide the iterator. You might ask, what happens if we remove MyArray2, but keep the iterate() method? We return to optimal code:

function $iterate(m){
var s, s$iterator;
for (s$iterator = $MyArray$StringArrayIterator(new MyArray$StringArrayIterator(), m.items);
s$iterator.index < s$iterator.items.length;) {
s = s$iterator.items[s$iterator.index++];
$wnd.alert(s);
}
}


Neat huh? If you're curious, try the same thing with Delegation, Decorator, Facade, or Flyweight, I think you'll be pleasantly surprised.

Fatality!, GWT Compiler. :)
-Ray