WebExtensions/Hacking
The core WebExtension code lives in the same repository as the Firefox browser. If you've never hacked on Firefox before, there is a quick guide to getting started, which will show you how to check out the code and do your first build.
Contents
Code Style
WebExtension JavaScript code follows the Mozilla coding style guide, with the following differences:
-
===
may be used in favor of==
when doing so makes sense. - The opening brace in function statements or expressions should be on the same line as the
function
keyword. -
let
orconst
should be used in place ofvar
wherever possible. - Function arguments and variable names should never use Hungarian Notation.
- All JavaScript files or inline
<script>
nodes must begin with"use strict";
- All members in multi-line object or array literals must be followed by a comma, including the last.
- Do not use proprietary, Mozilla-specific JavaScript extensions, including array and generator comprehensions,
catch
guard expressions, the__proto__
property, or theyield
operator in non-star-functions. - Use arrow functions in preference to function expressions where appropriate.
- Use rest parameters in preference to the
arguments
object where possible. - Use default parameters for optional arguments where appropriate.
- Use spread expressions in preference to
.apply()
where possible.
ESLint will enforce most of these rules.
Code Guidelines
For Chrome APIs we'll support the callback interface to maintain compatibility. For new APIs, not Chrome ones, we'll use the promise interface to all APIs. See the MDN docs for the difference.
Checking your code with ESLint
All WebExtension directories in mozilla-central
are configured with a set of ESLint rules, which all code is required to pass. Many of these rules help to catch serious errors, such as references to non-existent variables, or a missing trailing comma turning an array literal into an array dereference, so it is extremely important that you check you code against them before it lands.
The simplest way to check your code is using the mach eslint
command. This requires that you have NPM installed.
To setup ESLint, you need to run the following once:
./mach eslint --setup
This should install ESLint, along with the necessary plugins. After that's done, you can check the files you've been working on with the following:
./mach eslint path/to/file.js
Or you can check all WebExtension code with:
./mach eslint toolkit/components/extensions browser/components/extensions toolkit/modules/addons
Integrating ESLint with your editor
Since manually checking your code isn't always practical (and is easy to forget), it's best to integrate automatic checking with your code editor. Many editors have integration plugins, or built-in support.
Some editors may need special settings to work well with our codebase. Feel free to add other editors below, as you see fit.
There is some additional useful information in the developer tools hacking section of the wiki.
Vim
The easiest way to integrate ESLint with Vim is using the Syntastic plugin.
The following assumes that you use the Vundle package manager. It should be easy enough to adapt to any other package manager you happen to prefer, though.
mach eslint --setup
installs a specific ESLint version and some ESLint plugins to a subdirectory of the repository (changed in Firefox 55).
You should configure Syntastic roughly as follows (change /path/to/mozilla-central
as needed):
" Initialize Vundle. filetype off call vundle#rc() " Enable the Syntastic bundle. Bundle 'scrooloose/syntastic' " Enable the specific ESLint checker for files in mozilla-central/ only. " Enable the HTML plugin, and enable JavaScript linting for HTML files. autocmd FileType javascript,html \ if stridx(expand("%:p"), "/mozilla-central/") != -1 | \ let b:syntastic_checkers = ['eslint'] | \ let b:syntastic_eslint_exec = '/path/to/mozilla-central/node_modules/.bin/eslint' | \ let b:syntastic_html_eslint_args = ['--plugin', 'html'] | \ endif
After you've added this to your configuration (and have installed Vundle, if necessary), launch Vim and run:
:BundleInstall
This will install the Syntastic plugin. After this, you should be good to go.
Version control hooks
The mozilla-central
tree contains a Mercurial plugin to automatically check any changed files, using ESLint, when you commit. This can be enabled by adding the following to your global .hgrc
file, or to .hg/hgrc
in your repository:
[extensions] # Replace "~/src/mozilla-central/" with the full path to your mozilla-central repository. mozeslint = ~/src/mozilla-central/tools/mercurial/eslintvalidate.py
On non-Windows systems, this can be achieved with:
printf '[extensions]\nmozeslint = %s/tools/mercurial/eslintvalidate.py' "$(pwd)" >>.hg/hgrc
Now Mercurial will check your code for you, and show any warnings or errors, every time you commit.
Unit tests
All WebExtension code is required to be covered by comprehensive unit tests. There is some information on MDN about the various test suites used in Mozilla code. WebExtension code primarilly uses:
- Mochitests
- These reside under
toolkit/components/extensions/test/mochitest/
, and are used to test most APIs that live in thetoolkit/components/extensions/
directory. - Browser chrome mochitests
- These reside under
browser/components/extensions/test/browser/
, and are used to test most APIs that live in thebrowser/components/extensions/
directory. - xpcshell tests
- These reside under
toolkit/components/extensions/test/xpcshell/
, and are used to test low-level modules which do not require a browser UI, including those undertoolkit/modules/addons/
,toolkit/components/utils/simpleServices.js
, and various pieces of C++ code.
WebExtensions/Try_Server has more information about using the try server to test WebExtension code.
Test tag
All tests in WebExtensions are now tagged with the tag: webextensions
, so you can pass the --tag
argument to mochitest
, xpcshell-test
and the try server syntax.
Code coverage tests
Ideally, our test suite should exercise as close to 100% of our codebase as possible. 100% coverage may not always be practical, or desirable, but it's still an ideal we'd like to strive for. To that end, we run code coverage tests weekly, for the above three main test suites. If you land any code in a given week, it's best to check the end-of-week coverage tests to make sure it has adequate test coverage.
The test results are published in three separate sets:
- Total code coverage
- This is code coverage for all code run in either the main browser process or in a tab content process.
- Main process code coverage
- This is code coverage for code run in the main browser process, excluding code run only in a content process.
- Content process code coverage
- This is code coverage for code run in a tab content process, excluding code run only in the main browser process.
In general, the total code coverage numbers are what we focus on. However, it is extremely important for code which is expected to run in both the main browser process and in a content process to be tested in both. If you know that your code falls into this category, please check that it's tested appropriately.
The above results are generated using this patch, which could generously be described as a fairly gross hack. If you'd like to run the tests yourself, you can do so with something like the following:
# Install the Istanbul code coverage tool npm install -g istanbul
# Apply the code coverage patch hg import https://people.mozilla.org/~kmaglione/webext-coverage.patch
# Instrument all WebExtension code, run the various test suites, and # generate the coverage output files. ./toolkit/components/extensions/test_coverage.sh
Code layout
WebExtension code lives in several different parts of the tree, depending on its purpose:
- Generic APIs
- These are the base APIs which don't rely on a browser UI.
- They are defined in files matching
toolkit/components/extensions/ext-*.js
- Each of these APIs also has a schema definition file, located in
toolkit/components/extensions/schemas/
- Content Script APIs
- These are a very simple set of APIs which are available to content scripts, and run in the content process.
- They are all defined in a single file,
toolkit/components/extensions/ExtensionContent.jsm
- Firefox Desktop APIs
- These are APIs which are specific to desktop Firefox, and generally deal directly with the browser UI.
- They are defined in files matching
browser/components/extensions/ext-*.js
- Each of these APIs also has a schema definition file, located in
browser/components/extensions/schemas/
- Low-level helper modules
- These modules deal with some of the low-level functionality required by WebExtension code, including match pattern handling and web request monitoring.
- They live in
toolkit/modules/addons/
- Core WebExtension modules
- These deal with the core functionality of WebExtensions, including initializing and tearing down extension instances.
- They live in files matching
toolkit/components/extensions/*.jsm
- XPCOM components
- These are primarily stub interfaces to allow native code to interact with WebExtension management code.
- They are currently all defined in a single file,
toolkit/components/utils/simpleServices.js
API modules
All API modules are loaded into a single module, toolkit/components/extensions/Extension.jsm
, and share the same global namespace. All global variables in the Extension.jsm
module are automatically available to code in the ext-*.js
files. Code in these files may make variables available to other modules by defining them as properties of the global
object. For instance, if one module defines global.FooBar = {}
, other modules may access it as simply FooBar
.
Each API module must be explicitly registered, in order to be loaded. It must also register a schema if it exports any APIs to extensions.
Starting from Firefox 50 (Bug 1285063, hg commit e9ca8dc4b42e) all the APIs schema and ext-*.js
files must be registered to the category manager through one of the following manifest files:
- toolkit/components/extensions/extensions-toolkit.manifest, for Generic APIs registered at toolkit level
- browser/components/extensions/extensions-browser.manifest, for Firefox desktop APIs
- mobile/android/components/extensions/extensions-mobile.manifest, for Firefox for Android APIs
As an example, here is a snippet of the extension-toolkit.manifest
:
# scripts category webextension-scripts alarms chrome://extensions/content/ext-alarms.js ... # schemas category webextension-schemas alarms chrome://extensions/content/schemas/alarms.json
In previous Firefox versions, schemas and ext-*.js
files were statically
registered through Extension.jsm
and nsBrowserGlue.js
source
files:
Generic APIs were registered in Extension.jsm
as follows:
ExtensionManagement.registerScript("chrome://extensions/content/ext-foobar.js"); ExtensionManagement.registerSchema("chrome://extensions/content/schemas/foobar.json");
Firefox desktop APIs were registered in nsBrowserGlue.js:
ExtensionManagement.registerScript("chrome://browser/content/ext-bazquux.js"); ExtensionManagement.registerSchema("chrome://browser/content/schemas/bazquux.json");
Permission strings
The strings for permissions can be found in browser.properties.
If you add an API that requires permissions, then you'll need to make sure the corresponding permission strings are also landed.