(function() {
/**************************************************************************
 * Работа со временем
 */
var deltaTime = 0;
function timeCorrector(lastTime) {
    var time = new Date().getTime();
    if (lastTime && time < lastTime) {            // Если часы опаздывают, корректируем их
        deltaTime += lastTime - time + 20;
    }
    setTimeout(function() { timeCorrector(time); }, 20);
}

var lastNowTime = 0;
function nowTime() {
    var time = new Date().getTime() + deltaTime;
    if (time < lastNowTime) {
        time = lastNowTime + 10;
    }
    lastNowTime = time;
    return time;
}
timeCorrector(0);

function nowEventTime() {
    return Math.round((nowTime() - pageLoadTime) / 50);
}
//-------------------------------------------------------------------------
//#ifdebug
if (!window.console) {
    console = {
        log: function() {},
        error: function() {},
        info: function() {},
        debug: function() {}
    };
}
//#endif
var userId; // Идентификатор юзера. Приходит после отправки первичного запроса на сервер или лежит в куке
var tmpUserId = null; //Временный идентификатор запроса, используется до прихода userId от сервера

// Версия протокола передачи данных
var PROTO_VERSION = '$Rev: 3182 $'.match(/(\d+)/)[1];
var ticket; // Уникальный идентификатор хита
var clientHash; // Идентификатор клиента

// Идентификатор сайта, который пришел к нам из вызова визора клиентом
var siteId;

// Момент загрузки страницы, от которого отсчитываем события
var pageLoadTime = nowTime();

// Список, удерживающий ссылки на запощенные объекты
var debug = ''; // Отладочная информация
var SERVER_ADDRESS; // Адрес сервера
var targetDocument = document;
var targetWindow = window;
// Флаг полной загрузки страницы (onLoad). После полной загрузки
// надо перепроверить координаты узлов, уже отправленных на сервер - они могут измениться (см. guessedNodes)
var loaded = false;
// Узлы, координаты которых были отправлены на сервер до полной загрузки страницы
// т.е. у них надо перепроверить координаты после загрузки
var guessedNodes = [];

function keys(object) {
    var keys = [];
    for (var i in object) {
        keys[keys.length] = i;
    }
    return keys;
}

function toQueryString(params) {
    var result = [];
    for (var param in params) {
        result[result.length] = param + '=' + encodeURIComponent(params[param]);
    }
    return result.join('&');
}var encodeURIComponent, Array_push;
(function() {
    var iframe = document.createElement('iframe'), loaded = false;
    iframe.style.width = '1px';
    iframe.style.height = '1px';
    iframe.style.position = 'absolute';
    var onloadIframe = function() {
        if (!loaded && iframe.contentWindow) {
            loaded = true;
            iframe.contentWindow.document.write('<body onload=""></body>');
            iframe.contentWindow.document.close();

            encodeURIComponent = iframe.contentWindow.encodeURIComponent;
            Array_push = iframe.contentWindow.Array.prototype.push;

            document.documentElement.removeChild(iframe);
            setTimeout(function() {
                // Без этого таймера IE удаляет выдернутые функции, в результате чего они становятся
                // какими-то объектами.
            }, 3600 * 24 * 1000);
        }
    };
    iframe.onload = onloadIframe;
    document.documentElement.insertBefore(iframe, document.documentElement.firstChild);
    onloadIframe();
})();
function toObjectString(obj) {
    return Object.prototype.toString.call(obj);
}

function isArray(obj) {
    return toObjectString(obj) == '[object Array]';
}

function isDate(obj) {
    return toObjectString(obj) == '[object Date]';
}

function isRegExp(obj) {
    return toObjectString(obj) == '[object RegExp]';
}

function isDOMElement(obj) {
    return obj && obj.ownerDocument == targetDocument;
}

function isNativeFunction(name, scope) {
    return new RegExp('^\\s*(function (' + name + ')?\\(\\)\\s*{\\s*\\[native code\\]\\s*}|function ' + name + '\\(\\) { /\\* source code not available \\*/ })\\s*$').test(String((scope || targetWindow)[name]));
}/**************************************************************************
 * DOM-функции
 */
var compatMode = targetDocument.compatMode,
    isCSS1Compat = compatMode == 'CSS1Compat';

function getBodyProperty(property) {
    return targetDocument.body ? targetDocument.body[property] : 0;
}

function getViewportHeight() {
    var height = self.innerHeight; // Safari, Opera
    if ((compatMode || userAgent.ie) && !userAgent.opera) { // IE, Gecko
        height = isCSS1Compat ? targetDocument.documentElement.clientHeight : getBodyProperty('clientHeight');
    }
    return height || 0;
}

function getViewportWidth() {
    var width = self.innerWidth;  // Safari
    if (compatMode || userAgent.ie) { // IE, Gecko, Opera
        width = isCSS1Compat ? targetDocument.documentElement.clientWidth : getBodyProperty('clientWidth');
    }
    return width || 0;
}

function getDocumentHeight() {
    var docHeight = isCSS1Compat ? targetDocument.documentElement.scrollHeight : getBodyProperty('scrollHeight');
    return Math.max(docHeight, getViewportHeight());
}

function getDocumentWidth() {
    var docWidth = isCSS1Compat ? targetDocument.documentElement.scrollWidth : getBodyProperty('scrollWidth');
    return Math.max(docWidth, getViewportWidth());
}

function getDocumentScrollLeft() {
    return Math.max(targetDocument.documentElement ? targetDocument.documentElement.scrollLeft : 0, getBodyProperty('scrollLeft'));
}

function getDocumentScrollTop () {
    var result = Math.max(targetDocument.documentElement ? targetDocument.documentElement.scrollTop : 0, getBodyProperty('scrollTop'));
    if (isNaN(result)) {
        debug = 'gdst: ' + targetDocument.documentElement && targetDocument.documentElement.scrollTop + ', ' + getBodyProperty('scrollTop');
    }
    return result;
}

function classExists(el, className) {
    return new RegExp('(^|\\s)' + className + '(\\s|$)').test(el.className);
}/**************************************************************************
 * base64 encoder
 */
var b64a  = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789*-'.split('');

function base64(buf) {
    var n = buf.length;
    var s = [];
    var lPos = n - n % 3;
    var t;
    for (var i = 0; i < lPos; i += 3) {
        t = (buf[i] << 16) + (buf[i + 1] << 8) + buf[i + 2];
        s.push(b64a[(t >> 18) & 0x3f] + b64a[(t >> 12) & 0x3f] + b64a[(t >> 6) & 0x3f] + b64a[t & 0x3f]);
    }
    //noinspection SwitchStatementWithNoDefaultBranchJS
    switch (n - lPos) {
        case 1:
            t = buf[lPos] << 4;
            s.push(b64a[(t >> 6) & 0x3f] + b64a[t & 0x3f] + '__');
            break;
        case 2:
            t = (buf[lPos] << 10) + (buf[lPos + 1] << 2);
            s.push(b64a[(t >> 12) & 0x3f] + b64a[(t >> 6) & 0x3f] + b64a[t & 0x3f] + '_');
            break;
    }
    return s.join('');
}
//-------------------------------------------------------------------------
/**************************************************************************
 * Определение браузера
 */
//todo заменить висящий в памяти объект на boolean флаги
var userAgent = function() {
    var o={

        /**
         * Internet Explorer version number or 0.  Example: 6
         * @property ie
         */
        ie:0,

        /**
         * Opera version number or 0.  Example: 9.2
         * @property opera
         */
        opera:0,

        /**
         * Gecko engine revision number.  Will evaluate to 1 if Gecko
         * is detected but the revision could not be found. Other browsers
         * will be 0.  Example: 1.8
         * <pre>
         * Firefox 1.0.0.4: 1.7.8   <-- Reports 1.7
         * Firefox 1.5.0.9: 1.8.0.9 <-- Reports 1.8
         * Firefox 2.0.0.3: 1.8.1.3 <-- Reports 1.8
         * Firefox 3 alpha: 1.9a4   <-- Reports 1.9
         * </pre>
         * @property gecko
         */
        gecko:0,

        /**
         * AppleWebKit version.  KHTML browsers that are not WebKit browsers
         * will evaluate to 1, other browsers 0.  Example: 418.9.1
         * <pre>
         * Safari 1.3.2 (312.6): 312.8.1 <-- Reports 312.8 -- currently the
         *                                   latest available for Mac OSX 10.3.
         * Safari 2.0.2:         416     <-- hasOwnProperty introduced
         * Safari 2.0.4:         418     <-- preventDefault fixed
         * Safari 2.0.4 (419.3): 418.9.1 <-- One version of Safari may run
         *                                   different versions of webkit
         * Safari 2.0.4 (419.3): 419     <-- Current Safari release
         * Webkit 212 nightly:   522+    <-- Safari 3.0 (with native SVG) should
         *                                   be higher than this
         *
         * </pre>
         * http://developer.apple.com/internet/safari/uamatrix.html
         * @property webkit
         */
        webkit:0,

        /**
         * The mobile property will be set to a string containing any relevant
         * user agent information when a modern mobile browser is detected.
         * Currently limited to Safari on the iPhone/iPod Touch, Nokia N-series
         * devices with the WebKit-based browser, and Opera Mini.
         * @property mobile

         */
        mobile: null
    };

    //noinspection PlatformDetectionJS
    var ua=navigator.userAgent, m;

    // Modern KHTML browsers should qualify as Safari X-Grade
    if ((/KHTML/).test(ua)) {
        o.webkit=1;
    }
    // Modern WebKit browsers are at least X-Grade
    m=ua.match(/AppleWebKit\/([^\s]*)/);
    if (m&&m[1]) {
        o.webkit=parseFloat(m[1]);

        // Mobile browser check
        if (/ Mobile\//.test(ua)) {
            o.mobile = "Apple"; // iPhone or iPod Touch
        } else {
            m=ua.match(/NokiaN[^\/]*/);
            if (m) {
                o.mobile = m[0]; // Nokia N-series, ex: NokiaN95
            }
        }

    }

    if (!o.webkit) { // not webkit
        m=ua.match(/Opera[\s\/]([^\s]*)/);
        if (m&&m[1]) {
            o.opera=parseFloat(m[1]);
            m=ua.match(/Opera Mini[^;]*/);
            if (m) {
                o.mobile = m[0]; // ex: Opera Mini/2.0.4509/1316
            }
        } else { // not opera or webkit
            m=ua.match(/MSIE\s([^;]*)/);
            if (m&&m[1]) {
                o.ie=parseFloat(m[1]);
            } else { // not opera, webkit, or ie
                m=ua.match(/Gecko\/([^\s]*)/);
                if (m) {
                    o.gecko=1; // Gecko detected, look for revision
                    m=ua.match(/rv:([^\s\)]*)/);
                    if (m&&m[1]) {
                        o.gecko=parseFloat(m[1]);
                    }
                }
            }
        }
    }
    return o;
}();
//-------------------------------------------------------------------------
/**
 * Функции для работы с событиями.
 */

/**
 * Кроссбраузерное навешивание событий. Предусматривает вариант отсутствия по какой-либо причине
 * функции addEventListener/attachEvent у элемента. Оборачивает обработчик в перехватчик ошибок.
 * @param {Object} el Элемент, на который навешивается событие.
 * @param {String} name Имя события.
 * @param {Function} fn Обработчик события.
 */
function attachEvent(el, name, fn) {
    fn = wrap(fn);
    if (/function addEventListener\(\)\s*{\s*\[native code\]\s*}/.test(String(el.addEventListener))) {
        el.addEventListener(name, fn, false);
    } else if (/function attachEvent\(\)\s*{\s*\[native code\]\s*}/.test(String(el.attachEvent))) {
        el.attachEvent("on" + name, fn);
    } else {
        // Заход сюда означает, что какой-то придурок переопределил методы addEventListener|attachEvent
        // в глобальном скопе, а мы пытаемся повесить событие на window
        // В частности, не мешало бы пристрелить создателя http://www.kingsland.ru/
        var node = targetDocument.createElement('div');
        if (node.addEventListener) {
            node.addEventListener.call(el, name, fn, false);
        } else {
            node.attachEvent.call(el, name, fn);
        }
    }
}

/**
 * Определяет источник события.
 * @param {Event} evt Объект события.
 * @return {HTMLElement} 
 */
function extractTarget(evt) {
    var targ = evt.target || evt.srcElement;
    if (targ) {
        if (targ.nodeType == 3) { // defeat Safari bug
            targ = targ.parentNode;
        }
        if (targ.nodeName == "AREA") {
            // AREA криво работает в IE - он считает что ImageMap не наложен на Image, а начинается от точки (0,0) на странице
            // Поэтому попытаемся найти image, который ему соответсвует
            var map = targ.parentNode;
            if (map) {
                targ = findMapTarget(map);
            }
        } else if (targ.nodeName == "MAP") { // На всякий случай
            targ = findMapTarget(targ);
        }
    }
    var parent = targ, realTarg = targ;
    do {
        if (/(^|\s)-webvisor-random-area(\s|$)/.test(parent.className)) {
            realTarg = parent;
        }
        if (parent.vvid) {
            break;
        }
    } while (parent = getParentNode(parent));
    return realTarg;
}
/**************************************************************************
 * Перехват ошибок
 */
var errorSended = false;
var wrap = function(callback) {
    return function() {
        try {
            return callback.apply(window, arguments);
        } catch (e) {
            //#ifdebug
            throw new Error('debug = ' + debug + '; \nfn = ' + (callback.toString().match(/function\s([^(]*)/) || {1: ''})[1] + ';\n' + e.message);
            //#endif
            if (!errorSended && (!e.message || !/B0_3Ch|Cr3Cl/.test(e.message))) {
                errorSended = true;
                var params = ['pv=' + PROTO_VERSION];
                params.push("fn=" + (callback.toString().match(/function\s([^(]*)/) || {1: ''})[1]);
                params.push('debug=' + debug);
                params.push('time=' + (nowTime() - pageLoadTime));
                params.push('uid=' + userId);
                for (var i in e) {
                    params.push(i + '=' + encodeURIComponent(e[i]));
                }
                new Image().src = SERVER_ADDRESS + 'er?' + params.join('&');
            }
        }
    };
};
//-------------------------------------------------------------------------

/**************************************************************************
 * Функции для работы с таймером
 */
// Аналогично setTimeout, но вызываемая функция оборачивается сборщиком ошибок
function setTimer(fun, timeout) {
    return setTimeout(wrap(fun), timeout);
}

// В IEMobile нет setInterval, поэтому его не используем
// Для простоты не возвращаем ничего, поэтому прервать будет невозможно
// Если нужен не бесконечный таймер, используем setTimer
// Вызываемую функцию, разумеется, оборачиваем безошибочным врапером
function setPeriodicalTimer(fun, timeout) {
    var callback = wrap(fun);
    (function() {
        callback();
        setTimeout(arguments.callee, timeout);
    })();
}
//-------------------------------------------------------------------------
function appendScript(src) {
    var s = targetDocument.createElement('script');
    s.type = 'text/javascript';
    s.src = src;
    var ct = targetDocument.getElementsByTagName('head')[0] || targetDocument.body || targetDocument.documentElement;
    if (ct.firstChild) {
        ct.insertBefore(s, ct.firstChild);
    } else {
        ct.appendChild(s);
    }
    return s;
}

// Максимальная длина пакета данных
var maxPacketSize;

function createIETransport() {
    maxPacketSize = 6500;
//    var transferDoc = window.open().document;
    var transferDoc = new ActiveXObject('htmlfile');
    transferDoc.open();
    transferDoc.write('<html><body>' +
                      '<iframe name="ifr"></iframe>' +
                      '</body></html>');
    transferDoc.close();

    function createForm(fields) {
        var form = transferDoc.createElement('form');
        form.id = fields.join('');
        form.method = 'post';
        form.action = SERVER_ADDRESS + 'e.gif';
        form.target = 'ifr';
        transferDoc.body.appendChild(form);
        for (var i = 0; i < fields.length; i++) {
            form[fields[i]] = transferDoc.createElement('textarea');
            form[fields[i]].name = fields[i];
            form.appendChild(form[fields[i]]);
        }
        return form;
    }

    return function(params) {
        var fields = keys(params);
        var form = transferDoc.getElementById(fields.join('')) || createForm(fields);
        for (var field in params) {
            form[field].value = params[field];
        }
        form.submit();
    };
}

function createImgTransport() {
    maxPacketSize = 750;
    return function(params) {
//        var img = new Image();
//        img.src = SERVER_ADDRESS + 'e.gif?' + toQueryString(params);
        var script = appendScript(SERVER_ADDRESS + 'e.js?' + toQueryString(params));
        setTimer(function() {
            script.parentNode.removeChild(script);
        }, 3000);
    };
}

function createIframeTransport(callback) {
    if (isNativeFunction('postMessage')) {
        var iframe = targetDocument.createElement('iframe');
        iframe.onload = function() {
            maxPacketSize = 750;
            callback(function(params) {
                iframe.contentWindow.postMessage(toQueryString(params), '*');
            });
        };
        iframe.style.position = 'absolute';
        iframe.style.left = '-9999px';
        iframe.style.top = '-9999px';
        iframe.src = SERVER_ADDRESS + 'c.html?2';
        document.documentElement.appendChild(iframe);

    }
}

var flushData, safeFlushData;
function createTransport() {
    try {
        flushData = createIETransport();
        safeFlushData = flushData;
    } catch (e) {
        flushData = createImgTransport();
        safeFlushData = flushData;
//        createIframeTransport(function(transport) {
//            flushData = transport;
//        });
    }
}
var initPing;
var send = (function() {
    var MAX_HIT_LENGTH = 8 * 60 * 60 * 1000;
    var MAX_INACTIVE_INTERVAL = 5 * 60 * 60 * 1000;
    var PING_INTERVAL = 55000;

    var buffer = [], preBuffer = [], packetSequence = 1, hostname = '',
            pingTimeoutId, disabled = false, lastActiveTime = pageLoadTime, isPing = false;

    try { // IE не всегда дает доступ к window.location
        hostname = window.location.hostname;
    } catch (e) {}

    function ping() {
        if (!disabled) {
            if (nowTime() - lastActiveTime < MAX_INACTIVE_INTERVAL) {
                if (buffer.length) {
                    flushBuffer();
                } else {
                    isPing = true;
                    sendPing();
                    isPing = false;
                }
            } else {
                disabled = true;
            }
        }
    }

    function resetPing() {
        if (pingTimeoutId) {
            clearTimeout(pingTimeoutId);
        }
        pingTimeoutId = setTimer(ping, PING_INTERVAL);
    }

    function flushBuffer() {
        if (nowTime() - pageLoadTime > MAX_HIT_LENGTH) {
            disabled = true;
        }
        if (buffer.length && !disabled) {
            resetPing();
            var params = {
                s: packetSequence++,
                tk: ticket,
                c: clientHash,
                fl: fletcher(buffer, true).toString(36),
                pv: PROTO_VERSION,
                h: hostname,
                gc: 1,
                si: siteId,
                d: base64(buffer)
            };
            if (userId) {
                params.u = userId;
            } else {
                params.tu = tmpUserId;
            }
            flushData(params);
        }
        buffer.length = 0;
    }

    function $(bytes, forceFlush) {
        Array_push.apply(preBuffer, bytes);
        if (!isPing) {
            lastActiveTime = nowTime();
        }

        if (forceFlush === false) {
            // Сбрасывать этот кусок данных ни в коем случае нельзя, поэтому выходим.
            return;
        }

        if (buffer.length + preBuffer.length > maxPacketSize) {
            // Суммарный объём буферов слишком велик, сбрасываем содержимое основного буфера на сервер.
            flushBuffer();
        }

        Array_push.apply(buffer, preBuffer);
        preBuffer.length = 0;

        if (forceFlush === true) {
            // Нужно обязательно сбросить этот кусок данных.
            flushBuffer();
        }
    }

    initPing = resetPing;
    return $;
})();// Идентификаторы типов событий
var P_ELEMENT        = 1,
    P_MOUSE_MOVE     = 2,
    P_SCROLL         = 3,
    P_MOUSE_DOWN     = 4,
    P_KEYDOWN        = 5,
    P_LOAD           = 6,
    P_NEW_REGION     = 8,
    P_EOF            = 9,
    P_WINDOW_SIZE    = 12,
    P_DOCUMENT_SIZE  = 13,
    P_WINDOW_FOCUS   = 14,
    P_WINDOW_BLUR    = 15,
    P_FIELD_FOCUS    = 17,
    P_FIELD_BLUR     = 18,
    P_FIELD_CHANGE   = 19,
    P_GOAL           = 20,
    P_TAG            = 21,
    P_FIELD          = 22,
    P_USER_NAME      = 23,
    P_USER_ID        = 24,
    P_FETCH_URL      = 25,
    P_JS_CALL        = 26,
    P_TEXT_COPY      = 27,
    P_RESIZE         = 28,
    P_TEXT_SELECTION = 29,
    P_MOUSE_UP       = 30,
    P_NPBUTTON_STATE = 31,
    P_CLICK          = 32,
    P_PING           = 33;


function flush() {
    send([], true);
}

function sendPacket(packet) {
    send(packet.buffer);
}

function sendPing() {
//    sendPacket(new PacketWriter(P_PING));
//    flush();
}

var PacketWriter = function (packetType, noTime) {
    this.buffer = [];
    this.buffer.push(packetType);
    if (!noTime) {
        this.addScaled(nowEventTime());
    }
};

PacketWriter.prototype.push = function(n) {
    //#ifdebug
    if (n < 0) {
        throw new Error("Push value (" + n + ") < 0");
    }
    if (n > 255) {
        throw new Error("Push value (" + n + ") > 255");
    }
    if (typeof(n) != "number" || isNaN(n)) {
        throw new Error("Push bad value " + n);
    }
    //#endif
    // push не используем. на mg.ru какой-то мудак его переопределил так, что нули в массив не попадают.
    this.buffer[this.buffer.length] = n;
};

PacketWriter.prototype.pushShort = function (n) {
    this.push(n >> 8);
    //noinspection MagicNumberJS
    this.push(n & 0xFF);
};

PacketWriter.prototype.addByte = function(n) {
    n = Math.round(n);
    //noinspection MagicNumberJS
    this.push(n & 0xFF);
};

PacketWriter.prototype.addScaled = function(n) {
    n = Math.round(n);
    if (n < 0) {
        //#ifdebug
//        console.error("Negative scaled value. Pushing 0 instead", n);
        //throw("Negative scaled value " + n);
        //#endif
        n = 0;
    }
    while (n > 127) {
        this.push((n & 127)|128);
        n >>= 7;
    }
    this.push(n);
};

PacketWriter.prototype.addWord = function(n) {
    n = Math.round(n);
    if (n > 0xFFFF) {
        //#ifdebug
//        console.warn("Pushing word %d exceed maxumum word value", n);
        //#endif
        n = 0xFFFF;
    }
    if (n < 0) {
        //#ifdebug
//        console.warn("Pushing word %d - negative value", n);
        //#endif
        n = 0;
    }
    this.pushShort(n);
};


PacketWriter.prototype.addString = function (str) {
    var n = str.length;
    if (n > 255) {
        str = str.substr(0, 255);
    }
    this.push(str.length);
    for( var i=0; i<str.length; i++ ){
        var cch=str.charCodeAt(i);
        this.pushShort(cch);
      };
};

PacketWriter.prototype.addScaledString = function(str) {
    this.addScaled(str.length);
    for (var i = 0; i < str.length; i++) {
        this.addScaled(str.charCodeAt(i));
    }
};

PacketWriter.prototype.addByteArray = function(arr) {
    this.addScaled(arr.length);
    for (var i = 0; i < arr.length; i++) {
        this.addByte(arr[i]);
    }
};

PacketWriter.prototype.writeRegion = function (region) {
    this.addScaled(region.left);
    this.addScaled(region.top);
    this.addScaled(region.width);
    this.addScaled(region.height);
};


function sendGoal(goal) {
    if (goal < 0 || goal > 3) {
        throw new Error('Bad goal ' + goal);
    }
    var packet = new PacketWriter(P_GOAL, true);
    packet.addByte(goal);
    sendPacket(packet);
}

function sendTag(tag) {
    var packet = new PacketWriter(P_TAG, true);
    packet.addScaledString(tag);
    sendPacket(packet);
}

function sendKeyValue(key, value) {
    var packet = new PacketWriter(P_FIELD, true);
    packet.addScaledString(key);
    packet.addScaledString(value);
    sendPacket(packet);
}

function sendFetchUrl(url) {
    var packet = new PacketWriter(P_FETCH_URL, true);
    packet.addScaledString(url);
    sendPacket(packet);
}

function sendUserName(name) {
    var packet = new PacketWriter(P_USER_NAME, true);
    packet.addScaledString(name);
    sendPacket(packet);
}

function sendUserId(id) {
    var packet = new PacketWriter(P_USER_ID, true);
    packet.addScaledString(id);
    sendPacket(packet);
}

function sendWindowSize(width, height) {
    var packet = new PacketWriter(P_WINDOW_SIZE, true);
    packet.addWord(width);
    packet.addWord(height);
    sendPacket(packet);
}

function sendDocumentSize(width, height) {
    var packet = new PacketWriter(P_DOCUMENT_SIZE, true);
    packet.addScaled(width);
    packet.addScaled(height);
    sendPacket(packet);
}

function sendEOF(time) {
    flushData = safeFlushData;
    var packet = new PacketWriter(P_EOF, true);
    packet.addScaled(time);
    sendPacket(packet);
    flush();
}

function sendKeyDown(time, charCode, mask, node) {
    var packet = new PacketWriter(P_KEYDOWN, true);
    packet.addScaled(time);
    packet.addWord(charCode);
    packet.addByte(mask);
    packet.addScaled(node);
    sendPacket(packet);
}

function sendScroll(time, x, y) {
    var packet = new PacketWriter(P_SCROLL, true);
    packet.addScaled(time);
    packet.addScaled(x);
    packet.addScaled(y);
    sendPacket(packet);
}

function sendLoad(time) {
    var packet = new PacketWriter(P_LOAD, true);
    packet.addScaled(time);
    sendPacket(packet);
}

function sendWindowFocus(time) {
    var packet = new PacketWriter(P_WINDOW_FOCUS, true);
    packet.addScaled(time);
    sendPacket(packet);
}

function sendWindowBlur(time) {
    var packet = new PacketWriter(P_WINDOW_BLUR, true);
    packet.addScaled(time);
    sendPacket(packet);
    flush();
}

function sendFieldFocus(time, node) {
    var packet = new PacketWriter(P_FIELD_FOCUS, true);
    packet.addScaled(time);
    packet.addScaled(node);
    sendPacket(packet);
}

function sendFieldBlur(time, node) {
    var packet = new PacketWriter(P_FIELD_BLUR, true);
    packet.addScaled(time);
    packet.addScaled(node);
    sendPacket(packet);
}

function sendFieldChange(time, node, value) {
    var packet = new PacketWriter(P_FIELD_CHANGE, true);
    packet.addScaled(time);
    packet.addScaled(node);
    packet.addString(value);
    sendPacket(packet);
}

function sendRegion(node, region) {
    var regionPacket = new PacketWriter(P_NEW_REGION, true);
    regionPacket.addScaled(node);
    regionPacket.writeRegion(region);
    sendPacket(regionPacket);
}

function sendJsCall(time, code) {
    var packet = new PacketWriter(P_JS_CALL, true);
    packet.addScaled(time);
    packet.addByteArray(code);
    sendPacket(packet);
}

function sendResize(time, windowSize, documentSize) {
    var packet = new PacketWriter(P_RESIZE, true);
    packet.addScaled(time);
    packet.addScaled(windowSize[0]);
    packet.addScaled(windowSize[1]);
    packet.addScaled(documentSize[0]);
    packet.addScaled(documentSize[1]);
    sendPacket(packet);
}

function sendNonProfitButtonState(exists) {
    var packet = new PacketWriter(P_NPBUTTON_STATE, true);
    packet.addScaled(exists ? 2 : 1);
    sendPacket(packet);
}

function sendSelectedText(time, text) {
    var left, right;
    if (text.length == 0) {
        left = '';
        right = '';
    } else if (text.length <= 100) {
        left = text;
        right = '';
    } else if (text.length <= 200) {
        left = text.substr(0, 100);
        right = text.substr(100);
    } else {
        left = text.substr(0, 97);
        right = text.substr(text.length - 97);
    }
    var packet = new PacketWriter(P_TEXT_SELECTION, true);
    packet.addScaled(time);
    packet.addScaledString(left);
    packet.addScaledString(right);
    sendPacket(packet);
}

function sendCopy(time) {
    var packet = new PacketWriter(P_TEXT_COPY, true);
    packet.addScaled(time);
    sendPacket(packet);
}

function sendPageContent(content) {
    var doctype = '';
    if (window.ActiveXObject && window.ActiveXObject.toString() == '\nfunction ActiveXObject() {\n    [native code]\n}\n') {
        var htmlfile = new ActiveXObject('htmlfile');
        //    var htmlfile = window.open().document;
        htmlfile.open();
        htmlfile.write('<html>' +
                       '    <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>' +
                       '    <body><iframe name="tmp"></iframe><form method="post" id="form" action="' + SERVER_ADDRESS + 'content" target="tmp" enctype="multipart/form-data">' +
                       '        <input type="hidden" name="u" value="' + userId + '">' +
                       '        <input type="hidden" name="tk" value="' + ticket + '">' +
                       '        <input type="hidden" name="c" value="' + clientHash + '">' +
                       '        <input type="hidden" name="pv" value="' + PROTO_VERSION + '">' +
                       '        <input type="hidden" name="h" value="' + window.location.host + '">' +
                       '        <input type="hidden" name="si" value="' + siteId + '">' +
                       '        <input type="hidden" name="page" id="content">' +
                       '    </form></body>' +
                       '</html>');
        htmlfile.close();

        if (!content) {
            var isDoctype = function(el) {
                return /^(<!DOCTYPE|<\?xml)/i.test(el.text);
            };
            var comments = targetDocument.getElementsByTagName('!');
            for (var i = 0; isDoctype(comments[i]); i++) {
                doctype += comments[i].text;
            }
            content = doctype + '\n' + targetDocument.documentElement.outerHTML;
        }

        htmlfile.parentWindow.document.getElementById('content').value = content;
        htmlfile.parentWindow.document.getElementById('form').submit();
    } else if (isNativeFunction('postMessage')) {
        if (!content) {
            var htmlAttrs = [];
            if (targetDocument.doctype) {
                doctype = '<!DOCTYPE ' + targetDocument.doctype.name + ' PUBLIC "' + targetDocument.doctype.publicId + '" "' + targetDocument.doctype.systemId + '">';
            }
            var docElAttrs = targetDocument.documentElement.attributes;
            for (var j = 0; j < docElAttrs.length; j++) {
                htmlAttrs[htmlAttrs.length] = ' ' + docElAttrs[j].nodeName + '="' + docElAttrs[j].nodeValue + '"';
            }
            content = doctype + '\n<html' + htmlAttrs.join('') + '>' + targetDocument.documentElement.innerHTML + '</html>';
        }

        var iframe = targetDocument.createElement('iframe');
        var iframeWin = iframe.contentWindow || iframe.defaultView;
        iframe.onload = function() {
            iframe.contentWindow.postMessage('{u: "' + userId + '", tk: "' + ticket + '", c: "' + clientHash + '", pv: "' + PROTO_VERSION + '", h: "' + window.location.host + '", si: "' + siteId + '"}\r\n' + content, '*');
        };
        iframe.style.position = 'absolute';
        iframe.style.left = '-9999px';
        iframe.style.top = '-9999px';
        iframe.src = SERVER_ADDRESS + 'content.html';
        document.documentElement.appendChild(iframe);
    }
}
/**************************************************************************
 * Контрольные суммы элементов
 */
function fletcher(str, allBytes) {
    var getCode = typeof str == 'string' ?
                  function(n) {
                      return str.charCodeAt(n);
                  } :
                  function(n) {
                      return str[n];
                  };

    var len = str.length, i = 0, sum1 = 0xff, sum2 = 0xff;
    while (len) {
        var tlen = len > 21 ? 21 : len;
        len -= tlen;
        do {
            var ch = getCode(i++);
            if (ch > 32 || allBytes) {  // Учитываем только non-whitespaсe символы
                // Готовим 2 байта из одного char
                if (ch > 255) {
                    var ch2 = ch >> 8;
                    ch &= 0xFF;
                    ch ^= ch2;
                }
                sum1 += ch;
                sum2 += sum1;
            }
        } while (--tlen);
        sum1 = (sum1 & 0xff) + (sum1 >> 8);
        sum2 = (sum2 & 0xff) + (sum2 >> 8);
    }
    var result = (((sum1 & 0xff) + (sum1 >> 8)) << 8) | ((sum2 & 0xff) + (sum2 >> 8));
    return result == 0xffff ? 0 : result;
}

function calculateAttrSumm(node) {
    var attrNames = ["onclick", "onsubmit", "onmouseover", "onmouseout", "src", "href", "class", "width", "height", "align", "title", "alt", "name"];
    var result = '', ieAttrs;
    if (node.outerHTML && node.ownerDocument.parentWindow && !node.ownerDocument.parentWindow.opera) { // В IE проблемы с атрибутами, поэтому парсим outerHTML.
        // Эта регулярочка выбирает код тега с учетом того, что в значениях атрибутов также может быть >.
        var tagMatch = node.outerHTML.match(/<((?:('|").*?\2|[^>])+)>/);
        if (!tagMatch[1]) {
            throw new Error('attrsum: Bad regexp for outerHTML. outerHTML=' + node.outerHTML);
        }
        ieAttrs = {};
        //noinspection JSUnusedLocalSymbols
        tagMatch[1].replace(/([a-z]+)=(('|")(.*?)\3|[\S]+)/ig, function(pair, key, value, quote, valueWithoutQuotes) { // Перебираем атрибуты.
            ieAttrs[key] = valueWithoutQuotes || value;
        });
    }
    for (var i = 0; i < attrNames.length; i++) {
        var value = ieAttrs ? ieAttrs[attrNames[i]] : node.getAttribute(attrNames[i]);
        result += (value || '').toLowerCase();
    }
    return fletcher(result);
}

function calculateAttrSumm2(node) {
    var result = '', properties = ['className', 'width', 'height', 'align', 'title', 'alt', 'name'];
    if (node.tagName == 'IMG') {
        result += node.src.toLowerCase();
    }
    if (node.tagName == 'A') {
        result += node.href.toLowerCase();
    }
    for (var i = 0; i < properties.length; i++) {
        result += String(node[properties[i]] || '').toLowerCase();
    }
    return fletcher(result);
}

function calculateTextSumm(node) {
    var text = '';
    for (var i = 0; i < node.childNodes.length; i++) {
        if (node.childNodes[i].nodeType == 3) {
            text += node.childNodes[i].nodeValue;
        }
    }
    return fletcher(text);
}
function calculateChildrenSumm(node) {
    return fletcher((node.innerHTML || '').replace(/<[^>]*>/g, ''));
}
//-------------------------------------------------------------------------

var Region = function (t, l, w, h) {
    this.top = t;
    this.left = l;
    this.width = w;
    this.height = h;
};
//#ifdebug
Region.prototype.toString = function() {
    return ( "Region {"    +
             "top: "       + this.top    +
             ", left: "    + this.left   +
             ", width: "   + this.width  +
             ", height: "  + this.height +
             "}" );
};
//#endif

// http://www.w3.org/TR/html401/index/elements.html
var elCodes = {
    A:1,
    ABBR:2,
    ACRONYM:3,
    ADDRESS:4,
    APPLET:5,
    AREA:6,
    B:7,
    BASE:8,
    BASEFONT:9,
    BDO:10,
    BIG:11,
    BLOCKQUOTE:12,
    BODY:13,
    BR:14,
    BUTTON:15,
    CAPTION:16,
    CENTER:17,
    CITE:18,
    CODE:19,
    COL:20,
    COLGROUP:21,
    DD:22,
    DEL:23,
    DFN:24,
    DIR:25,
    DIV:26,
    DL:27,
    DT:28,
    EM:29,
    FIELDSET:30,
    FONT:31,
    FORM:32,
    FRAME:33,
    FRAMESET:34,
    H1:35,
    H2:36,
    H3:37,
    H4:38,
    H5:39,
    H6:40,
    HEAD:41,
    HR:42,
    HTML:43,
    I:44,
    IFRAME:45,
    IMG:46,
    INPUT:47,
    INS:48,
    ISINDEX:49,
    KBD:50,
    LABEL:51,
    LEGEND:52,
    LI:53,
    LINK:54,
    MAP:55,
    MENU:56,
    META:57,
    NOFRAMES:58,
    NOSCRIPT:59,
    OBJECT:60,
    OL:61,
    OPTGROUP:62,
    OPTION:63,
    P:64,
    PARAM:65,
    PRE:66,
    Q:67,
    S:68,
    SAMP:69,
    SCRIPT:70,
    SELECT:71,
    SMALL:72,
    SPAN:73,
    STRIKE:74,
    STRONG:75,
    STYLE:76,
    SUB:77,
    SUP:78,
    TABLE:79,
    TBODY:80,
    TD:81,
    TEXTAREA:82,
    TFOOT:83,
    TH:84,
    THEAD:85,
    TITLE:86,
    TR:87,
    TT:88,
    U:89,
    UL:90,
    VAR:91,
    NOINDEX:100

};

var F_CHILDREN_CHECKSUMM = 1,
    F_NAME               = 1 << 1,
    F_NO_NEIGH           = 1 << 2,
    F_PARENT_REGION      = 1 << 3,
    F_CONTENT_CHECKSUMM  = 1 << 4,
    F_ID                 = 1 << 5,
    F_EXTENDED_FLAGS     = 1 << 6,
    F_ATTRIB_CHECKSUMM   = 1 << 7,
    EF_ATTRIB_CHECKSUMM_V2 = 1 << 1;

var BadElementError = {};

function getNormalizedChildren(element, nodeName) {
    var result = [];
    if (element) {
        var children = element.childNodes;
        var clen = children.length;
        for (var i = 0; i < clen; i++) {
            var el = children[i];
            if (el.nodeType == 1) {
                if (el.nodeName == "NOINDEX") {
                    // Содержимое NOINDEX разворачивается в родителя
                    result = result.concat(getNormalizedChildren(el, nodeName));
                } else if (el.nodeName == "INPUT" && el.type && el.type.toLowerCase() == "hidden") {
                    // Hidden элементы форм пропускаются
                    continue;
                }
                if (!nodeName || el.nodeName == nodeName) { // Или nodeName не задан или имя узла совпало с nodeName
                    result.push(el);
                }
            }
        }
    }
    return result;
}

function regionByNode(node) {
    var xy = getXY(node);
    if (xy) {
        return regionByXY(node, xy);
    } else {
        return undefined;
    }
}



//Port from YUI
// branching at load instead of runtime
var getStyle;
if (targetDocument.defaultView && targetDocument.defaultView.getComputedStyle) { // W3C DOM method
    getStyle = function(el, property) {
        var value = null;
        var computed = el.ownerDocument.defaultView.getComputedStyle(el, '');
        if (computed) { // test computed before touching for safari
            value = computed[property];
        }

        return el.style && el.style[property] || value;
    };
} else if (targetDocument.documentElement.currentStyle && userAgent.ie) { // IE method
    getStyle = function(el, property) {
        // test currentStyle before touching
        var value = el.currentStyle ? el.currentStyle[property] : null;
        return ( el.style[property] || value );
    };
} else { // default to inline only
    getStyle = function(el, property) { return el.style[property]; };
}



function getNormalizedParent(element) {
    var result = getParentNode(element);
    while (result && result.nodeType == 1 && result.nodeName == "NOINDEX") {
        result = getParentNode(result);
    }
    return result;
}


function getParentNode(e) {
    return e == targetDocument.body ? targetDocument.documentElement : e.parentNode;
}
//-------------------------------------------------------------------------

var lastRegion;
function getElementPacket(e, includeOldAttrs) {
    if (!e.nodeName) {
        // В ИЕ бывает попадаются ноды без имени из-за наличия в коде всяких загадочных символов
        // Замечено, в частности, на http://multvideo.ru/, http://www.nomos.ru/personal/deposits/fixed-deposit/nomos-renta/
        // Такие ноды не пишем, хотя они даже ширину-высоту имеют
        throw BadElementError;
    }
    // Теперь у нас всегда будет два байта флагов
    var featureMask = F_EXTENDED_FLAGS, featureMask2 = 0;
    // Название узла
    var name = e.nodeName.toUpperCase();
    var nameIdx = elCodes[name];
    if (!nameIdx) {
        featureMask |= F_NAME;
    }
    // Позиция узла среди соседей
    var np = calcNeighborPosition(e);
    if (np == 0) {
        featureMask |= F_NO_NEIGH;
    }

    // Регион
    var xy = getXY(e);
    var region = null;
    if (xy) {
        region = regionByXY(e, xy);
        if (!isValidRegion(region)) {
            throw BadElementError;
        }
        if (lastRegion) {
            // Смотрим, не совпадает ли с родительским регионом
            if (region.left == lastRegion.left && region.width == lastRegion.width && region.top == lastRegion.top && region.height == lastRegion.height) {
                featureMask |= F_PARENT_REGION;
            }
        }
        if (!loaded) {
            guessedNodes.push(e);
            e["vvr"] = region;
        }
    }
    if (e == targetDocument.body || e == targetDocument.documentElement || e == targetDocument) {
        region = new Region(0, 0, getDocumentWidth(), getDocumentHeight());
    }
    lastRegion = region;

    // собственный ID
    if (e.id && typeof e.id == "string") {
        featureMask |= F_ID;
    }
    // Атрибуты и текстовый контент
    var textFletcher = calculateTextSumm(e);
    if (textFletcher) {
        featureMask |= F_CONTENT_CHECKSUMM;
    }
    if (includeOldAttrs) {
        var attrFletcher = calculateAttrSumm(e);
        if (attrFletcher) {
            featureMask |= F_ATTRIB_CHECKSUMM;
        }
    }

    var attrFletcher2 = calculateAttrSumm2(e);
    if (attrFletcher2) {
        featureMask2 |= EF_ATTRIB_CHECKSUMM_V2; 
    }

    var childFletcher = 0;
    if (!(featureMask & F_ID) && !textFletcher) {
        var writingChildrenChecksumm = false;
        for (var j = 0, sibling = e.nextSibling; j < 3 && sibling; j++, sibling = sibling.nextSibling) {
            if (sibling.nodeType != 1) {
                j--;
                continue;
            }
            var hasId = sibling.id && typeof sibling.id == 'string';
            if (!hasId && !calculateTextSumm(sibling) && calculateAttrSumm(sibling) == attrFletcher) {
                writingChildrenChecksumm = true;
                break;
            }
        }
        if (writingChildrenChecksumm) {
            featureMask |= F_CHILDREN_CHECKSUMM;
            childFletcher = calculateChildrenSumm(e);
        }
    }


    // Начинаем запись
    var elNumber = e['vvid'];
    var packet = new PacketWriter(P_ELEMENT, true);
    packet.addScaled(elNumber); // Уникальный номер
    packet.addByte(featureMask);
    // Родитель
    // Если узел это HTML, родителем записывается 0
    var pid = e == targetDocument.documentElement ? 0 : getNormalizedParent(e)['vvid'];
    packet.addScaled(pid);

    // Название
    if (nameIdx) {
        packet.addByte(nameIdx);
    } else {
        packet.addString(name);
    }
    // Позиция
    if (np) {
        packet.addScaled(np);
    }
    // Регион
    if (!(featureMask & F_PARENT_REGION)) {
        if (!region) { // Пустой регион - узел без координат
            region = new Region(0, 0, 0, 0);
        }
        packet.writeRegion(region);
    }

    // ID
    if (e.id && typeof e.id == 'string') { // Мало ли какой ID сидит в объекте... например поле формы с именем "id"
        packet.addString(e.id);
    }
    if (attrFletcher) {
        packet.addWord(attrFletcher);
    }
    if (textFletcher) {
        packet.addWord(textFletcher);
    }
    if (featureMask & F_CHILDREN_CHECKSUMM) {
        packet.addWord(childFletcher);
    }
    packet.addByte(featureMask2);
    if (attrFletcher2) {
        packet.addWord(attrFletcher2);
    }
    return packet;
}

// Находит позицию узла среди соседей
// Позиция начинается с 1!
function calcNeighborPosition(p) {
    var sz = 0;
    var nh = getNormalizedChildren(p.parentNode, p.nodeName);
    for (var i = 0; i < nh.length; i++ ) {
        var c = nh[i];
        //if (c.nodeType == p.nodeType && c.nodeName == p.nodeName) {
            if (c == p) {
                break;
            } else {
                sz++;
            }
        //}
    }
    return sz;
}

var getXYInternal = targetDocument.documentElement.getBoundingClientRect ?

    // В IE5+ и FF3 есть метод getBoundingClientRect
    function(el) {
        if (userAgent.ie && el.tagName == 'PARAM') {
            // В ИЕ при обращении к offsetParent у param возникает ошибка (иногда?)
            // По большому счету offset для param нафиг не нужен.
            return [0, 0];
        }
        var box = el.getBoundingClientRect();
        return [box.left + getDocumentScrollLeft(), box.top +
                getDocumentScrollTop()];
    } :

    // Для остальных вычисляем ручками
    function(el) {
        var pos = [el.offsetLeft, el.offsetTop];
        var parentNode = el.offsetParent;

        // safari: subtract body offsets if el is abs (or any offsetParent), unless body is offsetParent
        var accountForBody = (userAgent.webkit && getStyle(el, 'position') == 'absolute' && el.offsetParent == el.ownerDocument.body);

        if (parentNode != el) {
            while (parentNode) {
                pos[0] += parentNode.offsetLeft;
                pos[1] += parentNode.offsetTop;
                if (!accountForBody && userAgent.webkit && getStyle(parentNode,'position') == 'absolute' ) {
                    accountForBody = true;
                }
                parentNode = parentNode.offsetParent;
            }
        }

        if (accountForBody) { //safari doubles in this case
            pos[0] -= el.ownerDocument.body.offsetLeft;
            pos[1] -= el.ownerDocument.body.offsetTop;
        }
        //noinspection ReuseOfLocalVariableJS
        parentNode = el.parentNode;

        // account for any scrolled ancestors
        while ( parentNode.tagName && !/^body|html$/i.test(parentNode.tagName) )   {
            if (parentNode.scrollTop || parentNode.scrollLeft) {
                // work around opera inline/table scrollLeft/Top bug (false reports offset as scroll)
                if (getStyle(parentNode, 'display').search(/^inline|table-row.*$/i)) {
                    if (!userAgent.opera || getStyle(parentNode, 'overflow') !== 'visible') { // opera inline-block misreports when visible
                        pos[0] -= parentNode.scrollLeft;
                        pos[1] -= parentNode.scrollTop;
                    }
                }
            }
            parentNode = parentNode.parentNode;
        }

        var body = el.ownerDocument.body;
        if (getStyle(body, 'position') != 'static') {
            pos[0] += parseInt(getStyle(body, 'margin-left') || 0);
            pos[1] += parseInt(getStyle(body, 'margin-top') || 0);
        }
        return pos;
    };

function getXY(el) {
    if (!el.ownerDocument) {
        return false;
    }
    // Приводим позицию body и html к стандартному для всех браузеров виду, когда они
    // начинаются в точке (0,0)
    if (el == targetDocument.body || el == targetDocument.documentElement) {
        return [0, 0];
    }
    if ( (el.parentNode === null || (userAgent.ie && el.tagName != 'PARAM' && el.offsetParent === null) ||
                getStyle(el, 'display') == 'none') && el != el.ownerDocument.body) {
        return false;
    }
    return getXYInternal(el);
}

function regionByXY(node, xy) {
    //return new Region(top, left + node.offsetWidth, top + node.offsetHeight,  left);
    //noinspection IfStatementWithIdenticalBranchesJS
    var width, height, top, left;
    top = xy[1];  //y
    left = xy[0]; //x
    if (node == targetDocument.body || node == targetDocument.documentElement) {
        width = getDocumentWidth(); // патаму чта нефиг тут!
        height = getDocumentHeight(); // а то ваще браузеры распоясались!
    } else {
        width = node.offsetWidth;
        height = node.offsetHeight;
    }
    return new Region(top, left, width, height);
}

function isValidRegion(region) {
    var p = ['top', 'left', 'width', 'height'];
    for (var i = 0; i < p.length; i++) {
        if (typeof region[p[i]] != 'number' || isNaN(region[p[i]]))
            return false;
    }
    return true;
}
var A_UNDEFINED         = 1,
    A_NULL              = 2,
    A_NAN               = 3,
    A_NEGATIVE_INFINITY = 4,
    A_POSITIVE_INFINITY = 5,
    A_ZERO              = 6,
    A_TRUE              = 7,
    A_FALSE             = 8,
    A_STRING            = 9,
    A_POSITIVE_NUMBER   = 10,
    A_NEGATIVE_NUMBER   = 11,
    A_ARRAY             = 12,
    A_DATE              = 13,
    A_REG_EXP           = 14,
    A_DOM_ELEMENT       = 15,
    A_OBJECT            = 16;

function encodeObject(obj) {
    if (typeof obj == 'undefined') {
        return [A_UNDEFINED];
    }
    if (obj === null) {
        return [A_NULL];
    }
    if (typeof obj == 'boolean') {
        return [obj ? A_TRUE : A_FALSE];
    }
    if (typeof obj == 'number') {
        return encodeNumber(obj);
    }
    if (typeof obj == 'string') {
        return encodeString(obj);
    }
    if (isArray(obj)) {
        return encodeArray(obj);
    }
    if (isDate(obj)) {
        return encodeDate(obj);
    }
    if (isRegExp(obj)) {
        return encodeRegExp(obj);
    }
    if (isDOMElement(obj)) {
        return encodeDOMElement(obj);
    }
    if (obj.toString() == '[object Object]') {
        var data = [];
        for (var i in obj) {
            data.push(i);
            data.push(obj[i]);
        }
        var res = [A_OBJECT].concat(encodeScaled(data.length / 2));
        for (i = 0; i < data.length; i++) {
            var code = encodeObject(data[i]);
            if (!code) {
                return null;
            }
            res = res.concat(code);
        }
        return res;
    }
    return null;
}

function encodeNumber(num) {
    if (isNaN(num)) {
        return [A_NAN];
    }
    if (num == Number.NEGATIVE_INFINITY) {
        return [A_NEGATIVE_INFINITY];
    }
    if (num == Number.POSITIVE_INFINITY) {
        return [A_POSITIVE_INFINITY];
    }
    if (num === 0) {
        return [A_ZERO];
    }
    var negative = false;
    if (num < 0) {
        num *= -1;
        negative = true;
    }
    var parts = String(num).split('e');
    var power = +(parts[1] || 0) + (parts[0].indexOf('.') == -1 ? parts[0].length : parts[0].indexOf('.'));
    var mantissa = +(parts[0].replace('.', ''));
    return [negative ? A_NEGATIVE_NUMBER : A_POSITIVE_NUMBER].concat(encodeScaled(mantissa)).concat(encodeScaled(power));
}

function encodeString(str) {
    var res = [A_STRING].concat(encodeScaled(str.length));
    for (var i = 0; i < str.length; i++) {
        res = res.concat(encodeScaled(str.charCodeAt(i)));
    }
    return res;
}

function encodeArray(arr) {
    var res = [A_ARRAY].concat(encodeScaled(arr.length));
    for (var i = 0; i < arr.length; i++) {
        var code = encodeObject(arr[i]);
        if (!code) {
            return null;
        }
        res = res.concat(code);
    }
    return res;
}

function encodeDate(date) {
    return [A_DATE]
            .concat(encodeScaled(date.getFullYear()))
            .concat(encodeScaled(date.getMonth()))
            .concat(encodeScaled(date.getDate()))
            .concat(encodeScaled(date.getHours()))
            .concat(encodeScaled(date.getMinutes()))
            .concat(encodeScaled(date.getSeconds()))
            .concat(encodeScaled(date.getMilliseconds()));
}

function encodeRegExp(regexp) {
    var mask = 0;
    mask |= regexp.global     ? 1 : 0;
    mask |= regexp.ignoreCase ? 2 : 0;
    mask |= regexp.multiline  ? 4 : 0;
    mask |= regexp.lastIndex  ? 8 : 0;
    var source = encodeString(regexp.source);
    source.shift();
    return [A_REG_EXP, mask].concat(source).concat(regexp.lastIndex ? encodeScaled(regexp.lastIndex) : []);
}

function encodeDOMElement(el) {
    var elId = getElemenId(el);
    return [A_DOM_ELEMENT].concat(encodeScaled(elId));
}

function encodeScaled(num) {
    var res = [];
    while (num > 127) {
        res.push((num & 127) | 128);
        num >>= 7;
    }
    res.push(num);
    return res;
}var eventsBuffer = [];
var MAX_EVENT_WRITE_TIME = 100;
var writeAsyncTimeoutId;
var WRITE_ASYNC_INTERVAL = 10;

function writeSync(all) {
    clearTimeout(writeAsyncTimeoutId);
    writeAsyncTimeoutId = null;
    var now = new Date();
    while (eventsBuffer.length && (new Date() - now < MAX_EVENT_WRITE_TIME || all)) {
        eventsBuffer[0][0].apply(this, eventsBuffer[0][1]);
        eventsBuffer.shift();
    }
    writeAsync();
}

function writeAsync() {
    if (eventsBuffer.length && !writeAsyncTimeoutId) {
        writeAsyncTimeoutId = setTimer(writeSync, WRITE_ASYNC_INTERVAL);
    }
}

function writeEvent(func, args, sync) {
    eventsBuffer.push([func, args]);
    sync ? writeSync(true) : writeAsync();
}

function findMapTargetByTag(map, tagName) {
    var mapName = map.name.toLowerCase();
    var mapName2  = "#" + mapName;
    // Ищем IMG/INPUT/OBJECT, в котором прописан соотвествцющий useMap
    var images = targetDocument.getElementsByTagName(tagName);
    var mapTarget;
    for (var i = 0; i < images.length; i++) {
        var useMap = images[i].getAttribute("useMap");
        if (useMap) {
            useMap = useMap.toLowerCase();
            if (mapName == useMap || mapName2  == useMap) {
                mapTarget = images[i];
                break;
            }
        }
    }
    return mapTarget;
}

function findMapTarget(map) {
    var mapTarget = findMapTargetByTag(map, "IMG");
    if (!mapTarget) {
        mapTarget = findMapTargetByTag(map, "INPUT");
    }
    if (!mapTarget) {
        mapTarget = findMapTargetByTag(map, "OBJECT");
    }
    return mapTarget;
}


// Порт из YUI.Event. Возвращает координату события
function getPageX (ev) {
    var x = ev.pageX;
    if (!x && 0 !== x) {
        x = ev.clientX || 0;
        if ( userAgent.ie ) {
            x += lastScrollX;
        }
    }
    return x;
}

// Порт из YUI.Event. Возвращает координату события
function getPageY (ev) {
    var y = ev.pageY;
    if (!y && 0 !== y) {
        y = ev.clientY || 0;
        if ( userAgent.ie ) {
            y += lastScrollY;
        }
    }
    return y;
}
//-------------------------------------------------------------------------

/**************************************************************************
 * Работа с куками
 */
function createCookie(name, value, days) {
    var expires = "";
    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        expires = "; expires=" + date.toGMTString();
    }
    //noinspection JSUnusedLocalSymbols
    disabled = true; // На тот случай если браузер при выставлении cookie генерит exception
    targetDocument.cookie = name + "=" + value + expires + "; path=/";
    disabled = false;
}

function readCookie(name) {
    if (typeof targetDocument.cookie == 'string') {
        var nameEQ = name + "=";
        var ca = targetDocument.cookie.split(';');
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') {
                c = c.substring(1, c.length);
            }
            if (c.indexOf(nameEQ) == 0) {
                return c.substring(nameEQ.length, c.length);
            }
        }
    }
    return false;
}
//-------------------------------------------------------------------------

// Обработка запрещена - нет поддерживаются куки
var disabled = false;

var fragment; // Часть URI после #

// Минимальный интервал между записью двух повторяющихся событий (MouseMove, Scroll)
var MIN_EVENT_INTERVAL = 100;

// Префикс фрагмента, который включает режим вычисления и отправки координат DOM-узлов
//     var MAGIC_FRAGMENT_PREFIX = '_vtx';

// Для ограничения количества событий MouseMove в секунду
var lastMoveEventTime = 0;
var lastMouseX = 0;
var lastMouseY = 0;


// Для ограничения количества событий Scroll в секунду
var lastScrollEventTime = 0;
var lastScrollX = 0;
var lastScrollY = 0;
var currentScrollX = getDocumentScrollLeft();
var currentScrollY = getDocumentScrollTop();

// Для присваивания уникальных ID элементам, которые уже записали в поток
var elemIdCount = 0;





// Очередь 1-pix gif, ждущих загрузки с сервера
//var postQueue = [];

// Флаг, показывающий что мы вошди в обработчик выгрузки страницы
// Нужен, потому что регистрируется 2 обработчика, onunload и onbeforeunload и могут сработать оба
var beforeUnloadCalled = false;

// Сколько милисекунд ждать перед выгрузкой страницы, чтобы запостиились все данные?
var UNLOAD_TIMEOUT = 300;

// Готов ли DOM?
var ieDomReady = false;


//    var pageDomTicket; // Тикет, используемый при расчете структкры страницы
//    var pageDomPort; // Порт, куда отправятся данные расчета

/**
 * Размеры окна и документа отправляются в следующие моменты:
 * 1. при отправке запроса с хитом на сервер
 * 2. В момент получения события onDomReady
 * 3. В момент получения события onLoad
 * 4. В момент получения события onResize
 */

// Последние размеры окна - чтобы не постить несколько раз один и тот же размер
var winx = 0, winy = 0;

// Последние размеры документа - чтобы не постить несколько раз один и тот же размер
var docWidth = 0, docHeight = 0;

//var activityMapImg; // Изображение, в котором содержится текущий activity map






var K_ALT = 1;
var K_SHIFT = 2;
var K_CTRL = 4;
var K_META = 8;
var K_SPECIAL = 16;

var/*CONST*/ B_LEFT = 1;
var B_RIGHT = 2;
var B_MIDDLE = 4;


/**
 * Вычисляет и записывает размеры окна в выходной поток. Если размеры совпадают с уже записанными,
 * ничего не происходит.
 */
function postWindowSize() {
    var x = getViewportWidth();
    var y = getViewportHeight();
    if (x != winx || y != winy) {
        //#ifdebug
        console.debug("Posting P_WINDOW_SIZE packet");
        //#endif
        sendWindowSize(x, y);
        winx = x;
        winy = y;
    }
}

/**
 * Вычисляет и записывает размеры докуемента в выходной поток. Если размеры совпадают с уже записанными,
 * ничего не происходит.
 */
 function postDocumentSize() {
     var width = getDocumentWidth();
     var height = getDocumentHeight();
     if (width != docWidth || height != docHeight) {
         //#ifdebug
         console.debug("Posting P_DOCUMENT_SIZE packet");
         //#endif
         sendDocumentSize(width, height);
         docWidth = width;
         docHeight = height;
     }
 }

/**
 * Вычисляет и записывает размеры документа и окна и позицию скроллинга в выходной поток
 */
function postDimensions() {
    postDocumentSize();
    postWindowSize();
    // Скролиинг
    currentScrollX = getDocumentScrollLeft();
    currentScrollY = getDocumentScrollTop();
    if (currentScrollX != lastScrollX || currentScrollY != lastScrollY) {
        writeEvent(writeScrollEvent, [nowEventTime(), currentScrollX, currentScrollY])
    }
}


function fillVisitProperties(props) {
    /*
    var n = window.navigator;
    props['platform'] = n.platform;
    props['oscpu'] = n.oscpu;
    props['cpu'] = n.cpuClass;
    props['lang'] = n.language;
    props['java'] = n.javaEnabled();
    props['vendor'] = n.vendor;
    props['opera'] = window.opera ? "true" : null;
    props['av'] = n.appVersion;
    props['ua'] = n.userAgent;
    */
    var s = window.screen;
    //props['aheight'] = s.availHeight;
    //props['awidth'] = s.availWidth;
    props['height'] = s.height;
    props['width'] = s.width;
    //props['colors'] = s.colorDepth;
    //var d = new Date();
    //props['tz'] = d.getTimezoneOffset();
    //props['smooth'] = s.fontSmoothingEnabled;
    //fillWindowSize(props);
}

//noinspection LocalVariableNamingConventionJS
var VISIT_COOKIE = "__viss";
//noinspection LocalVariableNamingConventionJS
var USER_COOKIE = "__viser";
var NEW_USER_COOKIE = '__visu';
var TMP_USER_COOKIE = "__vitu";

function readSessValue() {
    var value = readCookie(VISIT_COOKIE);
    if (!value) {
        createCookie(VISIT_COOKIE, "1");
        if (!readCookie(VISIT_COOKIE)) {
            // Отключены куки, выходим нах
            disabled = true;
            return 0;
        }
        return 1;
    }
    return parseInt(value);
}

function writeUnload(time) {
    sendEOF(time);
    // Подержим контекст
    var limit = nowTime() + UNLOAD_TIMEOUT;
    while (nowTime() < limit) {
        // Что тут cделать полезного?
        [1, 2, 3].join("");
    }
}

function onUnload() {
    if (!beforeUnloadCalled) {
        beforeUnloadCalled = true;
        writeEvent(writeUnload, [nowEventTime()], true);
    }
}


function writeElements(newElements) {
    lastRegion = null;
    for (var i = newElements.length - 1; i >= 0 ; i--) {
        sendPacket(getElementPacket(newElements[i]));
    }
}

 // Возвращает сгенерированный ID HTML узла, записывая узел в поток, если он еще не описан
function getElemenId(el) {
    var id = el['vvid'];
    if (!id) {
        // Смотрим, присвоены ли vvid элементам выше этого
        var newElements = []; // Элементы, которые новые
        var p = el;
         // Поднимаемся вверх до узла, который еще не записан или до body(включительно).
        //var topNode = targetDocument.documentElement;
        while (!p['vvid']) {
            p['vvid'] = ++elemIdCount;
            if (!p['vvid']) {
                throw new Error('Not write vvid ' + p.nodeName);
            }
            if (p.nodeName != "NOINDEX") {
                newElements.push(p);
            }
            if (p == targetDocument.documentElement) {
                break;
            }
            p = getParentNode(p);

        }
        if (/(^|\s)-webvisor-random-area(\s|$)/.test(p.className)) {
            el = p;
        } else {
            writeElements(newElements);
        }
        id = el['vvid'];
    }
    return id;
}

function writeSelectedText(time, text) {
    sendSelectedText(time, text);
}

var currentSelectedText = '';
function checkSelectedText() {
    var text, target;
    if (isNativeFunction('getSelection')) {
        var sel = targetWindow.getSelection();
        text = sel.toString();
        target = sel.anchorNode;
    } else if (targetDocument.selection && targetDocument.selection.createRange) {
        var range = targetDocument.selection.createRange();
        text = range.text;
        target = range.parentElement();
    }
    while (target && target.nodeType != 1) {
        target = target.parentNode;
    }
    if (target && ((target.tagName == 'INPUT' && target.type == 'password') || classExists(target, '-webvisor-nokeys'))) {
        text = text.replace(/./g, '*'); 
    }
    if (text != currentSelectedText) {
        writeEvent(writeSelectedText, [nowEventTime(), text]);
        currentSelectedText = text;
    }
}

function writeCopy(time) {
    sendCopy(time);
}

var copyWriting = true;
function onCopy() {
    if (copyWriting) {
        copyWriting = false;
        if (currentSelectedText) {
            writeEvent(writeCopy, [nowEventTime()]);
        }
        setTimer(function() { copyWriting = true; }, 50);
    }
}

function onMouseUp(evt) {
    onMouseEvent(evt);
    checkSelectedText();
}

var __lastTime = 0;
function writeMouseEvent(time, evtType, targ, pageX, pageY, evtWhich, evtButton) {
    if (__lastTime > time) {
        console.log('fail');
    }
    __lastTime = time;

    var eventCode = {mousedown: P_MOUSE_DOWN, mouseup: P_MOUSE_UP, mousemove: P_MOUSE_MOVE, click: P_CLICK}[evtType];
    if (!eventCode) {
        return null;
    }
    if (!targ || targ.nodeName == 'SCROLLBAR') {
        return null;
    }
    var xy = getXY(targ);
    if ((evtType == 'mousedown' || evtType == 'mouseup') &&
        (pageX > getDocumentScrollLeft() + getViewportWidth() || pageY > getDocumentScrollTop() + getViewportHeight())) {
        // Событие произошло за пределами документа, т.е. скорее всего на скролбаре,
        // клики на скроллбаре нам неинтересны, поэтому не пишем их.
        return null;
    }
    lastMouseX = pageX;
    lastMouseY = pageY;
    if (xy) {
        if (!(typeof(pageX) == 'number' && typeof(pageY) == 'number')) {
            return null;
        }
        if (!(typeof(xy[0]) == 'number' && typeof(xy[1]) == 'number')) {
            return null;
        }
        var xOffset = pageX - xy[0];
        var yOffset = pageY - xy[1];
        if (targ.nodeName == "OPTION" || (xOffset < 0 || yOffset < 0 && targ.nodeName == "SELECT")) {
            return null;
        }
        // see http://www.evolt.org/article/document_body_doctype_switching_and_more/17/30655/
        try {
            var elId = getElemenId(targ); // Обязательно ПЕРЕД записью события, чтобы запись узлов не примешалась
        } catch (e) {
            if (e == BadElementError) {
                return null;
            }
            throw e;
        }
        var packet = new PacketWriter(eventCode, true);
        packet.addScaled(time);

        packet.addScaled(elId);

        if (xOffset < 0) {
            xOffset = 0;
        }
        if (yOffset < 0) {
            yOffset = 0;
        }
        packet.addScaled(xOffset);
        packet.addScaled(yOffset);
        if (eventCode == P_MOUSE_DOWN || eventCode == P_MOUSE_UP || eventCode == P_CLICK) {
            var button;
            if (evtWhich == null) {
               /* IE case */
               button = (evtButton < 2) ? B_LEFT :
                        ((evtButton == 4) ? B_MIDDLE : B_RIGHT);
            } else {
               /* All others */
               button = (evtWhich < 2) ? B_LEFT :
                        ((evtWhich == 2) ? B_MIDDLE : B_RIGHT);
            }
            packet.addByte(button);
        }
        sendPacket(packet);
    } else {
        // В Strict mode это скорее всего <html>. Если это <html>, юзер ездит мышом по самому краю
        // страницы или по скролбару. Записывать это нет особого смысла.
        // В Quirks mode это вообще непонятно что.
        return null;
    }
}

/**
 * Обрабатывает событие мыши с кодом eventCode. Возвращает пакет с событием, или null, если событине на надо записывать
 * @param evt Само событие
 */
function onMouseEvent(evt) {
    evt = evt || window.event;
    writeEvent(writeMouseEvent, [nowEventTime(), evt.type, extractTarget(evt), getPageX(evt), getPageY(evt), evt.which, evt.button]);
}

function onMouseMove(e) {
    var now = nowTime();
    var elapsed = now - lastMoveEventTime;
    if (elapsed < 10) {
        return; // Нет смысла записывать c разрешением меньше 10 милисекунд
    }
    e = e || window.event;
    var xdelta = lastMouseX - getPageX(e);
    var ydelta = lastMouseY - getPageY(e);
    var distance = Math.sqrt(xdelta * xdelta + ydelta * ydelta);
    if (distance < 4 && elapsed < 100) {
        return;  // Не записываем совсем маленькие перемещения
    }

    if (elapsed < 20 && distance < 16) {
        return; // Не записываем маленькие перемещения с частотой больше 1/50 секунды
    }
    lastMoveEventTime = now;
    onMouseEvent(e);
};

function writeKeyEvent(time, targ, charCode, mask) {
    var elId = 0;
    if (targ) {
        if ((targ.type && targ.type == 'password') || targ.className.match(/(^|\s)-webvisor-nokeys(\s|$)/)) {
            return;
        }
        elId = getElemenId(targ);
    }
    sendKeyDown(time, charCode, mask, elId);
}

function onKeyEvent(e, charCode, mask) {
    writeEvent(writeKeyEvent, [nowEventTime(), extractTarget(e), charCode, mask]);
}

function isSpecialKey(code) {
    var map = {3:1,8:1,9:1,13:1,16:1,17:1,18:1,19:1,20:1,27:1,33:1,34:1,35:1,36:1,37:1,38:1,39:1,40:1,45:1,46:1,91:1,92:1,93:1,106:1,110:1,111:1,144:1,145:1};
    return map[code] || (code >= 112 && code <= 123) || (code >= 96 && code <= 105);
}

function getKeyMask(e) {
    return (e.shiftKey ? K_SHIFT : 0)
         | (e.ctrlKey ? K_CTRL : 0)
         | (e.altKey ? K_ALT : 0)
         | (e.metaKey ? K_META : 0)
         | (e.ctrlKey || e.altKey ? K_SPECIAL : 0);
}

function onKeyDown(e) {
    e = e || window.event;
    var code = e.keyCode, mask = getKeyMask(e);
    if (isSpecialKey(code) || mask & K_SPECIAL) {        // В keydown обрабатываем только спец. клавиши
        if (code == 19 && (mask & ~K_SPECIAL) == K_CTRL) { // NumLock при нажатом Ctrl превращается в Break
            code = 144;
        }
        onKeyEvent(e, code, mask | K_SPECIAL);
        // keypress не обрабатываем
        onKeyPress.canceled = true;
        setTimeout(function() { onKeyPress.canceled = false; }, 1);

        if (code == 67 && mask & K_CTRL && !(mask & K_ALT) && !(mask & K_SHIFT)) {
            // Нажали CTRL+C
            onCopy();
        }
    }
}

// See also http://unixpapa.com/js/key.html
function onKeyPress(e) {
    if (onKeyPress.canceled || onKeyPress.processing)
        return;
    e = e || window.event;
    onKeyEvent(e, e.charCode || e.keyCode, getKeyMask(e));
    // Некоторые хитрожопые браузеры, типа оперы, посылают keypress несколько раз
    onKeyPress.processing = true;
    setTimeout(function() { onKeyPress.processing = false; }, 1);
}

function writeScrollEvent(time, x, y) {
    sendScroll(time, x, y);
}

function onScroll() {
    var now = nowTime();
    var elapsed = now - lastScrollEventTime;
    currentScrollX = getDocumentScrollLeft();
    currentScrollY = getDocumentScrollTop();
    var deltaX = Math.abs(currentScrollX - lastScrollX);
    var deltaY = Math.abs(currentScrollY - lastScrollY);
    if (elapsed < MIN_EVENT_INTERVAL && deltaX < 12 && deltaY < 12) {
        return;
    }
    lastScrollEventTime = now;
    lastScrollX = currentScrollX;
    lastScrollY = currentScrollY;
    writeEvent(writeScrollEvent, [nowEventTime(), currentScrollX, currentScrollY]);
}

function postNohit(options) {
    options = options || {};
    var params = [];
    for (var i in options) {
        if (options.hasOwnProperty(i)) {
            params[params.length] = i + '=' + encodeURIComponent(options[i]);
        }
    }
    params[params.length] = 'pv=' + PROTO_VERSION;
    params[params.length] = 'time=' + (nowTime() - pageLoadTime);
    new Image().src = SERVER_ADDRESS + 'nohit?' + params.join('&');
}

/**
 * Отправляет на сервер JS запрос и получает в ответ идентификатор юзера
 */
function postJs() {
    var scriptNode = targetDocument.createElement("script");
    if (scriptNode) { // В PlayStation Portable походу нельзя создавать новые элементы
        var callbackName = 'hitResponse_' + Math.round(Math.random() * 1e5);
        Visor[callbackName] = wrap(function(response) {
            if (response['gvihash'] && response['gvihash'] != 'null') {
                createCookie(NEW_USER_COOKIE, response['gvihash'], 365 * 5);
                userId = response['gvihash'];
            }
            //#ifdebug
            console.log("Got js-response from server", response['vihash'], response['gvihash']);
            //#endif
            scriptNode.parentNode.removeChild(scriptNode);
            if (response.uniqPage) {
                if (targetDocument.readyState) {
                    (function() {
                        if (targetDocument.readyState == 'complete') {
                            Visor.sendPageContent(response.uniqPage == 2);
                        } else {
                            setTimer(arguments.callee, 100);
                        }
                    })();
                } else {
                    Visor.sendPageContent(response.uniqPage == 2);
                }
            }
        });

        var cookieNumber = readSessValue();
        if (disabled) {
            postNohit({message: 'Cookies disabled', siteId: siteId});
            return;
        }

        var props = {};
        // Пользователь из локальной куки
        userId = readCookie(NEW_USER_COOKIE);
        if (userId) {
            props['un'] = userId;
        } else {
            userId = readCookie(USER_COOKIE);
            if (userId) {
                props['user'] = userId;
            } else {
                // Генерируем временный идентификатор юзера и кладем его в сессию
                tmpUserId = readCookie(TMP_USER_COOKIE);
                if (tmpUserId) {
                    // Выставляем флаг Old Tmp User Id, говорящий о том, что был реюзан tmpUserId от предыдущего хита
                    // и на сервере можно найти и продолжить сессию с этим tmpUserId
                    props['ot'] = 1;
                } else {
                    tmpUserId = generateRandom();
                    createCookie(TMP_USER_COOKIE, tmpUserId);
                }
                props['tu'] = tmpUserId;
            }
        }
        props['gc'] = 1;
        props['c'] = clientHash;
        props['pv'] = PROTO_VERSION;
        props['si'] = siteId;
        props.cf = callbackName;
        try { // ИЕ не всегда дает взять location
            props['url'] = window.location.href;
        } catch (e) {
            props['url'] = '';
        }
        props['rf'] = targetDocument.referrer;
        fillVisitProperties(props);
        props['cp'] = String(targetDocument.title).substr(0, 128);
        props['fr'] = fragment;
        props['cn'] = cookieNumber;
        props['tm'] = new Date().getTime();
        if (!window.name) {
            window.name = generateRandom();
        }
        props.wn = window.name;
        props.hl = String(history.length);
        try {
            props.on = window.opener.name;
        } catch (e) {
        }
        //var file = sessValue ? 'h' : "v";
        var address = SERVER_ADDRESS + "h.j?&tk=" +ticket;
        for (var p in props) {
            var value = props[p];
            if (value) {
                address = address + "&" + p + "=" + encodeURIComponent(value);
            }
        }

        scriptNode.type = 'text/javascript';
        scriptNode.src = address;
        var appendTo;
        // Ищем head
        var hels = targetDocument.getElementsByTagName("head");
        if (hels.length > 0) {
            appendTo = hels.item(0);
        } else { //noinspection XHTMLIncompatabilitiesJS
            if (targetDocument.body) { // Не нашли head, ищем body
                //noinspection XHTMLIncompatabilitiesJS
                appendTo = targetDocument.body;
            } else { // Не нашли ни того ни другог, ставим куда попало
                appendTo = targetDocument.documentElement;
            }
        }
        // Спасаемся от краха в ИЕ
        if (appendTo.childNodes.length)
            appendTo.insertBefore(scriptNode, appendTo.childNodes[0]);
        else
            appendTo.appendChild(scriptNode);
    }
}

function writeResize(time, viewport, docsize) {
    sendResize(time, viewport, docsize);
}

function onResize() {
    writeEvent(writeResize, [nowEventTime(), [getViewportWidth(), getViewportHeight()], [getDocumentWidth(), getDocumentHeight()]]);
}

/**
 * Вызовется в момент возникновения события DomReady
 */
function onDomReady() {
    //#ifdebug
    console.log('dom ready');
    //#endif
    markWebvisorLink(siteId);
    ieDomReady = true;
    postDimensions();
    setFieldsHandlers();
}

/**
 * Взводит обработчик для события onDomReady. Логика взята из YUI.Event
 */
function attachOnDomReadyHandler() {
    if (userAgent.ie) {
        (function() {
            try {
                var n = targetDocument.createElement('p');
                n.doScroll('left');
                n = null;
                onDomReady();
            } catch (e) {
                n = null;
                setTimeout(arguments.callee, 100);
            }
        })();
    } else if (userAgent.webkit && userAgent.webkit < 525) {
        wrap(function() {
            var rs=targetDocument.readyState;
            if (rs == 'loaded' || rs == 'complete') {
                onDomReady();
            } else {
                setTimer(arguments.callee, 100);
            }
        })();
    } else {
       attachEvent(targetDocument, "DOMContentLoaded", onDomReady);
    }
}

function setFieldsHandlers() {
    var tagNames = ['INPUT', 'BUTTON', 'TEXTAREA', 'SELECT'];
    for (var i = 0; i < tagNames.length; i++) {
        var elements = document.getElementsByTagName(tagNames[i]);
        for (var j = 0; j < elements.length; j++) {
            if (!elements[j].__vha) {
                elements[j].__vha = true;
                attachEvent(elements[j], 'focus', onFieldFocus);
                attachEvent(elements[j], 'blur', onFieldBlur);
                attachEvent(elements[j], 'change', onFieldChange);
            }
        }
    }
}

function onLoad() {
    //#ifdebug
//        console.group("Load");
    //#endif
    loaded = true;
    markWebvisorLink(siteId);
    sendLoad(nowEventTime());
    for (var i = 0; i < guessedNodes.length; i++){
        var node = guessedNodes[i];
        var oldRegion = node['vvr'];
        var currentRegion = regionByNode(node);
        if (currentRegion && oldRegion) {
            //noinspection OverlyComplexBooleanExpressionJS
            if (currentRegion.top != oldRegion.top
                    || currentRegion.width != oldRegion.width
                    || currentRegion.left != oldRegion.left
                    || currentRegion.height != oldRegion.height) {
                //#ifdebug
                console.debug("Node region changed", node);
                //#endif
                sendRegion(node['vvid'], currentRegion);
            }
        }
    }
    guessedNodes = [];
    // На всякий случай еще раз вычислим размеры окна и документа, вдруг еще контент подгрузилсо?
    postDimensions();
    //#ifdebug
//        console.groupEnd();
    //#endif

    // Вешаем обработчики на focus/blur/change полей ввода
    setFieldsHandlers();
}




function generateRandom() {
    var result = '';
    for (var i = 0; i < 11; i++) {
        var rnd = Math.floor(Math.random() * 36);
        result += rnd < 10 ? rnd : String.fromCharCode(rnd + 87);
    }
    return result;
}






// Обработка focus/blur окна
var windowFocused = true;

function writeFocus(time) {
    sendWindowFocus(time);
}

function onFocus() {
    if (!windowFocused) {
        windowFocused = true;
        writeEvent(writeFocus, [nowEventTime()]);
    }
}

function writeBlur(time) {
    sendWindowBlur(time);
}

function onBlur() {
    if (windowFocused) {
        windowFocused = false;
        writeEvent(writeBlur, [nowEventTime()], true);
    }
}
function onFocusIn() {
    if (!windowFocused || (window.event && !event.fromElement)) {
        onFocus();
    }
}
function onFocusOut() {
    if (window.event && !event.toElement) {
        onBlur();
    }
}


// Обработка focus/blur полей ввода
function writeFieldFocus(time, target) {
    var id = getElemenId(target);
    sendFieldFocus(time, id);
}

function onFieldFocus(e) {
    writeEvent(writeFieldFocus, [nowEventTime(), extractTarget( e || window.event)]);
}

function writeFieldBlur(time, target){
    var id = getElemenId(target);
    sendFieldBlur(time, id);
}

function onFieldBlur(e) {
    writeEvent(writeFieldBlur, [nowEventTime(), extractTarget(e || window.event)]);
}


// Обработка события change полей ввода
function writeFieldChange(time, target) {
    if (target.type == 'password' || target.className.match(/(^|\s)-webvisor-nokeys(\s|$)/)) {
        //#ifdebug
//            console.info('fieldChange: ', target, 'пароли не пишем');
        //#endif
        return;
    }
    var id = getElemenId(target);
    var value = target.type == 'radio' || target.type == 'checkbox' ? target.checked : target.value;
    value = String(value).substr(0, 100);
    sendFieldChange(time, id, value);
}

function onFieldChange(e) {
    writeEvent(writeFieldChange, [nowEventTime(), extractTarget(e || window.event)]);
}

function writeCallFunction(time, name, args) {
    var argsCode = encodeArray(args);
    if (argsCode) {
        argsCode.shift();
        var nameCode = encodeString(name);
        nameCode.shift();
        sendJsCall(time, nameCode.concat(argsCode));
    }
}

function onCallFunction(name, args) {
    writeEvent(writeCallFunction, [nowEventTime(), name, args]);
}

function setupVisor() {
    ticket = generateRandom();
    postJs();
    //postDimensions();
    attachOnDomReadyHandler();
    attachEvent(window, "load", onLoad);

    attachEvent(targetDocument, 'click', onMouseEvent);
    attachEvent(targetDocument, "mousemove", onMouseMove);
    attachEvent(targetDocument, "mousedown", onMouseEvent);
    attachEvent(targetDocument, "mouseup", onMouseUp);
    attachEvent(window, "scroll", onScroll);
    attachEvent(targetDocument, "keydown", onKeyDown);
    attachEvent(targetDocument, "keypress", onKeyPress);
    attachEvent(targetDocument, 'copy', onCopy);
    if (document.attachEvent && !window.opera) {
        attachEvent(targetDocument, "focusout", onFocusOut);
        attachEvent(targetDocument, "focusin", onFocusIn);
    } else {
        attachEvent(window, "focus", onFocus);
        attachEvent(window, "blur", onBlur);
        attachEvent(targetDocument, "blur", onBlur);
    }
    attachEvent(window, "beforeunload", onUnload);
    attachEvent(window, "unload", onUnload);
    attachEvent(window, "resize", onResize);
}

function markWebvisorLink(siteId) {
    // Делаем задержку, ИЕ не всегда успевает все отрендерить
    setTimeout(wrap(function() {
        var link = targetDocument.getElementById('webvisorlink'), visible = false;
        if (link && !/siteId=\d+/.test(link.href)) {
            link.href += (link.href.indexOf('?') == -1 ? '?' : '&') + 'siteId=' + siteId;
            var button = link.getElementsByTagName('img')[0];
            if (button) {
                visible = true;
                var buttonSize = [button.offsetWidth, button.offsetHeight];
                if (!buttonSize[0] || !buttonSize[1]) {
                    visible = false;
                } else {
                    var xy = getXY(button);
                    if (xy[0] < 0 || xy[1] < 0) {
                        visible = false;
                    }
                }
            }
            sendNonProfitButtonState(visible);
        }
    }), 500);
}

window.__visor = {
    init: function(sid, host) {
        this.cv(null, sid, host);
    },

    cv: wrap(function(ch, sid, host) {
        if (host == 'http://js.b.webvisor.ru/') {
            host = '//beta.web-visor.com/';
        }
        if (window.__disableVisor) {
            return;
        }
        if (!sid) {
            // Не пришел идентификатор сайта, выходим
            postNohit({message: 'Site id undefined'});
            return;
        }
        siteId = sid;
        clientHash = ch;
        if (host) {
            SERVER_ADDRESS = host;
        } else {
            SERVER_ADDRESS = "//c1.web-visor.com/";
        }
        wrap(markWebvisorLink)(sid);
        createTransport();
        initPing();

        var apiCache = [], readyFns = [];
        if (window.Visor) {
            apiCache = window.Visor._getCache();
            readyFns = window.Visor._getReadyFns();
        }
        window.Visor = {
            loaded: true,

            _getCache: function() {
                return [];
            },

            setGoal: wrap(function(goal) {
                if (goal >= 1 && goal <= 4) {
                    sendGoal(goal - 1);
                }
            }),

            addTag: wrap(function(tag) {
                sendTag(String(tag));
            }),

            addAttribute: wrap(function(key, value) {
                sendKeyValue(String(key), String(value));
            }),

            setFetchUrl: wrap(function(url) {
                sendFetchUrl(String(url));
            }),

            setUserName: wrap(function(name) {
                sendUserName(String(name));
            }),

            setUserId: wrap(function(id) {
                sendUserId(String(id));
            }),

            captureFunctions: wrap(function() {
                for (var i = 0; i < arguments.length; i++) {
                    var name = arguments[i];
                    if (typeof window[name] == 'function') {
                        window[name] = function(name, fn) {
                            return function() {
                                onCallFunction(name, arguments);
                                return fn.apply(this, arguments);
                            };
                        }(name, window[name]);
                    }
                }
            }),

            ready: function(fn, scope) {
                fn.call(scope);
            },

            sendPageContent: wrap(function(loadContent) {
                if (loadContent) {
                    var XMLHttpRequest = window.XMLHttpRequest || window.ActiveXObject && function() {
                        return new ActiveXObject('Msxml2.XMLHTTP');
                    };
                    var xhr = new XMLHttpRequest();
                    xhr.open('get', window.location.href, true);
                    (function() {
                        if (xhr.readyState == 4) {
                            if (xhr.status == 200) {
                                sendPageContent(xhr.responseText);
                            }
                        } else {
                            setTimer(arguments.callee, 50);
                        }
                    })();
                    xhr.send(null);
                } else {
                    sendPageContent();
                }
            })
        };

        for (var i = 0; i < apiCache.length; i++) {
            if (typeof Visor[apiCache[i][0]] == 'function') {
                Visor[apiCache[i][0]].apply(Visor, apiCache[i][1]);
            }
        }
        wrap(setupVisor)();
        setFieldsHandlers();
        postDimensions();

        // Выносим вызов обработчиков ready в отдельный поток, чтобы не перехватывались
        // ошибки нашим обработчиком ошибок.
        setTimeout(function() {
            for (var j = 0; j < readyFns.length; j++) {
                readyFns[j][0].call(readyFns[j][1]);
            }
            if (typeof window['onVisorReady'] == 'function') {
                window['onVisorReady']();
            }
        }, 10);
    })
};


window.__visor.cl = window.__visor;

if (typeof window.__visorInit == 'function') {
    setTimeout(window.__visorInit, 10);
}
})();
