Operation Aborted
Ran into a JavaScript problem with everybody’s favorite browser recently. Manipulating the head
element from within (say by adding additional script
or link
elements before the page loads) was resulting in an enormously helpful “Operation Aborted” error message in versions of Internet Explorer prior to 7. Of course, like all things IE, this error’s appearance was inconsistent.
Here’s a simplified example of the unassuming script that caused the error. This was executed by an external JavaScript file linked within the head
element before window.onload
fired:
var e = document.createElement('script');
e.type = 'text/javascript';
e.src = 'script.js';
document.getElementsByTagName('head')[0].appendChild(e);
I was eventually able to track the problem down to the presence of a base
tag. It didn’t matter if it was an open <base>
or a self-closing <base />
, if it came before the <script>
that was manipulating the head
, then IE stopped everything and issued the error. (At this point in script execution document.getElementsByTagName('head')[0]
is a valid HTML element so testing for its availability before attempting to appendChild()
doesn’t avoid the error. try/catch()
is also of no assistance.)
Closing the <base>
with something like <!--[if lt IE 7]></base><![endif]-->
seems to resolve the issue. In a deliciously redundant twist, this fix works for a self-closing <base />
as well. Moving either variety of base
after the script
in question sidesteps the issue completely.
But wouldn’t it be nice if the fix didn’t require restructuring the HTML? Conditional compilation to the rescue. With this proprietary JScript feature we can force Internet Explorer to check for any base
elements and delay the appendChild()
with a window.setInterval()
until after it finishes parsing the head
element. Since this problem only affects versions of the browser prior to 7 (and isn’t the result of a missing native JavaScript method) we toss in a version sniff to let the slightly more capable Seven off the hook:
var e = document.createElement('script');
e.type = 'text/javascript';
e.src = 'script.js';
/*@cc_on
// Somone set up us the IE!
var bases = document.getElementsByTagName('base');
var ie = parseFloat(navigator.appVersion.split('MSIE')[1]);
if (bases.length && ie < 7)
{
// All your base are belong to us
var IEBaseFixId = window.setInterval(function()
{
if (document.body)
{
document.getElementsByTagName('head')[0].appendChild(e);
window.clearInterval(IEBaseFixId);
};
}, 50);
}
else
{
@*/
document.getElementsByTagName('head')[0].appendChild(e);
//@cc_on };
It’s nasty hack but perhaps it will save someone else a few premature gray hairs. Or maybe you have a better solution (that doesn’t involve window.onload
)?
Update
Tino Zijdel wrote in with this “when in Rome” approach shortly after comments were closed.
var e = document.createElement('script');
e.type = 'text/javascript';
e.src = 'script.js';
/*@cc_on
var bases = document.getElementsByTagName('base');
if (bases.length && bases[0].childNodes.length)
{
// when in Rome
bases[0].appendChild(e);
}
else
{
@*/
document.getElementsByTagName('head')[0].appendChild(e);
//@cc_on };
I like it. If older versions of IE think the base
element should allow other elements inside it then who are we to argue—when the alternative is the browser equivalent of a hissy fit? It’s still a hack but less so than using setInterval()
.
012 Comments
I ran into similar issues with sIFR, switching to document.write solved the issue. I’ll be playing around with your test cases!
That’s pretty funny since the original script that revealed this problem shied away from
document.write()
specifically to avoid conflicting with sIFR!You should read up on Dean Edwards’ window.onload solution. And you should know better than executing this stuff inline before the document is parsed. :) Though neither WebKit no Gecko will complain, this undoubtedly gives them serious migraine! And they probably curse you under their breath when you feed them this stuff.
When your working with a style switcher or stats tracking you can’t wait for an entire 150Kb HTML page (plus images) to load before running this type of script.
Also, it’s not appending a
script
element within thehead
that’s causing the problem—the browser hasn’t even reached the end of thehead
(evidenced by the lack of error when a<base>
is absent). Saying, “oh btw, when you get down there, parse this too,” shouldn’t be any more of a headache than if the<script>
appeared directly in the source.The problem comes from IE < 7 parsing either variety of the
<base>
tag as an open tag unless explicitly closed with a</base>
. When the JavaScript engine looks ahead to append the newscript
element it panics because the DOM has been invalidated by improper nesting. The same issue affects appending a new<script>
to a<div>
containing an unclosed<span>
before thescript
element making the addition. Except in this case the code may be perfectly valid, it’s a bug in IE that treats the<base>
as an unclosed tag.I’ve had some problems with this in the past. I solved it by adding defer=”true” to the script tag, but I’m not sure if this will work if the scripts you are trying to attach have window.onload functions.
And Dimitri’s right, inline scripting is pretty nasty stuff. Why go to all this trouble?
Shaun, regardless of the sentiment, do read up on Dean’s stuff. It’s not just an onload.
Shaun,
This is interesting. I ran some test and it appears that using defer does not actually work (but I was testing using IEs4Linux, so you’ve got to take my tests with a grain of salt).
Moving the <base> tag after the script, as you mentioned, does appear to work, so my question is: why the insistence on not changing your markup structure?
I could understand if it were a major change, but moving the <base> tag to just before the closing </head> tag is pretty minor, and it still maintains the integrity of your document (it’s not like moving that tag to the bottom of the <head> changes the meaning of the markup).
I can’t help but feel like your solution, while effective, adds more complexity and overhead than might be necessary.
Is the desire to leave the HTML unchanged just a matter of principle?
Shaun, are you referring to how Mint writes an img tag using document.write? You could write a script tag, that’s what sIFR does to pre-fetch Flash movies in IE.
No, it’s a matter of reality. People using this code might be able to add to their templates but may not be able to change what’s already there, eg. a lay person using vBulletin.
Mark, I was (but Mint doesn’t use an an image anymore). I was under the impression that any use of
document.write
would trigger IE to create a newbody
element and prematurely fire the sIFR functions.I see. I think IE only does this for elements which belong in the body, say
embed
andimg
.Yes. The problem is simply inline scripts. Inline scripts are the bane of good javascript
Sorry, when I said “inline” I meant that the script was executed within the
head
element (before the</head>
). Not that the JavaScript code appeared within ascript
element in the document source.The original post read:
I’ve updated it to the following to avoid confusion:
The problem Dave isn’t simply inline scripts.