Разработка расширения для firefox, или мой первый опыт, на примере скриншотера

    После написания статьи Системные скрипты на php для linux, пишем скриншотер, у меня появилась идея «А почему бы, не написать расширение которое завязать на мой скрипт, с возможностью автоматической выгрузки на яндекс диск.»… Почитав документацию о разработке расширений я решил всё же начать писать.

    image

    Вот это маленькая кнопочка и скриншот на яндекс диске, результат прототипа расширения, написаного за пару часов. О процессе его создание под катом…

    Внимание: это первая версия приложения, в будущем в роли скриншотера будет выступать скрипт из предыдущей статьи…

    Расширение которое я разработал имеет следующую файловую структуру:

    Структура
    -chrome.manifest
    -install.rdf
    --content
    ----browserOverlay.js
    ----browserOverlay.xul
    ----options.xul
    --skin
    ----browserOverlay.css

    Сейчас нас интересуют файлы: chrome.manifest и install.rdf. chrome.manifest содержит в себе описание структуры проекта:

    chrome.manifest
    content   xulphpsrc                content/
    skin      xulphpsrc  classic/1.0   skin/
    
    overlay chrome://browser/content/browser.xul  chrome://xulphpsrc/content/browserOverlay.xul
    

    Первые две строки хром манифеста содержат: тип данных, название пакета, путь к файлам пакета.

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

    Файл install.rdf это манифест установки, который содержит информацию об добавляемом расширение:

    install.rdf
    <?xml version="1.0"?>
    
    <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
    
      <Description about="urn:mozilla:install-manifest">
        <em:id>phpsrc@phpsrc.ru</em:id>
        <em:name>xulphpsrc</em:name>
        <em:description>PHP screenshoter extensions!</em:description>
        <em:version>0.1</em:version>
        <em:optionsURL>chrome://xulphpsrc/content/options.xul</em:optionsURL>
        <em:creator>Naumov</em:creator>
        <em:type>2</em:type>
    
        <!-- Mozilla Firefox -->
        <em:targetApplication>
          <Description>
            <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
            <em:minVersion>4.0</em:minVersion>
            <em:maxVersion>10.*</em:maxVersion>
          </Description>
        </em:targetApplication>
      </Description>
    </RDF>
    

    Как вы видите. Он содержит в себе идентификатор, имя приложения, версию, создателя, тип приложения, URL панели настройки и д.р. информацию.

    Я не думаю что стоит заострять внимание на этом файле, так как всё вполне очевидно. Мы можем только разобрать одну опцию optionsURL, это url который ведёт к настройкам расширения, настройки в свою очередь описываются файлом options.xul:

    options.xul
    <?xml version="1.0"?>
    <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
    
    <prefwindow id="xulphpsrc-prefs"
                title="phpsrc api yandex settings"
                xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    
        <prefpane id="phpsrc_api_set" label="PHPsrc setting api yandex">
            <preferences>
                <preference id="phpsrc_login" name="extensions.xulphpsrc.login" type="string"/>
            </preferences>
    
            <preferences>
                <preference id="phpsrc_pass" name="extensions.xulphpsrc.pass" type="string"/>
            </preferences>
    
            <hbox align="center">
                <label control="phpsrc_login_label" value="Яндекс логин: "/>
                <textbox preference="phpsrc_login" id="phpsrc_login_label" maxlength="40"/>
    
                <label control="phpsrc_pass_label" value="Яндекс пароль: "/>
                <textbox preference="phpsrc_pass" id="phpsrc_pass_label" maxlength="40"/>
            </hbox>
        </prefpane>
    </prefwindow>
    

    В этом файле, мы описали всего 2-ва поля логин и пароль. Обратите внимание на имена «extensions.xulphpsrc.login» и «extensions.xulphpsrc.pass» они содержат как бы древовидную структуру, и их будет удобнее получать из общей массы записей, да и есть, некая гарантия уникальности ключей…

    Далее рассмотрим фаил browserOverlay.xul:

    browserOverlay.xul
    <?xml version="1.0"?>
    
    <?xml-stylesheet type="text/css" href="chrome://global/skin/" ?>
    <?xml-stylesheet type="text/css"
      href="chrome://xulphpsrc/skin/browserOverlay.css" ?>
    
    <overlay id="xulphpsrc-browser-overlay"
      xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    
      <script type="application/x-javascript"
        src="chrome://xulphpsrc/content/browserOverlay.js" />
    
      <toolbar>
        <hbox>
        <toolbarbutton id="xulphpsrc-button" class="button-screen"
                       label="Скриншот" tooltiptext="Скриншот"
                       oncommand="XULPHPsrcChrome.BrowserOverlay.makeScreen(event);"/>
        </hbox>
      </toolbar>
    </overlay>
    

    Что он делает? он подключает два файла browserOverlay.css, browserOverlay.js и создаёт toolbar и единственную кнопку на нём «Скриншот»:



    Вся логика приложения заложена в browserOverlay.js

    browserOverlay.js
    /**
     * XULPHPsrcChrome namespace.
     */
    if ("undefined" == typeof(XULPHPsrcChrome)) {
        var XULPHPsrcChrome = {};
    }
    
    /**
     * Controls the browser overlay.
     */
    XULPHPsrcChrome.BrowserOverlay = {
        /**
         * craete screen shot by rect
         * @param aEvent
         */
        makeScreen: function (aEvent) {
            var date = new Date();
            var fileScreen = date.getTime().toString() + '_screen.png';
    
            var args = ["-s", "/tmp/" + fileScreen];
    
            this.systemRequest(
                '/usr/bin/scrot',
                args
            );
    
            this.uploadToYandex(fileScreen);
        },
    
        /**
         * analog php system
         * @param shell
         * @param args
         */
        systemRequest: function (shell, args) {
            var file = Components.classes["@mozilla.org/file/local;1"]
                .createInstance(Components.interfaces.nsIFile);
    
            file.initWithPath(shell);
    
            var process = Components.classes["@mozilla.org/process/util;1"]
                .createInstance(Components.interfaces.nsIProcess);
            process.init(file);
    
            process.run(true, args, args.length);
        },
    
        /**
         * upload screen to yandex
         * @param name
         */
        uploadToYandex: function (name) {
            var xml = '<propertyupdate xmlns="DAV:"><set><prop><public_url xmlns="urn:yandex:disk:meta">true</public_url></prop></set></propertyupdate>';
            var auth = this.getPreference().login + ':' + this.getPreference().pass;
    
            this.systemRequest('/usr/bin/curl', [
                '-s',
                '--user', auth,
                '-T', '/tmp/' + name,
                '-X', 'PUT',
                'https://webdav.yandex.ru'
            ]);
    
            this.systemRequest('/usr/bin/curl', [
                '-s',
                '--user', auth,
                '-d', xml,
                '-X', 'PROPPATCH',
                'https://webdav.yandex.ru/' + name
            ]);
    
            alert("Скриншот сохранён");
        },
    
        /**
         * get system configuration
         * @returns {{login: *, pass: *}}
         */
        getPreference: function () {
            var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                .getService(Components.interfaces.nsIPrefService);
            var myPrefs = prefs.getBranch("extensions.xulphpsrc.");
    
            return {
                login: myPrefs.getCharPref('login'),
                pass: myPrefs.getCharPref('pass')
            }
        }
    };
    

    Данный фаил мы разберём чуть подробнее хотя он тривиален и выполняет всего 2-ва действия: делает скриншот, и загружает его на yandex disk. Скриншот он делает с помощью вызова системной утилиты crot, и згружает результат с помощью curl. Обработчик «клика» на кнопку «скриншот» мы поставили метод makeScreen:

        /**
         * craete screen shot by rect
         * @param aEvent
         */
        makeScreen: function (aEvent) {
            var date = new Date();
            var fileScreen = date.getTime().toString() + '_screen.png';
    
            var args = ["-s", "/tmp/" + fileScreen];
    
            this.systemRequest(
                '/usr/bin/scrot',
                args
            );
    
            this.uploadToYandex(fileScreen);
        },
    

    Это точка входа в приложение, здесь формируется имя будущего скриншота и происходит вызов, linux программы, scrot. Мы выбираем мышкой область экрана и делаем снимок, после этого программа переходит в следующий метод uploadToYandex, и передаёт имя файла для загрузки.

        /**
         * upload screen to yandex
         * @param name
         */
        uploadToYandex: function (name) {
            var xml = '<propertyupdate xmlns="DAV:"><set><prop><public_url xmlns="urn:yandex:disk:meta">true</public_url></prop></set></propertyupdate>';
            var auth = this.getPreference().login + ':' + this.getPreference().pass;
    
            this.systemRequest('/usr/bin/curl', [
                '-s',
                '--user', auth,
                '-T', '/tmp/' + name,
                '-X', 'PUT',
                'https://webdav.yandex.ru'
            ]);
    
            this.systemRequest('/usr/bin/curl', [
                '-s',
                '--user', auth,
                '-d', xml,
                '-X', 'PROPPATCH',
                'https://webdav.yandex.ru/' + name
            ]);
    
            alert("Скриншот сохранён");
        },
    

    В этом методе, как вы видите нет ни чего экстраординарного, он делает 2-ва запроса с помощью curl в yandex.disk, один загружает созданный файл, другой его публикуют. Спасибо lexore за коментарий. В конце концов, мы получаем уведомление что скриншот сохранён.

    Ну вот теперь у нас есть расширение как его установить? Для разработки мы можем, просто, создать файл под именем приложения в папке:

    ~/.mozilla/firefox/[id].[user]/extensions/

    Где id — идентификатор, [user] — пользователь firefox к примеру у меня получилось так:

    ~/.mozilla/firefox/pta4nm6g.default/extensions/phpsrc@phpsrc.ru

    И содержащий путь до директории с файлами расширения:~/extensions/phpsrc/ (к примеру у меня такой).

    Последним штрихом, нам необходимо отключить проверку подписи расширений в firefox. Для этого необходимо перейти по адресу about:config найти опцию xpinstall.signatures.required и выставить значение false.



    Перезагружаем firefox и всё готово… Настройка расширения, заходим в дополнения и ищем своё расширение, нажимаем на кнопочку настроить, вводим логин и пароль от yandex'a и готово, теперь можно нажать кнопочку и сделать скриншот…

    Выводы: Я в первый раз писал расширение для firefox, и времени у меня больше на написание статьи ушло. Это первая версия приложения в дальнейшем есть планы развиваться в направление, использования php в этой сфере. Все скриншоты сделаны разработанным расширением. Всем спасибо за внимание, всего хорошего…

    UPD: Репозитарий на github
    Поделиться публикацией

    Комментарии 30

      +3

      Вроде как использование XUL объявлено мозиллой устаревшим подходом. Сейчас предлагается переходить на Web Extensions. Правда, это началось недавно и будет работать только для последних версий Firefox… Поправьте пожалуйста, если я не прав.

        +1
        В выводах, я говорил что в первые разрабатывал, руководствуясь текущей документацией. Так что пока я незнаю сам…
        +2

        Да, это так, но API этих WebExtensions очень небогат. Тривиальные вещи с его помощью не сделать. Например, вам не удасться повторить расширение fb2reader (естественно, повторять его нет необходимости, но если вы хотите сделать нечто похожее, как например преобразование XML-файла с логом в таблицу, то делать придется по устаревшей технологии). Так что если оставят только его в таком виде, как есть сейчас, очень много чего похерят.

          0
          WebExtensions дорабатывают в каждом выпуске. Например в 49-й версии добавили возможность манипуляций с историей. И будут дорабатывать дальше до паритета с Chromium.
            0

            Так можно будет открыть в браузере XML-файл, словно это HTML (с jQuery и прочими полюбившимися веб-мастерам фишками) или нет? И из файловой системы, и из интернета?

              0
              Всё дело в том, что XUL гораздо мощнее WebExtensions. Паритет тут ничем не поможет.
            0
            В Firefox всё очень сильно завязано друг на друга.

            Переход на новый движок (Servo), оптимизированный под многопоточность и многоядерность (корни нынешнего движка Gecko уходят ещё во времена Netscape, когда в подавляющем большинстве ПК было не больше одного процессорного ядра), требует «смерти» XUL (который, как я понимаю, сильно прибит к старому движку гвоздями). А это, в свою очередь, требует и переписывания дополнений на новый API. Заодно и новый интерфейс будет, основанный на HTML5.
            0

            Представьте себе http://stackoverflow.com/questions/7575658/firefox-add-on-vs-extensions-vs-plugins три старых вида расширений (при том что в документации mdn зачастую забывают рассказать для какого апи примеры) + новый недопиленный (крайне недопиленный) web extension, использование которого отключает все остальные api (то есть вы не можете в web extension расширении использовать модули других апи файрфокса что бы реализовать недопиленный функционал) — и вы приблизительно поймете всю боль разработки расширения для ff. Именно в данный момент. До того как они взялись за webextensions и не забросили addon sdk — все было если не хорошо, то неплохо. Когда они допилят webextension — расширения под хром можно будет иcпользовать в ff (возможно с перепаковкой) — тоже будет неплохо. А вот прямо сейчас — есть некоторая боль от процесса разработки.

              +1
              Когда они допилят webextension — расширения под хром можно будет иcпользовать в ff (возможно с перепаковкой) — тоже будет неплохо.

              Проблема в том, что возможности web extension api ограниченны, и там вряд ли удастся реализовать дополнения типа Tree Style Tab или Advanced Locationbar.
                0

                Tree Style Tab можно реализовать, не вижу проблем. Advanced Locationbar — да, прямо в таком виде не реализуется на хроме. Но у меня больше эмоций по поводу низкоуровневого доступа к сети и файловой системе. Полезные фишки были, я буду скучать по ним.

                  0
                  Tree Style Tab можно реализовать, не вижу проблем.

                  Почему тогда его нет в хроме, где web extension api развит намного лучше, чем сейчас в ФФ?
                    0

                    Да я откуда знаю? Решили не заморачиваться на портирование, не захотели по религиозным причинам, мало ли. Просто я вижу функционал, я знаю api — исходя из этого говорю — это реализуемо. Advanced Locationbar — нет, в хроме можно только кнопки добавлять в бар, сам бар на кастомизацию не отдается.

                      0
                      И заморачивались, и хотели. Есть какие-то обрезанные подделки, в виде отдельного окна и отображающимся обычными, но до ФФного им далеко. Так что чего-то для изменения вкладок не хватает.
                      0
                      Есть подобное расширение Sidewise Tree Style Tabs
                        0
                        Как я и говорил:
                        обрезанные подделки, в виде отдельного окна и отображающимся обычными, но до ФФного им далеко

                        Или вы действительно не видите разницы между поворотом с переделкой оригинальных вкладок и размещением дубликата в соседнем окне?
                          0
                          Я бы просто не был столь категоричен.
                          Дерево вкладок строится. Откуда страничка была открыто видно. + Есть усыпление вкладки или группы вкладок. Это аналог, но не полная копия.
                            +1
                            Если аналог хуже (зачем мне две панели вкладок, почему вкладки в отдельном окне?), то для меня это обрезанная подделка. Это не категоричность, это факт.
                +3
                Последним штрихом, нам необходимо отключить проверку подписи расширений в firefox

                В актуальной версии не работает.
                  +1
                  Mozilla Firefox 48.0 у меня такая версия. И работает.
                    +1
                    49.0.1 актуальная, и в ней не работает
                      +1
                      image

                      и

                      image

                      обновил firefox до Mozilla Firefox 49.0
                        0
                        Сбросьте эту настройку и увидите, что её не существует.
                        0

                        там это теперь можно только в ночной сборке и девелопер эдишн, для гражданских эту фичу отрубили, видимо из соображений безопасности.

                      +3
                      How to override the Firefox Add-on Signing requirement
                      http://www.ghacks.net/2016/08/14/override-firefox-add-on-signing-requirement/
                        +2
                        Спасибо! Я ждал этот коммент джва года.
                      0
                      image
                      поправил кнопку, завтра надо будет реализовать добавление меток на страницу в виде квадратиков, лэйблов, и т.д. Может кто знает что использовать canvas или что то другое?
                        +3
                        Грамотнее не отключать проверку, а зарегистрироваться на addons.mozilla.org и отправить своё дополнение на проверку. Если там код простой, то оно сразу пройдёт автоматические тесты и вы сможете разместить его в каталоге, либо скачать подписанное и распространять самостоятельно.
                          0
                          Как писали выше, неподписанные дополнения подключать нельзя. На мой взгляд, один из самых удобных способов — скачать девелоперскую версию FF и тестить в ней, а потом публиковать на addons.mozilla.org. Чтобы запустить девелоперскую версию с уже встроенным дополнением, у Мозиллы есть неплохо инструмент — jpm.
                          На mdn есть хорошее howto для создания нового расширения.
                          https://developer.mozilla.org/en/Add-ons/SDK/Tutorials/Getting_Started_(jpm)
                            0
                            Вчера в addons.mozilla.org отправил расширение, как рас написанное с помощью jpm и webapi. В ближайшее время планирую написать статью о разработке на основе этих инструментов.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое