Закрытие окна прерывает предположение цикла цикла
Я столкнулся с небольшой досадой, которая взрывается, чтобы быть огромной проблемой.
Проблема 1: В обозревателе Internet Explorer при закрытии окна (которое вы открыли через window.open
), ownerDocument
исчезнет вместе с ним.
Следствием этого является то, что любой вызов DOM, такой как appendChild
или createElement
, потерпит неудачу с помощью SCRIPT70: Permission Denied
или SCRIPT1717: The interface is unknown
.
Я посмотрел на поведение других браузеров, таких как Chrome. В Chrome ownerDocument
все еще ссылаются на #document
, но ownerDocument.defaultView
в конечном итоге будет undefined
. Это имеет смысл для меня. Вызовы appendChild
и createElement
пройдут. Я думаю, что все в порядке, пока вы не пытаетесь напрямую ссылаться на defaultView
.
Проблема 2: В Internet Explorer, когда вы нажимаете кнопку закрытия закрытого окна, похоже, что это не соответствует циклу Event. Я приложил событие unload
к порожденному окну, и он немедленно запускается, а не ставит его в очередь в конце цикла Event. Для меня это не имеет смысла. Совершенно невозможно справиться с этой довольно тривиальной проблемой.
Если бы у нас была проблема 1, было бы болезненное, но простое решение: проверьте, существует ли ownerDocument
и пропустите, если это не так. Поскольку ownerDocument
исчезает в середине синхронного кода JavaScript.
Ожидаемое поведение: DOM node не должен исчезать, если вы ссылаетесь на него - разумность сборки мусора.
Ожидаемое поведение 2: DOM node не должно исчезать в синхронном коде. (если вы не удалите его, конечно).
Известное обходное решение: переместите весь код, который взаимодействует с DOM в окне, так что, когда окно закрыто, это среда выполнения JavaScript. Это не тривиальное решение и может потребовать значительных изменений в вашей архитектуре.
Решение Crappy: завершает любую функцию, которая взаимодействует с DOM в функции, которая будет потреблять ошибки, если обнаруживает, что окно элемента закрыто. Это довольно инвазивное и оказывает значительное влияние на производительность, и IE уже настолько медленный.
Есть ли лучшее решение?
То, что я хочу, по крайней мере, является способом игнорировать любой Error
, который вызывается, потому что пользователь закрыл окно. проблема 1 и проблема 2 нарушают основные предположения, которые вы делаете о JavaScript-коде: сбор мусора и цикл событий.
Демо script
<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
Если у кого-то есть лучшее решение, я пойду с дерьмовым решением. Вот мой код:
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
- это API, который я возвращаю для изменения DOM. Я завернул все функции в try-catch, который будет игнорировать ошибки, если видит, что окно было закрыто. Я вызываю эту функцию только для браузеров, которые ведут себя как Internet Explorer.
Кажется, что это очень незначительный удар. Конечно, это зависит от того, насколько интенсивно вы называете этот API.
Одним из второстепенных недостатков является то, что в некоторых браузерах исправление некоторых ошибок нарушено. Повторное использование DOMException сбрасывает стек в Internet Explorer и Chrome (и, возможно, другие). Я также не нашел способа получить имя файла и linenumber из DOMException в Internet Explorer. Еще раз, грубый контроль, который только что закончил тратить время для всех.
Ответ 2
Вы можете изменить свое "Crappy Solution". Вместо того, чтобы обертывать функции, которые взаимодействуют с DOM, вы можете переопределить их, когда событие unload
запускается таким образом, что они не взаимодействуют с DOM, например. myOldFunction = function (){};
.
Ответ 3
После попытки пары вещей (postMessage, Worker,...) всегда были проблемы с IE9, я нашел приятное решение. Я сделал цикл Parallel.For, используя функцию setInterval. Поймать здесь:
Метод setInterval() будет продолжать вызов функции до тех пор, пока clearInterval() вызывается или окно закрывается.
Логика для создания узлов находится в дочернем окне, но она запускается из родительского окна. Loop выполняется в кусках и, поскольку используется setInterval, закрытие дочернего окна в любой точке не приводит к ошибкам. Более того, браузер не зависает (ни родительский, ни дочерний, пока он работает). И это выглядит так:
![enter image description here]()
У нас есть 3 компонента: parent-ie.html, child-ie.html и маленький файл parallel.js. Один причуд был, для всех браузеров для работы setInterval (функция, -1). Положительными значениями были торможение IE, второй пропущенный параметр смущает Opera, поэтому он делает только 1-й кусок функции. Во всяком случае, код здесь:
родитель-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 был сгенерирован из файла 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);
}
}
Что все.
Ответ 4
Хорошо, позвольте мне попытаться упростить. Чтобы получить отзывчивость окон и избежать нарушения приложения, когда ребенок закрыт, необходимо объединить несколько вещей.
- Родитель не должен изменять дочерний DOM напрямую. Функции должны быть в
сам ребенок. Однако это само по себе ничего не решит, если
функции запускаются от родителя и выполняются синхронно.
Поврежденные окна и исключения по-прежнему существуют.
- Основная функция, которую родительский вызов имеет дочерний элемент, должен использовать
setTimeout
или
setInterval
, чтобы запланировать DOM-изменяющие функции, делегировать
исполнение ребенку и немедленное возвращение. Этот шаг делает автоматическую очистку возможной!
- Длительные операции DOM у ребенка должны быть разделены на более быстрые фрагменты
используя
setTimeout
или 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>