The extension adds a new button to the Firefox toolbar. When the user clicks the button, we display a popup enabling them to choose an animal. Once they choose an animal, we'll replace the current page's content with a picture of the chosen animal.

To implement this, we will:

define a browser action, which is a button attached to the Firefox toolbar.
For the button we'll supply:

an icon, called "beasts-32.png"

a popup to open when the button is pressed. The popup will include HTML, CSS, and JavaScript.

define an icon for the extension, called "beasts-48.png". This will be shown in the Add-ons Manager.

write a content script, "beastify.js" that will be injected into web pages.
This is the code that will actually modify the pages.

package some images of the animals, to replace images in the web page.
We'll make the images "web accessible resources" so the web page can refer to them.

You could visualise the overall structure of the extension like this:

It's a simple extension, but shows many of the basic concepts of the WebExtensions API:

default_popup is used if you want a popup to be shown when the user clicks the button. We do, so we've included this key and made it point to an HTML file included with the extension.

web_accessible_resources lists files that we want to make accessible to web pages. Since the extension replaces the content in the page with images we've packaged with the extension, we need to make these images accessible to the page.

Note that all paths given are relative to manifest.json itself.

The icon

The extension should have an icon. This will be shown next to the extension's listing in the Add-ons Manager (you can open this by visiting the URL "about:addons"). Our manifest.json promised that we would have an icon for the toolbar at "icons/beasts-48.png".

If you choose to supply your own icon, It should be 48x48 pixels. You could also supply a 96x96 pixel icon, for high-resolution displays, and if you do this it will be specified as the 96 property of the icons object in manifest.json:

"icons":{"48":"icons/beasts-48.png","96":"icons/beasts-96.png"}

The toolbar button

The toolbar button also needs an icon, and our manifest.json promised that we would have an icon for the toolbar at "icons/beasts-32.png".

If you don't supply a popup, then a click event is dispatched to your extension when the user clicks the button. If you do supply a popup, the click event is not dispatched, but instead, the popup is opened. We want a popup, so let's create that next.

The popup

The function of the popup is to enable the user to choose one of three beasts.

Create a new directory called "popup" under the extension root. This is where we'll keep the code for the popup. The popup will consist of three files:

choose_beast.html defines the content of the panel

choose_beast.css styles the content

choose_beast.js handles the user's choice by running a content script in the active tab

We have a <div> element with an ID of "popup-content" that contains an element for each animal choice. We have another <div> with an ID of "error-content" and a class "hidden". We'll use that in case there's a problem initializing the popup.

Note that we include the CSS and JS files from this file, just like a web page.

choose_beast.css

The CSS fixes the size of the popup, ensures that the three choices fill the space, and gives them some basic styling. It also hides elements with class="hidden": this means that our "error-content"<div> will be hidden by default.

The place to start here is line 96. The popup script executes a content script in the active tab as soon as the popup is loaded, using the browser.tabs.executeScript() API. If executing the content script is successful, then the content script will stay loaded in the page until the tab is closed or the user navigates to a different page.

A common reason the browser.tabs.executeScript() call might fail is that you can't execute content scripts in all pages. For example, you can't execute them in privileged browser pages like about:debugging, and you can't execute them on pages in the addons.mozilla.org domain. If it does fail, reportExecuteScriptError() will hide the "popup-content"<div>, show the "error-content"<div>, and log an error to the console.

If executing the content script is successful, we call listenForClicks(). This listens for clicks on the popup.

If the click was on a button with class="beast", then we call beastify().

If the click was on a button with class="reset", then we call reset().

The beastify() function does three things:

map the button clicked to a URL pointing to an image of a particular beast

The first thing the content script does is to check for a global variable window.hasRun: if it's set the script returns early, otherwise it sets window.hasRun and continues. The reason we do this is that every time the user opens the popup, the popup executes a content script in the active tab, so we could have multiple instances of the script running in a single tab. If this happens, we need to make sure that only the first instance is actually going to do anything.

After that, the place to start is line 40, where the content script listens for messages from the popup, using the browser.runtime.onMessage API. We saw above that the popup script can send two different sorts of messages: "beastify" and "reset".

if the message is "beastify", we expect it to contain a URL pointing to a beast image. We remove any beasts that might have been added by previous "beastify" calls, then construct and append an <img> element whose src attribute is set to the beast URL.

if the message is "reset", we just remove any beasts that might have been added.

The beasts

Finally, we need to include the images of the beasts.

Create a new directory called "beasts", and add the three images in that directory, with the appropriate names. You can get the images from the GitHub repository, or from here:

Testing it out

First, double check that you have the right files in the right places: