Writing Browser Extensions with Configuration
Tags: techtips, programming, javascript, browser
Published: 2020-06-26 13:42:58

---
title: Writing Browser Extensions with Configuration
published: true
date: 2020-06-26 11:33:02 UTC
tags: techtips,programming,javascript,browser
canonical_url: https://john.colagioia.net/blog/2020/06/24/store.html
---

As a quick note, I [released this post](https://john.colagioia.net/blog/2020/06/24/store.html) on my blog yesterday, so it can get to be (as I tend to be) a bit rambling.  Oh, and the original text is [on GitHub](https://github.com/jcolag/entropy-arbitrage) (licensed CC-BY-SA), so if anything seems muddy, by all means:

 * Leave a comment here,
 * Leave a comment on the blog,
 * File an issue on GitHub, or
 * Add a pull request!

* * *

This follows on to [my post from last week](https://dev.to/jcolag/writing-browser-extensions-4gkh).

{% link jcolag/writing-browser-extensions-4gkh %}

To recap it briefly, I have recently been investigating some possible projects that would benefit from having a simple browser extension to pass along real-time data on the user’s actions. It’s simple enough, but has enough detail to make a viable post…or two.

![Plugs](https://dev-to-uploads.s3.amazonaws.com/i/h1ssymqlthsti06ibt7w.png "Plugs")

In this case, our extension is going to report every visited URL to a configurable remote address. The [URL Rat](https://github.com/jcolag/url-rat) extension, by last week’s post, was able to record every visited URL and send that information to a hard-coded URL. So far, so good. I’ll assume that you read that, since it’s short.

But now, we need to make that user-configurable, so we need to work with the browser storage.

## Setting Configuration

First, we need to set up the extension to allow for some configuration work, which means adding some elements to `manifest.json`.

```json
"options_ui": {
  "page": "popup/configure.html",
  "browser_style": true
},
"browser_action": {
  "default_icon": "icons/urlrat32.png",
  "default_title": "URL Rat"
},
"background": {
  "scripts": [
    "background.js"
  ]
}
```

Each of the three does its small part.

### Browser Action

The `browser_action` element creates a toolbar button, in this case with an additional image that fits the button profile. All things considered, it’s pretty boring. Name and image.

The handlers go in…

### Background

In the `background.scripts` element, we list the files containing the toolbar button’s event handlers. In this case, we just have one file, since we’re only handling one click. But if we had multiple buttons and/or had multiple features, we might consider separating that code into multiple files, listing them there.

I’ll talk about our click-handler in a bit.

### Configuration Interface

Finally, the `options_ui` element tells the browser where to find the page with the configuration controls. In my case, I created a `popup` folder—even though it doesn’t actually pop up, but I initially considered that approach and never changed the name—where I’ve dumped all the code related to that options page.

These three items guide most of the work from here.

## Event Handlers

As mentioned, we keep the toolbar handlers in a script that the browser runs in the background. In our case, we don’t have much to do, so it’s just this.

```javascript
function handleClick() {
  browser.runtime.openOptionsPage();
}
browser.browserAction.onClicked.addListener(handleClick);
```

Just…open the options page when we hear that the button has been clicked.

Honestly, we don’t even need this, since the options page is going to be accessible from the list of browser extensions, but we might want to add features later and I wanted to make the extension visible, since it’s such a terrible idea to run it.

## Configuration

I’ll spare you the boring HTML and CSS for **URL Rat** ’s options page. It’s a form with controls. If you’re not familiar with how those work, I’m not being dismissive when I say that you can find a better explanation than I could write here in just about any HTML tutorial.

Instead, we’ll just focus on the JavaScript code, since that’s the part that interacts with the browser storage. There are a few pieces.

```javascript
function saveOptions(e) {
  browser.storage.sync.set({
    dest: document.querySelector('#dest').value,
    isActive: document.querySelector('#on').checked.toString()
  });
  e.preventDefault();
}
document.querySelector('form').addEventListener('submit', saveOptions);
```

Save options will take our two options (a URL and an on/off setting) and push them to `browser.storage.sync`, where we can pick them up later. I slightly reorganized the file for clarity, but the call happens when our options form is submitted.

In other words, the user clicks “Save” and the settings are stored in the browser.

```javascript
function restoreOptions() {
  var storageItem = browser.storage.managed.get();
  storageItem.then((res) => {
    setOptions(res);
  });

  var gettingItem = browser.storage.sync.get();
  gettingItem.then((res) => {
    setOptions(res);
  });
}
document.addEventListener('DOMContentLoaded', restoreOptions);
```

When we open the options page, we want the controls to reflect the saved data. So, `restoreOptions()` takes _two_ steps, instead of just the one involved in saving.

The first step is to pull the default options out of the `browser.storage.managed` area. After pushing that information to the options page, we check `browser.storage.sync` to see if the user has saved anything and, if so, set those options and overwrite the managed version.

The `setOptions()` function isn’t worth showing here, just a utility function to avoid duplicating some updates to the controls. You can find it in the repository along with the HTML and CSS, if you need it.

### Wait, What Default Values?

You noticed that there’s no way to populate the manage storage, too, right? Getting this to work was probably the least-appealing part of the process for me.

This may only be for Firefox, but we need a JSON file to get information there. You might remember that the `manifest.json` file included a `browser_specific_settings.gecko.id` element. Implausible as it sounds, we use that ID to identify a new JSON file that holds our default values. In this case, it’s literally named `urlrat@example.com.json`, because our identifier is that imaginary e-mail address. And it looks like the following.

```json
{
  "name": "urlrat@example.com",
  "description": "ignored",
  "type": "storage",
  "data":
  {
    "dest": "http://localhost:8080/",
    "isActive": "true"
  }
}
```

Copy, link, or move that file—depending on your preferences and how often you expect to update and keep the file around—to your `~/.mozilla/managed-storage/` folder, and you have now initialized your browser’s Managed Storage.

I *did* warn you that it wasn’t appealing.

## Use the Configuration

From here, we should already know what to do. The main extension code, `url-rat.js` for this example, now needs to replace its hard-coded values with values from the browser’s storage. I took the following approach.

```javascript
browser.storage.managed
  .get()
  .then((managed) => {
    browser.storage.sync
      .get()
      .then((local) => {
        var store = Object.assign(managed, local);

        // Assign the values in the "store" variable to individual
        // variables already used by the extension, and then put the
        // original extension code here.
      });
  });
```

I nested the `then()` handlers to make sure we have the data, even though that’s probably unnecessary, and then used [`Object.assign()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) to merge the two configuration objects into one.

Like the comment says, I should now have `store.dest` with the saved URL and `store.isActive` to decide whether to send the current URL to the server.

## Overall

All things considered, if we ignore the managed storage shenanigans, the development process seems smooth. The only real snag I hit was in not realizing that the `sync` and `managed` storage areas were different, breaking the configuration page.

Also, the configuration page is ugly.

Other than minor issues like that, though, the fact that it only took a couple of hours in total to hack out a fully functional browser extension that performs a defined task starts to make this look appealing for certain kinds of projects.

* * *

**Credits** : The header image is [Untitled](https://pxhere.com/en/photo/779821) by an anonymous PxHere user, made available under the terms of the [CC0 1.0 Universal Public Domain Dedication](https://creativecommons.org/publicdomain/zero/1.0/).