var __visor = 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);
//-------------------------------------------------------------------------


    //#ifdebug
if (!window.console) {
    console = {
        log: function() {},
        error: function() {},
        info: function() {},
        debug: function() {}
    };
}
//#endif
var userId; // Идентификатор юзера. Приходит после отправки первичного запроса на сервер или лежит в куке
var tmpUserId = null; //Временный идентификатор запроса, используется до прихода userId от сервера

// Версия протокола передачи данных
var PROTO_VERSION = '$Rev: 5310 $'.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 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;
}


    /**************************************************************************
 * 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('');
}
//-------------------------------------------------------------------------


    /**************************************************************************
 * crc hash
 */
// Тут мы засовываем в строку, чтобы компрессор не конвертировал в десятичные цифры,
// а то антивирусам не нравится
var crcArray = window['eval']('[0x00000000,0x77073096,0xee0e612c,0x990951ba,0x076dc419,0x706af48f,0xe963a535,0x9e6495a3,0x0edb8832,0x79dcb8a4,0xe0d5e91e,0x97d2d988,0x09b64c2b,0x7eb17cbd,0xe7b82d07,0x90bf1d91,0x1db71064,0x6ab020f2,0xf3b97148,0x84be41de,0x1adad47d,0x6ddde4eb,0xf4d4b551,0x83d385c7,0x136c9856,0x646ba8c0,0xfd62f97a,0x8a65c9ec,0x14015c4f,0x63066cd9,0xfa0f3d63,0x8d080df5,0x3b6e20c8,0x4c69105e,0xd56041e4,0xa2677172,0x3c03e4d1,0x4b04d447,0xd20d85fd,0xa50ab56b,0x35b5a8fa,0x42b2986c,0xdbbbc9d6,0xacbcf940,0x32d86ce3,0x45df5c75,0xdcd60dcf,0xabd13d59,0x26d930ac,0x51de003a,0xc8d75180,0xbfd06116,0x21b4f4b5,0x56b3c423,0xcfba9599,0xb8bda50f,0x2802b89e,0x5f058808,0xc60cd9b2,0xb10be924,0x2f6f7c87,0x58684c11,0xc1611dab,0xb6662d3d,0x76dc4190,0x01db7106,0x98d220bc,0xefd5102a,0x71b18589,0x06b6b51f,0x9fbfe4a5,0xe8b8d433,0x7807c9a2,0x0f00f934,0x9609a88e,0xe10e9818,0x7f6a0dbb,0x086d3d2d,0x91646c97,0xe6635c01,0x6b6b51f4,0x1c6c6162,0x856530d8,0xf262004e,0x6c0695ed,0x1b01a57b,0x8208f4c1,0xf50fc457,0x65b0d9c6,0x12b7e950,0x8bbeb8ea,0xfcb9887c,0x62dd1ddf,0x15da2d49,0x8cd37cf3,0xfbd44c65,0x4db26158,0x3ab551ce,0xa3bc0074,0xd4bb30e2,0x4adfa541,0x3dd895d7,0xa4d1c46d,0xd3d6f4fb,0x4369e96a,0x346ed9fc,0xad678846,0xda60b8d0,0x44042d73,0x33031de5,0xaa0a4c5f,0xdd0d7cc9,0x5005713c,0x270241aa,0xbe0b1010,0xc90c2086,0x5768b525,0x206f85b3,0xb966d409,0xce61e49f,0x5edef90e,0x29d9c998,0xb0d09822,0xc7d7a8b4,0x59b33d17,0x2eb40d81,0xb7bd5c3b,0xc0ba6cad,0xedb88320,0x9abfb3b6,0x03b6e20c,0x74b1d29a,0xead54739,0x9dd277af,0x04db2615,0x73dc1683,0xe3630b12,0x94643b84,0x0d6d6a3e,0x7a6a5aa8,0xe40ecf0b,0x9309ff9d,0x0a00ae27,0x7d079eb1,0xf00f9344,0x8708a3d2,0x1e01f268,0x6906c2fe,0xf762575d,0x806567cb,0x196c3671,0x6e6b06e7,0xfed41b76,0x89d32be0,0x10da7a5a,0x67dd4acc,0xf9b9df6f,0x8ebeeff9,0x17b7be43,0x60b08ed5,0xd6d6a3e8,0xa1d1937e,0x38d8c2c4,0x4fdff252,0xd1bb67f1,0xa6bc5767,0x3fb506dd,0x48b2364b,0xd80d2bda,0xaf0a1b4c,0x36034af6,0x41047a60,0xdf60efc3,0xa867df55,0x316e8eef,0x4669be79,0xcb61b38c,0xbc66831a,0x256fd2a0,0x5268e236,0xcc0c7795,0xbb0b4703,0x220216b9,0x5505262f,0xc5ba3bbe,0xb2bd0b28,0x2bb45a92,0x5cb36a04,0xc2d7ffa7,0xb5d0cf31,0x2cd99e8b,0x5bdeae1d,0x9b64c2b0,0xec63f226,0x756aa39c,0x026d930a,0x9c0906a9,0xeb0e363f,0x72076785,0x05005713,0x95bf4a82,0xe2b87a14,0x7bb12bae,0x0cb61b38,0x92d28e9b,0xe5d5be0d,0x7cdcefb7,0x0bdbdf21,0x86d3d2d4,0xf1d4e242,0x68ddb3f8,0x1fda836e,0x81be16cd,0xf6b9265b,0x6fb077e1,0x18b74777,0x88085ae6,0xff0f6a70,0x66063bca,0x11010b5c,0x8f659eff,0xf862ae69,0x616bffd3,0x166ccf45,0xa00ae278,0xd70dd2ee,0x4e048354,0x3903b3c2,0xa7672661,0xd06016f7,0x4969474d,0x3e6e77db,0xaed16a4a,0xd9d65adc,0x40df0b66,0x37d83bf0,0xa9bcae53,0xdebb9ec5,0x47b2cf7f,0x30b5ffe9,0xbdbdf21c,0xcabac28a,0x53b39330,0x24b4a3a6,0xbad03605,0xcdd70693,0x54de5729,0x23d967bf,0xb3667a2e,0xc4614ab8,0x5d681b02,0x2a6f2b94,0xb40bbe37,0xc30c8ea1,0x5a05df1b,0x2d02ef8d]');

function crc32(data) {
    var crc = 0xFFFFFFFF;
    var k;
    for (var i = 0; i < data.length; i++) {
        var d = data[i];
        if ((!d && d !== 0) || d > 255 || d < 0) {
            throw new Error("Invalid value in buffer: " + d);
        }
        k = (crc ^ d) & 0xFF;
        crc = ((crc >> 8) & 0x00FFFFFF ) ^ crcArray[k];
    }
    return ~crc;
};
//-------------------------------------------------------------------------


    /**************************************************************************
 * Определение браузера
 */
//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);
        }
    }
    return targ;
}


    /**************************************************************************
 * Перехват ошибок
 */
var errorSended = false;
var wrap = function(callback) {
    return function() {
        try {
            return callback.apply(window, arguments);
        } catch (e) {
            //#ifdebug
            throw e;
            //#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);
    })();
}
//-------------------------------------------------------------------------


    // Идентификаторы типов событий
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;


// Максимальный интервал между отправкой пакетов. Отправлять пакет после истечения интервала нет смысла, т.к на сервере
// уже закончился визит и пакет не будет принят.
var MAX_PACKET_INTERVAL = 1000 * 60 * 29;
// Номер текущего пакета
var packetSequence = 1;
// Зднсь собираются данные для отправки пакета
var packetBuffer = [];
var postTimerId; // Идентификаторр таймера, который постит события
var lastPacketTime = pageLoadTime; // Время, когда был отправлен последний пакет.
var postInterval = 55000; // Как часто постим события на сервер по таймеру

// Максимальная длина пакета данных
var MAX_PACKET_LEN = 750;

var sendUrl = (function() {
    var send, postLinks = [];

    function sendViaHtmlfile(url) {
        var transferDoc = new ActiveXObject("htmlfile");
        transferDoc.open();
        transferDoc.write('<html><img src="' + SERVER_ADDRESS + url +'"/></html>');
        transferDoc.close();
//        postLinks.push({obj:transferDoc, time: nowTime(), type:'html'});
    }

    function sendViaImg(url) {
        var img = new Image();
        //img.onload = postLoadImg;
        img.src = SERVER_ADDRESS + url;
        //postQueue.push(img);
//        postLinks.push({obj:img, time: nowTime(), type:'img'});
    }

/*
    (function() {
        var now = nowTime();
        while (postLinks.length > 0) {
            //alert('rrr');
            var link = postLinks[0];
            if (now - link.time > 60000) {
                //#ifdebug
                console.log("Cleaning post link %o", link);
                //#endif
                postLinks.shift();
                if (link.type == "img") {
                    var obj = link.obj;
                    if (obj.parentNode) {
                        obj.parentNode.removeChild(obj);
                    }
                }
            } else { // Встретили более поздний элемент, дальше идти нет смысла
                break;
            }
        }
        setTimer(arguments.callee, 20000);
    })();
*/

    return function(url) {
        //#ifdebug
        console.info("Sending data to server");
        //#endif
        if (send) {
            send(url);
        } else {
            try {
                sendViaHtmlfile(url);
                send = sendViaHtmlfile;
            } catch (ignore) {
                sendViaImg(url);
                send = sendViaImg;
            }
        }
    };
})();

function sendBuffer() {
    if (packetBuffer.length > 0) { // Проверяем есть ли что посылать, чтобы не было пакетов-пустышек, формируемых по таймеру
        var currentTime = nowTime();
        if (currentTime - lastPacketTime < MAX_PACKET_INTERVAL) { // Проверяем, не поздно ли посылать пакет
            var str = base64(packetBuffer);
            var crc =  crc32(packetBuffer).toString(36);
            var uidBlock = userId ? "u="+userId : "tu=" + tmpUserId;
            uidBlock += '&gc=1';
            var hostname = '';
            try { // IE не всегда дает доступ к window.location
                hostname = window.location.hostname;
            } catch (e) {}
            sendUrl("e.gif?" + uidBlock + "&s=" + packetSequence + "&tk=" + ticket + "&c=" + clientHash + "&cs=" + crc
                    + "&pv=" + PROTO_VERSION + "&h=" + hostname + "&si=" + siteId + "&d="+str);
            packetBuffer = Array();
            ++packetSequence;
            //noinspection ReuseOfLocalVariableJS
            lastPacketTime = currentTime;
            setPostingTimer();
        }
    }
}


function postByTimer() {
    sendBuffer();
    setPostingTimer();
}

function setPostingTimer() {
    if (postTimerId) {
        clearTimeout(postTimerId);
    }
    postTimerId = setTimer(postByTimer, postInterval);
}

function sendPacket(packet) {
    var buf = packet.buffer;
    if (packetBuffer.length + buf.length > MAX_PACKET_LEN) {
        sendBuffer();
    }
    for (var i = 0; i < buf.length; i++ ) {
        var d = buf[i];
        if ((!d && d !== 0) || d > 255 || d < 0) {
            throw new Error("Invalid value in buffer: " + d + " packet: " + buf);
        }
        packetBuffer[packetBuffer.length] = d;
    }
}


var PacketWriter = function (packetType, noTime) {
    this.packetType = packetType;
    this.buffer = [];
    //#ifdebug
//        cons.debug('packet', "Packet header type %d", packetType);
    //#endif
    this.buffer.push(packetType);
    if (!noTime) {
        var now = nowTime();
        var elapsed = Math.round((now - pageLoadTime)/50);
        this.addScaled(elapsed);
    }
};

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() {
    var packet = new PacketWriter(P_EOF);
    sendPacket(packet);
    sendBuffer();
}

function sendKeyDown(charCode, mask, node) {
    var packet = new PacketWriter(P_KEYDOWN);
    packet.addWord(charCode);
    packet.addByte(mask);
    packet.addScaled(node);
    sendPacket(packet);
}

function sendScroll(x, y) {
    var packet = new PacketWriter(P_SCROLL);
    packet.addScaled(x);
    packet.addScaled(y);
    sendPacket(packet);
}

function sendLoad() {
    var packet = new PacketWriter(P_LOAD);
    sendPacket(packet);
}

function sendWindowFocus() {
    var packet = new PacketWriter(P_WINDOW_FOCUS);
    sendPacket(packet);
}

function sendWindowBlur() {
    var packet = new PacketWriter(P_WINDOW_BLUR);
    sendPacket(packet);
    sendBuffer();
}

function sendFieldFocus(node) {
    var packet = new PacketWriter(P_FIELD_FOCUS);
    packet.addScaled(node);
    sendPacket(packet);
}

function sendFieldBlur(node) {
    var packet = new PacketWriter(P_FIELD_BLUR);
    packet.addScaled(node);
    sendPacket(packet);
}

function sendFieldChange(node, value) {
    var packet = new PacketWriter(P_FIELD_CHANGE);
    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(code) {
    var packet = new PacketWriter(P_JS_CALL);
    packet.addByteArray(code);
    sendPacket(packet);
}

function sendResize(windowSize, documentSize) {
    var packet = new PacketWriter(P_RESIZE);
    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(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);
    packet.addScaledString(left);
    packet.addScaledString(right);
    sendPacket(packet);
}

function sendCopy() {
    sendPacket(new PacketWriter(P_TEXT_COPY));
}

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 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 += getDocumentScrollLeft();
            }
        }
        return x;
    }

    // Порт из YUI.Event. Возвращает координату события
    function getPageY (ev) {
        var y = ev.pageY;
        if (!y && 0 !== y) {
            y = ev.clientY || 0;
            if ( userAgent.ie ) {
                y += getDocumentScrollTop();
            }
        }
        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;

    // Для присваивания уникальных ID элементам, которые уже записали в поток
    var elemIdCount = 0;





    // Очередь 1-pix gif, ждущих загрузки с сервера
    //var postQueue = [];

    // Флаг, показывающий что мы вошди в обработчик выгрузки страницы
    // Нужен, потому что регистрируется 2 обработчика, onunload и onbeforeunload и могут сработать оба
    var beforeUnloadCalled = false;

    // Сколько милисекунд ждать перед выгрузкой страницы, чтобы запостиились все данные?
    var UNLOAD_TIMEOUT = 300;

    // Здесь живет идентификатор таймера, пробующий готовность DOM
    var domReadyTimer;

    // Готов ли 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();
        // Скролиинг
        var x = getDocumentScrollLeft();
        var y = getDocumentScrollTop();
        if (x != lastScrollX || y != lastScrollY) {
            writeScrollEvent(x, y);
        }
    }


    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 beforeUnloadHandler() {
        beforeUnloadCalled = true;
        sendEOF();
    }

    function unloadHandler() {
        if (!beforeUnloadCalled) {
            sendEOF();
        }
        beforeUnloadCalled = false;
        // Подержим контекст
        var limit = nowTime() + UNLOAD_TIMEOUT;
        while (nowTime() < limit) {
            // Что тут cделать полезного?
            [1, 2, 3].join("");
        }
    }


/**************************************************************************
 * Контрольные суммы элементов
 */
function fletcher(str) {
    var len = str.length, i = 0, sum1 = 0xff, sum2 = 0xff;
    while (len) {
        var tlen = len > 21 ? 21 : len;
        len -= tlen;
        do {
            var ch = str.charCodeAt(i++);
            if (ch > 32) {  // Учитываем только 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 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_NO_CHILDREN        = 1 << 2,
    F_PARENT_REGION      = 1 << 3,
    F_CONTENT_CHECKSUMM  = 1 << 4,
    F_ID                 = 1 << 5,
    F_ATTRIB_CHECKSUMM   = 1 << 7;
//var F_NON_STANDARD_POSITIONING = 1 << 6;

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) {
    if (!e.nodeName) {
        // В ИЕ бывает попадаются ноды без имени из-за наличия в коде всяких загадочных символов
        // Замечено, в частности, на http://multvideo.ru/, http://www.nomos.ru/personal/deposits/fixed-deposit/nomos-renta/
        // Такие ноды не пишем, хотя они даже ширину-высоту имеют
        throw BadElementError;
    }
    var featureMask = 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;
    }
    var attrFletcher = calculateAttrSumm(e);
    if (attrFletcher) {
        featureMask |= F_ATTRIB_CHECKSUMM;
    }
    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);
    }
    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;
}


    
    function writeElements(newElements) {
        lastRegion = null;
        for (var i = newElements.length - 1; i >= 0 ; i--) {
            sendPacket(getElementPacket(newElements[i]));
        }
    }

     // Возвращает сгенерированный ID HTML узла, записывая узел в поток, если он еще не описан
    function getElemenId(el) {
/*        if (el == targetDocument) { // Документ не обрабатываем!
            return 0;
        } */
        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);

            }
            // Записываем
            writeElements(newElements);
            id = el['vvid'];
        }
        return id;
    }

    var currentSelectedText = '';
    function checkSelectedText() {
        var text;
        if (isNativeFunction('getSelection')) {
            text = targetWindow.getSelection().toString();
        } else if (targetDocument.selection && targetDocument.selection.createRange) {
            text = targetDocument.selection.createRange().text;
        }
        if (text != currentSelectedText) {
            sendSelectedText(text);
            currentSelectedText = text;
        }
    }

    var copyWriting = true;
    function onCopy() {
        if (copyWriting) {
            copyWriting = false;
            if (currentSelectedText) {
                sendCopy();
            }
            setTimer(function() { copyWriting = true; }, 50);
        }
    }

    function onMouseUp(evt) {
        onMouseEvent(evt);
        checkSelectedText();
    }

    /**
     * Обрабатывает событие мыши с кодом eventCode. Возвращает пакет с событием, или null, если событине на надо записывать
     * @param evt Само событие
     */
    function onMouseEvent(evt) {
        evt = evt || window.event;
        var eventCode = {mousedown: P_MOUSE_DOWN, mouseup: P_MOUSE_UP, mousemove: P_MOUSE_MOVE, click: P_CLICK}[evt.type];
        if (!eventCode) {
            return null;
        }
        var targ = extractTarget(evt);
        if (!targ || targ.nodeName == 'SCROLLBAR') {
            return null;
        }
        var xy = getXY(targ);
        var pageX = getPageX(evt);
        var pageY = getPageY(evt);
        if ((evt.type == 'mousedown' || evt.type == '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;
            }
            //todo Проверка, не ездим ли мышом по скроллбару
            // 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);

            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 (evt.which == null) {
                   /* IE case */
                   button = (evt.button < 2) ? B_LEFT :
                            ((evt.button == 4) ? B_MIDDLE : B_RIGHT);
                } else {
                   /* All others */
                   button = (evt.which < 2) ? B_LEFT :
                            ((evt.which == 2) ? B_MIDDLE : B_RIGHT);
                }
                packet.addByte(button);
            }
            sendPacket(packet);
        } else {
            // В Strict mode это скорее всего <html>. Если это <html>, юзер ездит мышом по самому краю
            // страницы или по скролбару. Записывать это нет особого смысла.
            // В Quirks mode это вообще непонятно что.
            return null;
        }
    }

    function onMouseMove(e) {
        var now = nowTime();
        var elapsed = now - lastMoveEventTime;
        if (elapsed < 10) {
            return; // Нет смысла записывать c разрешением меньше 10 милисекунд
        }

        if (!e) {
             //noinspection AssignmentToFunctionParameterJS
             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;
        var packet = onMouseEvent(e);
        if (packet) {
            sendPacket(packet);
        }
    };

    function writeKeyEvent(e, charCode, mask) {
        var targ = extractTarget(e);
        var elId = 0;
        if (targ) {
            if ((targ.type && targ.type == 'password') || targ.className.match(/(^|\s)-webvisor-nokeys(\s|$)/)) {
                //#ifdebug
//                console.info('Не пишем пароли');
                //#endif
                return;
            }
            elId = getElemenId(targ);
        }
        sendKeyDown(charCode, mask, elId);
    }

    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 keyDownHandler(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;
            }
            writeKeyEvent(e, code, mask | K_SPECIAL);
            // keypress не обрабатываем
            keyPressHandler.canceled = true;
            setTimeout(function() { keyPressHandler.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 keyPressHandler(e) {
        if (keyPressHandler.canceled || keyPressHandler.processing)
            return;
        e = e || window.event;
        writeKeyEvent(e, e.charCode || e.keyCode, getKeyMask(e));
        // Некоторые хитрожопые браузеры, типа оперы, посылают keypress несколько раз
        keyPressHandler.processing = true;
        setTimeout(function() { keyPressHandler.processing = false; }, 1);
    }

    function writeScrollEvent(x, y) {
        sendScroll(x, y);
        lastScrollX = x;
        lastScrollY = y;
    }

    function scrollHandler() {
        var now = nowTime();
        var elapsed = now - lastScrollEventTime;
        var x = getDocumentScrollLeft();
        var y = getDocumentScrollTop();
        var deltaX = Math.abs(x - lastScrollX);
        var deltaY = Math.abs(y - lastScrollY);
        if (elapsed < MIN_EVENT_INTERVAL && deltaX < 12 && deltaY < 12) {
            return;
        }
        lastScrollEventTime = now;
        writeScrollEvent(x, y);
    }

    /**
     * Возвращает закодированное в Integer с точностью до 100 ms текущее время
     * от начала суток.
     */
    function makeCurrentTime() {
        var baseDate = new Date();
        var ms = baseDate.getUTCMilliseconds();
        var s = baseDate.getUTCSeconds();
        var m = baseDate.getUTCMinutes();
        var h = baseDate.getUTCHours();
        //noinspection OverlyComplexArithmeticExpressionJS
        return Math.round(ms / 100) + s * 10 + m * 10 * 60 + h * 10 * 60 * 60;
    }

    /**
     * Отправляет на сервер 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) {
                    Visor.sendPageContent(response.uniqPage == 2);
                }
            });

            var cookieNumber = readSessValue();
            if (disabled) {
                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'] = makeCurrentTime();
            //var file = sessValue ? 'h' : "v";
            var file = "h";
            var address = SERVER_ADDRESS + file + ".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 onResize() {
        sendResize([getViewportWidth(), getViewportHeight()], [getDocumentWidth(), getDocumentHeight()]);
    }

    /**
     * Вызовется в момент возникновения события DomReady
     */
    function domReadyHandler() {
        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;
                    domReadyHandler();
                } catch (e) {
                    n = null;
                    setTimeout(arguments.callee, 100);
                }
            })();
        } else if (userAgent.webkit && userAgent.webkit < 525) {
            //todo: избавиться от setInteval
            domReadyTimer = setInterval(function() {
                var rs=targetDocument.readyState;
                if ("loaded" == rs || "complete" == rs) {
                    clearInterval(domReadyTimer);
                    domReadyTimer = null;
                    domReadyHandler();
                }
           }, 100);
        } else {
           attachEvent(targetDocument, "DOMContentLoaded", domReadyHandler);
        }
    }

    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', fieldFocusHandler);
                    attachEvent(elements[j], 'blur', fieldBlurHandler);
                    attachEvent(elements[j], 'change', fieldChangeHandler);
                }
            }
        }
    }

    function onLoadHandler() {
        //#ifdebug
//        console.group("Load");
        //#endif
        loaded = true;
        markWebvisorLink(siteId);
        sendLoad();
        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();
    }




    /**
     * Пытается сконвертироватт значение в em, pt и прочих ебанутых единицах изменения
     * в значение в пикселях
     * @param _str  Значение
     * @param _context DOM узел, для которого рассчитывается значение
     */
    /*
    function convertToPixels(_str, _context) {
		// if the units are already pixels, no conversion is necessary, just parseInt and return
		if (/px$/.test(_str)) { return parseInt(_str, 10); }

		// if no _context was specified, default to the <body>
		if (!_context) { _context = targetDocument.body; }

		// If we get here, we've got some work to do:
		// 1. Create an invisible div that we'll use to measure the style represented by _str
		// 2. Make sure its position is absolute so it doesn't shift other elements on the page
		// 3. Make sure line-height is 0 or IE will add mysterious extra space to our measurements
		var tmp = targetDocument.createElement('div');
		tmp.style.visbility = 'hidden';
		tmp.style.position = 'absolute';
		tmp.style.lineHeight = '0';

		// Two additional checks are needed before we conver to pixels
		// 1.  If we're tyring to convert a % to pixels, we have to do it in the context of a parent element
		// 2.  Or if the _context element is an <img>, we also need to use the parent element, b/c we're going to append the
		// invisible div (tmp) to the _context, but it's not valid to append an element to an <img> tag
		if (/%$/.test(_str) || _context.tagName === 'IMG')
		{
			_context = _context.parentNode || _context;
			tmp.style.height = _str;
		}
		else
		{
			tmp.style.borderStyle = 'solid';
			tmp.style.borderBottomWidth = '0';
			tmp.style.borderTopWidth = _str;
		}

		_context.appendChild(tmp);
		var px = tmp.offsetHeight;
		_context.removeChild(tmp);

		return px || 0;
	}


    function getNormalizedPositionStyle(el, style) {
        var val = getStyle(el, style);
        if (!val || val == "auto") { // Ничего явным образом не поставлено
            return 0;
        } else if (val.substr(val.length - 2) == "px") { // Значение в пикселах
            return parseInt(val.substr(0, val.length - 2));
        } else if (/(em|ex|%|in|cm|mm|pt|pc)$/.test(val)) { // Значение в других единицах
            return parseInt(convertToPixels(val, el));
        } else if (/^\d+$/.test(val)) { // Считаем что это просто цифра
            return parseInt(val);
        } else { // Нечто другое
            return 0;
        }

    } */



    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 focusHandler() {
        if (!windowFocused) {
            windowFocused = true;
            sendWindowFocus();
        }
    }
    function blurHandler() {
        if (windowFocused) {
            windowFocused = false;
            sendWindowBlur();
        }
    }
    function focusInHandler() {
        if (!windowFocused || (window.event && !event.fromElement)) {
            focusHandler();
        }
    }
    function focusOutHandler() {
        if (window.event && !event.toElement) {
            blurHandler();
        }
    }


    // Обработка focus/blur полей ввода
    function fieldFocusHandler(e) {
        e = e || window.event;
        var target = extractTarget(e);
        var id = getElemenId(target);
        sendFieldFocus(id);
    }
    function fieldBlurHandler(e) {
        e = e || window.event;
        var target = extractTarget(e);
        var id = getElemenId(target);
        sendFieldBlur(id);
    }


    // Обработка события change полей ввода
    function fieldChangeHandler(e) {
        e = e || window.event;
        var target = extractTarget(e);
        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(id, value);
    }

    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;
}

    function onCallFunction(name, args) {
        var argsCode = encodeArray(args);
        if (argsCode) {
            argsCode.shift();
            var nameCode = encodeString(name);
            nameCode.shift();
            sendJsCall(nameCode.concat(argsCode));
        }
    }

    function setupVisor() {
        ticket = generateRandom();
        postJs();
        //postDimensions();
        attachOnDomReadyHandler();
        attachEvent(window, "load", onLoadHandler);

        attachEvent(targetDocument, 'click', onMouseEvent);
        attachEvent(targetDocument, "mousemove", onMouseMove);
        attachEvent(targetDocument, "mousedown", onMouseEvent);
        attachEvent(targetDocument, "mouseup", onMouseUp);
        attachEvent(window, "scroll", scrollHandler);
        attachEvent(targetDocument, "keydown", keyDownHandler);
        attachEvent(targetDocument, "keypress", keyPressHandler);
        attachEvent(targetDocument, 'copy', onCopy);
        if (document.attachEvent && !window.opera) {
            attachEvent(targetDocument, "focusout", focusOutHandler);
            attachEvent(targetDocument, "focusin", focusInHandler);
        } else {
            attachEvent(window, "focus", focusHandler);
            attachEvent(window, "blur", blurHandler);
            attachEvent(targetDocument, "blur", blurHandler);
        }
        attachEvent(window, "beforeunload", beforeUnloadHandler);
        attachEvent(window, "unload", unloadHandler);
        attachEvent(window, "resize", onResize);


        // включаем постинг событий по таймеру
        setPostingTimer();
    }
    
    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);
    }

    return {
        init: function(sid, zoneId, host) {
            this.cv(null, sid, host);
        },

        cv: wrap(function(ch, sid, host) {
            if (host == 'http://js.b.webvisor.ru/') {
                host = 'http://beta.web-visor.com/';
            }
            // this.init(sid, '1', host)
            // http://www.quirksmode.org/js/detect.html
            //http://www.quirksmode.org/js/flash.html
            // Вычисляем адрес серверного хоста
            if (window.__disableVisor) {
                return;
            }
            if (!sid) {
                // Не пришел идентификатор сайта, выходим
                return;
            }
            siteId = sid;
            clientHash = ch;
            if (host) {
                SERVER_ADDRESS = host;
            } else {
                SERVER_ADDRESS = "http://c1.web-visor.com/";
            }
            wrap(markWebvisorLink)(sid);

            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);
        })
    };
}();

__visor.cl = __visor;

if (typeof window.__visorInit == 'function') {
    window.__visorInit();
}