Development of addon firefox, or another screenshot using webExtensions and addon sdk

Published on September 26, 2016

Development of addon firefox, or another screenshot using webExtensions and addon sdk

    In this article we will look at the development of the extension for Firefox using addon sdk, and also discuss key development points: installing sdk (jpm), initializing the project, testing, compiling and publishing our extension on addons.mozila.org, using the same example screenshoter ...

    image

    I must immediately make a reservation that the extension is supported only by linux systems and is developed on linux.

    And so, from the beginning we must install the jpm package on our system, for this we install nodejs and npm (if not):

    $ sudo apt-get install nodejs nodejs-legacy npm

    After installing node.js and npm, install jpm using the command:

    $ sudo npm install jpm --global

    This command will install us jpm in the global scope, it can also be installed locally by omitting the --global switch.

    Next, we need to create a directory for future expansion. Let's call it habrscreen:

    $ mkdir ~/habrscreen

    And we initialize the extension in this directory, for this we need to go to the folder and execute the initialization command:

    $ cd ~/habrscreen
    $ jpm init

    After executing this command, the skeleton of the future application is created in the directory.

    habrscreen
    - index.js
    - package.json
    - test
    - test-index.js

    Consider the configuration of our addon package.json:

    {
      "title": "habrahabr screenshoter",
      "name": "habrscreen",
      "version": "0.0.1",
      "description": "This add-on for make screenshot and upload to yandex disk",
      "main": "index.js",
      "author": "Roman",
      "engines": {
        "firefox": ">=38.0a1"
      },
      "license": "MIT",
      "keywords": [
        "jetpack"
      ],
      "preferences": [
        {
          "name": "hClientId",
          "title": "client id",
          "description": "client id",
          "type": "string",
          "value": "8fc231e60575439fafcdb3b9281778a3"
        },
        {
          "type": "control",
          "label": "get oAuth token",
          "name" : "getYaToken",
          "title": "Token"
        },
        {
          "description": "oauth token",
          "name": "oauthKey",
          "type": "string",
          "title": "oauth token"
        },
        {
          "description": "automaticaly copy to clipboard",
          "title": "autocopy to clipboard",
          "name":"autoCopy",
          "type":"bool",
          "value":true
        }
      ]
    }

    What could be interesting here? this is the same manifest in json format only. Yes, this is also the same manifest with the version, description and name of addons. And also we determined the settings of our module in it while there will be 4:

    1. client id - client id yandex oauth
    2. get OAuth token - button when clicked, go to Yandex and get the authorization token
    3. oauth token - field for entering the token itself
    4. autocopy to clipboard - a flag that automatically copies the link to the screenshot to the clipboard.

    After determining the settings, we need to think about a button, when clicked, our screenshoter will be launched. To do this, open index.js for editing and describe this button in it:

    var ui = require('sdk/ui');
    var {ActionButton} = require('sdk/ui/button/action');
    var button = ui.ActionButton({
        id: "mozilla-link",
        label: "Make screenshot",
        icon: {
            "16": "./image/camera16.png",
            "32": "./image/camera32.png",
            "64": "./image/camera64.png"
        },
        onClick: makeScreen
    });

    The code is quite simple, first we import the sdk components we need and create a button object. We need to create the data folder in it the image directory, in the last copy the pre-prepared button images, the image should be 3 sizes 64, 32, 16 ... Done, the images are uploaded, the setting is created. Let's try to run:

    $ jpm run

    After the command is executed, firefox will start with our module, we will see the result is a button from the top right, and the addon setting.

    We process the logic of the getOauth button in the extension settings, for this it is necessary to process the event generated by the button:

    var sp = require("sdk/simple-prefs");
    sp.on("getYaToken", function () {
        tabs.open('https://oauth.yandex.ru/authorize'
            + '?response_type=token'
            + '&client_id='
            + require('sdk/simple-prefs').prefs['hClientId']);
    });
    

    And so, at the click of a button, the browser will open a new one in the Yandex authentication masonry, where we can agree with the permissions and get the token, which we will copy into the token field in the settings.

    Next, we process the click of the 'make screen' button above, we indicated that the makeScreen function is the handler:

    /**
     * click to button screenshot
     * @param state
     */
    function makeScreen(state)
    {
        var date = new Date();
        var fileScreen = date.getTime().toString() + '_screen.png';
        var args = ["-s", "/tmp/" + fileScreen];
        system(
            '/usr/bin/scrot',
            args
        );
       uploadToYandex(fileScreen);
    }

    This function is trivial; it launches a package with which a screenshot of the selected area is taken. Next, the uploadToYandex function is launched with the screenshot file name parameter.

    /**
     * upload screenshot to yandex
     * @param name
     */
    function uploadToYandex(name) {
        var Request = require('sdk/request').Request;
        const fileIO = require("sdk/io/file");
        Request({
            url: "https://cloud-api.yandex.net/v1/disk/resources/upload?path=" + name,
            headers: getHeaders(),
            onComplete: function (response) {
                var result = JSON.parse(response.text);
                if (result.method == "PUT") {
                    putRequest(result.href, '/tmp/' + name);
                    // publicate file
                    publicateFile(name);
                }
            }
        }).get();
    }

    This function makes a request using the Request class to the api disk. Unfortunately, it was not possible to implement the request using the put method, it is in the object, but the content parameter accepts either the object or the string, and therefore the binary data turns into the request body string, which leads to the unloading of the broken files. I had to write my putRequest using XMLHttpRequest, if anyone knows how to implement using Request write in the comments. I will be grateful if someone helps bring the code to a common denominator.

    /**
     * put request to yandex api
     * @param url
     * @param file
     */
    function putRequest(url, file) {
        const {Cc, Ci} = require("chrome");
        // Make a stream from a file.
        var stream = Cc["@mozilla.org/network/file-input-stream;1"]
            .createInstance(Ci.nsIFileInputStream);
        var fileIo = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
        fileIo.initWithPath(file);
        stream.init(fileIo, 0x04 | 0x08, 0644, 0x04); // file is an nsIFile instance
        var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
            .createInstance(Ci.nsIXMLHttpRequest);
        req.open('PUT', url, false);
        req.setRequestHeader('Content-Type', "application/binary");
        req.send(stream);
    }

    Everything here is also simple, we open the file and create a stream, which, in fact, is passed to the request body. According to the documentation of the “disk” we should always send “Content-Type: application / binary” and we do it. All our request is gone, the last request we publish the file publicateFile:

    /**
     * publicate file on yandex disk
     * @param name
     */
    function publicateFile(name) {
        var Request = require('sdk/request').Request;
        var result;
        Request({
            url: "https://cloud-api.yandex.net/v1/disk/resources/publish?path=" + name,
            headers: getHeaders(),
            onComplete: function (responsePublic) {
                result = JSON.parse(responsePublic.text);
                if (result.method == "GET") {
                    Request({
                        url: result.href,
                        headers: getHeaders(),
                        onComplete: function (resp) {
                            result = JSON.parse(resp.text);
                            if (require('sdk/simple-prefs').prefs['autoCopy']) {
                                var clipboard = require("sdk/clipboard");
                                clipboard.set(result.public_url);
                            }
                            tabs.open(result.public_url);
                        }
                    }).get();
                }
            }
        }).put();
    }
    

    This function sends a publication request to yandex, which returns a link for the next request, confirmation. The second request returns an object with information about the file, in which there is a link to the file public_url. If everything is successfully opened, the browser tab with a screenshot, and depending on the settings, copy the link to the clipboard ...

    When everything is ready and the functionality of the extension is checked, we need to collect it in the xpi file and enter the command:

    $ jpm xpi

    Next, go through the registration process at addons.mozilla.org and fill out the form for submitting the extension for moderation here by developer hub .

    image

    And this is where we will end our screenshoter application developed and published in addons.mozilla.org ...

    "As always, I’m sharing a link to the complete github.com code: firefox-sdk-addons
    " Here is ours, a link to the habrahabr-screenshoter extension

    Thank you all for attention, and all the best.