2013-02-15 54 views
15

Tôi đã gặp phải một sự khó chịu nhỏ mà thổi lên là một vấn đề lớn.đóng cửa sổ phá vỡ sự kiện phá vỡ cửa sổ sự kiện

Sự cố 1: Trong Internet Explorer khi bạn đóng cửa sổ (mà bạn đã mở qua window.open) ownerDocument sẽ biến mất cùng với nó.

Ý nghĩa của việc này là mọi lệnh gọi đến DOM, chẳng hạn như appendChild hoặc createElement, sẽ không thành công với số SCRIPT70: Permission Denied hoặc SCRIPT1717: The interface is unknown.

Tôi đã xem xét hành vi của các trình duyệt khác như Chrome. Trong Chrome ownerDocument vẫn tham chiếu đến #document nhưng ownerDocument.defaultView cuối cùng sẽ là undefined. Điều này có ý nghĩa với tôi. Các cuộc gọi tới appendChildcreateElement sẽ hết. Tôi nghĩ mọi thứ đều ổn, miễn là bạn không cố gắng tham khảo trực tiếp số defaultView.

Bài 2: Trong Internet Explorer khi bạn bấm vào nút đóng của cửa sổ được sinh ra, nó dường như không tôn trọng vòng lặp Sự kiện. Tôi đã đính kèm sự kiện unload vào cửa sổ sinh ra và nó kích hoạt ngay lập tức thay vì xếp hàng vào cuối vòng lặp Sự kiện. Điều này không không hợp lý với tôi. Nó trở thành khá không thể đối phó với vấn đề này khá tầm thường.

Nếu chúng tôi chỉ có vấn đề 1 sẽ có một giải pháp đau đớn nhưng đơn giản: kiểm tra xem ownerDocument tồn tại và bỏ qua nếu không. Vì nó là ownerDocument biến mất ở giữa mã JavaScript đồng bộ.

Hành vi mong đợi: nút DOM không được biến mất nếu bạn đã tham chiếu - tính năng thu thập rác thải.

Hành vi mong đợi 2: nút DOM không được biến mất trong mã đồng bộ. (trừ khi bạn xóa nó tất nhiên).

Cách giải quyết đã biết: di chuyển tất cả mã tương tác với DOM vào cửa sổ, sao cho khi cửa sổ đóng lại như vậy là môi trường chạy JavaScript. Đây không phải là một giải pháp tầm thường và có thể yêu cầu những thay đổi đáng kể trong kiến ​​trúc của bạn.

Giải pháp crappy: bọc bất kỳ chức năng nào tương tác với DOM trong một hàm sẽ tiêu thụ lỗi nếu phát hiện cửa sổ của phần tử đã bị đóng. Điều này là khá xâm lấn và có một tác động đáng kể đến hiệu suất, và IE đã quá chậm.

Có giải pháp nào tốt hơn không?

Điều tôi muốn, ít nhất, là cách để bỏ qua bất kỳ số nào Error s được ném vì người dùng đã đóng cửa sổ. vấn đề 1vấn đề 2 phá vỡ các giả định cơ bản mà bạn thực hiện về mã JavaScript: thu thập rác và vòng lặp sự kiện.


Demo kịch bản

<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 tập tin)

Hướng dẫn:

  • mở trong Internet Explorer 9
  • Bấm "Open cửa sổ bật lên"
  • Đóng cửa sổ trong khi nó render
  • Quan sát "Permission Denied"

Dưới đây là một JSFiddle: http://jsfiddle.net/C9p2R/1/

+1

Điều này có xảy ra trong tất cả các phiên bản IE không? –

+1

Nó xảy ra trong IE9. Tôi chưa dám thử nghiệm các phiên bản cũ. Tôi không biết về IE10. – Halcyon

+1

Tôi có hiểu rằng Chrome cho phép bạn điều chỉnh DOM của các cửa sổ đã đóng miễn là bạn giữ tham chiếu đến nó? Và bạn muốn hành vi đó trong IE? – Bergi

Trả lời

1

Trừ khi có ai có giải pháp tốt hơn, tôi sẽ đi với giải pháp crappy crappy. Đây là mã của tôi:

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 là API mà tôi trả về để sửa đổi DOM. Tôi đã bọc tất cả các chức năng trong một try-catch mà sẽ bỏ qua lỗi nếu nó thấy cửa sổ đã được đóng lại. Tôi gọi chức năng này chỉ dành cho các trình duyệt hoạt động như Internet Explorer.

Dường như chỉ có một hiệu suất rất nhỏ. Tất nhiên điều này phụ thuộc vào mức độ mạnh mẽ mà bạn gọi API này.

Một nhược điểm nhỏ là hiện đang khôi phục một số Lỗi bị hỏng trong một số trình duyệt. Việc khôi phục lại DOMException sẽ đặt lại ngăn xếp trong Internet Explorer và Chrome (và có thể cả những người khác). Tôi cũng không tìm thấy cách nào để lấy tên tệp và linenumber từ DOMException trong Internet Explorer. Một lần nữa, một sự giám sát tổng thể mà chỉ kết thúc lãng phí thời gian cho tất cả mọi người.

0

Bạn có thể sửa đổi "Giải pháp Crappy" của bạn. Thay vì gói các hàm tương tác với DOM, bạn có thể xác định lại chúng khi sự kiện unload được kích hoạt theo cách mà chúng không tương tác với DOM, ví dụ: myOldFunction = function(){};.

+0

Điều này không hoạt động. Khi sự kiện 'unload' xảy ra thì vòng lặp sự kiện khác đang ở giữa việc xử lý một số hàm khác (như tạo các Nút DOM và các thuộc tính thiết lập).Bạn không thể hồi tố bọc một hàm đã được gọi. – Halcyon

+1

Sau đó, bạn có thể đảm bảo rằng các hàm được đẩy vào vòng lặp sự kiện gọi các hàm khác thực hiện thao tác DOM cho bạn mà không thực hiện bất kỳ thao tác DOM nào. Sau đó, bạn có thể sử dụng sự kiện 'unload' để định nghĩa lại các hàm thực hiện thao tác DOM trước khi chúng được gọi từ các hàm đã có trong vòng lặp sự kiện. –

+0

Tôi hiểu ý kiến ​​của bạn và tôi đã thử nó nhưng tôi đã không thành công. Tôi đã cố gắng theo dõi tất cả các phần tử DOM mà tôi tạo ra, và sau đó thay thế các hàm của chúng bằng các hàm và thuộc tính rỗng bằng các thuộc tính giả. Nó không có tác dụng gì cả. Bạn đã thử điều này trước đây chưa? Bạn có giải pháp hoạt động không? Về lý thuyết, tôi đồng ý rằng nó sẽ làm việc nhưng về lý thuyết tôi cũng không nên gặp vấn đề này. – Halcyon

0

Sau khi thử vài thứ (postMessage, Worker, ...) luôn gặp sự cố với IE9, tôi đã tìm được giải pháp tốt. Tôi đã thực hiện vòng lặp Parallel.For sử dụng hàm setInterval. Nắm bắt được ở đây:

Phương pháp setInterval() sẽ tiếp tục gọi hàm cho đến khi clearInterval() được gọi, hoặc cửa sổ được đóng.

Logic để tạo nút nằm trong cửa sổ con, nhưng được kích hoạt từ cửa sổ chính. Vòng lặp được thực hiện theo khối và, bởi vì setInterval được sử dụng, đóng cửa sổ con tại bất kỳ điểm nào không tạo ra lỗi. Hơn nữa, trình duyệt không treo (không phải cha mẹ hoặc con trong khi điều này đang chạy). Và nó trông như thế này:

enter image description here

Chúng tôi có 3 thành phần: cha mẹ-ie.html, trẻ em ie.html và tập tin parallel.js nhỏ. Một quirk là, cho tất cả các trình duyệt để làm việc setInterval (function, -1) đã được sử dụng.Các giá trị tích cực được phanh IE, tham số bỏ qua thứ 2 gây nhầm lẫn Opera vì vậy nó chỉ làm cho đoạn đầu tiên của hàm. Nhưng dù sao, đang ở đây:

mẹ-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> 

trẻ em 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 được tạo từ tệp bản thảo (có thể hơi rõ ràng hơn sau đó 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); 
    } 
} 

Đó là tất cả.

+0

Xin lỗi để phá vỡ bong bóng của bạn ở đây nhưng nếu tôi có một 'child-ie.html' tôi sẽ không có vấn đề này vì tôi có thể đặt tất cả các chế biến vào cửa sổ con, mà sẽ làm sạch chính nó lên gọn gàng khi bạn đóng nó. Có phải là nó hoặc là có nhiều hơn để giải pháp này? 'setInterval' chắc chắn đi kèm với sự cân nhắc về hiệu suất của riêng nó. – Halcyon

+0

Bạn đã giả định rằng tôi có một quá trình giống như trình vòng lặp, tức là. cái gì đó dễ viết như một quá trình lặp lại. Bản trình diễn, nhưng đó là một sự đơn giản hóa hơn để chứng minh hành vi không thể tha thứ của IE9. Tôi đánh giá cao những nỗ lực nhưng tôi cảm thấy như bạn có một chút mang đi ở đây. – Halcyon

+0

Vâng, có nhiều hơn nữa. Nếu bạn chỉ cần đặt vòng lặp của bạn trong cửa sổ con, nó sẽ chặn cha mẹ cho đến khi hoạt động được thực hiện, ngay cả khi nó được đóng lại. Lý do, như bạn đã đề cập, là họ cho phép hoạt động tiếp tục trên cửa sổ đóng. IE sẽ ném ngoại lệ. Vì vậy, chỉ cần di chuyển mã của bạn để con không làm bất cứ điều gì hữu ích. setInterval hãy "vòng lặp sự kiện" thành hơi thở. – Nenad

0

OK, hãy để tôi cố gắng đơn giản hóa. Để đạt được sự đáp ứng của các cửa sổ và tránh phá vỡ các ứng dụng khi trẻ em đóng cửa, một số điều cần phải được kết hợp.

  1. Cha mẹ không được sửa đổi DOM con một cách trực tiếp. Các chức năng phải có trong số chính con. Tuy nhiên, điều này một mình sẽ không giải quyết bất cứ điều gì, nếu chức năng được kích hoạt từ cha mẹ và thực hiện đồng bộ. Cửa sổ treo và ngoại lệ vẫn còn ở đó.
  2. chức năng chính mà mẹ kêu gọi các con phải sử dụng setTimeout hoặc setInterval, lịch trình chức năng DOM-sửa đổi, ủy thực hiện cho đứa trẻ và trở về ngay lập tức. Bước này giúp tự động dọn dẹp có thể!
  3. Hoạt động DOM lâu dài ở trẻ em phải được chia thành các đoạn nhanh hơn nữa sử dụng setTimeout hoặc setInterval, do đó vòng lặp sự kiện không bị khóa trong một thời gian dài. Điều này mang lại sự phản hồi cho cả hai cửa sổ.

Ví dụ đơn giản nhất về javascript con. Chức năng được gọi từ cha mẹ là LongLastingOperation và nó đã được chia thành 4 khối:

<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> 
Các vấn đề liên quan