2013-02-15 2 views
15

Я столкнулся с небольшой досадой, которая взрывается, чтобы стать огромной проблемой.Закрытие предположения о прерываниях оконных перерывов

Проблема 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, когда вы нажимаете кнопку закрытия в порожденном окне, похоже, что это не соответствует циклу события. Я прикрепил событие unload к порожденному окну, и он стреляет сразу же вместо очереди в конце цикла Event. Это делает мне не. Совершенно невозможно справиться с этой довольно тривиальной проблемой.

Если мы только что проблема 1 было бы -still болезненными но- простое решение: проверить, если ownerDocument существует и пропустить, если он не делает. Так как ownerDocument исчезает в середине синхронного кода JavaScript.

Ожидаемое поведение: Узел DOM не должен исчезать, если вы указали его - разумность сбора мусора.

Ожидаемое поведение 2: Узел DOM не должен исчезать синхронным кодом. (если вы не удалите его, конечно).

Известное обходное решение: переместите весь код, который взаимодействует с DOM в окне, так что, когда окно закрыто, это среда выполнения JavaScript. Это не тривиальное решение и может потребовать значительных изменений в вашей архитектуре.

Crappy solution: Оберните любую функцию, которая взаимодействует с DOM в функции, которая будет потреблять ошибки, если она обнаружит, что окно элемента было закрыто. Это довольно инвазивное и оказывает значительное влияние на производительность, и IE уже настолько медленный.

Есть ли лучшее решение?

Что я хочу, по крайней мере, является способом для игнорировать любые Error s, которые выбрасываются, потому что пользователь закрыл окно. проблема 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? –

+1

Это происходит в IE9. Я не осмелился проверить старые версии. Я не знаю об IE10. – Halcyon

+1

Я правильно понимаю, что Chrome позволяет вам модифицировать DOM закрытых окон, пока вы держите ссылки на него? И вы хотите, чтобы это поведение в IE? – Bergi

ответ

1

Если у кого-то нет лучшего решения, я пойду с помощью crappy solution. Вот мой код:

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. Еще раз, грубый контроль, который только что закончил тратить время для всех.

0

Вы можете изменить свой "Crappy Solution". Вместо того, чтобы обертывать функции, взаимодействующие с DOM, вы можете переопределить их, когда событие unload запускается таким образом, что они не взаимодействуют с DOM, например. myOldFunction = function(){};.

+0

Это не работает. Когда происходит событие 'unload', другой цикл событий находится в середине обработки некоторой другой функции (например, создания узлов DOM и свойств настройки).Вы не можете ретроактивно обернуть функцию, которая уже была вызвана. – Halcyon

+1

Затем вы можете убедиться, что функции, вложенные в цикл событий, вызывают другие функции, которые выполняют DOM-манипуляцию для вас, без каких-либо манипуляций с DOM. Затем вы можете использовать событие 'unload' для переопределения функций, выполняющих манипуляции с DOM, прежде чем они будут вызваны из функций, которые уже находятся в цикле событий. –

+0

Я понимаю ваше предложение, и я пробовал, но у меня не было успеха. Я попытался отслеживать все элементы DOM, которые я делаю, а затем заменяя их функции пустыми функциями и свойствами с помощью фиктивных свойств. Это не имеет никакого эффекта. Вы пробовали это раньше? У вас есть решение, которое работает? В теории я согласен, что он должен работать, но в теории я тоже не должен иметь эту проблему. – Halcyon

0

После попытки пары вещей (postMessage, Worker, ...) всегда были проблемы с IE9, я нашел приятное решение. Я сделал цикл Parallel.For, используя функцию setInterval. Поймайте здесь:

Метод

setInterval() будет продолжать вызов функции до clearInterval() вызывается, или окно закрыто.

Логика для создания узлов находится в дочернем окне, но она запускается из родительского окна. Loop выполняется в кусках и, поскольку используется setInterval, закрытие дочернего окна в любой точке не приводит к ошибкам. Более того, браузер не зависает (ни родительский, ни дочерний, пока он работает). И это выглядит следующим образом:

enter image description here

У нас есть 3 компонента: родитель-ie.html, ребенок-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 был создан из машинописи файла (может быть бит более ясно, то 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

+0

Вы сделали предположение, что у меня есть итераторный процесс, т. Е. то, что легко написать как итеративный процесс. Демонстрация делает, но это более простое, чтобы продемонстрировать непростительное поведение IE9. Я ценю усилия, но чувствую, что ты немного увлекся здесь. – Halcyon

+0

Да, есть еще кое-что. Если вы просто поместите свой цикл в дочернее окно, он заблокирует родителя до завершения операции, даже когда он будет закрыт. Причина, о которой вы говорили, заключается в том, что они позволяют продолжить работу в закрытом окне. IE будет генерировать исключение. Поэтому просто перемещение вашего кода в дочерний процесс не делает ничего полезного. setInterval давайте «цикл событий» для дыхания. – Nenad

0

Хорошо, позвольте мне попытаться упростить. Чтобы получить отзывчивость окон и избежать нарушения приложения, когда ребенок закрыт, необходимо объединить несколько вещей.

  1. Родитель не должен изменять дочерний DOM напрямую. Функции должны быть у ребенка . Однако это само по себе ничего не решит, если функции запускаются из родителя и выполняются синхронно. Поврежденные окна и исключения по-прежнему там.
  2. Основная функция, которую родитель обращается к ребенку должна использовать setTimeout или setInterval, планировать DOM-модифицирующие функции, делегировать выполнение ребенка и немедленно вернуться. Этот шаг делает автоматическую очистку возможной!
  3. Продолжительные операции 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>