ak_47: (Default)
[personal profile] ak_47
(Здесь собраны воедино отрывки дискуссий отсюда и отсюда.)

Описание проблемы

При загрузке LJ страницы браузер IE7(IE6) зависает на несколько секунд (или минут). При этом загрузка CPU находится на отметке ~100%. Проблема проявляется особенно остро на страницах с большим количеством комментариев.

Причина

Причина кроется в файле "http://www.livejournal.com/js??dom.js" и конкретно в следующих трёх функциях:
  • DOM.filterElementsByClassName

  • DOM.filterElementsByAttribute

  • DOM.filterElementsByTagName
Во всех трёх к проблеме приводит одинаковый участок кода, поэтому разбор полётов будет проводится на примере `DOM.filterElementsByClassName'. Вот как выглядит эта функция:
    filterElementsByClassName: function( es, className ) {
        var filtered = [];
        for( var i = 0; i < es.length; i++ ) {
            var e = es[ i ];
            if( DOM.hasClassName( e, className ) )
                filtered[ filtered.length ] = e;
        }
        return filtered;
    }
где `es' это коллекция DOM элементов, возвращённая из вызова `document.getElementsByTagName("*")'. В частности, к зависанию приводит вот эта строка:
    var e = es[ i ];
Почему же в браузерах на движке IE такие грабли? Я считаю что корень проблемы заключается в различной имплементации коллекции элементов и следовательно, различном поведении оператора "[]" (subscript operator). Я буду говорить IE или FireFox, подразумевая на самом деле JavaScript движок, используемый этими браузерами (MS JScript и SpiderMonkey, соответственно).

Судя по всему, в FireFox коллекция элементов представляет собой массив последовательных элементов, либо содержит hash table для быстрого доступа к элементу по индексу. Поэтому оператор "[]" работает быстро, т.к. доступ к каждому элементу это всего лишь оффсет от первого элемента (либо готовая ссылка из hash table).

В IE, по-видимому, элементы коллекции хранятся в виде linked list или похожей структуре данных. Поэтому оператору "[]" для доступа к каждому i-му элементу надо сначала пройтись по всем i-1 элементам до него. Если это происходит несколько раз во время загрузки страницы (а так и происходит в скриптах LJ), то суммарное время итераций может быть вполне ощутимым.

Например, для страницы с 1000-й элементов (не такая уж и большая страница) и тремя проходами по всем элементам имеем:
    (1 + 2 + 3 + ... + 999 + 1000) * 3 = 1 498 500.
Для грубой оценки можно считать полтора миллиона итераций по элементам. Допустим что компьютер мощный и каждая итерация занимает треть миллисекунды. Выходит что для полного пробега нужно 500 секунд. А это ~8.3 минуты. Вот вам и зависание браузера на 5-10 минут на больших страницах.

Решение

Суть решения заключается в замене способа итерации по коллекции. В IE для этой цели существует специальный объект `Enumerator'. Вот как выглядит функция `DOM.filterElementsByClassName' с применением этого объекта:
    filterElementsByClassName: function( es, className ) {
        var filtered = [];
        for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) {
            var e = en.item();
            if( DOM.hasClassName( e, className ) )
                filtered.push(e);
        }
        return filtered;
    }
Также, добавление найденых элементов в новый массив `filtered' было изменено на более быстрое `filtered.push(e);' вместо `filtered[filtered.length] = e;'.

Теперь осталось исправить вышеуказанные функции в скрипте LJ. Это можно сделать двумя способами (в скобках указаны примеры продуктов, с помощью которых это можно осуществить):
  1. Поставить некий фильтр на соединение с сервером вне браузера и исправить там страницу (Ad Muncher, Privoxy и т.д.).

  2. Поставить на браузер расширение, которое даёт доступ к содержимому страницы и написать костыль, который будет страницу чинить  (IE7Pro, Turnabout и т.д.).
На своё счастье я уже давно пользуюсь Ad Muncher'ом, поэтому не пришлось ничего специально ставить. Тем не менее, нижеприведённое решение можно применять и в других похожих программах, независимо от выбранного способа (естественно, после доработки напильником).

Решение с помощью Ad Muncher

Должен сразу предупредить что это решение грубое и может портачить всплывающее оконце (Contextual Hover Menu) над иконкой юзера. Итак:
  1. Скопировать этот код в Notepad (или любой другой любимый текстовой редактор):
    if (document.domain.indexOf('livejournal.com') != -1)
    {
        DOM = {};
    
        DOM.filterElementsByClassName = function( es, className ) {
            var filtered = [];
            for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) {
                var e = en.item();
                if( DOM.hasClassName( e, className ) )
                    filtered.push(e);
            }
            return filtered;
        };
        
        DOM.filterElementsByAttribute = function( es, attr ) {
            if( !es )
                return [];
            if( !defined( attr ) || attr == null || attr == "" )
                return es;
            var filtered = [];
            for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) {
                var element = en.item();
                if( !element )
                    continue;
                if( element.getAttribute && ( element.getAttribute( attr ) ) )
                    filtered.push(element);
            }
            return filtered;
        };
        
        DOM.filterElementsByTagName = function( es, tagName ) {
            if( tagName == "*" )
                return es;
            var filtered = [];
            tagName = tagName.toLowerCase();
            for( var en = new Enumerator(es); !en.atEnd(); en.moveNext() ) {
                var e = en.item();
                if( e.tagName && e.tagName.toLowerCase() == tagName )
                    filtered.push(e);
            }
            return filtered;
        };
    }

  2. Отформатировать вышеприведённый код так, чтобы он был в одну (очень длинную) строку. Это ограничение Ad Muncher'а, ничего не поделаешь.

  3. Добавить получившуюся строку в фильтры Ad Muncher'а; категория фильтра: "Add javascript to all pages".

  4. Всё!

Обходной путь

Решение для бедных крайне простое и одновременно чрезвычайно эффективное. Примерно как топор в качестве средства от головной боли. Всё что нужно сделать это добавить строку *.livejournal.com в список Restricted Sites в настройках IE. Тогда вообще никаких скриптов на страницах LJ исполняться не будет. Нет скриптов - нет проблемы.

Дополнительная информация

После долгого перерыва Microsoft разродилась новой версией движков для скриптов: 5.7. В новой версии зачинены многие баги, произведены серьёзные улучшения в работе garbage collector'а и сделаны другие полезные изменения. Всем настоятельно рекомендуется обновить свои системы.После обновления значительно смягчились симптомы подвисания IE.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org

Profile

ak_47: (Default)
АК-47

Most Popular Tags

Expand Cut Tags

No cut tags
Powered by Dreamwidth Studios