16

我遇到了一个小烦恼,后来变成了一个大问题。

问题 1:在 Internet Explorer 中,当您关闭一个窗口(您通过打开的窗口window.openownerDocument时,它会随之消失。

这意味着任何对 DOM 的调用,例如appendChildor createElement,都会以SCRIPT70: Permission Deniedor失败SCRIPT1717: The interface is unknown

我查看了其他浏览器(例如 Chrome)的行为。在 ChromeownerDocument中仍然引用#documentownerDocument.defaultView最终会是undefined. 这对我来说很有意义。调用appendChild并将createElement通过。只要您不尝试defaultView直接引用,我认为一切都很好。

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

如果我们只是遇到问题 1,那么将有一个 - 仍然很痛苦但 - 直接的解决方案:检查是否ownerDocument存在,如果不存在则跳过。因为它ownerDocument在同步 JavaScript 代码的中间消失了。

预期行为:如果您引用了 DOM 节点,则它不应消失 - 垃圾收集理智。

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

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

糟糕的解决方案:将任何与 DOM 交互的函数包装在一个函数中,如果它检测到元素的窗口已关闭,则会消耗错误。这是相当有侵略性的,对性能有很大的影响,而且 IE 已经很慢了。

有更好的解决方案吗?

我想要的至少是一种忽略Error由于用户关闭窗口而引发的任何s 的方法。问题 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" />

(另存为 .html 文件)

指示:

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

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

4

4 回答 4

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;
                }
            }
        };
    });
}

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

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

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

于 2013-02-19T11:21:24.730 回答
0

好的,让我试着简化一下。为了获得窗口的响应能力并避免在孩子关闭时中断应用程序,必须结合几件事。

  1. 父级不应直接修改子 DOM。功能应该在孩子本身。但是,如果函数从父级触发并同步执行,仅此一项并不能解决任何问题。挂起的窗口和异常仍然存在。
  2. 父调用子函数的主函数必须使用setTimeoutor setInterval来调度 DOM 修改函数,将执行委托给子函数并立即返回。这一步使自动清理成为可能!
  3. 子进程中持久的 DOM 操作应使用setTimeoutor拆分为更快的块setInterval,以便事件循环不会长时间锁定。这给了两个窗口的响应能力

子 javascript 的最简单示例。从父级调用的函数是LongLastingOperation,它已被分成 4 个块:

<script>
    function wasteTime(message) {
        // TODO: waste time
    }
    function FirstPartOfLongOperation(){
        wasteTime('long... part 1');
        setTimeout(SecondPartOfLongOperation, 0);
    }
    function SecondPartOfLongOperation() {
        wasteTime('long... part 2');
        setTimeout(ThirdPartOfLongOperation, 0);
    }
    function ThirdPartOfLongOperation() {
        wasteTime('long... part 3');
        setTimeout(FourthPartOfLongOperation, 0);
    }
    function FourthPartOfLongOperation() {
        wasteTime('long... part 4');
        alert('Done!');
    }

    // Main entry, called from parent.
    // Ex: popup.LongLastingOperation({ data: ....})
    function LongLastingOperation(parametersPassedFromParentWindow) {
        // decompose long operation
        setTimeout(FirstPartOfLongOperation, 0);
    }
</script>
于 2013-02-23T11:46:42.040 回答
0

在尝试了几件事(postMessage,Worker,...)之后总是遇到 IE9 问题,我找到了很好的解决方案。我使用 setInterval 函数制作了 Parallel.For 循环。捕获在这里:

setInterval() 方法将继续调用该函数,直到调用 clearInterval() 或关闭窗口

创建节点的逻辑在子窗口中,但它是从父窗口触发的。循环是分块执行的,因为使用了 setInterval,所以在任何时候关闭子窗口都不会产生错误。此外,浏览器不会挂起(运行时无论是父母还是孩子)。它看起来像这样:

在此处输入图像描述

我们有 3 个组件:parent-ie.html、child-ie.html 和小的 parallel.js 文件。一个怪癖是,所有浏览器都使用了 setInterval(function, -1)。正值是制动 IE,第二个省略的参数使 Opera 感到困惑,因此它只产生了第一个功能块。无论如何,代码在这里:

父id.html

<!DOCTYPE html>
<html>
<head>
    <title>Parent</title>
</head>
<body>
    <script type="text/javascript">
        'use strict';
        (function(){
            var popup;
            var pathname = 'child-ie.html';

            window.openPopup = function _open() {
                popup = window.open(pathname, 'open', 'width=500,height=300,scrollbars=yes,resizable=yes');
            }

            window.createElements = function _createElements() {
                if (popup == null || popup.closed == true) {
                    alert("Open new popup window first.");
                    return;
                }
                var numberOfElements = parseInt(document.getElementById('numberOfElements').value);
                popup.writeElements(numberOfElements);
            }
        })();
    </script>
    <button onclick="openPopup()">Open popup</button><br />
    <button onclick="createElements()">Create elements</button>
    <input id="numberOfElements" type="number" value="10000" />
</body>
</html>

child-ie.html

<!doctype html>
<html>
<head>
    <title>Child window</title>
</head>
<body>
<script src="/Scripts/Parallel.js"></script>
<script>
    (function(){
        function _iterator(index) {
            var node = document.createTextNode(index + " ");
            document.body.appendChild(node);
        }

        window.writeElements = function (numberOfElements) {
            document.body.innerHTML = '';
            Parallel.For(0, numberOfElements, 100, _iterator);
        }
    })();
</script>
</body>
</html>

/Scripts/Parallel.js

'use strict';
var Parallel;
(function (Parallel) {
    var Iterator = (function () {
        function Iterator(from, to, step, expression) {
            this._from = from;
            this._to = to;
            this._step = step;
            this._expression = expression;
        }
        Object.defineProperty(Iterator.prototype, "intervalHandle", {
            set: function (value) {
                this._intervalHandle = value;
            },
            enumerable: true,
            configurable: true
        });
        Iterator.prototype.next = function () {
            var max = this._to > this._step + this._from ? this._step + this._from : this._to;
            for(var i = this._from; i < max; i += 1) {
                this._expression(i);
            }
            if(max === this._to) {
                clearInterval(this._intervalHandle);
            } else {
                this._from = max;
            }
        };
        return Iterator;
    })();    
    function For(from, to, step, expression) {
        var _iterator = new Iterator(from, to, step, expression);
        _iterator.intervalHandle = setInterval(function () {
            _iterator.next();
        }, -1);
    }
    Parallel.For = For;
})(Parallel || (Parallel = {}));

Javascript 是从 typescript 文件生成的(可能比 javascript 更清晰):

'use strict';
module Parallel {
    class Iterator {
        private _from: number;
        private _to: number;
        private _step: number;
        private _expression: (index: number) => {};
        private _intervalHandle: number;

        public set intervalHandle(value: number) {
            this._intervalHandle = value;
        }

        constructor(from: number, to: number, step: number, expression: (index: number) => {}) {
            this._from = from;
            this._to = to;
            this._step = step;
            this._expression = expression;
        }

        next() {
            var max: number = this._to > this._step + this._from ? this._step + this._from : this._to;
            for (var i = this._from; i < max; i += 1) {
                this._expression(i);
            }
            if (max === this._to) {
                clearInterval(this._intervalHandle);
            }
            else {
                this._from = max;
            }
        }
    }
    export function For(from: number, to: number, step: number, expression: (index: number) => {}) {
        var _iterator = new Iterator(from, to, step, expression);
        _iterator.intervalHandle = setInterval(function () {
            _iterator.next();
        }, -1);
    }
}

就这样。

于 2013-02-23T02:18:27.290 回答
0

你可以修改你的“糟糕的解决方案”。您可以在事件被触发时重新定义它们,而不是包装与 DOM 交互的函数,以unload使它们不与 DOM 交互,例如myOldFunction = function (){};.

于 2013-02-18T05:24:22.123 回答