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).