This article is a technical comparison of the Add-on SDK and WebExtensions. It's intended to help orient people who have an add-on that uses the SDK, and who are planning to port the add-on to WebExtensions.
If you're planning to port an overlay extension or a bootstrapped extension, see Comparison with XUL/XPCOM extensions.
The basic structure and concepts of the Add-on SDK are shared by WebExtensions. Both technologies include:
- manifest files defining metadata for the add-on and some aspects of its behavior.
- persistent scripts that get access to a set of privileged JavaScript APIs and that stay loaded for as long as the add-on itself is enabled.
- content scripts that can be injected into web pages, and that can communicate with persistent scripts using an asynchronous messaging API.
- the ability to add specific UI elements, such as buttons, to the browser. Buttons in turn can have popups that are defined using HTML, JavaScript, and CSS.
- a command-line tool that developers can use to test their add-ons.
Beyond these broad similarities, there are a lot of differences in the details, and these are summarised in the following sections.
Manifest files
In both technologies you have a JSON manifest file in the add-on's root directory. In the SDK this is called "package.json", while in WebExtensions it's called "manifest.json". Both files contain basic metadata such as the add-on's name, description, and icons.
However, "manifest.json" includes many keys that define parts of the add-on's capabilities and behavior, which in the SDK are more often defined in code. For example:
Feature | Add-on SDK | WebExtensions |
---|---|---|
Content scripts matching URL patterns | page-mod API |
content_scripts key |
Toolbar buttons | ui/button/action API |
browser_action key |
Access privileged APIs | require() function |
permissions key |
This makes developing WebExtensions more declarative and less programmatic, compared with SDK add-ons.
With the SDK you'll typically use jpm init
to create a new package.json. WebExtensions don't have an equivalent of jpm init
, so you'll probably write the manifest from scratch or copy and adapt an existing one.
Persistent scripts
Both technologies have the concept of persistent scripts that stay loaded for the add-on's lifetime, have access to privileged APIs, and can communicate with other parts of the add-on such as content scripts.
In the SDK this script is by default called "index.js", and it can load other scripts using the module loader.
In WebExtensions these scripts are called "background scripts". You can define a set of scripts using the background
manifest key, and they will all be loaded into the same document, which is a hidden, auto-generated, blank HTML page. You can also define your own custom document using the background
key.
An important difference is that background scripts get a window
global, with all the DOM objects you'd expect to be present on a window. This makes writing WebExtensions more like writing web pages, with direct access to all the normal Web APIs like XMLHttpRequest or IndexedDB.
Also note that WebExtensions have a Content Security Policy applied to them. You can specify your own policy, but the default policy, among other things, disallows potentially unsafe practices such as the use of eval()
.
Learn more
Content scripts
In both the Add-on SDK and WebExtensions, persistent scripts can't directly access the content of web pages. Instead, add-ons can attach content scripts to web pages. These scripts:
- do get direct access to web content
- don't have access to privileged APIs
- can communicate with the persistent scripts using a messaging API.
In both technologies, there are two ways to attach scripts: you can automatically attach a set of scripts to pages whose URL matches a given pattern, or you can programmatically attach a script to the page hosted by a given tab. The way to do this is different in each technology, though:
Operation | Add-on SDK | WebExtensions |
---|---|---|
Attach scripts to pages matching URL pattern | page-mod API |
content_scripts key |
Attach scripts to pages hosted by a tab | tab.attach() |
tabs.executeScript() |
The match patterns used for URLs are different:
In both technologies you can pass options to control when the script runs and whether it will be attached to subframes. WebExtensions don't include an equivalent of contentScriptOptions
, though.
In both technologies, content scripts can communicate with persistent scripts using an asynchronous messaging API:
Operation | Add-on SDK | WebExtensions |
---|---|---|
Send message | port.emit() |
runtime.sendMessage() / tabs.sendMessage() |
Receive message | port.on() |
runtime.onMessage |
- Communicating with persistent scripts in the SDK
- Communicating with persistent scripts in WebExtensions
In both cases, content scripts can communicate with scripts loaded by the page using window.postMessage
and window.addEventListener
.
In both technologies, have access to the page they're injected into, but get "a clean view of the DOM", meaning that they don't get to see modifications made to the DOM by scripts loaded by the page.
To pass configuration options to a content script instead of defining values on self.options
from the add-on logic, you would either have to send those via messaging or store them in storage.local
.
In the SDK, content scripts can share objects with page scripts, using techniques like unsafeWindow
and createObjectIn
. The unsafeWindow
is available via the wrappedJSObject
instead. All the export helper functions are available, too.
Learn more
UI elements
Both technologies provide APIs to create a UI for your add-on. UI options for WebExtensions are more limited.
UI element | Add-on SDK | WebExtensions |
---|---|---|
Button | ui/button/action |
browser_action / page_action |
Toggle button | ui/button/toggle |
browser_action / page_action |
Toolbar | ui/toolbar |
None |
Sidebar | ui/sidebar |
None |
Panel | panel |
browser_action / page_action popup |
Context menu | context-menu |
contextMenus |
Panels and popups
Panels and popups are both transient dialogs specified using HTML, CSS, and JavaScript.
Unlike panels, popups are always attached to a button (either a browser action or a page action) and can't be displayed programmatically: they are only shown when the user clicks the button.
Also unlike panels, popup scripts get access to all the same APIs that background scripts do. They can even get direct access to the background page, via runtime.getBackgroundPage()
.
Settings
The Add-on SDK and WebExtensions both have some support for settings (sometimes also called options or preferences).
With the SDK you can define preferences using a preferences
key in package.json. The user can see and change these preferences in the add-on's entry in the Add-ons Manager. The add-on in turn can listen for changes using the simple-prefs
API.
With WebExtensions you have to implement your own UI for presenting settings, and your own code for persisting them. You do this by writing an HTML file that presents the settings UI, which can include a script for persisting the settings. The script gets access to all the WebExtensions APIs, and it's generally expected that you should use the storage
API to persist settings.
You then assign the HTML file's URL to the options_ui
key in manifest.json. Your settings page then appears in the add-on's entry in the Add-ons Manager.
Note that WebExtensions does not give you access to the browser's own preferences (that is, the preferences exposed in the SDK by preferences/service
).
Learn more
Internationalization
The Add-on SDK and WebExtensions both include tools for localizing user-visible text. They offer mostly similar functionality:
Feature | Add-on SDK | WebExtensions |
---|---|---|
Strings in add-on scripts | Yes | Yes |
Strings in content scripts | No | Yes |
Strings in HTML | Yes | No |
Strings in CSS | No | Yes |
Title & description | Yes | Yes |
Plural forms | Yes | No |
Placeholders | Yes | Yes |
In both systems, you supply localized strings as a collection of files, one for each locale.
To retrieve localized strings in add-on code, there's a JavaScript API - l10n
in the SDK and i18n
in WebExtensions - that returns a localized string given an ID.
WebExtensions don't have direct support for localizing strings appearing in HTML, so you have to do this yourself, using JavaScript to retrieve localized strings and to replace the HTML with the localized version.
Learn more
- WebExtensions Internationalization guide.
- Example internationalized WebExtension.
- Example script for WebExtensions to translate HTML in the SDK style
Command-line tool
The Add-on SDK comes with a command-line tool, jpm, that you can use for testing and packaging add-ons. There's an equivalent tool for WebExtensions, called web-ext. web-ext doesn't yet support all the same commands that jpm does, but it has the basics: run, build, and sign.
It's also now possible to install (and reload) SDK add-ons and WebExtensions in Firefox from their source directory, without needing to package them as an XPI. See Temporary Installation in Firefox.
Learn more
JavaScript APIs
In both the SDK and WebExtensions, the main power of the add-on comes from a set of dedicated JavaScript APIs. For most of the SDK high-level APIs, there is a WebExtensions equivalent.
One big limitation of WebExtensions compared with the SDK is that SDK add-ons can use require("chrome") to get access to the full range of XPCOM APIs in Firefox. This is not possible in WebExtensions.
To access privileged APIs in the SDK, you use require():
var tabs = require("sdk/tabs"); tabs.open("https://developer.mozilla.org/");
In WebExtensions most APIs are made available already, with no need to import them:
chrome.tabs.create({ "url": "https://developer.mozilla.org/" });
For some WebExtension APIs, you need to ask permission first, using the permissions
manifest.json key. In the example below, WebExtensions need to ask for the "tabs" permission if they want access to the tab's URL:
manifest.json:
... "permissions": [ "tabs" ] ...
background script:
function logUrl(tabs) { console.log(tabs[0].url); } chrome.tabs.query( {active: true, currentWindow: true}, logUrl );
Add-on SDK => WebExtensions
The tables in this section list every SDK API and describe what the equivalent WebExtensions API would be, if there is one implemented in the current Developer Edition.
The first table covers high-level SDK APIs, the second covers low-level APIs.
High-level APIs
Add-on SDK | WebExtensions |
---|---|
addon-page | Use tabs.create() to load pages packaged with your add-on into normal browser tabs. |
base64 | window.atob() and btoa() |
clipboard | document.execCommand without using select() and similar in the background page. |
context-menu | contextMenus |
hotkeys | commands |
indexed-db | window.indexedDB |
l10n | i18n |
notifications | notifications |
page-mod | content_scripts |
page-worker | Use the background page, or load remote iframes into the background page. |
panel | See UI elements above. |
passwords | None |
private-browsing | Tab.incognito and Window.incognito . |
querystring | window.URLSearchParams |
request | window.fetch or window.XMLHttpRequest |
selection | Use a content script that sends the selection data to the add-on. |
self | runtime.getManifest() and extension.getURL() for data.url() |
simple-prefs | storage and options_ui |
simple-storage | storage |
system | Partly provided by runtime . |
tabs | tabs |
timers | alarms |
ui | See UI elements above. |
url | window.URL |
widget | None |
windows | windows |