Pull to refresh

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

Reading time 5 min
Views 7.1K
Каждый раз, когда мне говорят «о! новый хромиум стал еще быстре, а новая опера — еще мелодичнее», в ответ я задаю простой вопрос: «А в вашем браузере есть 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:
Hubs:
+48
Comments 34
Comments Comments 34

Articles