Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

closing a window breaks event loop assumption

I've have encountered a small annoyance that blows up to be a huge problem.

Problem 1: In Internet Explorer when you close a window (that you opened via window.open) the ownerDocument will disappear along with it.

The implication of this is that any call to the DOM, such as appendChild or createElement, will fail with a SCRIPT70: Permission Denied or SCRIPT1717: The interface is unknown.

I've looked at the behaviour of other browsers such as Chrome. In Chrome ownerDocument still references the #document but ownerDocument.defaultView will eventually be undefined. This makes sense to me. Calls to appendChild and createElement will pass. I think everything is fine so long as you don't try to reference the defaultView directly.

Problem 2: In Internet Explorer when you click on the close button of the spawned window it doesn't seem to respect the Event loop. I attached an unload event to the spawned window and it fires immediately instead of queuing it at the end of the Event loop. This does not make sense to me. It becomes quite impossible to deal with this rather trivial problem.

If we just had problem 1 there would be a -still painful but- straightforward solution: check if the ownerDocument exists and skip if it doesn't. As it is ownerDocument disappears in the middle of synchronous JavaScript code.

Expected behaviour: a DOM node should not disappear if you've referenced it - garbage collection sanity.

Expected behaviour 2: a DOM node should not disappear in synchronous code. (unless you delete it of course).

Known workaround: move all the code that interacts with the DOM into the window, so that when the window is closed so is the JavaScript runtime environment. This is not a trivial solution and may require significant changes in your architecture.

Crappy solution: wrap any function that does interaction with the DOM in a function that will consume errors if it detects the window of the element has been closed. This is quite invasive and has a significant impact on performance, and IE is already so slow.

Is there a better solution?

What I want, at the very least, is a way to ignore any Errors that are thrown because the user closed a window. problem 1 and problem 2 break basic assumptions you make about JavaScript code: garbage collection and event loop.


Demo script

<script type="text/javascript">
function go() {
    var popup = window.open('', 'open', 'width=500,height=300,scrollbars=yes,resizable=yes');
    popup.document.open();
    popup.document.write('<html><head></head><body></body></html>');
    popup.document.close();
    for (var i = 0; i < 10000; i += 1) {
        var node = popup.document.createTextNode(i + " ");
        popup.document.body.appendChild(node);
    }
}
</script>
<input type="button" onclick="go();" value="Open popup" />

(save as .html file)

Instructions:

  • Open in Internet Explorer 9
  • Click "Open popup"
  • Close the window while it's rendering
  • Observe "Permission Denied"

Here it is a JSFiddle: http://jsfiddle.net/C9p2R/1/

like image 886
Halcyon Avatar asked Feb 15 '13 13:02

Halcyon


People also ask

How does an event loop work?

The event loop works by making a request to some internal or external "event provider" (that generally blocks the request until an event has arrived), then calls the relevant event handler ("dispatches the event").

What is the precedence in event loop?

The event loop is actually composed of one or more event queues. In each queue, events are handled in a FIFO order. It's up to the browser to decide how many queues to have and what form of prioritisation to give them. There's no Javascript interface to individual event queues or to send events to a particular queue.

What will happen if the call stack and the event loop is empty in node?

The event loop checks the call stack and if the call stack is empty, it then looks to the queue. If there is anything in the queue, the event loop takes the first task and pushes it to the stack to run.

What is an event loop in JavaScript?

JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks. This model is quite different from models in other languages like C and Java.


1 Answers

Unless anyone has a better solution, I'll go with the crappy solution. Here is my code:

function apply_window_close_fix(dom_element, wrapped_element) {
    var ignore_errors = false;
    dom_element.ownerDocument.defaultView.addEventListener("unload", function () {
        ignore_errors = true;
    });
    return map(wrapped_element, function (key, func) {
        return function () {
            try {
                return func.apply(this, arguments);
            } catch (e) {
                if (ignore_errors === false) {
                    throw e;
                }
            }
        };
    });
}

wrapped_element is the API that I return for modifying the DOM. I've wrapped all functions in a try-catch that will ignore errors if it sees the window has been closed. I call this function only for browsers that behave like Internet Explorer does.

There seems to be only a very minor performance hit. Of course this depends on how intensively you call this API.

One minor downside is that currently rethrowing some Errors is broken in some browsers. Rethrowing a DOMException resets the stack in Internet Explorer and Chrome (and possibly others). I've also found no way to fetch a filename and linenumber from a DOMException in Internet Explorer. Once again, a gross oversight that just ends up wasting time for everybody.

like image 73
Halcyon Avatar answered Sep 28 '22 08:09

Halcyon