JavaScript is wonderfully dynamic, so it is odd that its eval function is so unportable. I already knew that it was tricky if not impossible to use eval to install code in an arbitrary nested scope. Today I learned that even the simple case of installing code into the global scope is different on each browser. Here's what I found after some digging around on the web and some experimentation.
http://piecesofrakesh.blogspot.com/2008/10/understanding-eval-scope-spoiler-its.html
The following page also discusses the problem, but has a really good collection of comments:
UPDATE: Prototype has gone through the same issue, and come up with similar conclusions as mine. Here is a page with all the bike shedding:
Based on reading these and on tinkering on different web browsers, here are some techniques that look interesting:
What I did in each case was try to use the technique to define a function foo() at the global scope, and then try to call it. I tested these browsers, which I happen to have handy:
- window.eval, what I tried to begin with
- window.eval, but with a with() clause around it. Some people report better luck this way.
- window.execScript, a variant of window.eval
- window.setTimeout
- adding a script tag to the document
What I did in each case was try to use the technique to define a function foo() at the global scope, and then try to call it. I tested these browsers, which I happen to have handy:
- Safari/Mac 3.1.1
- Firefox/Mac 3.0.6
- Firefox/Linux 2.0.0.20
- Firefox/Windows 3.0.3
- IE 6.0.2900.xpsp_sp3_gdr.080814-1236 updated to SP3
- Chrome 1.0.154.48
- window.eval: FF
- window.eval with with: FF
- window.execScript: IE, Chrome
- window.setTimeout: Chrome, FF, Safari
- script tag: IE, Chrome, FF, Safari
Conclusions
- The window.execScript function is available on IE and Chrome, and when present it does the right thing.
- The window.eval function only works as desired on Firefox.
- Adding a with(window) around the window.eval does make a difference, but I couldn't get it to do precisely what is needed for GWT. In particular, GWT does not have a bunch of "var func1,func2, func3" declarations up front, but such vars are assumed in some of the other web pages I read.
- I could not find a synchronous solution for Safari. Instead, setTimeout and script tags work, but they won't load the code until a few milliseconds have gone by.
- Script tags work on all browsers.
- Surprisingly, I couldn't get setTimeout to work on IE. From some web browsing, it looks like the setTimeout callback might run in the wrong scope, but I didn't investigate far. On IE, execScript is a better solution for the present problem.
Based on these, the following chunk of code is one portable way to install code on any of the major browsers. It uses execScript if it's available, and otherwise it adds a script tag.
if (window.execScript) {window.execScript(script)} else {var tag = document.createElement("script")tag.type = "text/javascript"tag.text = scriptdocument.getElementsByTagName("head").item(0).appendChild(tag)}
The Code
Here is the code for the above examples, for anyone who wants to know the details and/or to try it for themselves.
The wrapper script is as follows:
For the versions that install the code asynchronously (setTimeout or script tags), I changed the window.foo() line to be:function installFoo() {var script = "function foo() { alert('hi') }"// varying part}installFoo()window.foo()
window.setTimeout(function() { window.foo() }, 100)
The "varying part" is as follows for each way to load the code. Note that some of them include a gratuitous reassignment of window to $w; that's how I first ran the test and I don't want to go back and redo all of those.
// window.evalwindow.eval(script)// window.execScriptwindow.execScript(script)// window.eval with a withvar $w = windowwith($w) { $w.eval(script) }// setTimeoutwindow.setTimeout(script, 0)// script tagvar tag = document.createElement("script")tag.type = "text/javascript"tag.text = scriptdocument.getElementsByTagName("head").item(0).appendChild(tag)
1 comment:
Nice post. Nice solution.
Post a Comment