关闭窗口会打破事件循环假设

Hal*_*yon 15 javascript internet-explorer internet-explorer-9

我遇到了一个令人烦恼的小问题.

问题1:在Internet Explorer中关闭窗口(通过它打开window.open)时,ownerDocument它将随之消失.

这意味着任何对DOM的调用,如appendChildcreateElement,将失败,SCRIPT70: Permission Denied或者SCRIPT1717: The interface is unknown.

我看过其他浏览器的行为,比如Chrome.在Chrome中ownerDocument仍然引用#documentownerDocument.defaultView最终会引用undefined.这对我来说很有意义.调用appendChildcreateElement会通过.只要你不试图defaultView直接引用,我认为一切都很好.

问题2:在Internet Explorer中单击生成窗口的关闭按钮时,它似乎不尊重事件循环.我将一个unload事件附加到生成的窗口,它立即触发,而不是在事件循环结束时排队.这对我来说没有意义.处理这个相当微不足道的问题变得十分不可能.

如果我们遇到问题1,那么将会出现一个令人痛苦但又直截了当的解决方案:检查是否ownerDocument存在,如果不存在则跳过.因为它ownerDocument在同步JavaScript代码中间消失了.

预期的行为:如果您引用它,DOM节点不应该消失 - 垃圾收集整理.

预期的行为2: DOM节点不应该在同步代码中消失.(除非你当然删除它).

已知的解决方法:将与DOM交互的所有代码移动到窗口中,以便在窗口关闭时,JavaScript运行时环境也是如此.这不是一个简单的解决方案,可能需要对您的架构进行重大更改.

Crappy解决方案:在一个函数中包装任何与DOM交互的函数,如果它检测到元素的窗口已经关闭,它将消耗错误.这是非常具有侵略性并且对性能有重大影响,IE已经非常缓慢.

有更好的解决方案吗?

我想要的是,至少可以忽略Error由于用户关闭窗口而抛出的任何内容.问题1问题2打破了你对JavaScript代码的基本假设:垃圾收集和事件循环.


演示脚本

<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" />
Run Code Online (Sandbox Code Playgroud)

(另存为.html文件)

说明:

  • 在Internet Explorer 9中打开
  • 点击"打开弹出窗口"
  • 在渲染时关闭窗口
  • 观察"权限被拒绝"

这是一个JSFiddle:http://jsfiddle.net/C9p2R/1/

Hal*_*yon 1

除非有人有更好的解决方案,否则我将采用蹩脚的解决方案。这是我的代码:

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;
                }
            }
        };
    });
}
Run Code Online (Sandbox Code Playgroud)

wrapped_element是我返回的用于修改 DOM 的 API。我将所有函数包装在一个 try-catch 中,如果它看到窗口已关闭,它将忽略错误。我仅针对行为类似于 Internet Explorer 的浏览器调用此函数。

似乎只有很小的性能影响。当然这取决于你调用这个API的密集程度。

一个小缺点是,目前在某些浏览器中重新抛出某些错误的功能被破坏。重新抛出 DOMException 会重置 Internet Explorer 和 Chrome(可能还有其他浏览器)中的堆栈。我还发现无法从 Internet Explorer 中的 DOMException 获取文件名和行号。再一次,粗暴的疏忽最终只会浪费每个人的时间。