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 Error
s 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:
Here it is a JSFiddle: http://jsfiddle.net/C9p2R/1/
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").
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With