Exercising software freedom on Firefox

I’m a little unusual. I use Emacs.

That alone is unusual. But I get the impression that even amongst Emacs users, I’m in the minority in another way: I use the default keybindings. I love them. A lot of new Emacs users seem to insist on jamming vim keys into Emacs, but not me. These are my friends: C-p C-n C-f C-b C-a C-e C-k; down up left right start end kill.

I’m so gung-ho about Emacs keybindings that I made them the default keybinding of GTK+, which means that any application that uses GTK+ will respect Emacs keybindings for motion. They also work in anything that uses readline or readline-like input, like bash, python, or psql (postgresql’s default CLI client). Being used to Emacs keys has paid off for me. I have a consistent interface across the software that matters to me.

I’m becoming a minority in another way: I use Firefox. And Firefox uses GTK+. That means I can use Emacs keybindings in Firefox.

Ah, but there’s a rub. Firefox binds C-n (or as most people would call it, “ctrl-n”) to new window. This is probably okay for people who don’t have the intersectionality of Emacs keybindings everywhere and Firefox. But for me, it’s intolerable. If I want to move a cursor down, I have to instead perform a very unnatural-feeling motion of moving my right hand to the arrow keys and hit the arrow down button. For those accostumed to using arrow keys, imagine if every time you pressed the down arrow Firefox would open a new window. Imagine software reacting so at odds to your habituation.

Up until Firefox 56 there was an easy workaround. You could download extensions that would let you configure Firefox’s keyboard shorcuts, including disable some of them. I used to do this. The world, however, marches on and so does Firefox. Many extensions cannot do what they once did and the easy fix was gone.

I tried to cope, for a while. After all, it’s just one key. I can still use the arrow keys. I tried.

But no. It wouldn’t work. I couldn’t help myself. I often wanted to move the cursor down three or four rows and would accidentally open up three or four new windows. It was even worse because I could move in every other direction and it all felt natural, but if I made the mistake of going down, the software would react in the wrong way. Everything else did it right except Firefox. And one day, I had enough.

Software Freedom

Enough was enough. I had accidentally opened a new window for the last time. I want to go down, you donut! And you won’t stop me anymore!

Freedom
FREEDOMMM!

I had the motivation. I have some skill. We can rebuild Firefox. Make it better. More consistent. We have the technology.

I didn’t want to get involved in Firefox’s build drama, though. I didn’t want to figure out how to clone its repo, how to setup a development environment, how to configure the build, what kinds of builds there are, and how to integrate all of this with my operating system. Luckily, someone else has already done all of this work for me: the Debian packagers.

A Debian package knows what dependencies are required to build a package and has all of the tooling ready to build that package and make it fit exactly with my operating system. Right system libraries, right compilation options, everything. I know how to build Debian packages:

  1. Get the source (apt-get source $packagename)
  2. Get the dependencies (sudo apt build-dep $packagename)
  3. Build the package (dpkg-buildpackage)

Easy enough.

Firefox, the behemoth

As I started following the steps above, something was immediately evident. Firefox is huge. Enormous. Gargantuan. The biggest codebase I have ever seen. At a glance I saw a mix of Python, C++, Rust, and XML which I later came to recognise as XUL (“XUL?” I hear you ask. Yes. XUL. More on this below.) I can see why few dare tread in here.

I, on the other hand, with my motivation going strong, felt undaunted. I would tame The Beast of oxidised metal.

But I wouldn’t do it alone. I know that the Mozilla project still has a fairly active IRC network over at irc.mozilla.org, so I headed down that way. I started talking about my problem, asking for advice. While I waited for replies, I tried to do it on my own. I figured, GTK+, keybindings, C. I was looking for some C or C++ source file that would define the GTK+ keybindings. I would find this file and destroy the keybinding. I have done something similar in the past for other GTK+ programs.

My solo search proved unfruitful. I couldn’t find anything about new window in C++ source files. I even tried the Rust files, maybe they’ve done something there, but again nothing. My grepping did find new window commands in XML files, but I figured that couldn’t still be of use. Everyone knows it, it’s all over the software news: Firefox disabled XUL as part of its move to a Rust engine.

In the meantime, helpful people from IRC pushed me along my quest and pointed me in the right direction. Yes, XUL is all I needed.

There is no Rust. There is only XUL!

Yep! Firefox has been lying to us! It’s still all XUL. All they’ve disabled is the external interface for extensions, but under the hood, Firefox is still the XUL mess it always was. They say they’re ripping it out, yet the process seems slow.

So I followed the advice. I changed a single XML file. I built the Debian package. I was expecting a long compilation time and I got it. I was worried I wouldn’t have enough RAM for the build, but looks like 16 gigabytes with four cores (Thinkpad X1 Carbon 5th gen) was enough. People in IRC reassured me that it would take about two hours. They were right! Two hours later, I had a new Firefox in a neat little Debian package. I installed it (dpkg -i *.deb) eager to see the results and…

XML parsing error. Undefined entity.

Oh no! I had made a mistake! All I could do was close this error window. Firefox just wouldn’t start.

However, this confirmed two things. One, the XUL really is still being used. In fact, it’s so important that Firefox won’t even start if you get it wrong. And two… I was on the right track. Modifying XUL could very well get me to my goal of disabling one key.

The error window reminded me a lot of similar errors I had seen in the past when XUL was available to 3rd party extension authors. It seems that not as much as advertised has changed.

Bad XUL
XUL parsing error

I tried again. I had removed the key but I hadn’t removed a few references to that key. Another build. Another two hours. In the meantime, Mozilla employees and enthusiasts in IRC kept asking me if I was doing an artifact build. I said no, that I wanted to learn as little as possible about Firefox’s build process. Turns out that an artifact build is an interesting thing where you download pre-built Firefox components and the build just puts them together, greatly reducing the compilation times.

I had the very specific goals of building a Debian package and not wanting to get too involved in build drama, so I politely refused the suggestions of artifact builds.

I just want my cursor to move down, man.

My second try also didn’t work. I had neglected one further reference to the new window key. I didn’t think it was necessary, but the XML again failed to parse because the key for undoing closing a window is defined in terms of the key for opening a new window. I decided that if I wasn’t going to be opening new windows, I also wasn’t going to undo close them, so I also deleted this reference.

By now it was getting late, I had to sleep, and I couldn’t wait for another two-hour build. I made the change, started the build, and went to bed like a kid excited for Christmas morning.

Free at last!

The morning came. My new build was ready. I installed the third Debian package I built.

This time Firefox started. No more XML errors.

Could it be…?

I went to the first website I could think of that had a textarea element I could try to type in, paste.debian.net.

I typed some text. I hit enter a few times. I pressed C-p to go back up.

The moment of truth!

I hit C-n.

No new window.

The cursor moved down.

YES!!

Victoly!
Great success!

The patch

So here’s the patch, for anyone else who wants it. I made it against ESR (currently Firefox 60) because that’s what’s packaged for Debian stable, but all of these modified files are still there in the current Mercurial repository I just checked right now.

diff --git a/firefox-esr-60.5.1esr/browser/base/content/browser-menubar.inc b/firefox-esr-60.5.1esr/browser/base/content/browser-menubar.inc
--- a/firefox-esr-60.5.1esr/browser/base/content/browser-menubar.inc
+++ b/firefox-esr-60.5.1esr/browser/base/content/browser-menubar.inc
@@ -27,7 +27,6 @@
                 <menuitem id="menu_newNavigator"
                           label="&newNavigatorCmd.label;"
                           accesskey="&newNavigatorCmd.accesskey;"
-                          key="key_newNavigator"
                           command="cmd_newNavigator"/>
                 <menuitem id="menu_newPrivateWindow"
                           label="&newPrivateWindow.label;"
diff --git a/firefox-esr-60.5.1esr/browser/base/content/browser-sets.inc b/firefox-esr-60.5.1esr/browser/base/content/browser-sets.inc
--- a/firefox-esr-60.5.1esr/browser/base/content/browser-sets.inc
+++ b/firefox-esr-60.5.1esr/browser/base/content/browser-sets.inc
@@ -196,10 +196,6 @@
   </broadcasterset>
 
   <keyset id="mainKeyset">
-    <key id="key_newNavigator"
-         key="&newNavigatorCmd.key;"
-         command="cmd_newNavigator"
-         modifiers="accel" reserved="true"/>
     <key id="key_newNavigatorTab" key="&tabCmd.commandkey;" modifiers="accel"
          command="cmd_newNavigatorTabNoEvent" reserved="true"/>
     <key id="focusURLBar" key="&openCmd.commandkey;" command="Browser:OpenLocation"
@@ -378,7 +374,6 @@
 #ifdef FULL_BROWSER_WINDOW
     <key id="key_undoCloseTab" command="History:UndoCloseTab" key="&tabCmd.commandkey;" modifiers="accel,shift"/>
 #endif
-    <key id="key_undoCloseWindow" command="History:UndoCloseWindow" key="&newNavigatorCmd.key;" modifiers="accel,shift"/>
 
 #ifdef XP_GNOME
 #define NUM_SELECT_TAB_MODIFIER alt
diff --git a/firefox-esr-60.5.1esr/browser/components/customizableui/content/panelUI.inc.xul b/firefox-esr-60.5.1esr/browser/components/customizableui/content/panelUI.inc.xul
--- a/firefox-esr-60.5.1esr/browser/components/customizableui/content/panelUI.inc.xul
+++ b/firefox-esr-60.5.1esr/browser/components/customizableui/content/panelUI.inc.xul
@@ -205,7 +205,6 @@
         <toolbarbutton id="appMenu-new-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newNavigatorCmd.label;"
-                       key="key_newNavigator"
                        command="cmd_newNavigator"/>
         <toolbarbutton id="appMenu-private-window-button"
                        class="subviewbutton subviewbutton-iconic"
diff --git a/firefox-esr-60.5.1esr/browser/locales/en-US/chrome/browser/browser.dtd b/firefox-esr-60.5.1esr/browser/locales/en-US/chrome/browser/browser.dtd
--- a/firefox-esr-60.5.1esr/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/firefox-esr-60.5.1esr/browser/locales/en-US/chrome/browser/browser.dtd
@@ -298,7 +298,6 @@ These should match what Safari and other
 <!ENTITY newUserContext.label             "New Container Tab">
 <!ENTITY newUserContext.accesskey         "B">
 <!ENTITY newNavigatorCmd.label        "New Window">
-<!ENTITY newNavigatorCmd.key        "N">
 <!ENTITY newNavigatorCmd.accesskey      "N">
 <!ENTITY newPrivateWindow.label     "New Private Window">
 <!ENTITY newPrivateWindow.accesskey "W">

So there you have it. You can still alter Firefox’s XUL. You just have to compile it in instead of doing an extension.

7 thoughts on “Exercising software freedom on Firefox

  1. When you originally posted this to lobste.rs, I wrote several comments detailing the Emacs-like architecture that Firefox has (compiled core, with a high-level, memory-safe language for lots of application logic).

    I’ve been hearing *a lot* about Zotero on HN lately. I installed it yesterday and was delighted to find that it’s powered by Gecko and XUL. (Naturally, it shares the same kind of architecture.)

Leave a Reply

Your email address will not be published. Required fields are marked *