Write a Firefox Extension in Python
Tags:
Bottom Line: One can write a Firefox extension in (mostly) Python via Pyodide.
Disclaimer: I’m not a huge fan of JavaScript, and I don’t use it much, so I am likely not following best practices. I’ve also never written a Firefox extension, so the below is pretty bare-bones, but hopefully enough to get you off the ground.
With the recent amazing advancements in Python and wasm, brought to us in large part by way of Pyodide (repo) and PyScript (repo), I thought it would be interesting to try to build a Firefox extension in Python.
I found a very helpful Medium article and corresponding GitHub repo
for building a Chrome extension in Python, which provided some examples and a
framework. I wanted to do things a little differently (for no good reason) –
specifically, I didn’t want to rely on an html
-based pop-up page, which that
project uses to load all the JavaScript files.
I struggled to get PyScript to work in the way I wanted, but I was eventually able to get Pyodide to help me create an extension that contains its own Python wasm runtime (and therefore doesn’t need to load it from their web-hosted version and should be a little snappier to load in some cases).
To try out the toy extension:
- clone the example repo, which is at https://github.com/n8henrie/python_firefox_ext.git
- inspect (for safety)
setup.sh
(MacOS / probably Linux) orsetup.ps1
(Windows) and afterwards run them; this will download the necessary files from Pyodide so you can embed them in your extension.- You can also consider changing the script to download the debug version during development
- open Firefox to
about:debugging
- click the link for
This Firefox
- click
Load Temporary Add-on...
- Select the
manifest.json
from the cloned repo
In short, I found that you can import pyodide.js
in your manifest.json
using a local path. That defines a function loadPyodide
, which can accept an
object with an indexURL
argument. manifest.json
then loads a local
JavaScript file, hello.js
, which calls loadPyodide
with indexURL
set to
a local path to the rest of the necessary files.
From here, loading and running some Python is a little janky, but seems to work
– I just read the contents of hello.py
and pass it (as a string) to
pyodide.runPython
. One reason I wanted to structure things this way is it
allows me to use my usual Python workflow to write / edit / lint / format the
Python code.
In hello.py
, I demonstrate very basic functionality for both a
content_script
extension, which can modify the content one sees, as well as a
background extension, which has access to inspect, open, and close tabs (among
many other things). To demonstrate the content_script
functionality, the
extension sets a red border around the currently open webpage. In
manifest.json
, I restrict the extension to only run this content script on
n8henrie.com
, so if you open a page to my site you should see a red border.
For the background script functionality, I print out a list of currently open
tabs into the devtools console; to view this, click the Inspect
button in
Firefox’s about:debugging
tab, then go to the Console
tab. I also print out
the current webpage’s URL, and open a new page to this blog post (which should
get a red border).
This was a fun project, if a little frustrating to sort out (given my unfamiliarity with JavaScript). If you have any recommendations or other example projects, I’d love to hear about it in the comments below!