Pull to refresh

Добавляем нужное в Firefox за час

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



Низкий старт


Сразу хочу оговориться: все нижесказанное будет работать только в 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-ом — внутри будет собственно код.
Tags:
Hubs:
Total votes 80: ↑72 and ↓8+64
Comments26

Articles