aboutsummaryrefslogblamecommitdiff
path: root/posts/2023-08-14-emacsify-firefox-init.org
blob: 109a37aa23de4122c2f4777484d40bc4b9e5d41f (plain) (tree)











































































                                                                                                                                                                                                     
                                                         






































































































                                                                                                                                                                                                                
# 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 <https://www.gnu.org/licenses/>.

# 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
<https://github.com/mozilla/policy-templates>.

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