Предотвращая сценарии прерывания операции

Эта статья дополняет мою предыдущую статью о прерывании операции, чтобы предоставить некоторую дополнительную информацию и помощь владельцам веб-сайтов или сторонним библиотекам скриптов.

clip_image002

Напоминание

Примерно полтора года назад я написал статью об ошибке, которая может произойти на некоторых веб-сайтах, генерирующих контент при помощи скрипта. Этот контент может привести HTML-анализатор Internet Explorer в невосстановимое состояние, что вдвойне усложняет поиск и диагностику причин этой ошибки. Когда анализатор HTML приходит в такое состояние, он не может продолжить работу и просто опускает руки, признавая: «Операция прервана!»

В начале разработки IE8 мы занялись смягчением побочных явлений этой проблемы. Вместо того, чтобы выводить диалоговое окно и покидать страницу сразу после того, как вы нажимаете ОК, мы удалили диалоговое окно и перенесли уведомление об ошибке в строку состояния (область уведомления о скриптовых ошибках). В итоге вашу работу не прерывают диалоговым окном, и вы можете продолжить просматривать текущую веб-страницу. Вы можете даже не заметить, что эта ошибка произошла; однако работа анализатора HTML резко останавливается (только для этой вкладки) и никакой дополнительный контент никогда не будет обработан.

Вскоре после выпуска IE8 мы начали получать сообщения от пользователей IE8, продолжавших видеть старое диалоговое окно о прерывании операции! Хотя мы знали, что не исправили все возможные сценарии, которые могут вызывать это диалоговое окно (оно может срабатывать из-за разных подсистем, таких как навигационный стек или сеть), мы были уверены, что учли худшие случаи. После получения недавних сообщений о том, что пользователи продолжают сталкиваться с окном «Операция прервана» в IE8, мы провели дальнейшие исследования для обнаружения дополнительных сценариев, которые могут вызывать появление этого диалогового окна (помимо скриптовой ошибки).

В последующих двух сценариях основная причина проблемы прерывания операции все та же (для подробностей, пожалуйста, прочтите мою предыдущую статью), но то, как она происходит, в этих сценариях вынуждает IE обойти те изменения, которые мы внесли в IE8.

Сценарий 1: Вложенный анализ после прерывания операции

 <html>
 <body>
  <div>
   <script type="text/javascript">
    document.body.appendChild(document.createElement('div'));
    document.write("Testing");
   </script>
  </div>
 </body>
</html>

В приведенном выше коде HTML первая строка скрипта призвана вызвать проблему с прерыванием операции. В IE8, как мы уже упоминали ранее, это учтено. Однако если где-то дальше происходит вызов API document.write, как показано во второй строке скрипта, все версии Internet Explorer, включая 8, выведут старое диалоговое окно о прерывании операции.

Сценарий 2: Операция прервана в обработчике ошибок

 <html>
 <body>
  <script type="text/javascript">
   window.onerror = function() {
    var el = document.getElementById("div2");
    el.appendChild(document.createElement("div"));
   }
  </script>
  <div id="div1"></div>
  <div id="div2" onclick="alert('hi';"></div>
 </body>
</html>

Код:

В этом HTML-файле в скрипте (в обработчике события onclick) присутствует ошибка выполнения, которая приводит к вызову обработчика объектов onerror. В этом случае, когда операция прерывается в обработчике ошибок, диалоговое окно также будет показано в IE8.

Программное определение прерывания операции

Когда выводится данное диалоговое окно, веб-разработчикам очень сложно найти проблему и исправить ее. Часто (и в большинстве случаев, с которыми мы сталкивались) проблема происходит из-за сторонних скриптов, присутствующих на странице. Чтобы помочь веб-разработчикам быстро найти и исправить проблему, мы написали небольшой скрипт, который должен помочь им.

Этот скрипт должен выполняться первым на странице с ошибкой прерывания операции. Он не учитывает использование innerHTML и appendChild, проверяя границы разбора прежде, чем позволить действие. AppendChild, безусловно, самая широко используемая точка входа DOM, которая может вызвать прерывание операции, следующее за innerHTML. Этот скрипт может отметить ошибочные результаты, но мы хотели лишний раз перестраховаться.

Этот скрипт основан на функции, включенной только в стандартном режиме IE 8 – Mutable DOM Prototypes. Таким образом, он будет работать только для страниц, использующих наиболее соответствующий стандартам режим IE. Ознакомьтесь с этой статьей о режиме совместимости, чтобы узнать подробности о режиме, в котором IE отображает вашу страницу. Однако те проблемы прерывания операции, которые обнаруживает этот скрипт (в стандартном режиме IE8), также относятся к IE7 и IE6, помогая, таким образом, исправить проблему в любой версии IE.

Чтобы воспользоваться скриптом, следуйте нижеприведенным инструкциям:

1.Добавьте скриптовый элемент в код веб-страницы. Этот скриптовый элемент должен располагаться перед любыми другими скриптовыми элементами на странице.

2. Поместите следующий текст в скриптовый элемент (или соответствующий файл, обращение к которому происходит с помощью атрибута src)

3. Установите значения «f1» и «f2»

a. Установка истины для значения «f1» приведет к пропуску вызовов DOM, которые потенциально могут вызвать ошибку прерывания операции. Однако это также скажется в процессе выполнения, так что могут произойти другие скриптовый ошибки.

b. Установка истины для значения «f2» останавливает процесс выполнения в момент потенциальной ошибки прерывания операции и запускает отладчик (внешний или встроенный отладчик JavaScript). Здесь вы можете проанализировать каждое событие и увидеть, какие допущения были сделаны и как процесс выполнения может быть изменен для предотвращения проблемы.

4. В IE пройдите на данную страницу.

5. Запустите отладчик JavaScript нажатием «F12», затем выберите вкладку «Script» в Developer Tools и нажмите кнопку «Start Debugging».

 (function() {
    // Feature switches
    // WARNING: 'true' may cause alternate program flow.
    var f1 = PREVENT_POTENTIAL_OCCURANCES = false;          
    var f2 = BREAK_INTO_DEBUGGER_AT_POTENTIAL_OCCURANCES = true;
    if (!window.console) {
        window.console = {};
        window.console.warn = function() { };
    }
    var frontierCheck = function(host) {
        // Is host on the frontier?
        while (host && (host != document.documentElement)) {
            if (host.parentNode && (host.parentNode.lastChild != host))
            // This is not on the frontier
                return true;
            host = host.parentNode;
        }
        if (!host || (host != document.documentElement))
            return true; // This node is not on the primary tree
        // This check is overly cautious, as appends to 
        // the parent of the running script element are 
        // OK, but the asynchronous case means that the 
        // append could be happening anywhere and intrinsice
        // knowledge of the hosting application is required
        console.warn("Potential case of operation aborted");
        if (f2)
            debugger;
        // Step up two levels in the call stack 
        // to see the problem source!!
        if (f1)
            return false;
        else
            return true;
    }
    var nativeAC = Element.prototype.appendChild;
    Element.prototype.appendChild = function() {
        // call looks like this:
        //    object.appendChild(object)
        // Go back one more level in the call stack!!
        if (frontierCheck(this))
            return nativeAC.apply(this, arguments);
    }
    var nativeIH = Object.getOwnPropertyDescriptor(Element.prototype, "innerHTML").set;
    Object.defineProperty(Element.prototype, "innerHTML", { set: function() {
        if (frontierCheck(this))
            nativeIH.apply(this, arguments);
    }
    });
})();

Мы понимаем, что диалоговое окно прерывания операции и его измененный вариант из IE8 остаются источником значительных неудобств для веб-разработчиков. Мы надеемся, что эта информация и приведенный скрипт помогут вам в диагностике и исправлении проблем, связанных с прерыванием операции в IE8 (и старших версиях IE).

Трэвис Лейтхед (Travis Leithead)

Руководитель команды разработчиков