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

3 comments:

eggsy84 said...

Hi there,

I wonder if you can help me?
I'm currently writing a cross site widget myself at the moment.

I have successfully coded an application that can communicate with a server using JSONP and wrote a quick tutorial here:

http://eggsylife.blogspot.com/2008/10/gwt-and-cross-site-jsonp-in-j2ee.html

But I'm now having a problem in that I cannot maintain state because each request I make is under a different session? Is this something you have experienced? Would using the window.name fix this possibly?

thank you in advance

eggsy84 said...

Hi

Just for your info I have solved this problem.

Gave some answers to what was happening here:

http://groups.google.com/group/Google-Web-Toolkit/browse_thread/thread/9f7677322422a4cd

Chris said...

Do you know if this works with GWT 1.6? I can get the POST to make the post call to a secondary domain.

I can not seem to get the POST results back into any usable state. I see the response back in FF using firebug; however, the window.name always returns the iframe name set by GWT, which is an incremented name like “FormPanel_XX”.

I’m trying to get a simple sample working and any pointers would be much appreciated. Here is a thread with a full description of what I am trying: http://groups.google.com/group/Google-Web-Toolkit/browse_frm/thread/139a99baf2382340#

Hopefully it is something simple that I have overlooked on my part.

Thanks,
Chris….