Emacsify Firefox init
Published on 2023-08-14

I wanted to deploy Firefox the same way I deploy Emacs, namely version-controlled plain text config files as the single source of truth that determines browser settings, extensions and extension settings on a new session, but during a live session I should still be able to update my preferences. So for example, when I start firefox it only loads extensions specified in the config files, but after it is started I can install new extensions the session, and if I do not add these extensions to the config files, they should disappear at the next startup. However user data like history do not belong to configs and should not be affected.

Apparently this is too much to ask of Firefox.

There's user.js, which describes user preferences and should be put under the user profile directory. It can control things like warnings in about:config, the startup homepage and more. All settings are in the form of

user_pref("pref.name", pref_value);

For example, the following disables DRM and removes it from the UI settings:

user_pref("media.eme.enabled", false);
user_pref("browser.eme.ui.enabled", false);

A well-known, well-documented and actively-updated user.js with a privacy focus is by arkenfox.

Then there's also autoconfig, which locks / disables certain user preferences, from either the user or extensions. Coming from Emacs, I find it rather redundant, because the user should be able to experiment with different preference values in a live session, and either an extension is trusted and won't change any user preferences or it is not trusted and should not be installed.

Another, more powerful tool is omni.ja, which is an compressed archive and needs to be inflated and deflated with specific command flags. It is also updated automatically by firefox updates. Apparently it can be used to modify "basic" keybindings otherwise impossible to modify, like C-w (I lost count how many times I pressed C-w to kill some text while composing in a textbox, only to get the tab killed). I have not tried it, and I am not sure if it still works given the MDN page has disappeared.

Finally, to actually deploy extensions, one has to resort to policies.json. In GNU/Linux it can only be configured system-wide and needs to be put in the firefox install directory. It is intended for sysadmin to use and exert power on poor users, but for our purpose, we can wear the two hats simultaneously in the hope of achieving some control.

As a bonus, on developer/nightly/ESR versions with xpinstall.signatures.required set to false, you can use policies.json to deploy any unsigned extensions. So you don't need mozilla's approval to write custom extensions and have it permanently available across sessions.

There is some overlap in settings covered by user.js and policies.json. For example, just to make sure DRM is definitely disabled and removed from settings system-wide, include the following:

{
  "policies": {
    "EncryptedMediaExtensions": {
      "Enabled": false,
      "Locked": true
    }
  }
}

Back to extensions, the way to do it is ExtensionSettings. For example, the following installs uBlock and librejs, as well as removes google search and firefox dark mode. The install_url field supports http:// and file:// protocols, the latter does not support home expansion or relative paths which is a bit of a pain. Also, once an extension is installed this way, the user cannot remove it during the session. The full documentation of policies.json can be found at https://github.com/mozilla/policy-templates.

"ExtensionSettings": {
  "uBlock0@raymondhill.net": {
    "installation_mode": "normal_installed",
    "install_url": "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi"
  },
  "jid1-KtlZuoiikVfFew@jetpack": {
    "installation_mode": "normal_installed",
    "install_url": "file:///tmp/librejs/librejs.xpi"
  },
  "google@search.mozilla.org": {
    "installation_mode": "blocked"
  },
  "firefox-compact-dark@mozilla.org": {
    "installation_mode": "blocked"
  }
}

A problem is that there is no way to allow installing new extensions just for a session. New extensions installed during one session will persist across sessions even if it is not specified in ExtensionSettings.

The real dead end for this project was reached when I tried deploying extensions preferences. These preferences include for example whitelisted js for librejs and userscripts for Greasemonkey. The obstacle lies with the way firefox store these preferences, which is sqlite databases with some nontrivial encoding for both keys and values. Therefore it is generally not possible to deploy with plaintext configuration. Firefox does provide a managed storage manifests that allows specifying such preferences in policies.json. However, it seems to me most extensions do not use it. The only extension I use that enables this is uBlock, and here's an example that disables the uBlock item in the context menu (i.e. the menu that pops up when you right-click in a webpage)

"3rdparty": {
  "Extensions": {
    "uBlock0@raymondhill.net": {
      "userSettings": [
        [ "contextMenuEnabled", "false" ]
      ]
    }
  }
}

A workaround would be creating custom versions of extensions, with user preference built in the xpi files. The appealing part of this idea is the role change in hacking the source code of extensions. The default Firefox experience with the extension signing requirement etc. has made users a rather passive party and left all development responsibilities to extension devolopers. Checking out source code of all extensions and customising them make it closer to an Emacs experience that smoothly transforms users to stakeholders. The downside of this approach is all the churns required to keep converting user preferences to patches whenever the former updates, and to keep patching extensions whenever the upstream projects are updated. The level of ease of converting user preferences to a patch for an extension also highly depends on the extension's source code organisation. It would be much better if extension maintainers could make this task easier, or even better support managed storage manifest.

As for me, I am going to put a bookmark on this project, and try out nyxt. I have heard it is more customisable with lisp as the configuration language. There are not as many extensions in the ecosystem community, but I suspect most useful extensions can be easily implemented, and I suspect ublock is not needed if one disables nonfree javascript. If it turns out nyxt works well, I will port librejs there too. Having librejs implemented more than once will also help extract the main logical part of it into a library usable by more projects (e.g. Emacs).