Firefox is one of the most used web browser in the world: it's a free and open source software built by the Mozilla foundation, and it's available for all the major operating systems. The browser has all the features that nowadays are considered standard: tabbed browsing, private navigation, a synchronization system and its functionalities can be extended using third party addons written in Javascript. In this tutorial we will see how to create, build and sign a simple web extension.

In this tutorial you will learn:
  • How to build and test a simple Firefox web extension
  • How to package and sign an extension

Software Requirements and Conventions Used

Software Requirements and Linux Command Line Conventions
Category Requirements, Conventions or Software Version Used
System Os-independent
Software The Firefox browser The web-ext utility to package and sign the extension
Other Knowledge of the Javascript programming language
Conventions # - requires given linux commands to be executed with root privileges either directly as a root user or by use of sudo command
$ - requires given linux commands to be executed as a regular non-privileged user

The purpose of our extension

The purpose of our extension is very simple and so is its implementation: all it must do is allow us to search highlighted text in a webpage inside the linuxconfig.org site as if we were using the dedicated search bar. The first thing we want to do is to create a directory for our project. We will call our extension "linuxconfig-search", and we will use the same name for the directory that will host the extension files:

$ mkdir linuxconfig-search

SUBSCRIBE TO NEWSLETTER
Subscribe to Linux Career NEWSLETTER and receive latest Linux news, jobs, career advice and tutorials.


The manifest.json file

Once the directory is in place, we must create the most important file for our extension, which is manifest.json. Inside this json formatted file, we must specify metadata and functionalities of our extension. There are many keys we can use in the file, however for our example only few of them will be necessary. Here is the content of our manifest.json:

{
  "manifest_version": 2,
  "name": "linuxconfig-search",
  "version": "0.1",
  "author": "Egidio Docile",
  "description": "Perform a search on linuxconfig.org based on the highlighted text",
  "icons": {
    "48": "icons/logo.jpg"
  },

  "permissions": ["contextMenus"],
  "background" : {
    "scripts": ["background.js"]
  }
}

Let's see what is the purpose of each key we used.

First of all we used manifest_version: this key is mandatory, and its purpose is to state what version of the manifest is used by the extension. For modern addons its value should always be 2.

Another mandatory key is name and it is used to specify a name for the extension: the name will appear in the browser interface and also in the extension catalogue on addons.mozilla.org if we decide to publish it.

With version we provided the web extension version: the key is mandatory and its value should be formatted as numbers separated by dots. Immediately after it, we used the optional author keyword to specify who made the extension.

The description key is also optional, but it's always good to provide it in order to make clear what is the purpose of the extension.

The next key we used in our manifest.json file is icon and it is also optional but recommended: using this key we can provide an object describing the icons to use for the extensions. The icons must be specified in key-value pair format, where each key is a string representing the size (in pixels) of the image, and the related value is a string representing the path of the image relative to the root of the project.

The permissions key is very important for our extension to work correctly. The value of this key must be an array of strings, each specifying the name of a web extension API needed by our addon. At installation time, the user will be informed that the extension requires the specified privileges. In this case we just requested the contextMenus privilege since we want to access and operate on the browser context menu.

The last key we used in our basic manifest file is background. It is also optional, but it is needed to specify the list of background scripts used by the extension. What are background scripts? They are the files we can use to code long-term state or long-term operations performed by our extension: in this case we only have one file, background.js; we will see its content in the next paragraph.

Our background script: background.js

We closed the previous paragraph saying that background scripts are used to implement long-term state and logic for our web extension. In this case what we want to do is create a new element in the browser context menu, which is displayed when the user right-clicks on highlighted text, and perform an action when the menu entry is clicked. All of this can be accomplished with few lines of code. In the root of our directory we create the background.js file and start by coding the context-menu entry:

browser.contextMenus.create({
    id: "search-in-linuxconfig",
    title: "Search in linuxconfig.org",
    contexts: ["selection"],
});


Let's explain the code: we used the contextMenus.create() method to create a context menu entry for our extension. The argument passed to this method is an object used to specify the ID of our menu entry, it's title, that is basically the text that will appear on the menu, and the contexts: the specific cases in which the entry should appear in the menu. In this case we just used the selection context, to indicate that the menu entry should appear only when a part of the page is selected. Other possible contexts are, for example, link or image: they refer to the cases when the user clicks on a link or on an image element in the page, respectively.

The next and final step is to make our menu entry react and perform a task when the user clicks on it. Here is the code we add to the script:

browser.contextMenus.onClicked.addListener(function(info, tab) {
  switch (info.menuItemId) {
    case "search-in-linuxconfig":
      const url = encodeURI(`https://linuxconfig.org/linux-config?searchword=${info.selectionText}&searchphrase=all`);
      browser.tabs.create({
        active: true,
        url
      });

      break;
  }
});

The onClicked event on contextMenus is fired when the user clicks on an menu item. To it we attach an event listener, using the addListener() method which takes a callback as an argument. This callback accepts two arguments itself: info and tab. The former is an object which contains information about the element that was clicked in the menu, and the context in which the click took place; the second contains details about the browser tab where the click happened.

Inside the callback we used a switch statement using the info.menuItemId as the expression that should be verified by its cases. The menuItemId property contains the id of the menu item that was clicked: we want to be sure the action is performed only when the user clicks on the element with the "search-in-linuxconfig" id.

When the case is matched, we perform our action. First we define the "url" constant: its value is the string representing the URL that will be used to perform the search, encoded using the encodeURI function. The URL string is obtained by using the selectionText property of the info object, which contains the text selected by the user, as the value of the searchword GET parameter.

We then used the tabs.create() method to create a new tab. This method returns a promise (check our tutorial about promises if you are not familiar with them), and accepts an object as a parameter which is used to specify the properties of the new tab. In this case we just declared the tab as active, so that it will become the new active tab in the browser window and provided the url to be opened in the tab. You may notice that we only provided the url key in the object, and not the corresponding value. This is an ES6 shorthand to avoid repetitions: when an object key have the same name as the variable passed as property, we can simply pass the key name, and avoid writing things like {url: url}.

Last steps and extension installation

Before we install and test our extension, we must create the "icons" directory, and put our "logo.jpg" file in it. Once we are done, to temporary install our extension, we can use two methods we will now explain.

Temporary install the extension using firefox interface

To install the extension this way, navigate to about:debugging in the browser:

firefox-about-debugging-page
Firefox about:debugging page


On the left sidebar, click on "This Firefox", and than on the "Load temporary addon" button. At this point you should select anyone of the files contained in the extension directory, and, if no errors are encountered, the extension will be installed. Since the installation is temporary it will be removed when the browser is closed:

extension-installed
Extension installed

From this interface we can also inspect the behavior of our extension by clicking on the "Inspect" button.

Temporary install the extension using the web-ext utility

An alternative way to install the extension is by using the web-ext utility which can be installed via npm, the node package manager. For convenience we want to install the package globally. If you, like me don't want to install packages in the global filesystem via npm, can modify the ~/.npmrc file (or create it if doesn't exist), and add this entry in it:

prefix = ${HOME}/.local

Now, when a package is installed using the -g flag, its files will be installed relatively to the ${HOME}/.local directory. The software installed via npm using this configuration will be available only for the user who performs the installation. To be able to invoke utilities installed this way, the ~/.local/bin directory must be added to the user PATH. To install web-ext we can run:

$ npm install -g web-ext

To install our extension we can launch the following command:

$ web-ext run

A new browser window will be launched with our extension loaded.

Text the extension

Just to text that our extension works, we highlight a word in our web page and right click on it to open the context menu; we should find a new entry was added:

firefox-context-menu-extension-entry
Our extension entry in Firefox context menu

If we click on the "Search in linuxconfig.org" entry, we will be redirected to the site searching page where the results of the query will appear.

Packaging and signing the extension

If we want to install our extension in a non-temporary way or we want to publish it and make it available to the world, we must package it and sign it. The signing can happen via our own keys if we want to use the addon just for ourselves, or via addons.mozilla.org if we want to distribute the extension. Here we will cover only the former case.


SUBSCRIBE TO NEWSLETTER
Subscribe to Linux Career NEWSLETTER and receive latest Linux news, jobs, career advice and tutorials.


To be able to sign an extension, we need to create an account on the Mozilla developer hub. Once the account is created we go to this page and generate our API keys by clicking on the "Generate new credentials" button. Two credentials will be created: JWT issuer and JWT secret. To sign our package we must use them both and launch the following command from inside the extension directory:

$ web-ext sign --api-key=<JWT issuer> --api-secret=<JWT secret>

The web-ext-artifacts directory will be created: inside of it we will find the signed .xpi file that we can install by visiting the about:addons firefox page. The command will also upload our extension to our firefox developer account.

Conclusions

In this article we saw how to create a very simple Firefox addon, using the web extensions APIs. In the process we learned that the manifest.json is the most important file and we use it to declare, among the other things, the extension metadata and the permissions it needs. In our extension code we saw how to add an entry in the browser context menu, and how to perform an action when the click event occurs on it. We learned how to install the extension temporary, and how we can package and sign it. All the code provided in this tutorial can be downloaded from this repository.

ARE YOU LOOKING FOR A LINUX JOB?
Submit your RESUME, create a JOB ALERT or subscribe to RSS feed on LinuxCareers.com.
LINUX CAREER NEWSLETTER
Subscribe to NEWSLETTER and receive latest news, jobs, career advice and tutorials.
DO YOU NEED ADDITIONAL HELP?
Get extra help by visiting our LINUX FORUM or simply use comments below.

You may also be interested in:



Comments and Discussions