Revisiting the script-via-<iframe> default linkage

86 views
Skip to first unread message

Matt Mastracci

unread,
Nov 24, 2009, 6:38:32 PM11/24/09
to Google-Web-Tool...@googlegroups.com
Hey all,

If I recall correctly, the original reason that GWT used iframe-
wrapped scripts was to work around the buggy compression of Javascript
some early versions of IE (example: http://support.microsoft.com/default.aspx?scid=kb;en-us;823386&Product=ie600)
.

The number of users on IE 6 SP1 is likely fairly small right now and
at some point, IE6 is going to drop off the map entirely. I wonder if
it might be a good time for GWT to consider the switch to raw JS files
and consider IE6 SP1 as a "special case" to work around.

There's a number of advantages to serving raw JS:

- You can serve it from a CDN (we do this right now, but it
requires a lot of work on our side to track the hosted mode code)
- It doesn't require an additional browser rendering context
- XSLinker is no longer a special case: wrapping the code in a
closure to prevent namespace pollution can be a configuration property
- Better guarantees on parallel downloads for loading leftover and
exclusive fragments simultaneously
- Window events no longer need selection-script support to be
hooked up (to work around the IE security bugs)
- Less magical overall: a design closer to that of other modular JS
toolkits

It seems like the post-2.0 GWT codebase would be a reasonable time to
proceed along this path. I've been using this approach for some time
and it's been working very well.

Thoughts?

Matt.

Scott Blum

unread,
Nov 24, 2009, 7:46:53 PM11/24/09
to google-web-tool...@googlegroups.com
I think this is a good direction to be heading in.  It's a bit non-trivial, though, because the plugin expects to be nested in an iframe.  I'm sure that's solvable, but it will be some work.


Matt Mastracci

unread,
Nov 24, 2009, 9:39:21 PM11/24/09
to google-web-tool...@googlegroups.com
Hey Scott,

I've ported hosted.html over to hosted.js a few times and it's not actually a problem. The only significant differences between the two files are s/parent/window/ and how you pass the name of the module to the hosted-mode bootstrap script.  Each version of the plugin tested (Safari, FF and IE from a few months ago and recent Safari/FF builds) has been able to work in this environment without modification.

I don't think the hosted-mode plugin would work with the privately-scoped XSLinker today, but it definitely works with our modified version that links the JS at the global scope.

Scott Blum

unread,
Nov 24, 2009, 11:33:12 PM11/24/09
to google-web-tool...@googlegroups.com
On Tue, Nov 24, 2009 at 9:39 PM, Matt Mastracci <mat...@mastracci.com> wrote:
Hey Scott,

I've ported hosted.html over to hosted.js a few times and it's not actually a problem. The only significant differences between the two files are s/parent/window/ and how you pass the name of the module to the hosted-mode bootstrap script.  Each version of the plugin tested (Safari, FF and IE from a few months ago and recent Safari/FF builds) has been able to work in this environment without modification.

I don't think the hosted-mode plugin would work with the privately-scoped XSLinker today, but it definitely works with our modified version that links the JS at the global scope.

Okay, that makes sense to me.  I think to really ship this, though, we want the plugins to put their symbols into a private namespace.  Otherwise two modules on one page would step all over each other, for example.

Ray Cromwell

unread,
Nov 25, 2009, 2:29:55 AM11/25/09
to google-web-tool...@googlegroups.com

IMHO, pollution of the global namespace is a big problem for general purpose apps. It's ok to do it when you control everything (e.g. Wave), but probably a bad idea for enterprise users who have a habit of composing lots of small GWT modules on a single dashboard. We probably need to support both explicit scope and polluting options, and allow the developer to choose.

-Ray


Ray Cromwell

unread,
Nov 25, 2009, 2:39:30 AM11/25/09
to google-web-tool...@googlegroups.com

err, not explicit scope, I mean "predeclare all your symbols with 'var' privarte scope".

-Ray

Matt Mastracci

unread,
Nov 25, 2009, 10:59:23 AM11/25/09
to google-web-tool...@googlegroups.com
I put some thought into this last night and there are some interesting
questions raised:

By default, the IFrameLinker allows multiple modules to coexist on
the same page. To replicate this with scripts, you need to ensure that
all scripts are privately scoped. There are conflicts with privately-
scoped scripts and dynamically-loaded code. You can mutate the global
scope of a window, but I don't know of any way to add new identifiers
to the scope of a function used as a private namespace after that
function has terminated.

It is possible to evaluate code within an existing private scope. As
long as you can structure the symbol dependencies between runAsync
modules such that it can be represented as a DAG, two fragments never
providing visible identifiers for each other, you can load the various
modules as a tree of scopes. I'm not deeply familiar with runAsync,
so I don't know if it is always true. The worst-case end-result of
loading 10 different fragments, each depending on symbols from the
last n-1 loaded fragments would effectively be the equivalent of 10-
deep nested scope chain. I don't know what effect this would have on
performance.

Here's a summary of the linking options that I know of. I've added in
an alternative iframe linker that doesn't load HTML from a remote
source, but dynamically constructs the iframe contents and injects the
scripts into it:

1. Monolithic iframe (IFrameLinker):

Supports multimodule: Yes
Supports off-domain loading: No
Supports runAsync: Yes
Global namespace pollution: Only within the iframe

2. Dynamic iframe, injected with scripts, ie: create iframes
dynamically with src="javascript:;" and document.write()ing an HTML
document:

Supports multimodule: Yes
Supports off-domain loading: Yes
Supports runAsync: Yes
Global namespace pollution: Only within the iframe

3. Globally-scoped script:

Supports multimodule: No
Supports off-domain loading: Yes
Supports runAsync: Yes
Global namespace pollution: Yes

4. Privately-scoped script (XSLinker):

Supports multimodule: Yes
Supports off-domain loading: Yes
Supports runAsync: Possibly, but with potential size/speed consequences
Global namespace pollution: None

Matt.
> --
> http://groups.google.com/group/Google-Web-Toolkit-Contributors

Lex Spoon

unread,
Nov 30, 2009, 3:29:12 PM11/30/09
to google-web-tool...@googlegroups.com, Matt Mastracci
(Reposting to get it on the mailing list; first try bounced.)


Hey, Matt,

I agree with your analysis about the code-splitting issues.

I've worked out a preliminary patch to do var renaming, but I haven't
shared it yet because it's in a pretty early state. I could share it
if you or someone is eager enough to see it that you're willing to
hack some code to get to use it.

To really get it polished up into a committable state, the main issue
will be figuring out when to enable the rewrites. Whether to enable
it or not depends on the choice of linker.

For the off-domain loading, I was thinking to look into a JSONP-like
downloader. That, too, is something that should only optionally be
enabled, because it has worse download failure reporting. Thus, again
the hardest part will be figuring out when to enable it.

Ray Cromwell

unread,
Nov 30, 2009, 7:09:36 PM11/30/09
to google-web-tool...@googlegroups.com, Matt Mastracci

Lex,
  JSONP loading + dynamic iframe seems like a straightforward viable option that doesn't require a lot of complicated compiler work, what do you think of providing this as an option?

-Ray

Matt Mastracci

unread,
Nov 30, 2009, 10:28:31 PM11/30/09
to Lex Spoon, google-web-tool...@googlegroups.com
Sure, I'd love to take a look at it. I've got a basic version of
globally-scoped, cross-domain code-splitting up and running that uses
simple script-tags for the cross-domain load right now.

Re: <script> tag error reporting. In my investigations, this has been
particularly bad and highly variable across browsers. I came away
with three conclusions:

1. onload or onreadystatechange works across all browsers to detect a
successful load, but it's moot because you can use JSONP callbacks to
do this.
2. onerror works some of the time in some of the browsers. It fails
on various combinations of resolve errors, error status codes and
other failure conditions. For all browsers (except Opera) that don't
support it directly, It can be emulated with onreadystatechange/onload
and lack of a JSONP callback.
3. Opera can't detect that a script failed to load in any way that
I've found: none of window.onerror, script.onerror,
script.onreadystatechange will fire when the script fails to load.

Matt.

On 30-Nov-09, at 1:27 PM, Lex Spoon wrote:

> Hey, Matt,
>
> I agree with your analysis about the code-splitting issues.
>
> I've worked out a preliminary patch to do var renaming, but I haven't
> shared it yet because it's in a pretty early state. I could share it
> if you or someone is eager enough to see it that you're willing to
> hack some code to get to use it.
>
> To really get it polished up into a committable state, the main issue
> will be figuring out when to enable the rewrites. Whether to enable
> it or not depends on the choice of linker.
>
> For the off-domain loading, I was thinking to look into a JSONP-like
> downloader. That, too, is something that should only optionally be
> enabled, because it has worse download failure reporting. Thus, again
> the hardest part will be figuring out when to enable it.
>
> Lex

Lex Spoon

unread,
Dec 1, 2009, 9:51:14 AM12/1/09
to Matt Mastracci, google-web-tool...@googlegroups.com
On Mon, Nov 30, 2009 at 10:28 PM, Matt Mastracci <mat...@mastracci.com> wrote:
> 2.  onerror works some of the time in some of the browsers. It fails on
> various combinations of resolve errors, error status codes and other failure
> conditions. For all browsers (except Opera) that don't support it directly,
> It can be emulated with onreadystatechange/onload and lack of a JSONP
> callback.

Can you expand on that? IE has script-tag callbacks that should be
usable to detect download errors. What did you get working on other
browsers?

If there's a way to detect download failures on Firefox and on
Webkit-based browser, then JSONP downloads are better than I thought.

Lex

Matt Mastracci

unread,
Dec 1, 2009, 11:05:24 AM12/1/09
to Lex Spoon, google-web-tool...@googlegroups.com
onerror is reasonably reliable across modern browsers. For IE, you can
use onreadystatechange to detect that the script finished loading and
then check to see if a JSONP callback wasn't made or a marker global
wasn't initialized:

Firefox 2/3/3.5: onerror will fire
Safari 3/4: onerror will fire
Safari 2: no events will fire
Chrome: onerror will fire
IE6/7/8: onreadystatechange(loaded) will fire, but JS won't be loaded
Opera: no events will fire

As mentioned before, I couldn't get Opera to tell me when a script
failed. Safari 2 also seemed to ignore error conditions, but its
overall browser share is negligible.

All of the browsers that responded to errors threw the same errors
when retrieving a 404 response or an error while resolving a domain.
I've seen some notes elsewhere that inferred that some browsers
treated some error conditions differently, but I couldn't reproduce
that in testing.

Note that the events only seem to work consistently across browsers
when assigning a function to the onevent properties before the src is
assigned. This is basically the code I used for my test:

function testLoad() {
!!marker ? success() : error();
}

var script = document.createElement('script');
script.onreadystatechange = function(e) {
if (this.readyState == 'loaded')
testLoad();
}
script.onload = function(e) {
testLoad();
}
script.onerror= function(e) {
error(e);
}
script.src = 'notfound.js';
document.body.insertBefore(script, null);

Matt.


Matt Mastracci

unread,
Dec 1, 2009, 11:15:40 AM12/1/09
to Ray Cromwell, google-web-tool...@googlegroups.com
Ray/Lex,

I'm starting to think that the dynamic iframe might not be a bad first approach to this problem either.  A single linker would be able to provide cross-domain-capable, multi-module-safe code that doesn't require any additional post-processing to support loading of fragments.  It also runs in the global scope, saving the extra few ms per global access (http://blog.j15r.com/2009/08/where-should-i-define-javascript.html). 

The method used to load the first and additional fragments could become a linker property. For our cross-domain loads, I'd like to have a strategy that tries to use cross-domain XMLHttpRequest first, assuming appropriate Access-Control-* headers on the other end, then falls back to <script> tags if this isn't available.  The default could be something simple like <script> tags, or even standard XMLHttpRequest, assuming the auxiliary scripts are stored on the same domain.

It still has the disadvantage of being slightly more magical, requiring tricks to work around IE's window event security, but the advantages are pretty substantial.  It should be possible to write this linker as a drop-in replacement for today's XSLinker and IFrameLinkers without touching any code outside of com.google.gwt.core.linker.

Matt.

Lex Spoon

unread,
Dec 1, 2009, 11:44:15 AM12/1/09
to google-web-tool...@googlegroups.com, Ray Cromwell, Joel Webber
Thanks for the test code and data, Matt! It sounds like enough
browsers are covered that error reporting is no longer a major decider
between XHR vs. script tags.

Regarding iframes, be aware that some GWT users can't use them. I
don't know all the reasons why, but one example reason is that iframes
don't work reasonably on iPhones. So, we need to support non-iframe
linkers for at least some use cases.


Lex

Joel Webber

unread,
Dec 1, 2009, 11:59:54 AM12/1/09
to Lex Spoon, google-web-tool...@googlegroups.com, Ray Cromwell
To be clear, I don't know of any cases where people *can't* use iframes. There are some cases where teams have chosen to use <script> tags for an assumed performance benefit, but I don't know of anyone ever having measured it. Most of the time people use <script> tags for cross-site or late-loading behavior. If we could make iframes work for these cases, and show that there's no performance benefit to <script> tags, it may be less of an issue.

The iPhone only has problems rendering iframes (it essentially drops their content into the outer page as though it were inline), but that's about it.

Matt Mastracci

unread,
Dec 1, 2009, 12:43:04 PM12/1/09
to google-web-tool...@googlegroups.com
For clarification, I've included an approximation of what I meant by dynamic iframe.

The idea is to create the iframe using src='javascript:;' to keep it in the same domain as the parent iframe, use document.write() to initialize a full HTML document inside of it, and finally, add scripts dynamically inside of its body.  This method works across every browser (including android and iPhone webkit).  I've not done any performance tests to see what sort of hit you take referencing symbols across window boundaries but it seems reasonable to assume it would be the same as today's IFrameLinker.  

Note that IE also has the "new ActiveXObject('htmldocument')" that works much like a headless iframe but doesn't have a navigation click. 

var iframe = document.createElement('iframe');
iframe.src = 'javascript:;';
iframe.style.cssText = 'position:absolute; top:-1000px; left: -1000px;';
document.body.appendChild(iframe);

var iframeDoc = iframe.contentWindow.document;
iframeDoc.open();
iframeDoc.write('<html><body></body></html>');
iframeDoc.close();

var script = iframeDoc.createElement('script');
script.type = 'text/javascript';
script.src = 'http://myoffdomaincdn.example.com/' + scriptName;

iframeDoc.body.appendChild(script);

Additional scripts would be created in the same way as the primary script.

Matt Mastracci

unread,
Dec 9, 2009, 12:47:22 PM12/9/09
to Lex Spoon, google-web-tool...@googlegroups.com
I've got a set of testcases that I ran through on each browser. You
can see one of them here:

http://grack.com/errortest3.html

I couldn't get onerror to fire on script elements in every browser.
It'll fire in Firefox, Safari and Chrome, but IE will only fire the
readystatechange events on error. You can work around this by setting
a global as each script fragment is loaded, then testing against that
global in the onreadystatechange or onload event handlers.

Do you know how to get onerror to fire in IE? It didn't seem to work
in my testing.

The errors that are available in the event handlers aren't really
useful for details on the failure (that's pretty universal, sadly).

Matt.

Lex Spoon

unread,
Dec 9, 2009, 4:55:02 PM12/9/09
to Matt Mastracci, google-web-tool...@googlegroups.com
On Wed, Dec 9, 2009 at 12:47 PM, Matt Mastracci <mat...@mastracci.com> wrote:
> Do you know how to get onerror to fire in IE?  It didn't seem to work in my
> testing.

No, but why do you need it if you have onreadystatechanged? It should
be no problem to hook up both callbacks.

Lex

Matt Mastracci

unread,
Dec 9, 2009, 5:30:28 PM12/9/09
to Lex Spoon, google-web-tool...@googlegroups.com
Yeah, you don't need the onerror stuff in the end - everything can be
done via onload and onreadystatechange. I was hoping that one of the
browsers would justify adding onerror to the mix by providing some
additional information on why the script failed to load, but there's
nothing useful in any of the errors that I've seen.

Setting up both onload and onreadystatechanged, directly assigned to
the properties on the script element's DOM object seems to be the way
to go. It covers both the success and error cases and works on
everything but Opera.

Matt.

Lex Spoon

unread,
Dec 15, 2009, 2:48:57 PM12/15/09
to Matt Mastracci, google-web-tool...@googlegroups.com
Hey, Matt,

I've now double checked on several browsers other than Opera, and I
agree that onerror works on non-IE and onreadystatechange works on IE.
Details here:

http://blog.lexspoon.org/2009/12/detecting-download-failures-with-script.html


One tricky aspect is that I don't see how to get IE to say whether or
not the download really failed. Sometimes the "loaded" state is
reached when loading a page that is not in cache.

Ideas would be welcome about how to deal with that. In the
experiments I did, the callback always happens after the script
evaluation. If that sequencing is reliable, then it will work to
always call the on-failed handler but to have AsyncFragmentLoader
quietly ignore such calls if the fragment has already loaded
successfully. It tracks the already-loaded fragments anyway, these
days, so this would be easy to do. As a bonus, always calling,
whether in state "loaded" or "complete", would give good handling to
situations where the browser downloads *some* content but it's not the
real JS code, e.g. the "please log in" pages that hotel wifi networks
insert.

Lex

John Tamplin

unread,
Dec 15, 2009, 2:53:55 PM12/15/09
to google-web-tool...@googlegroups.com, Matt Mastracci
On Tue, Dec 15, 2009 at 2:48 PM, Lex Spoon <sp...@google.com> wrote:
Ideas would be welcome about how to deal with that. 

Could the fragments include some JS at the end which calls a well-known "I loaded successfully" method? 

--
John A. Tamplin
Software Engineer (GWT), Google

Lex Spoon

unread,
Dec 15, 2009, 3:02:07 PM12/15/09
to google-web-tool...@googlegroups.com
On Tue, Dec 15, 2009 at 2:53 PM, John Tamplin <j...@google.com> wrote:
> On Tue, Dec 15, 2009 at 2:48 PM, Lex Spoon <sp...@google.com> wrote:
>>
>> Ideas would be welcome about how to deal with that.
>
> Could the fragments include some JS at the end which calls a well-known "I
> loaded successfully" method?

Yes, and in fact they already do. I'm leaning at this point toward
indicating "failure" on any of: onload, onerror,
onreadystatechange(loaded), onreadystatechange(complete).

Lex

Matt Mastracci

unread,
Dec 15, 2009, 3:31:41 PM12/15/09
to Lex Spoon, google-web-tool...@googlegroups.com
On 15-Dec-09, at 12:48 PM, Lex Spoon wrote:

> I've now double checked on several browsers other than Opera, and I
> agree that onerror works on non-IE and onreadystatechange works on IE.
> Details here:
>
> http://blog.lexspoon.org/2009/12/detecting-download-failures-with-script.html
>
> One tricky aspect is that I don't see how to get IE to say whether or
> not the download really failed. Sometimes the "loaded" state is
> reached when loading a page that is not in cache.

I just ran some further tests - it looks like 'complete' is always
fired while cached, 'loaded' always when not cached (and always after
a loading event).

Note that it will also fire 'interactive' sometimes if you use alert()
from the script, or if the 'error on page' dialog pops up. I can't
quite pin down the circumstances in which this readystate chooses to
fire.

I whipped up a slightly improved test based on your post that logs the
order of events here: http://grack.com/errortest.html For all of the
cached/uncached runs I did on IE6, IE7 and IE8 the script was always
evaluated before complete or loaded was fired.

Matt.

Reply all
Reply to author
Forward
0 new messages