# Copyright (C) 2023 Yuchen Pei. # Permission is granted to copy, distribute and/or modify this # document under the terms of the GNU Free Documentation License, # Version 1.3 or any later version published by the Free Software # Foundation; with no Invariant Sections, no Front-Cover Texts, and # no Back-Cover Texts. You should have received a copy of the GNU # Free Documentation License. If not, see . # This work is licensed under the Creative Commons # Attribution-ShareAlike 4.0 International License. To view a copy of # this license, visit http://creativecommons.org/licenses/by-sa/4.0/ # or send a letter to Creative Commons, PO Box 1866, Mountain View, CA # 94042, USA. #+title: Emacsify Firefox init #+date: <2023-08-14> I wanted to deploy Firefox the same way [[https://g.ypei.me/dotted.git/tree/emacs/README.org][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 [[https://kb.mozillazine.org/User.js_file][=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 #+begin_src js user_pref("pref.name", pref_value); #+end_src For example, the following disables DRM and removes it from the UI settings: #+begin_src js user_pref("media.eme.enabled", false); user_pref("browser.eme.ui.enabled", false); #+end_src A well-known, well-documented and actively-updated =user.js= with a privacy focus is by [[https://github.com/arkenfox/user.js][arkenfox]]. Then there's also [[https://support.mozilla.org/en-US/kb/customizing-firefox-using-autoconfig][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 [[http://web.archive.org/web/20200929180805/https://developer.mozilla.org/en-US/docs/Mozilla/About_omni.ja_(formerly_omni.jar)][=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 [[https://support.mozilla.org/en-US/products/firefox-enterprise/policies-customization-enterprise/policies-overview-enterprise][=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 [[https://extensionworkshop.com/documentation/enterprise/enterprise-distribution/][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: #+begin_src json { "policies": { "EncryptedMediaExtensions": { "Enabled": false, "Locked": true } } } #+end_src Back to extensions, the way to do it is ExtensionSettings. For example, the following installs uBlock and [[https://gnu.org/s/librejs][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 . #+begin_src json "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" } } #+end_src 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 [[https://www.greasespot.net/][Greasemonkey]]. The obstacle lies with the way firefox store these preferences, which is sqlite databases with some [[https://stackoverflow.com/questions/54920939/parsing-fb-puritys-firefox-idb-indexed-database-api-object-data-blob-from-lin/59923297#59923297][nontrivial encoding for both keys and values]]. Therefore it is generally not possible to deploy with plaintext configuration. Firefox does provide a [[https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#managed_storage_manifests][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 [[https://github.com/gorhill/uBlock/wiki/Deploying-uBlock-Origin:-configuration][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) #+begin_src json "3rdparty": { "Extensions": { "uBlock0@raymondhill.net": { "userSettings": [ [ "contextMenuEnabled", "false" ] ] } } } #+end_src 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 [[https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/managed][support managed storage manifest]]. As for me, I am going to put a bookmark on this project, and try out [[https://nyxt.atlas.engineer/][nyxt]]. I have heard it is more customisable with lisp as the configuration language. There are not as many extensions in the [[https://www.gnu.org/philosophy/words-to-avoid.en.html#Ecosystem][+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).