19 January 2009

Пример использования telnet-сервера в firefox

Firefox
Каждый раз, когда мне говорят «о! новый хромиум стал еще быстре, а новая опера — еще мелодичнее», в ответ я задаю простой вопрос: «А в вашем браузере есть telnet-сервер? А вот в firefox — есть», — после чего адепты других религий понимают, что пропаганда бесполезна.

В этой заметке речь пойдет о том, как можно расширять и управлять огненной лисицей из других приложений через вышеупомянутый telnet-сервер, реализуемый плагином mozrepl. В качестве примера я покажу, как реализовать функцию создания скриншота сайта с минимальными усилиями.

Но сперва несколько дополнительных предусловий. Во-первых, скриншот должен быть полноразмерным, а не обрезанным по viewport. Во-вторых, я не очень хочу писать без необходимости на низкоуровневых языках.

В числе прочих были рассмотрены варианты использования khtml2png и пары из xwd+imagemagic, но рендерялка khtml3 безбожно глючила, а второй вариант был не так тривиален, как хотелось бы.

Если все крутится вокруг бразуера — значит надо либо дописывать код к браузеру, либо воздействовать на браузер из внешней программы. В процессе поиска решения был найден чудесный плагин Screen grab!, который снимал нужные варианты скриншотов и оставалось только научиться управлять этим плагином. Вспоминая, что в случае с OpenOffice подобную функциональность можно получить используя возможность тестирования пользовательского интерфейса, я отправился на поиски тестовых плагинов. Поиск увенчался успехом и после просмотра вдохновляющего скринкаста о управлении firefox-ом из emacs-а был достаточно быстро написан черновик кода.

Идея проста до невозможности — на лету патчится функция открытия файла screengrab-а после чего вызывается одна из функций плагина и, соответственно, скриншот сохранятеся прямо в файл без каких-либо вопросов к пользователю.

Впоследствие код превратился вот в это:
var ScreenshotSaver = {<br>
lambda: null, // EventListener<br>
mixin: null, // our file opener<br>
pure: null, // screengrab's file opener<br>
answer: 42, // to check `this' validity<br>
};<br>
<br>
ScreenshotSaver.assert = function(bool) {<br>
if (!bool) {<br>
repl.print('assertion failed');<br>
repl.print(arguments.callee.caller);<br>
}<br>
};<br>
<br>
ScreenshotSaver.save = function(url, fpath) {<br>
this._install_onload(fpath);<br>
window.content.location.href = url;<br>
};<br>
<br>
ScreenshotSaver._install_onload = function(fpath) {<br>
this.assert(this.lambda === null);<br>
<br>
// Looks like hack, but I need valid `this' in _dump_screenshot<br>
// I need this.lambda to be precise.<br>
this.lambda = function(ev) {<br>
repl.print('ScreenshotSaver: some `load\' event catched...');<br>
if (!ev.target.mIsBusy)<br>
ScreenshotSaver._dump_screenshot(fpath);<br>
};<br>
window.getBrowser().addEventListener("load", this.lambda, false);<br>
};<br>
<br>
ScreenshotSaver._uninstall_onload = function() {<br>
this.assert(this.lambda !== null);<br>
window.getBrowser().removeEventListener("load", this.lambda, false);<br>
this.lambda = null;<br>
};<br>
<br>
ScreenshotSaver._dump_screenshot = function(fpath) {<br>
this.assert(this.answer === 42);<br>
this.assert(this.lambda !== null);<br>
<br>
repl.print('ScreenshotSaver._dump_screenshot starts');<br>
/* Wanna deregister handler from itself? Here are some useless links:<br>
* http://code.activestate.com/recipes/576366/<br>
* http://en.wikipedia.org/wiki/Fixed_point_combinator<br>
* eval:"repl.print(arguments.callee)" */<br>
this._uninstall_onload();<br>
<br>
this._install_screengrab_mixin(fpath);<br>
Screengrab.grabCompleteDocument();<br>
this._uninstall_screengrab_mixin();<br>
<br>
repl.print('ScreenshotSaver._dump_screenshot exits');<br>
};<br>
<br>
ScreenshotSaver._install_screengrab_mixin = function(fpath) {<br>
this.assert(this.mixin === null && this.pure === null);<br>
<br>
this.mixin = function(defaultName) {<br>
var file = Components.classes["@mozilla.org/file/local;1"].<br>
createInstance(Components.interfaces.nsILocalFile);<br>
file.initWithPath(fpath);<br>
return file;<br>
}<br>
<br>
this.pure = SGNsUtils.askUserForFile;<br>
SGNsUtils.askUserForFile = this.mixin;<br>
};<br>
<br>
ScreenshotSaver._uninstall_screengrab_mixin = function() {<br>
this.assert(SGNsUtils.askUserForFile === this.mixin);<br>
this.assert(this.mixin !== null && this.pure !== null);<br>
<br>
SGNsUtils.askUserForFile = this.pure;<br>
this.pure = null;<br>
this.mixin = null;<br>
};<br>

Скачать файл можно тут.

Загружается такое «расширение» на лету не менее тривиально:
$ firefox &
$ telnet localhost 4242 # 4242 — порт, на котором слушает mozrepl
repl> repl.load('file:///home/luser/ScreenshotSaver.js');
repl> ScreenshotSaver.save('http://habrahabr.ru', '/tmp/habr.png');
repl> ScreenshotSaver.save('http://linux.org.ru', '/tmp/lor.png');

После чего в /tmp появляется два соответствующих скриншота.

Как еще можно использовать mozrepl? Да как угодно — от автоматизированного тестирования веб-сайтов и «интерактивной консоли» при разработке плагинов для firefox до www-ботов, которые будут практически не отличимы от людей без проведения теста Тьюринга.

UPD: спасибо за карму, перенёс топик в «огненного лиса».

Из открытых вопросов остаются, например, обработка ошибок и скрывание вертикального scrollbar-а. При разрешении 1024x768 сделанный таким образом скриншот будет иметь ширину 1009px, т.е. будет уже на ширину полосы прокрутки — 15px.
Tags:firefoxmozillamozilla firefoxjavascriptscreenshotscreenshotsscreenshootermozreplscreengrab
Hubs: Firefox
+48
6.4k 54
Comments 34
Popular right now
QA Manual Engineer Remote
from 2,500 $BK-datingRemote job
Javascript разработчик
from 130,000 to 180,000 ₽ArtezioНижний Новгород
Javascript разработчик
from 160,000 to 220,000 ₽ArtezioМосква
JavaScript разработчик
from 150,000 to 200,000 ₽SportrecsМоскваRemote job
Top of the last 24 hours