This post is the sequel Your first Firefox (Web)extension in Kotlin. You should read it for the requirements and the basic setup of a Firefox extension written in Kotlin.
In this post we are going to rewrite Your second extension from the Mozilla tutorials in Kotlin. The extension consists of a toolbar button with a popup that allows you to replace the current tab’s content with an image of a beast.
The interesting bit about this addon is that, unlike the previous one, it needs to interface with the WebExtensions API. We will explore two possibilities to do this, one type safe but a bit tedious way and one dynamic way that involves less typing.
Note: You can get the complete project over at https://github.com/cypressious/second-firefox-extension-kotlin.
The main entry to our extension is a toolbar button. When clicking the button, a popup is shown where you can select the beast you want to show.
Because extension scripts run isolated from the content process, a script running in a toolbar popup can’t manipulate the DOM of a tab directly. That’s why we require a second content script that is injected in the current tab which handles the DOM manipulation. The two scripts will then communicate via messages.
As required for all extensions, we need to declare a
manifest.json that tells the browser about the capabilities of our extension.
The first obstacle
As discussed earlier, our extension will consist of two separate script files. However, this poses a small problem because the Kotlin JS compiler merges all the Kotlin code to a single JS file. KT-6168 tracks the feature request to allow compiling to multiple files.
To overcome this limitation we will create two modules, one containing the popup script and one containing the content script. Here are the
setting.gradle files required for the setup:
The Kotlin code will be placed in
Implementing the popup
manifest.json we declared that our extension has a toolbar button and that clicking it will show a popup whose layout is located in
Even though we write our code in Kotlin, the HTML has to reference the compiled JS output as well as the mandatory
The entry point to our code is a
main function that runs when the popup is opened. It immediately injects the content script in the current tab and then listens for clicks in the popup.
Injecting a script is asynchronous and returns a
Promise which Kotlin JS supports out of the box.
Working with external declarations - the static way
To interact with the browser, we use the top-level property
browser which contains properties for all the different APIs. However, the Kotlin compiler can’t magically know about these declarations which is why we have to write them down first. For this usecase, the language offers the
The process of writing these sorts of declarations is basicly reading the API documentation and manually translating it to Kotlin. The translation is straight forward and we don’t have to declare properties we don’t use. For example, the parameter for
executeScript has 6 different properties but because we only need the one named
file, we omit the other 5.
The compiler can’t verify that the declarations are actually correct so it takes our word for it and lets the code compile.
UPDATE: To save you the work of typing the declarations yourself, you can now include the automatically generated declarations from https://github.com/cypressious/kotlin-webextensions-declarations.
Next, we implement the listening for clicks and sending a message to our content script.
We set a click listener for the whole document. When an element is clicked, we query the active tab (because we need the id when we want to send a message). We then call
handleClick which shows or hides the content of the current tab and sends a message to show or hide the image of a beast.
jsObject . We use the special built-in function
dynamic meaning the compiler won’t perform any type checks and will let as us make any calls on it. We can use this to assign arbitrary properties. The resulting syntax on the call-site even looks a bit like an object literal.
The code depends on some more
external declarations which you can find on Github.
Implementing the content script
The content script runs directly in the tab and listens for messages from the popup. Here’s the whole code:
To prevent the script from running multiple times, we set a property
hasRun of the
true. We use the built-in function
asDynamic to cast
dynamic so that we can read and write arbitrary properties on it.
Next, we listen for messages and upon receiving them either display or remove an image of a beast.
Working with external declarations - the dynamic way
Again, we need to call an API defined in the external property
browser. As an alternative to writing the whole declaration, we can take a shortcut and simply declare
external val browser: dynamic
As we’ve already seen, this lets us make any calls on the object but we sacrafice any compile-time safety.
Testing the extension
To launch an instance of Firefox with our extension installed, we run
./gradlew runDceKotlinJs — continuous
Changes to the code will be picked up nearly instantly.
Debugging the content script
If we need to, we can also take a look at the code in the browser. To debug the content script, open the developer tools and click on Debugger. You should be able to see the (Kotlin!) code of the content script and even set breakpoints.
Debugging the popup script
To debug the popup script, open
about:debugging and click on “Debug” beneath your extension.
A new developer tools window should open. Make sure to toggle the visibility for popup scripts in the top-right corner.
Unfortunately, here we only see the generated JS code. There is an open issue on the Firefox bugtracker, so hopefully this is fixed soon.
For debugging other types of scripts, take a look at https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Debugging.
In this post we saw how to write a moderately complex Firefox extension in Kotlin. During our journey we needed to solve a couple of problems:
- To generate multiple JS output files, we needed to make multiple modules.
- To call externally defined APIs we needed to either write the definitions by hand or to give up type safety.
- To create JS objects, we needed a magical function to write some plain JS
Aside from those, the coding experience was quite pleasant. Kotlin’s syntax and language features made the code concise and fun to write.
UPDATE: To make calling the WebExtensions API easier, I’ve created a tool that programmatically generates the declarations from the official schema. You can simply include the declarations by following the instructions at https://github.com/cypressious/kotlin-webextensions-declarations.