javascript

User.js и FF

Сегодня написал небольшой user.js для «Оперы» и решил его переписать под плагин «Greasemonkey», под «Firefox». Оказалось, что есть немаловажные различия.

Во-первых, объект window браузера напрямую недоступен — нужно пользоваться «unsafeWindow», но руководство не рекомендует его использовать. Мне в user.js window браузера нужен был для доступа к jQuery, который был подгружен на странице, где работает скрипт, мне было удобно использовать этот framework. Объект «window» тоже есть, это внутренний объект Greasemonkey и выглядит как настоящий, только не радует на него, например, рекомендуется вешать все события.

Во-вторых, обращаться к функциям, определённым на странице, рекомендуется через

location.href="javascript:void(pageFunc(123))"

, утвержается, что это более безопасно, видимо, чтобы не передавать контекст выполнения наружу. Говорят, что в Chrome 4 это единственный способ добраться до «window».

В-третьих, из скриптов, выполняющихся из Greasemonkey, можно использовать функции «GM_xmlhttpRequest» (XHR с возможностью делать запросы на любые адреса), «GM_log» (логгирование, как можно догадаться) и так далее.

Ну и можно использовать localStorage (FF 3.5+) и globalStorage (FF 2.0+) для сохранения данных user.js.

8 комментариев

TestLink

Время от времени занимаюсь модификацией используемого внутри компании инструмента «TestLink». Сложная задача — приделать к нему ЧПУ (TestLink целиком на фреймах, у него не то что ЧПУ, обычных-то URL нет).

С задачей справился чуть менее, чем полностью — осталась пара мелочей, а в пятницу по половины одиннадцатого вечера боролся с деревом тест-кейсов. Всё просто — получили линк на test case, нужно раскрыть дерево на определённом узле. Дерево выполнено не на православном jQuery, а на сатанинском ExtJS (кстати, привет авторам ExtJS и вопрос — для чего вы делали собственные ID, но не потрудились сделать возможность привязаться к ним из select и get?).

Осложняется всё тем, что дерево снаружи недоступно (изолируется внутри функции), а менять JS-код продукта я не могу, это крайне нежелательно. Пришлось эмулировать события, чем я раньше никогда не занимался. Оказывается на эту тему есть весьма развесистые плагины (в том числе и для jQuery), я эмулировал вполне конкретное событие — клик мышью, поэтому сделал свою небольшую функцию, как водится, две ветки — для IE и всего остального. Запишу сюда, чтобы не потерять:

function fireEvent(element, eventname){
    if (document.createEventObject){
        return element.fireEvent('on' + eventname, document.createEventObject())
    } else{
        var event = document.createEvent("HTMLEvents")
        event.initEvent(eventname, true, true)
        return !element.dispatchEvent(event)
    }
}

В итоге получилась дикая вещь — когда человек вводит этот самый понятный ему URL, браузер спрашивает у сервера путь по дереву, получает его, по таймеру долбит дерево в плюсики (дерево грузит каждый узер через AJAX), чтобы развернуть каждый узел по пути следования к test case, а когда добирается до цели, таймер тушится.

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

Добавлено 8 декабря 2009 0:41: потом я переписал всё на чистом ExtJS, воспользовавшись getCmp, expandPath и внутренней реализацией fireevent, но код всё равно тут оставлю — пригодится.

3 комментария

Локальные переменные и JS

Читал тут по диагонали стандарт ECMAScript и подумалось мне, что сэмулировать локальные переменные (да, я осознаю, что это свойства объекта) можно вот так:

var a = 10;
with ( { a : 20 }) {
    alert(a);
}
alert(a);

Ожидаемый результат: сначала код выведет «20», потом — «10». Я ещё не задумывался над тем насколько это практично и изящно, просто придумалось.

Кстати, обратите внимание, что следующий код выведет «30», «30», «10», «30»:

var a = 10, b = 10;

with ({a:20}) {
    var a = 30, b = 30;
    alert(a);
    alert(b);
}

alert(a);
alert(b);

Добавлено 10 ноября 10:55: в комментариях почему-то подумали, что я не знаю о лексических замыканиях. Знаю, конечно.

6 комментариев

Компрессоры JavaScript и точка с запятой

Точка с запятой — один байт, перевод строки — один байт. Мне тут подумал, что лучше бы компрессоры JS выкидывали при сжатии не переводы строки, а точки с запятой (там где это возможно, конечно, например, пустой return в некоторых случаях нельзя завершить только переводом строки).

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

14 комментариев

Batch-запуск

Пока писал книгу, часто приходилось пропускать по нескольку сотен, а то и тысяч картинок через различные программы-оптимизаторы. Хорошо, когда эти программы имеют пакетный режим, а если нет? Выход, в общем-то простой — подумать какую последовательность кнопок надо жать в программе, чтобы оптимизировать всю папку с файлами и как-то запрограммировать нажатие.

Я пользовался для этой цели Windows Script Host, описывал что нужно жать на языке JScript. Вот, например, обрабатываются 492 изображения программой «A Smaller GIF 1.22»:

with (WScript.CreateObject("WScript.Shell")) {
    AppActivate("A Smaller GIF 1.22")

    for (var command, j = 0; j<492; j++) {
        WScript.sleep(100)

        for (command = '^O+{TAB}{DOWN}{UP}', i = 0; i<j; i++) {
            command += '{DOWN}'
        }

        SendKeys(command+'{ENTER}^S')
        WScript.sleep(100)
        SendKeys('{ENTER}')
    }
}

Всё понятно, по-моему, почти по-английски написано. О том как отсылать приложению специфические комбинации (например, Ctrl+Alt+Backspace) можно прочитать в на сайте MSDN.

7 комментариев

«Консультант» и IE

Вчера на «Хабре» люди в очередной раз подняли тему сайта «Консультанта» и Internet Explorer — первый, своей интересной частью, работает только на втором. Как я и ожидал, код завязан на особенности Internet Explorer, браузер богатый на возможности, на сайте использовано их немало. Впрочем, всё, если вдуматься, переводится и на более стандартные рельсы.

У меня давно руки чесались разобраться поподробнее как устроены userjs в «Опере» и посмотреть на отличия реализации XSLT-процессинга в IE и остальных браузерах. Userjs в «Опере» устроены исключительно удобно (даже предусмотрели патчи к выполняемому коду), а с XSLT в IE нестандартно, зато много проще.

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

Текст userjs помещаю под кат.

// ==Userscript==
// @name         Make Consultant base works
// @namespace    /
// @description  Make Consultant base works in non-IE browsers.
// @include      http://base.consultant.ru/*
// @version      1.0
// @licence      MIT
// ==/UserScript==

(function(){

    /* TODO
       saveCard — это behavior userData, сделать эмуляцию
       VBArray  — одна из переменных использует этот специфический массив
       isBaseVisible, ForEachRow — убрать заглушку, сделать нормальную функцию
       showModalDialog — дописать с передачей параметров (например, через window.name)
    */


    document.addEventListener('load', function (e) {
        showModalDialog = window.open;

        return true;
    }, false);

    DOMParser.prototype.loadXML = function (text) {
        return this.parseFromString(text,"text/xml")
    }

    opera.addEventListener('BeforeScript', 
        function(e){
                if (e.element.getAttribute('for')) {
                    var script = e.element.text
                    var id     = e.element.getAttribute('for')
                    var ev     = e.element.getAttribute('event')

                    e.element.text = ''
                    document.addEventListener('load', new Function('',                     
                        'document.getElementById("' + id +
                        '").attachEvent("' + ev +
                        '", function () {'+ script + '})'
                    ))
                }

                var repl = {
                    'document\\.body\\.attachEvent'   : 'document.attachEvent',
                    'document\\.all\\.fieldsXML\\.XMLDocument\\.transformNode': 'document.transformNode',
                    'document\\.all'                  : 'document.getElementsByTagName("*")',
                    'document\\.frames\\('            : 'document.getElementsByTagName("iframe").item(',
                    '\\.XMLDocument\\.text'           : '.textContent',
                    'document\\.body'                 : 'document.documentElement',
                    '\\.parentElement'                : '.parentNode',
                    '\\.XMLDocument\\.documentElement': '',
                    '\\.XMLDocument'                  : '',
                    'basesRow\\.all\\.basesTable'     : 'document.getElementById("basesTable")',
                    '\\.all\\.tags\\('                : '.getElementsByTagName(',
                    '\\.all\\.([^\\.;\\s]+)'          : '.getElementById("$1")',
                    '\\.all\\('                       : '.getElementById(',
                    '\\w+\\.getElementById'           : 'document.getElementById',
                    'p\\.type\\.'                     : 'p.getAttribute("type").',
                    '\\.isEnabled\\s*=([^;]+)'        : '.setAttribute("isEnabled", $1)',
                    '\\.isEnabled'                    : '.getAttribute("isEnabled")',
                    '(\\w+)\\.loadXML'                : '$1=$1.loadXML',
                }

                for (var re in repl) {
                    e.element.text = e.element.text.replace(new RegExp(re, 'g'), repl[re])
                }
            }
        ,false);

    opera.defineMagicFunction('writeInsertion', function () {});

    opera.defineMagicFunction('getBasesTable', function (a, b, tDivRow) {

        var basesRow = tDivRow && tDivRow.id ? document.getElementById("bases_" + tDivRow.id) : null;
        return basesRow == null ? null : document.getElementById('basesTable');
    })

    opera.defineMagicFunction('forEachRow', function (a, b) { return true })

    opera.defineMagicFunction('createXMLStylesheet', function (a, b, url) {
        var r = new XMLHttpRequest()
        r.open('GET', url, false)
        r.send(null)

        if (r.status == 200) {
            var xml = new DOMParser()

            return xml.parseFromString(r.responseText, "text/xml")
        }

        return null
    })

    opera.defineMagicFunction('isBaseVisible', function (a, b, code) {
        return true
    })

    HTMLElement.prototype.transformNode = XMLDocument.prototype.transformNode = function (style) {
        var xsltProcessor=new XSLTProcessor();
        xsltProcessor.importStylesheet(style);

        if (typeof this == 'HTMLElement') {
            var resultDocument = (new DOMParser()).parseFromString(this.innerHTML, 'text/xml');
        } else {
            var resultDocument = xsltProcessor.transformToDocument(this);
        }
          
        var xmls = new XMLSerializer();  
        return xmls.serializeToString(resultDocument);
    }

    HTMLHtmlElement.prototype.selectSingleNode = HTMLInputElement.prototype.selectSingleNode = function (path) {
        var xpe = new XPathEvaluator();
        var nsResolver = xpe.createNSResolver(this.ownerDocument == null ? this.documentElement : this.ownerDocument.documentElement);
        var results = xpe.evaluate(path,this,nsResolver,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        return results.singleNodeValue;
    }

    ActiveXObject = function (str) {
        return new ({
            'Microsoft.XMLDOM':  DOMParser,
            'Microsoft.XMLHTTP': XMLHttpRequest,
            'Scripting.Dictionary': Array,
        })[str]
    }

})()

Кстати, «Опере» нужно не забыть сказать, чтобы она маскировалась под Internet Explorer (правая клавиша мыши, «Настройки сайта», закладка «Сеть»).

Обращаюсь к тем, кто читает RSS и трансляцию, текст userjs по ссылке на исходный пост.

7 комментариев

HTML5: события offline и online

На сайте JavaScript.ru появилась статья о новых событиях в JavaScript — offline и online (реализованы в Firefox 3.5 и Internet Explorer 8.0). Цитирую выводы:

Эти события никак не связаны с подключением к сети интернет и к сайту.

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

Они также триггерятся, когда отключаются все сетевые адаптеры, или подключается хотя бы один. При этом не важно, ведет ли он в реальный интернет.

Firefox глючит, в отличие от IE

6 комментариев

JavaScript split

Азат Разетдинов подкинул хорошую ссылку на сайте XRegExp по поводу особенностей реализации метода split в JS-движках различных браузеров. Больше всего особенностей у IE, но и остальные браузеры грешны.

4 комментария

Загадки JavaScript

Что-то Internet Explorer меня нехорошо удивляет в последнее время. Кто-нибудь, не запуская, может сказать что получится, если выполнить в IE следующий JavaScript:

alert('a###a'.split(/#/))
alert('a###a'.split('#'))

Самое главное, мне непонятно — зачем это так сделано?

10 комментариев

FTPd.js работает под Opera 10.00 RC

Случайно обнаружил, что FTP-сервер, который я восемь лет писал на JavaScript (с использованием LiveConnect к Java) работает, оказывается под Opera 10 RC, которая вчера вышла. Интересно, что запрос к PrivilegeManager «Опера» понимает, но обрабатывает как-то неправильно.

LiveConnect — это придуманный фирмой Netscape для их браузера Netscape 4.xx (удобный) способ скрещивания различных плагинов и JavaScript, в частности, можно вызывать из JavaScript Java и наоборот. Насколько я понимаю, после смерти браузера Netscape, наследниками технологии LiveConnect стали FireFox (из-за того, видимо, что Netscape принимала участие в разработке Gecko) и Opera, по всей видимости, просто из-за того, что она поддерживает бинарные плагины Gecko.

А вот под новыми браузерами FireFox мой сервер не работает — не хватает полномочий. Мне, если честно, не хочется разбираться что там ещё надо запрашивать.

11 комментариев

Быстрый trim для JavaScript

Самый быстрый trim на JavaScript выглядит странно:

function trim12 (str) {
    var	str = str.replace(/^\s\s*/, ''),
        ws = /\s/,
        i = str.length;
    while (ws.test(str.charAt(--i)));
    return str.slice(0, i + 1);
}

Код взят из блога Flagrant Badassery. Цикл с регуляркой быстрее, чем простое регулярное выражение /\s+$/, а кроме того, \s\s* быстрее, чем \s+? OMG!

13 комментариев

Знаете ли вы JavaScript

У Дмитрия Барановского (это автор Raphaël — JavaScript-библиотеки для построения диаграмм) в бложике нашёл чудесный тест на знание JavaScript, привожу его здесь полностью. Попробуйте, я прошёл его довольно посредственно.

Итак, что выведет каждый кусок кода?

if (!("a" in window)) {
    var a = 1;
}
alert(a);
var b = function a(x) {
    x && a(--x);
};
alert(a);
function a(x) {
    return x * 2;
}
var a;
alert(a);
function b(x, y, a) {
    arguments[2] = 10;
    alert(a);
}
b(1, 2, 3);
function a() {
    alert(this);
}
a.call(null);

38 комментариев

Opera и document.all

«Опера» в одно время пыталась притворяться Internet Explorer’ом — копировала его user-agent, эмулировала его свойства и так далее. В частности, в «Опере» есть document.all, как часть поддержки так называемых «коллекций», которые были разработаны и использовались в стародавние времена. Один из популярнейших методов определить «а не IE ли это» заключался в проверке существования document.all, иногда — исключения «Оперы»:

var isIE = document.all && self.opera

Впрочем, на Западе «Опера» не популярна, поэтому «Оперу» не исключают и считают её разновидностью IE.

Так вот, в последние годы «Опера» пытается открестится от своего «иешного» прошлого, например, где-то по дороге с 8-й версии в 9-ю user-agent перестал притворяться Internet Explorer’ом. А вот в 9-й версии (в 10-ке это тоже так) изменения начались, на мой взгляд, какие-то довольно печальные: ломают логику языка JavaScript.

В новых «Операх» конструкция alert(document.all ? 1 : 0) выдаст «0», тогда как alert(document.all) — «[object HTMLCollection]». Сделано понятно для чего: обратная совместимость не ломается, но проверка «не IE ли» теперь уже выдаёт false.

11 комментариев

О5 25: JavaScript — не Java!

Ллео, +1, зачетно обосрал JS ))) Ненавижу javascript, а еще заточку на кроссбраузерность, где код JS вырастает раза в 3 из-за разных браузеров. Говно это. Кастрат Java. Долой JS!

Пишет у Каганова в блоге какой-то чудик об одном из самых гармоничных языков современности.

В 21-м веке есть люди, который до сих пор считают, что JavaScript «кастрат Java». Если кто до сих пор не знает — JavaScript и Java не родственники, название языка — маркетинговый ход, хотя первоначальное название языка («Mocha») было не лучше.

Ну и про «заточку на кроссбраузерность». Так уже давно никто не делает (если не считать каких-то узкоспециальных вещей), большинство использует фреймворки, где всё уже сделано за нас (я, например, неистово люблю jQuery), да и проблем совместимости остаётся всё меньше.

7 комментариев

Opera и GeoIP

В конце 2008-го года в W3C появился черновик спецификации Geolocation API — технологии, позволяющей приложению в браузере узнать географические координаты компьютера на котором оно исполняется. Используется информация от GPS (если он есть), MAC-адресов окружающих WiFi и BT точек, сигналы RFID-меток, а так же информация с GSM/CDMA сот. В общем, что доступно.

В JavaScript это выглядит примерно так:

function showMap(position) {
      // Show a map centered at (position.coords.latitude, position.coords.longitude).
    }

    // One-shot position request.
    navigator.geolocation.getCurrentPosition(showMap);

Тем, кто видел Google Gears это уже знакомо. Причём, в момент, когда клиентское приложение в первый раз пытается получить координаты, браузер задаёт пользовалю вопрос, разрешает ли пользователь узнать приложению его координаты.

К чему это я всё вёл. Вчера появился специальный билд «Оперы», поддерживающий GeoIP. Что, правда, ничего не говорит о сроках внедрения технологии в стабильную версию. Например, тег VIDEO появился в эксперементальном билде аж в конце 2007-го, а специльный билд, проходящий ACID3, вышел в начале 2008-го. А первая публичная версия, которая будет проходить ACID3 и содержать тег VIDEO — Opera 10. Которая даже пока ещё не в стадии бета-тестирования.

Оставить комментарий

JavaScript: два синтаксиса определения функции

Смотрите-ка, не я один думал, что два определения функции (через «function f() {}» и «f = function () {}») эквивалентны. Я дочитываю книгу «JavaScript: The Good Parts» (автор — Douglas Crockford), вот до какого места я только что добрался:

A function statement is shorthand for a var statement with a function value.

The statement:

function foo() {}

means about the same thing as:

var foo = function () {}

Мне кажется, что поведение, описанное мною под заголовком «JavaScript: баг с замыканием в FireFox», является хаком браузеров, для того, чтобы все функции, описанные ниже, можно было вызывать из вышестоящих функций.

Дополнение. Надо было вчера дочитать главу, прежде чем спать ложиться. Ниже описывается отличие этих конструкций, упоминается то, что я назвал «хаком браузеров» и даже есть намёк на различное поведение function под if в разных браузерах:

function statement is hoisting. This means that regardless of where a function a placed, it is moved to the top of the scope in which it is defined. This relaxes the requirement that function should be declared before used, which I think leads to sloppiness. It also prohibits the use of function statement in if statement, but they vary in how that should be interpreted. That creates portability problems.

9 комментариев

JavaScript: баг с замыканием в FireFox

Какой alert должен показать следующий JavaScript в браузере?

<html>
<head>
<script>
/* When are functions defined? */
function really() { alert("Original"); }
if (0) {
    alert("No");
    function really() { alert("Yes, really"); }
}
really();
</script>
</head>
<body>Really</body>
</html>

Поведение тут зависит от браузера, FF выведет «Original», а остальные браузеры — «Yes, really».

29 комментариев

JavaScript: преобразование в integer

Читая исходники плагина autocomplete для jQuery (документация описывает не всю функциональность плагина), нашёл замечательный небольшой паттерн. Обычный короткий способ преобразовать объект другого типа в число — прибавить к нему ноль или умножить на 1. Но есть способ лучше, оцените:

var extraParams = {
    timestamp: +new Date()
}

Просто добавляем знак перед объектом. Просто и изящно.

14 комментариев

Ошибка в валидации поля: виноват PHP

function validateDigit(e) {
    var re = /[0-9x0Dx08]/i;
    var code;
    if (!e || !e.which) {
        if (window.event) {
            code = event.keyCode;
        }
    }
    else code = e.which;
    if(!code) return;

    if (!re.test(String.fromCharCode(code))) return false;
}

Что бывает от невнимательности (скрипт с сайта управления ГИБДД МВД от РТ): пропали слеши и в поле ввода вводятся буквы «d» и «x». А всё от stripslashes.

3 комментария

Сжатие JavaScript с использованием PNG и Canvas

Jacob Seidelin в своём блоге «Nihilogic» опубликовал небезынтересную статью, где он рассказывает о своих экспериментах по сжатию кода JavaScript при помощи Canvas и PNG.

Идея простая, из разряда «как мне раньше не пришло это в голову». JavaScript переводится в последовательность байт, которая записывается как изображение. Изображение сохраняется в PNG-формате, в PNG используется довольно эффективное сжатие, что даже при расходах на заголовок даёт ощутимые результаты — известная библиотека Prototype (124 килобайта) стала занимать всего 30 килобайт в восьмибитном PNG-файле. Каждый пиксель в 24-битном PNG позволяет хранить три байта информации, в 8-битном формате — один.

Далее PNG-изображение загружается в тег CANVAS (нужный функционал поддерживается FireFox, WebKit/Safari и бета-версией «Оперы») и считывается попиксельно функцией getImageData, после чего полученный код выполняется (eval) и мы получаем исходный JavaScript.

Единственный недостаток метода — скорость. Извлечение скрипта в 255Кб может занять несколько секунд, что, впрочем, не всегда является проблемой.

32 комментария

← 1 2 3 4 5 6 →