Многие наверняка сталкивались с тем, что любимый браузер не умеет делать что-то очень простое, но насущно необходимое. Я хочу рассказать, как на коленке за час можно обучить FF4 хорошему. Написание расширений — процесс очень простой и даже временами приятный, но, к сожалению, документация не всегда легко находится, how-to разбросаны по разным углам сайта MDN, внятных рекомендаций мне найти тоже не удалось… Все это порождает (по крайней мере, в моей голове — породило) миф о том, что это трудоемкий процесс, доступный только гуру. Вот этот миф и призвана развеять данная статья. Мы напишем расширение, которое будет править типографику в полях ввода, обращаясь за помощью к сервису «Типограф».

Сразу хочу оговориться: все нижесказанное будет работать только в FF4. Коллеги из Mozilla так переработали API, что проще создать два расширения для обеих версий браузера, чем писать рекомендуемую лапшу вида
Итак.
Для начала — создаем скелет нашего будущего расширения. О нас позаботились, поэтому достаточно просто обратиться за помощью. Заполняем простую формочку — и скачиваем готовый скелет. Мы для нашего примера выбрали расширение без диалога настройки, только с одной кнопкой на тулбаре. Вот, что за нас сделал генератор:

Иконки я рисовать не умею, потому позаимствуем оную у сервиса, который будет делать за нас основную работу (на скриншоте сверху она уже добавлена мною в папку
Для начала исправим надпись на кнопке и всплывающую подсказку в файле
Затем подставим нашу иконку вместо той, что прикатилась к нам по умолчанию:
Теперь осталось только переписать реакцию на нажатие.
Мне удобно использовать типограф следующим образом: если фокус в поле ввода (
Что ж, приступим. Все эти правки попадут в файл
Эту магию я откопал где-то в недрах MDN:
Возвращаем содержимое буфера обмена, либо пустую строку. Комментировать тут, вроде бы, нечего.
Еще нам потребуется отлавливать фокус ввода (этот код в силу его тривиальности я оставлю за кадром).
Ну и, наконец, текст нужно отослать типографскому сервису:
Вот, собственно, и все. Осталось вызвать функцию
И запаковать все это наше барахло в архив:
Весь код примера и получившееся расширение можно взять на народе. XPI — это файл расширения, если его распаковать zip-ом — внутри будет собственно код.

Низкий старт
Сразу хочу оговориться: все нижесказанное будет работать только в FF4. Коллеги из Mozilla так переработали API, что проще создать два расширения для обеих версий браузера, чем писать рекомендуемую лапшу вида
appversion>=4.0.Итак.
Для начала — создаем скелет нашего будущего расширения. О нас позаботились, поэтому достаточно просто обратиться за помощью. Заполняем простую формочку — и скачиваем готовый скелет. Мы для нашего примера выбрали расширение без диалога настройки, только с одной кнопкой на тулбаре. Вот, что за нас сделал генератор:

Иконки я рисовать не умею, потому позаимствуем оную у сервиса, который будет делать за нас основную работу (на скриншоте сверху она уже добавлена мною в папку
skin). Итак, что же делать дальше с этим набором файлов?Для начала исправим надпись на кнопке и всплывающую подсказку в файле
/chrome/locale/en-US/overlay.dtd:<!ENTITY typographToolbarButton.label "Typo!"> <!ENTITY typographToolbarButton.tooltip "Proceed typography of selected text against Lebedev's tool">
Затем подставим нашу иконку вместо той, что прикатилась к нам по умолчанию:
pushd chrome/skin && mv favicon.png toolbar-button.png && popd
Теперь осталось только переписать реакцию на нажатие.
Немного кода
Мне удобно использовать типограф следующим образом: если фокус в поле ввода (
textarea) и там выделен кусок текста — типографим его. Если же текст не выделен — типографим содержимое буфера обмена. Ну а если фокус где-то еще — ничего не делаем :-). Поэтому нам потребуется научиться работать с буфером обмена (это самая нетривиальная часть). Что ж, приступим. Все эти правки попадут в файл
/chrome/content/overlay.js.Эту магию я откопал где-то в недрах MDN:
var getClipboard = function (flavor) { if (!flavor || flavor == "") flavor = "text/unicode"; var trans = Components.classes["@mozilla.org/widget/transferable;1"] .createInstance(Components.interfaces.nsITransferable); var clip = Components.classes["@mozilla.org/widget/clipboard;1"] .getService(Components.interfaces.nsIClipboard); if (!clip || !trans) return ''; trans.addDataFlavor(flavor); clip.getData(trans, clip.kGlobalClipboard); var str = new Object(); var strLength = new Object(); try { trans.getTransferData(flavor, str, strLength); if (str) str = str.value.QueryInterface(Components.interfaces.nsISupportsString); } catch (e) { // it's OK, skipping str = null; } return str ? str.data.substring(0, strLength.value / 2) : ''; }
Возвращаем содержимое буфера обмена, либо пустую строку. Комментировать тут, вроде бы, нечего.
Еще нам потребуется отлавливать фокус ввода (этот код в силу его тривиальности я оставлю за кадром).
Ну и, наконец, текст нужно отослать типографскому сервису:
typo : function () { var focused = document.commandDispatcher.focusedElement; if (!focused || focused.tagName.toLowerCase() != 'textarea') { return; } if (focused.selectionStart != focused.selectionEnd ) { text = focused.value.substring(focused.selectionStart, focused.selectionEnd); } else { text = getClipboard(); } text = text.replace (/&/g, '&'); text = text.replace (/</g, '<'); text = text.replace (/>/g, '>'); var xmlRequest = '<?xml version="1.0" encoding="UTF-8"?>' + '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' + 'xmlns:xsd="http://www.w3.org/2001/XMLSchema" ' + 'xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' + '<soap:Body>' + '<ProcessText xmlns="http://typograf.artlebedev.ru/webservices/">' + '<text>' + text + '</text>' + '<entityType>' + 3 + '</entityType>' + '<useBr>' + 0 + '</useBr>' + '<useP>' + 0 + '</useP>' + '</ProcessText>' + '</soap:Body>' + '</soap:Envelope>'; var req = new XMLHttpRequest(); req.open('POST', 'http://typograf.artlebedev.ru/webservices/typograf.asmx', true); req.onreadystatechange = function (aEvt) { if (req.readyState == 4) { if (req.status == 200) { var response = req.responseText; var re = /<ProcessTextResult>\s*((.|\n)*?)\s*<\/ProcessTextResult>/m; response = re.exec (response); response = RegExp.$1; response = response.replace (/>/g, '>'); response = response.replace (/</g, '<'); response = response.replace (/&/g, '&'); insertText(response, focused); } else { // smth went wrong — just skip it } } }; req.send(xmlRequest); }
Вот, собственно, и все. Осталось вызвать функцию
typo из обработчика события «кнопка нажата»:onToolbarButtonCommand: function(e) { typograph.typo(); }
И запаковать все это наше барахло в архив:
#!/bin/bash if [ -e build ]; then rm -rf build; fi mkdir -p build cp -r install.rdf chrome.manifest chrome defaults build pushd build/chrome >/dev/null && zip -r typograph.jar content/* skin/* locale/* && popd >/dev/null || echo 'Unable to produce jar :-(\n' pushd build && zip -r typograph-4.0.xpi install.rdf chrome.manifest chrome/* defaults/* && popd >/dev/null || echo 'Unable to produce .xpi :-(\n' if [ -e dist ]; then rm -rf dist; fi mkdir -p dist cp build/typograph-4.0.xpi dist/
Весь код примера и получившееся расширение можно взять на народе. XPI — это файл расширения, если его распаковать zip-ом — внутри будет собственно код.
