At CoApp, we’ve developed a feature-rich Chrome (Chromium-based) browser extension as a cornerstone of our product offering. In doing so, we’ve learned a great deal about extension development in Chrome and the APIs it offers for message passing, tab management, storage, and more. These capabilities allow our flagship browser extension to carry out its objectives effectively.
Our Chrome extension source code is also supported by other Chromium-based browsers, such as Edge and Brave. This means source code portability within the Chromium ecosystem is excellent and requires no additional work from us as developers. This raises the question: what does it take to build an extension for a browser that is not Chromium-based?
Recently, we developed a Firefox-compatible add-on, which puts us in a unique position to share key points of comparison between the two ecosystems.
For the remainder of this article, we will focus on answering that question as it relates to Firefox, which is built on the Gecko engine. We will compare and contrast the two browser ecosystems in the context of extension development and share takeaways from our experience building an extension compatible with Firefox.
Chrome == Firefox
Lets start with some similarities between Chrome and Firefox extension development:
- both ecosystems expect an extension to contain a manifest
- The manifest is the blueprint of an extension and informs the browser of what your extensions intended functions are based on the permissions you define for your extension, the files necessary for your browser extension to function, and other important metadata like your extensions name and current version
- both ecosystems expose APIs for interacting with the browsers different storage mechanisms (local storage, IndexedDB, etc.), message passing, accessing manifest metadata at runtime, etc.
- both ecosystems have separate “sides of the house”, those sides being the content script side and the background side
- content scripts run in the context of a web page and are responsible for interacting with the web pages content (DOM manipulation)
- background scripts run independent of any tab or web page and for example, can be used to receive and respond to messages from content-scripts
- both ecosystems support the idea of self-hosting or in-store hosting of your browser extension
As you may already tell, there are a lot of similar ideas going on between the two ecosystems as it relates to browser extensions, and that is for good reason. Browsers are extremely common tools nowadays and, by extension (punny, I know), so are browser extensions because of how they allow the developer world to take the native capabilities of a browser into the stratosphere. Clearly, the similarities are not by coincidence, and a concerted effort has been made by major browser providers to ensure as much functionality as possible can cross browsers. This is similar to how web standards like the MutationObserver API are generally defined as an interface that each browser implements. The similarities are great and all, literally, but the more interesting aspect of cross-browser extension development lies in the differences and how, as developers, those differences impact how we go about extension development.
Chrome <> Firefox
Lets dive into some key differences between the two browsers when it comes to extension development:
- Name
- In Chrome, they are referred to as “extensions”
- In Firefox, they are referred to as “add ons” or “extensions”
- Extension file extensions
- In Chrome, extensions have a .crx file extension
- In Firefox, add ons have a .xpi file extension
- Manifest compatibility
- Chrome has fully and aggressively migrated to Manifest V3, siting security as the main reason for deprecation of V2
- Firefox has not fully deprecated Manifest V3 due to it hamstringing existing add ons networking capabilities, specifically popular ad blocking add ons
- Firefox is committed to fully supporting Manifest V2 for the foreseeable future and will provide 12 months notice before any deprecation of support of Manifest V2
- Extension ID
- In Chrome, it cannot be directly defined in the manifest and is either defined by the Chrome Web Store when you publish an extension on the store or it is derived from the extensions public key when self-hosting your extension
- In Firefox, it can be defined by populating the browser_specific_settings.gecko.id property of the extensions manifest which is required for self-hosting
- Programmatic extension/add on signing
- In Chrome, you can provide a .pem file to sign your extension using the crx3 CLI tool
- You can bring your own .pem or use the one Chrome outputs when packaging an extension with Developer Mode on in chrome://extensions
- In Firefox, you can use the web-ext CLI tool with a pre-generated signing key/secret pair from the Add On Developer Hub
- You must generate a signing key/secret pair from Mozillas Add On Developer Hub
- In Chrome, you can provide a .pem file to sign your extension using the crx3 CLI tool
- Self-hosted updates
- In Chrome, you provide an update_url property at the top level of your manifest
- In Firefox, you provide an update_url property in the browser_specific_settings.gecko.update_url
The differences may present as subtle, and admittedly, many are just that, subtle differences that do not meaningfully impact the way one would go about standing up a browser extension/add on in either ecosystem. Others deeply impact how we go about developing and distributing an extension/add on in either ecosystem.
Add On Development for Firefox
How do I install my add on when I am developing?
The first noticeable difference from a developer perspective is the way you load in the extension you are developing. In Chrome, you load in your extension unpacked by selecting the folder containing your source code and manifest file, and in Firefox, you only select your manifest file. You can install your “Temporary Add-on” by visiting about:debugging and clicking “This Firefox.” Here, you will have the option to upload your add-on temporarily. This is also the space you’d visit when you want to inspect the logs from your background scripts, observe outbound network requests, look through persistent data stores like local storage or IndexedDB, and so on.
Can I use the chrome.* namespace in a Firefox add on?
Another key consideration to make when developing a Firefox extension is whether you want to access the WebExtension APIs through the chrome.* or browser.* namespace. Firefox supports both the chrome.* and browser.* namespaces for accessing key WebExtension APIs like .runtime.getManifest() and .storage.local.get(), and Chrome only supports the chrome.* namespace unless you choose to polyfill the browser.* namespace. The reason the chrome.* namespace is supported natively within Firefox is so it is simpler to port an extension over to Firefox from Chrome. With that, there exists a total possibility that no changes need to be made to the source code of your extension for it to function in either browser, which us developers love. Naturally, you’d want to build with the chrome.* namespace if you want easy portability between the browser ecosystems for your extension. So the long and short is, yes, you can absolutely use the chrome.* namespace when developing an add-on for Firefox. In fact, it’s recommended if portability of your source code is important for your use case!
Why do I get an error from Firefox when I use the background.service_worker property in my manifest?
If you are coming from Chrome extension development, you may be familiar with defining a background.service_worker in your manifest that points to the entry point of your scripts that run in the background context of your extension. If you attempt to use this in Firefox, you will receive a “background.service_worker is currently disabled. Add background.scripts.” error, and your temporary add-on will be rejected by the browser. This is an intricacy between Chrome and Firefox’s decisions when it comes to supporting Manifest V3. At the time of writing, Firefox does not support the background.service_worker property in the manifest and relies on Manifest V2’s usage of background.scripts to define event listeners and other aspects of your extension that should run in the background.
An important note here is that with the background.scripts array in Firefox, you need to define your scripts in the order that the declarations and definitions are expected to be used, as they are loaded and executed in order. For example, if you have an add function defined in an add.js file and want to use it in your doMath.js file, the add.js file must appear earlier in the background.scripts array of your manifest. Otherwise, you’ll get a ReferenceError: add is not defined when trying to use the add function in doMath.js.
Add On Distribution for Firefox
Similar to Chrome, you have the choice of hosting your Add On for distribution on the Add Ons store or you can self-host your add on. At CoApp, we chose to self-host our add on to simplify distribution for our enterprise customers. There are a few key aspects one needs to keep in mind when self-hosting an add on for Firefox. Firstly, you are required to provide an update_url in your browser_specific_settings.gecko configuration (where your add on ID goes) and secondly, host an update manifest that holds the current version of your add on and the publicly available update_link. Examples are provided below:
update_url definition in the add ons manifest
update manifest definition
The update manifest is key if you are self-hosting your extension. In fact, this is what allows the browser to update on the fly (without a browser restart) when there is a new version of the extension available!
Add On Deployment for Firefox
Using the web-ext tool, deploying a Firefox add on via a dedicated CI/CD pipeline is fairly straightforward. All that is needed is a signing key/secret pair that can be obtained from the Add On Developer Hub before you can start using the web-ext tool. Our workflow is just an example, but we choose to distribute our Firefox add on using CloudFront for content delivery and we store our update manifest and .xpi files in S3 following the below steps
- Get the currently defined version from the manifest file
- Build and sign the add on using the web-ext CLI tool in the workflow
- Rename the .xpi that web-ext outputs to your defined schema (we follow <env>-<version>.xpi)
- Update the update manifest with new version and update_link properties
- Upload the new .xpi and update manifest to S3
- Invalidate the CloudFront distribution to ensure the files are immediately available to consumers
Next Steps @ CoApp
We have went through quite a few iterations and improvements for our browser extension compatible with Chrome and continue to improve upon it whenever we can. Notably with Chrome, we started by distributing our extension on the Chrome and Edge stores and came to the conclusion it was not for us due to the unpredictability of the review cycle. The review cycle would take anywhere from hours, to many days in some cases, with no consistent patterns. This made rolling out versions difficult and required us to back support old extension versions frequently in our accompanying web application. Due to the aforementioned pain points, we moved to self-hosted and have not looked back. I bring this up because we learned our lesson and were able to start off fresh with the Firefox extension by going self-hosted from the start. Now, the inverse is happening! With what we’ve learned about pipelining the deployments of our Firefox add on, we’ve learned more about building and signing extensions in a pipeline and will begin looking into introducing the crx3 tool to automate the builds of our Chrome extension .crx’s. This will remove what is currently a manual build step where we have to zip, pack, and upload our new extension versions.
Conclusion
All in all, transitioning from Chrome extension development to Firefox add on development is not especially difficult. There are some considerations to keep in mind, such as how you plan to deploy new versions or distribute the add on. However, as with other browser-related development, there is significant overlap between the browser ecosystems. This makes life as an extension developer a pretty rosy one.
Leave A Comment