2013-02-15 6 views
15

Ich habe einen kleinen Ärger angetroffen, der explodiert, um ein riesiges Problem zu sein.Schließen eines Fensters bricht Ereignisschleife Annahme

Problem 1: In Internet Explorer, wenn Sie ein Fenster zu schließen (die Sie über window.open geöffnet) die ownerDocument wird es verschwinden zusammen.

Die Folge davon ist, dass jeder Aufruf des DOM, wie appendChild oder createElement, mit einem SCRIPT70: Permission Denied oder SCRIPT1717: The interface is unknown fehl.

Ich habe mir das Verhalten anderer Browser wie Chrome angesehen. In Chrome ownerDocument bezieht sich immer noch auf die #document aber ownerDocument.defaultView wird schließlich undefined sein. Das macht für mich Sinn. Anrufe an appendChild und createElement werden bestehen. Ich denke, alles ist in Ordnung, solange Sie nicht versuchen, die defaultView direkt zu verweisen.

Problem 2: In Internet Explorer, wenn Sie auf die Schließen-Schaltfläche des erstellten Fensters klicken, scheint es nicht die Ereignisschleife zu beachten. Ich habe ein unload Ereignis an das erzeugte Fenster angehängt und es feuert sofort ab, anstatt es am Ende der Ereignisschleife einzureihen. Dies macht nicht Sinn für mich. Es ist ziemlich unmöglich, mit diesem eher trivialen Problem umzugehen.

Wenn wir hatten nur Problem 1 es würde eine Noch schmerzliche aber- einfache Lösung sein: prüfen, ob die ownerDocument existiert und überspringen, wenn es nicht der Fall ist. Wie es ist ownerDocument verschwindet in der Mitte der synchronen JavaScript-Code.

Erwartetes Verhalten: Ein DOM-Knoten sollte nicht verschwinden, wenn Sie darauf verwiesen haben - Garbage Collection Vernunft.

Erwartetes Verhalten 2: Ein DOM-Knoten sollte nicht im synchronen Code verschwinden. (es sei denn, du löschst es natürlich).

Bekannte Problemumgehung: Verschieben Sie den gesamten Code, der mit dem DOM interagiert, in das Fenster, sodass beim Schließen des Fensters auch die JavaScript-Laufzeitumgebung angezeigt wird. Dies ist keine triviale Lösung und erfordert möglicherweise erhebliche Änderungen in Ihrer Architektur.

Crappy Lösung: Wrap jede Funktion, die Interaktion mit dem DOM in einer Funktion, die Fehler, wenn es erkennt, das Fenster des Elements wurde geschlossen hat, macht. Dies ist ziemlich invasiv und hat erhebliche Auswirkungen auf die Leistung, und IE ist bereits so langsam.

Gibt es eine bessere Lösung?

Was ich mindestens möchte, ist ein Weg zu ignorieren alle Error s, die geworfen werden, weil der Benutzer ein Fenster geschlossen. Problem 1 und Problem 2 brechen grundlegende Annahmen Sie über JavaScript-Code: Garbage Collection und Event-Schleife.


Demo Skript

<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" /> 

(speichern als HTML-Datei)

Anleitung:

  • Open in Internet Explorer 9
  • Klicken Sie auf "Öffnen Popup"
  • Schließen Sie das Fenster, während es
  • beachten "Zugriff verweigert"

Hier ist es ein JSFiddle ist Rendering ist: http://jsfiddle.net/C9p2R/1/

+1

Ist dies in allen IE-Versionen passieren? –

+1

Es tritt in IE9 auf. Ich habe es nicht gewagt, ältere Versionen zu testen. Ich weiß nichts über IE10. – Halcyon

+1

Verstehe ich richtig, dass Chrome das DOM geschlossener Fenster so lange ändern kann, wie Sie darauf verweisen? Und Sie wollen dieses Verhalten in IE? – Bergi

Antwort

1

Wenn niemand eine bessere Lösung hat, werde ich mit der crappy Lösung gehen. Hier ist mein 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 ist die API, die ich wieder für das DOM zu verändern. Ich habe alle Funktionen in einem try-catch umgangen, das Fehler ignoriert, wenn es sieht, dass das Fenster geschlossen wurde. Ich rufe diese Funktion nur für Browser auf, die sich wie Internet Explorer verhalten.

Es scheint nur ein sehr geringer Leistungseinbruch zu sein. Dies hängt natürlich davon ab, wie intensiv Sie diese API nennen.

Ein kleiner Nachteil ist, dass derzeit einige erneut Fehler wird in einigen Browsern gebrochen. Wenn Sie eine DOMException erneut starten, wird der Stapel in Internet Explorer und Chrome (und möglicherweise anderen) zurückgesetzt. Ich habe auch keine Möglichkeit gefunden, einen Dateinamen und eine Zeilennummer aus einer DOMException in Internet Explorer abzurufen. Noch einmal, eine grobe Aufsicht, die am Ende nur Zeit für alle verschwendet.

0

Sie Ihre "Furchtbares Solution" ändern könnte. Anstatt die Funktionen, die mit DOM interagieren, zu umhüllen, können Sie sie neu definieren, wenn das Ereignis unload so ausgelöst wird, dass sie nicht mit dem DOM interagieren, z. myOldFunction = function(){};.

+0

Dies funktioniert nicht. Wenn das'Entladen' -Ereignis auftritt, befindet sich die andere Ereignisschleife in der Mitte der Verarbeitung einer anderen Funktion (wie das Erstellen von DOM-Knoten und das Festlegen von Eigenschaften).Sie können eine bereits aufgerufene Funktion nicht rückgängig machen. – Halcyon

+1

Dann können Sie sicherstellen, dass die in die Ereignisschleife gedrückten Funktionen andere Funktionen aufrufen, die die DOM-Manipulation für Sie ausführen, ohne selbst eine DOM-Manipulation durchzuführen. Dann könnten Sie das 'unload' -Ereignis verwenden, um die Funktionen neu zu definieren, die die DOM-Manipulation durchführen, bevor sie von den Funktionen aufgerufen werden, die bereits in der Ereignisschleife sind. –

+0

Ich verstehe Ihren Vorschlag und ich habe es versucht, aber ich hatte keinen Erfolg. Ich habe versucht, alle von mir erstellten DOM-Elemente im Auge zu behalten und dann ihre Funktionen durch leere Funktionen und Eigenschaften durch Dummy-Eigenschaften zu ersetzen. Es hat keinen Effekt was auch immer. Hast du das schon mal probiert? Haben Sie eine Lösung, die funktioniert? In der Theorie stimme ich zu, dass es funktionieren sollte, aber in der Theorie sollte ich dieses Problem auch nicht haben. – Halcyon

0

Nach dem Versuch, paar Dinge (postMessage, Worker, ...) immer Probleme mit IE9, fand ich nette Lösung. Ich machte Parallel.For Schleife mit setInterval-Funktion. Fang ist hier:

Die setInterval() -Methode wird auch weiterhin die Funktion, bis clearInterval Aufruf() aufgerufen wird, oder das Fenster geschlossen.

Logik zum Erstellen von Knoten befindet sich im untergeordneten Fenster, wird jedoch vom übergeordneten Fenster ausgelöst. Die Schleife wird in Blöcken ausgeführt, und da setInterval verwendet wird, führt das Schließen des untergeordneten Fensters an keiner Stelle zu Fehlern. Mehr, Browser nicht hängen (weder Eltern oder Kind, während dies ausgeführt wird). Und es sieht wie folgt aus:

enter image description here

Wir haben 3 Komponenten: Eltern-ie.html, kinder ie.html und kleine parallel.js Datei. Eine Besonderheit war, dass für alle Browser setInterval (function, -1) verwendet wurde.Positive Werte bremsten den IE, der zweite ausgelassene Parameter verwirrt Opera, so dass es nur den ersten Teil der Funktion macht. Wie auch immer, Code ist hier:

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

kinder 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 aus Typoskript-Datei generiert wurde (Bit vielleicht deutlicher dann 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); 
    } 
} 

Das ist alles.

+0

Entschuldige, dass du deine Blase platzen ließt, aber wenn ich eine 'child-ie.html' hätte, hätte ich dieses Problem nicht, da ich die gesamte Verarbeitung in das untergeordnete Fenster legen könnte, das sich sauber säubert, wenn du es schließt. Ist das oder gibt es mehr zu dieser Lösung? 'setInterval' kommt sicherlich mit seinen eigenen Leistungsaspekten. – Halcyon

+0

Sie haben die Annahme gemacht, dass ich einen iteratorartigen Prozess habe, d. etwas, das als iterativer Prozess einfach zu schreiben ist. Die Demo macht das, aber das ist eine übermäßige Vereinfachung, um das unentschuldbare Verhalten des IE9 zu demonstrieren. Ich schätze die Anstrengung, aber ich fühle mich, als ob du ein bisschen davon weggetragen wurdest. – Halcyon

+0

Ja, da ist mehr dran. Wenn Sie Ihre Schleife einfach in das untergeordnete Fenster einfügen, wird das übergeordnete Element blockiert, bis die Operation abgeschlossen ist, selbst wenn es geschlossen ist. Der Grund, wie Sie erwähnt haben, ist, dass sie den Betrieb auf dem geschlossenen Fenster fortsetzen lassen. IE wird Ausnahme auslösen. Es macht also nichts Sinn, den Code einfach zu child zu verschieben. setInterval lassen Sie "Ereignisschleife" zu atmen. – Nenad

0

OK, lass mich versuchen zu vereinfachen. Um Reaktionsfähigkeit von Fenstern zu erhalten und zu vermeiden, dass Anwendungen beim Schließen eines Kindes unterbrochen werden, müssen mehrere Dinge kombiniert werden.

  1. Parent sollte Child-DOM nicht direkt ändern. Funktionen sollten in Kind selbst sein. Dies allein würde jedoch nichts lösen, wenn Funktionen von Parent ausgelöst und synchron ausgeführt werden. Gehängte Fenster und Ausnahmen sind immer noch da.
  2. Hauptfunktion, die Eltern über Kinder Anrufe hat setTimeout oder setInterval, verwenden DOM-modifizierenden Funktionen zu planen, delegieren sofort Ausführung für das Kind und zurück. Dieser Schritt macht Auto-Cleanup möglich!
  3. Langlebige DOM-Operationen in Kind sollen schnelle Stücke aufgeteilt werden mit setTimeout oder setInterval zu fördern, so dass Ereignissen Schleife nicht für eine lange Zeit gesperrt ist. Dies gibt Reaktionsfähigkeit auf beide Windows.

Einfachstes Beispiel für Kind Javascript. Funktion von Eltern aufgerufen ist LongLastingOperation und es wurde in 4 Chunks aufgeteilt:

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