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().

Previous
More Doorbell
Next
Arbitrage CS3
Author
Shaun Inman
Posted
April 13th, 2007 at 3:12 pm
Categories
JavaScript
Comments
012 (Now closed)

012 Comments

001

I ran into similar issues with sIFR, switching to document.write solved the issue. I’ll be playing around with your test cases!

Author
Mark Wubben
Posted
Apr 13th, 2007 5:02 pm
002

That’s pretty funny since the original script that revealed this problem shied away from document.write() specifically to avoid conflicting with sIFR!

Author
Shaun Inman
Posted
Apr 13th, 2007 5:17 pm
003

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.

Author
Dimitri Glazkov
Posted
Apr 13th, 2007 9:52 pm
004

Or maybe you have a better solution (that doesn’t involve window.onload)?

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 the head that’s causing the problem—the browser hasn’t even reached the end of the head (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 new script 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 the script 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.

Author
Shaun Inman
Posted
Apr 13th, 2007 11:50 pm
005

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?

Author
Rob Goodlatte
Posted
Apr 14th, 2007 12:08 am
006

Shaun, regardless of the sentiment, do read up on Dean’s stuff. It’s not just an onload.

Author
Dimitri Glazkov
Posted
Apr 14th, 2007 10:12 pm
007

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?

Author
Stephen Stchur
Posted
Apr 15th, 2007 2:29 am
008

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.

Author
Mark Wubben
Posted
Apr 15th, 2007 6:08 am
009

Is the desire to leave the HTML unchanged just a matter of principle?

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 new body element and prematurely fire the sIFR functions.

Author
Shaun Inman
Posted
Apr 15th, 2007 9:48 am
010

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 new body element and prematurely fire the sIFR functions.

I see. I think IE only does this for elements which belong in the body, say embed and img.

Author
Mark Wubben
Posted
Apr 15th, 2007 3:04 pm
011

Yes. The problem is simply inline scripts. Inline scripts are the bane of good javascript

Author
Dave
Posted
Apr 15th, 2007 11:53 pm
012

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 a script element in the document source.

The original post read:

This was executed inline before window.onload fired.

I’ve updated it to the following to avoid confusion:

This was executed by an external JavaScript file linked within the head element before window.onload fired.

The problem Dave isn’t simply inline scripts.

Author
Shaun Inman
Posted
Apr 16th, 2007 12:26 am