Preventing Operation Aborted Scenarios

This post follows up on my original Operation Aborted post to provide some additional information and assistance for web site owners or 3rd party script libraries.

operation aborted dialog box

Recap

Nearly a year-and-a-half ago, I blogged about an error that can occur on some websites that generate content via script. This content can cause Internet Explorer’s HTML parser to get into an unrecoverable state, which makes it doubly-hard to find and diagnose why this error is happening. When this state occurs, the HTML parser cannot continue, and simply throws up its hands and admits: “Operation aborted!”

Early in IE8’s development, we put in a mitigation that alleviated the worst side-effects of this problem. Rather than show a modal dialog and then navigate away from the page after you press OK, instead we removed the dialog and transferred the error notification into the status bar (to the script error notification area). As a result, you are not interrupted by a dialog and you can continue to view the current web page. You may not have even noticed that this error occurred; yet the HTML parser does come to a grinding halt (for that tab only) and any additional content will never be processed.

Not too long after IE8 was released, we began hearing reports of IE8 customers continuing to see the old operation aborted dialog! While we knew that we hadn’t fixed every possible scenario that could cause the dialog to appear (it’s triggered as a catch-all for many subsystems such as the navigation stack and networking), we believed that we had mitigated the worst-cases. With recent reports of users seeing the Operation Aborted dialog in IE8 we investigated further to find any additional scenarios that could be triggering the dialog to appear (rather than the script error mitigation).

In the following two scenarios, the root cause of the Operation Aborted issue is the same (for details, please read my previous post), but the way in which it happens in these scenarios causes IE to bypass the mitigation that we put in place for IE8.

Scenario 1: Nested Parsing after Operation Aborted

 <html>
 <body>
  <div>
   <script type="text/javascript">
    document.body.appendChild(document.createElement('div'));
    document.write("Testing");
   </script>
  </div>
 </body>
</html>

In the HTML above, the effect of the first line of the script is to trigger the Operation Aborted problem. In IE8 this is mitigated as previously mentioned. However, if sometime later a document.write API call is issued as shown in the second line of script, all versions of Internet Explorer, including 8, will present you with the old operation aborted dialog.

Scenario 2: Operation Aborted in error handlers

 <html>
 <body>
  <script type="text/javascript">
   window.onerror = function() {
    var el = document.getElementById("div2");
    el.appendChild(document.createElement("div"));
   }
  </script>
  <div id="div1"></div>
  <div id="div2" onclick="alert('hi';"></div>
 </body>
</html>

In this HTML file, a script error (in the onclick event handler) has a run-time error, which causes the window object's onerror handler to be invoked. In this scenario, if Operation Aborted is triggered in the error handler, the dialog will also show in IE8.

Programmatically Detecting Operation Aborted

When this error dialog occurs, it is very hard for web developers to find the problem and fix it. Often (and in most cases we’ve seen) the problem is introduced in third-party scripts that are referenced by the affected page. To help web developers quickly find and fix the problem, we’ve written a little script that should help.

This script must be run as the first script in the page that is experiencing the Operation Aborted error. It overrides the usage of innerHTML and appendChild by first checking the parsing frontier before allowing the action. AppendChild is by far the most common DOM entry point that can trigger Operation Aborted, followed by innerHTML. The script may flag false positives, but we wanted to err on the side of being overly cautious.

This script relies on a feature enabled in IE8 standards mode only—Mutable DOM Prototypes. Thus, it will only work for pages that use IE's most standards-compliant mode. See this post on compatibility view for more details on the mode that IE is interpreting your page in. However, the operation aborted problems that this script identifies (in IE8 standards mode) also apply to IE7 and IE6 thereby helping you diagnose and fix this issue in any version of IE.

To use the following script follow these steps:

  1. Add a script element to the head of the page in question. This script element should be before any other script element on the page.
  2. Place the following script text within that script element (or reference a file containing it from the src attribute)
  3. Set the "f1" and "f2" feature values
    1. Setting "f1" to true will skip DOM calls that could potentially cause the Operation Aborted error. However, this will also result in a change in program flow, and other script errors could result.
    2. Setting "f2" to true stops program flow at the point of a potential Operation Aborted error and breaks into the debugger (external or built-in JavaScript debugger). This is where you can analyze each occurrence to see what assumptions were being made and how the program flow can be altered to prevent the problem.
  4. In IE, navigate to the page in question.
  5. Start the JavaScript debugger by pressing "F12" and then selecting the "Script" tab in the Developer Tools, and press the button "Start Debugging".
 (function() {
    // Feature switches
    // WARNING: 'true' may cause alternate program flow.
    var f1 = PREVENT_POTENTIAL_OCCURANCES = false;          
    var f2 = BREAK_INTO_DEBUGGER_AT_POTENTIAL_OCCURANCES = true;
    if (!window.console) {
        window.console = {};
        window.console.warn = function() { };
    }
    var frontierCheck = function(host) {
        // Is host on the frontier?
        while (host && (host != document.documentElement)) {
            if (host.parentNode && (host.parentNode.lastChild != host))
            // This is not on the frontier
                return true;
            host = host.parentNode;
        }
        if (!host || (host != document.documentElement))
            return true; // This node is not on the primary tree
        // This check is overly cautious, as appends to 
        // the parent of the running script element are 
        // OK, but the asynchronous case means that the 
        // append could be happening anywhere and intrinsice
        // knowledge of the hosting application is required
        console.warn("Potential case of operation aborted");
        if (f2)
            debugger;
        // Step up two levels in the call stack 
        // to see the problem source!!
        if (f1)
            return false;
        else
            return true;
    }
    var nativeAC = Element.prototype.appendChild;
    Element.prototype.appendChild = function() {
        // call looks like this:
        //    object.appendChild(object)
        // Go back one more level in the call stack!!
        if (frontierCheck(this))
            return nativeAC.apply(this, arguments);
    }
    var nativeIH = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML").set;
    Object.defineProperty(Element.prototype, "innerHTML", { set: function() {
        if (frontierCheck(this))
            nativeIH.apply(this, arguments);
    }
    });
})();

We recognize that the operation aborted dialog and its mitigated cousin in IE8 are still the source of significant web developer pain. We hope this information and prevention script help you to diagnose and fix issues related to Operation Aborted in IE8 (and older versions of IE).

-Travis Leithead
Program Manager