If you've been through the Your first WebExtension article, you've already got an idea of how to write a WebExtension. In this article we'll write a slightly more complex add-on that demonstrates a few more of the APIs.
The add-on 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 add-on, 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 add-on like this:
It's a simple add-on, but shows many of the basic concepts of the WebExtensions API:
- adding a button to the toolbar
- defining a popup panel using HTML, CSS, and JavaScript
- injecting content scripts into web pages
- communicating between content scripts and the rest of the add-on
- packaging resources with your add-on that can be used by web pages
You can find complete source code for the add-on on GitHub.
To write this add-on, you'll need Firefox 45 or newer.
Writing the WebExtension
Create a new directory and navigate to it:
mkdir beastify cd beastify
manifest.json
Now create a new file called "manifest.json", and give it the following contents:
{ "manifest_version": 2, "name": "Beastify", "version": "1.0", "description": "Adds a browser action icon to the toolbar. Click the button to choose a beast. The active tab's body content is then replaced with a picture of the chosen beast. See https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Examples#beastify", "homepage_url": "https://github.com/mdn/webextensions-examples/tree/master/beastify", "icons": { "48": "icons/beasts-48.png" }, "permissions": [ "activeTab" ], "browser_action": { "default_icon": "icons/beasts-32.png", "default_title": "Beastify", "default_popup": "popup/choose_beast.html" }, "web_accessible_resources": [ "beasts/frog.jpg", "beasts/turtle.jpg", "beasts/snake.jpg" ] }
- The first three keys:
manifest_version
,name
, andversion
, are mandatory and contain basic metadata for the add-on. description
andhomepage_url
are optional, but recommended: they provide useful information about the add-on.icons
is optional, but recommended: it allows you to specify an icon for the add-on, that will be shown in the Add-ons Manager.permissions
lists permissions the add-on needs. We're just asking for theactiveTab
permission here.browser_action
specifies the toolbar button. We're supplying three pieces of information here:default_icon
is mandatory, and points to the icon for the buttondefault_title
is optional, and will be shown in a tooltipdefault_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 add-on.
web_accessible_resources
lists files that we want to make accessible to web pages. Since the add-on replaces images in the page with images we've packaged with the add-on, we need to make these images accessible to the page.
Note that all paths given are relative to manifest.json itself.
The icon
The add-on should have an icon. This will be shown next to the add-on'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".
Create the "icons" directory and save an icon there named "beasts-48.png". You could use the one from our example, which is taken from the Aha-Soft’s Free Retina iconset, and used under the terms of its license.
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".
Save an icon named "beasts-32.png" in the "icons" directory. You could use the one from our example, which is taken from the IconBeast Lite icon set and used under the terms of its license.
If you don't supply a popup, then a click event is dispatched to your add-on 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 add-on 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 panelchoose_beast.css
styles the contentchoose_beast.js
handles the user's choice by running a content script in the active tab
choose_beast.html
The HTML file looks like this:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="choose_beast.css"/> </head> <body> <div class="beast">Frog</div> <div class="beast">Turtle</div> <div class="beast">Snake</div> <script src="choose_beast.js"></script> </body> </html>
We just have an element for each animal choice. 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:
html, body { width: 100px; } .beast { margin: 3% auto; padding: 4px; text-align: center; font-size: 1.5em; background-color: #E5F2F2; cursor: pointer; } .beast:hover { background-color: #CFF2F2; }
choose_beast.js
In the JavaScript for the popup, we listen for click events. If the click was on one of our three animal choices, we inject a content script into the active tab. Once the content script is loaded, we send it a message with the animal choice:
/* Given the name of a beast, get the URL to the corresponding image. */ function beastNameToURL(beastName) { switch (beastName) { case "Frog": return chrome.extension.getURL("beasts/frog.jpg"); case "Snake": return chrome.extension.getURL("beasts/snake.jpg"); case "Turtle": return chrome.extension.getURL("beasts/turtle.jpg"); } } /* Listen for clicks in the popup. If the click is not on one of the beasts, return early. Otherwise, the text content of the node is the name of the beast we want. Inject the "beastify.js" content script in the active tab. Then get the active tab and send "beastify.js" a message containing the URL to the chosen beast's image. */ document.addEventListener("click", function(e) { if (!e.target.classList.contains("beast")) { return; } var chosenBeast = e.target.textContent; var chosenBeastURL = beastNameToURL(chosenBeast); chrome.tabs.executeScript(null, { file: "/content_scripts/beastify.js" }); chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { chrome.tabs.sendMessage(tabs[0].id, {beastURL: chosenBeastURL}); }); });
It uses three WebExtension API functions:
chrome.tabs.executeScript
to inject a content script found at "content_scripts/beastify.js" into the active tabchrome.tabs.query
to get the active tabchrome.tabs.sendMessage
to send a message to content scripts running in the active tab. The message contains the URL to an image of the chosen beast.
The content script
Create a new directory, under the add-on root, called "content_scripts" and create a new file in it called "beastify.js", with the following contents:
/* beastify(): * removes every node in the document.body, * then inserts the chosen beast * then removes itself as a listener */ function beastify(request, sender, sendResponse) { removeEverything(); insertBeast(request.beastURL); chrome.runtime.onMessage.removeListener(beastify); } /* Remove every node under document.body */ function removeEverything() { while (document.body.firstChild) { document.body.firstChild.remove(); } } /* Given a URL to a beast image, create and style an IMG node pointing to that image, then insert the node into the document. */ function insertBeast(beastURL) { var beastImage = document.createElement("img"); beastImage.setAttribute("src", beastURL); beastImage.setAttribute("style", "width: 100vw"); beastImage.setAttribute("style", "height: 100vh"); document.body.appendChild(beastImage); } /* Assign beastify() as a listener for messages from the extension. */ chrome.runtime.onMessage.addListener(beastify);
The content script adds a listener to messages from the add-on (specifically, from "choose_beast.js" above). In the listener, it:
- removes every element in the
document.body
- creates an
<img>
element pointing to the given URL, and inserts it into the DOM - removes the message listener.
The beasts
Finally, we need to include the images of the animals.
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:
beastify/ beasts/ frog.jpg snake.jpg turtle.jpg content_scripts/ beastify.js icons/ beasts-32.png beasts-48.png popup/ choose_beast.css choose_beast.html choose_beast.js manifest.json
Starting in Firefox 45, you can install WebExtensions temporarily from disk.
Open "about:debugging" in Firefox, click "Load Temporary Add-on", and select your manifest.json file. You should then see the add-on's icon appear in the Firefox toolbar:
Open a web page, then click the icon, select a beast, and see the web page change: