Unexpected asynchronous script execution
17:14
I spent today debugging strange JavaScript behaviour in Firefox. The scenario was as follows:
The main interface of the app I’m working on loads document fragments on demand. These fragments contain script blocks. We use jQuery to load these fragments.
When jQuery.append
ing a new fragment into the DOM jQuery cleverly extracts all script nodes from the fragment and then synchronously executes them in the order they appear, regardless if their source is external or inline, and only after all scripts have been evaluated returns. A blocking operation, which in our case is what we want.
The script handling the main interface (and pulling in the needed bits) depends on this behaviour —(which might be silly and could be avoided by redesigning the architecture, but that’s a different story)— and today it broke for no apparent reason. After some hours of peeking and poking I found the culprit to be Facebooks’ loader script which appends more scripts with external sources into the DOM. The fact that the script came from Facebook is irrelevant here, the injected script nodes aren’t1.
Safari and Chrome handles this elegantly, leaving jQuery’s behaviour unchanged, but in Firefox it caused jQuery to return prematurely. In Firefox, it looks as if the dynamically injected externally sourced script node is forcing asynchronous evaluation to all dynamically injected script nodes, including the ones with inline source code.
The solution was simple enough: keep the Facebook share script at the bottom, but locating the issue was a pain. Whether or not this is a Firefox bug, I don’t know, but I couldn’t find anything on the issue so I thought I’d give it my 2 cents.
A simple demo, for those who are curious. Latest production version to date of all mentioned browsers were used in testing.
Sorry, the comment form is closed at this time.
2 Responses to “Unexpected asynchronous script execution”
Great! Thanks for the Post! I was running into a similar issue, but first couldn’t find a way to isolate the problem completely.
One thing I don’t really understand by looking into the jQuery sources is how “jQuery circumvents this behaviour by downloading external scripts’ source code explicitly synchronously and then appending them as inline scripts” is achieved? append() calls domManip() which loops at the end over all script nodes and does the following in evalScript():
1) if src attribute is present: append a script element with the appropriate src attribute to the head and fire the ajax event handlers and remove the script element again.
2) otherwise: evaluate the script content due to a script element with the appropriate content by adding it to the head.
-> 1) I don’t see an event / queue processing here, which will strictly keep the sequence of the each loop. I think this is a async call just like you appending bar.js to the head.
-> 2) This one should indeed be executed immediatley
So back to the test-case :)
“The expected behaviour is that foo.js and the inline script are loaded and executed before jQuery.append returns and bar.js to come in to play whenever, but due to nature of javascript events it should be after jQuery.append has returned.”
I don’t see the issue is foo.js, which according to my assumption 2) could be executed whenever it is loaded. But why is logMessage(‘append returned’); called before logMessage(‘load.html inline script completed’); ? That’s scary right?
I filed and issue for Apache Wicket, this is what I was actually struggling with. I also stripped down the problem you were facing and added it to the issue https://issues.apache.org/jira/browse/WICKET-3473.
Cheers,
Jannis
Comment by Jannis Bloemendal — 23.02.2011 12:53
Hi Jannis,
I’m glad if I’ve managed to help you in any way.
Regarding how jQuery changes a normally asynchronous behaviour (script tag with src-attribute injected into the DOM) into synchronous.
Jquery accomplishes this by downloading the script referenced in the src-attribute and then injecting that source as a inline script block.
The Firefox sync to async bug (?) occurs when in that loaded script, a script-tag with external source is injected into the DOM.
HTH.
Comment by nikc — 23.02.2011 14:54