javascript - pestaña - los scripts no pueden cerrar ventanas que no hayan sido abiertas por un script
al cerrar una ventana se rompe el supuesto del ciclo de eventos (4)
Podrías modificar tu "Solución Crappy". En lugar de ajustar las funciones que interactúan con DOM, puede redefinirlas cuando el evento de unload
se activa de tal forma que no interactúen con el DOM, por ejemplo, myOldFunction = function (){};
.
Me he encontrado con una pequeña molestia que se convierte en un gran problema.
Problema 1: en Internet Explorer cuando cierras una ventana (que abriste vía window.open
) el ownerDocument
desaparecerá junto con él.
La consecuencia de esto es que cualquier llamada al DOM, como appendChild
o createElement
, fallará con un SCRIPT70: Permission Denied
o SCRIPT1717: The interface is unknown
.
He observado el comportamiento de otros navegadores como Chrome. En Chrome, ownerDocument
aún hace referencia al documento #document
pero ownerDocument.defaultView
finalmente estará undefined
. Esto tiene sentido para mí. Las llamadas a appendChild
y createElement
pasarán. Creo que todo está bien siempre y cuando no intentes hacer referencia a la vista defaultView
directamente.
Problema 2: en Internet Explorer, al hacer clic en el botón de cerrar de la ventana generada, no parece respetar el bucle de eventos. Anexé un evento de unload
a la ventana engendrada y se dispara inmediatamente en lugar de ponerlo en cola al final del ciclo de eventos. Esto no tiene sentido para mí. Resulta casi imposible lidiar con este problema bastante trivial.
Si ownerDocument
tener el problema 1 , habría una solución ownerDocument
dolorosa, pero- sencilla: verifique si el ownerDocument
del ownerDocument
existe y omita si no lo hace. Como es ownerDocument
desaparece en el medio del código JavaScript síncrono.
Comportamiento esperado: un nodo DOM no debería desaparecer si lo has mencionado: cordura de recolección de basura.
Comportamiento esperado 2: un nodo DOM no debería desaparecer en el código síncrono. (a menos que lo elimines por supuesto).
Solución conocida: mueva todo el código que interactúa con el DOM a la ventana, de modo que cuando se cierre la ventana también lo haga el entorno de tiempo de ejecución de JavaScript. Esta no es una solución trivial y puede requerir cambios significativos en su arquitectura.
Solución desordenada: ajusta cualquier función que interactúe con el DOM en una función que consumirá errores si detecta que la ventana del elemento se ha cerrado. Esto es bastante invasivo y tiene un impacto significativo en el rendimiento, y IE ya es muy lento.
¿Hay una mejor solución?
Lo que quiero, como mínimo, es una forma de ignorar los Error
que se producen porque el usuario cerró una ventana. el problema 1 y el problema 2 rompen suposiciones básicas sobre el código JavaScript: recolección de basura y ciclo de eventos.
Guion de demo
<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" />
(guardar como archivo .html)
Instrucciones:
- Abrir en Internet Explorer 9
- Haga clic en "Abrir ventana emergente"
- Cierra la ventana mientras está renderizando
- Observe "Permiso denegado"
Aquí está un JSFiddle: http://jsfiddle.net/C9p2R/1/
A menos que alguien tenga una mejor solución, iré con la solución horrible . Aquí está mi código:
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
es la API que devuelvo para modificar el DOM. He envuelto todas las funciones en un try-catch que ignorará los errores si ve que la ventana se ha cerrado. Llamo a esta función solo para navegadores que se comportan como lo hace Internet Explorer.
Parece que solo hay un golpe de rendimiento muy leve. Por supuesto, esto depende de cuán intensamente llame a esta API.
Un inconveniente menor es que, actualmente, volver a lanzar algunos errores está roto en algunos navegadores. Retirar una excepción DOMEx restablece la pila en Internet Explorer y Chrome (y posiblemente otros). Tampoco encontré la forma de recuperar un nombre de archivo y el número de lote de una DOMException en Internet Explorer. Una vez más, una descuidada supervisión que acaba por desperdiciar tiempo para todos.
Después de intentar un par de cosas (postMessage, Worker, ...) siempre teniendo problemas con IE9, encontré una buena solución. Hice Parallel.For loop usando la función setInterval. Catch está aquí:
El método setInterval () continuará llamando a la función hasta que se invoque clearInterval () o se cierre la ventana .
La lógica para crear nodos se encuentra en la ventana secundaria, pero se activa desde la ventana principal. Loop se ejecuta en fragmentos y, dado que se usa setInterval, el cierre de la ventana secundaria en cualquier punto no produce errores. Además, el navegador no se cuelga (ni padre ni hijo mientras esto se está ejecutando). Y se ve así:
Tenemos 3 componentes: parent-ie.html, child-ie.html y small parallel.js file. Una peculiaridad era que, para que todo el navegador funcionara, se utilizó setInterval (función, -1). Los valores positivos fueron de frenado IE, el segundo parámetro omitido confunde a Opera por lo que solo hace el primer trozo de función. De todos modos, el código está aquí:
parent-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 se generó a partir de un archivo mecanografiado (tal vez un poco más claro que 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);
}
}
Eso es todo.
OK, déjame intentar simplificar. Para ganar capacidad de respuesta de Windows y evitar la interrupción de la aplicación cuando el niño está cerrado, se deben combinar varias cosas.
- El padre no debe modificar DOM hijo directamente. Las funciones deben estar en el propio niño. Sin embargo, esto solo no resolvería nada, si las funciones se desencadenan desde el padre y se ejecutan de forma sincrónica. Las ventanas colgadas y las excepciones todavía están allí.
- La función principal que el padre llama al hijo tiene que usar
setTimeout
osetInterval
, programar funciones de modificación del DOM, delegar la ejecución al hijo y regresar inmediatamente. ¡Este paso hace posible la auto limpieza! - Las operaciones de DOM de larga duración en el niño se deben dividir en fragmentos más rápidos utilizando
setTimeout
osetInterval
, de modo que el bucle de eventos no se bloquee durante un tiempo prolongado. Esto le da capacidad de respuesta a ambas ventanas .
El ejemplo más simple de javascript para niños. La función llamada desde el padre es LongLastingOperation
y se ha dividido en 4 fragmentos:
<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>