2013-02-15 154 views
15

我已经遇到了一个小烦恼,这是一个巨大的问题。关闭窗口中断事件循环假设

问题1:在Internet Explorer中,当你关闭一个窗口(如果您通过window.open打开)的ownerDocument将随之消失。

这意味着任何对DOM的调用(例如appendChildcreateElement)都将失败,产生SCRIPT70: Permission DeniedSCRIPT1717: The interface is unknown

我看过其他浏览器(如Chrome)的行为。在Chrome中,ownerDocument仍引用#document,但ownerDocument.defaultView最终将为undefined。这对我有意义。致电appendChildcreateElement将通过。我认为一切都很好,只要你不直接引用defaultView即可。

问题2:在Internet Explorer中,当您点击产生的窗口的关闭按钮时,它似乎不遵守Event循环。我附加了一个unload事件到生成的窗口,它立即触发,而不是在事件循环结束时排队。这是而不是对我有意义。处理这个相当微不足道的问题变得不可能。

如果我们只是有问题1会有-still痛苦但是 - 简单的解决办法:检查ownerDocument存在,如果不跳。因为它是ownerDocument在同步JavaScript代码中消失。

预期行为:如果您已经参考了DOM节点,垃圾收集的完整性应该不会消失。

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

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

蹩脚的解决方案:在功能中包含与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" /> 

(保存为.html文件)

说明:

  • 打开Internet Explorer 9中
  • 点击 “打开弹出”
  • 关闭窗口,而它的渲染
  • 观察“权限被拒绝”

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

+1

这是否发生在所有IE版本中? – 2013-02-15 13:34:21

+1

它发生在IE9中。我不敢测试旧版本。我不知道IE10。 – Halcyon 2013-02-15 13:34:49

+1

我是否明白,只要您持有对其的引用,Chrome就可以修改关闭窗口的DOM?你想在IE中的行为? – Bergi 2013-02-15 13:58:48

回答

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获取文件名和行号。再一次,总的来说,最终会浪费大家的时间。

0

你可以修改你的“蹩脚的解决方案”。除了包装与DOM交互的函数外,您还可以在事件被触发时以不与DOM交互的方式重新定义这些函数,例如, myOldFunction = function(){};

+0

这不起作用。当发生'unload'事件时,其他事件循环处于某个其他函数的处理过程中(如制作DOM节点和设置属性)。你不能追溯包装一个已被调用的函数。 – Halcyon 2013-02-18 11:15:05

+1

然后,您可以确保推入事件循环的函数调用其他为您执行DOM操作的函数,而无需执行任何DOM操作。然后,您可以使用'unload'事件重新定义执行DOM操作的函数,然后它们将从已经在事件循环中的函数调用。 – 2013-02-18 17:59:31

+0

我了解你的建议,我已经尝试过,但我没有成功。我试着跟踪我制作的所有DOM元素,然后用虚拟属性通过空函数和属性替换它们的函数。它从来没有效果。你以前试过这个吗?你有解决方案吗?理论上我同意它应该起作用,但理论上我不应该有这个问题。 – Halcyon 2013-02-19 10:57:15

0

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

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

用于创建节点的逻辑位于子窗口中,但是它是从父窗口触发的。循环是以块的形式执行的,并且因为使用了setInterval,所以在任何点关闭子窗口都不会产生错误。更多的是,浏览器不会挂起(这是运行时父母或小孩)。它看起来像这样:

enter image description here

我们有3个部分组成:父母ie.html,儿童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> 

儿童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是从打字稿文件生成的(也许有点更清晰的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); 
    } 
} 

这就是全部。

+0

对不起,你的泡泡破裂了,但如果我有一个'child-ie.html'我不会有这个问题,因为我可以把所有的处理放到子窗口中,当你关闭它时它会自动清理干净。这是它还是有更多的解决方案? 'setInterval'肯定有它自己的性能考虑因素。 – Halcyon 2013-02-23 02:42:55

+0

你做了一个假设,我有一个类似于迭代器的过程,即。作为一个迭代过程很容易编写的东西。该演示确实如此,但这仅仅是为了展示IE9不可原谅的行为。我很欣赏这种努力,但我觉得你在这里有点受伤。 – Halcyon 2013-02-23 02:49:16

+0

是的,还有更多。如果你只是把你的循环放在子窗口中,它将阻塞父对象直到操作完成,即使它关闭。正如你所提到的,原因是他们让操作在关闭的窗口上继续进行。 IE会抛出异常。所以,简单地将你的代码移动到孩子不会做任何有用的事情。 setInterval让我们的“事件循环”来呼吸。 – Nenad 2013-02-23 10:28:50

0

好的,让我试着简化。为了获得窗口的响应并避免在儿童关闭时中断应用,必须结合几件事情。

  1. 家长不应该直接修改子DOM。功能应该在 孩子本身。 但是,如果 功能是从父级触发并同步执行的,则仅凭这一点不会解决任何问题。悬挂窗户和异常仍然存在。
  2. 是父母对孩子要求主要功能有使用setTimeoutsetInterval,安排DOM的修改功能,委托 执行到孩子并立即返回。 这一步使自动清理成为可能!在孩子
  3. 长效DOM操作应该被拆分,以进一步加快块 使用setTimeoutsetInterval,让事件循环未锁定 很长一段时间。 这给出了对窗口的响应。

子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>