Switching tabs in Firefox with a mouse wheel

📅 2022-05-02⏳ 5 min read

I prefer to switch tabs in my browser with a mouse wheel and this is an important part of my workflow. It’s so much easier to shove a mouse pointer to the top of a screen and do a single scroll rather than reaching for a keyboard to press Crtl+PgUp/Down or hunting the desired tab with a cursor. Unfortunately it doesn’t work in Firefox by default… Let’s see if we can fix this!

If you want to skip right to the solution, feel free to do so.

🔗Looking for options

If we just search the problem on the Internet, there are a couple of options popping up:

  1. Use an extension. However, due to limitations of WebExtension API all of them require you to hold a right mouse button while scrolling the wheel, which is cumbersome.
  2. Enable toolkit.tabbox.switchByScrolling setting. There are two issues with this:
    • The direction is the opposite of what I prefer (could live with that)
    • It stops working once there are so many tabs that they start overflowing. There is an open bug and even a proposed patch that I’ve tested, and it seems to work well, but I want neither to patch every new version locally nor to wait for fix to be merged
  3. Use a CSS hack to inject XUL bindings. This injection doesn’t work anymore, since the support was removed.

I’ve asked for guidance and was pointed by moko1960 to a project called userChrome.js. It restores the hack #3 from the list above but in a slightly different form: instead of injecting bindings we can now create a userChrome.js file and simply put our code in there.

I was also provided with a script that looked like this:

WARN: not the ideal solution. See below the modified version.

(function() {
  if (location != 'chrome://browser/content/browser.xul')
  const scrollRight = true;
  const wrap = true;
  gBrowser.tabContainer.addEventListener("wheel", function(event) {
    let dir = (scrollRight ? 1 : -1) * Math.sign(event.deltaY);
    setTimeout(function() {
      gBrowser.tabContainer.advanceSelectedTab(dir, wrap);
    }, 0);
  }, true);

Unfortunately this didn’t work well…

🔗Debugging Firefox with Firefox

The first problem was pointed by moko1960 right away: the location should be changed to chrome://browser/content/browser.xhtml

The second issue was an absurd speed at which tabs were scrolling when using a trackpad. It was just impossible not to scroll all the way to the end of a tab list. And since the wrap option was set to true in the script above, it took some time for a scrolling to complete after even a slightest touch.

The third minor inconvenience was that in case of a tab overflow, the tab list was scrolling alongside with switching tabs which caused some glitches.

Turns out that debugging Firefox is really easy and enjoyable. See these docs to enable the “Browser Toolbox” (set two ticks in settings). After this you can press Ctrl + Alt + Shift + I to open a debugger that will treat the current browser instance as a regular web page.

You can find your script under DebuggerSourcesfile://

Firefox debugger debugging Firefox

Firefox debugger debugging Firefox

You can set breakpoints, see variables, etc. Really handy!

🔗The Final Solution

In the end I was able to create a script that is:

  1. Trackpad-friendly
  2. Leaves horizontal scroll for scrolling the tab list without switching tabs
  3. Counts Shift + VScroll as horizontal scroll

Here is how to set it up:

🔗1. Locate you Firefox profile directory

Go to about:support, and you should see the path under Profile Directory entry. Take a note of it.

🔗2. Install userChrome.js

ArchLinux users can simply install firefox-userchromejs from AUR. It is outdated, but does the job. Or you can follow instructions in the project’s README. You’ll need to copy a couple of files both into the Firefox installation directory and into the profile directory that we’ve just found.

🔗3. Install the tab scroller script

In your <profile_directory>/chrome create a file called something like TabWheelScroll.uc.js and put the following in there:

// Created by @aborix https://www.camp-firefox.de/forum/viewtopic.php?t=121503
// Modified by @bemyak https://bemyak.ml/dev/ff-tab-scroll
(function () {
  if (location != "chrome://browser/content/browser.xhtml") return;
  const scrollRight = true;
  const wrap = false;

  const dir = scrollRight ? 1 : -1;
  const n = 9;
  let acc = 0;
    function (event) {
      let x = 0;
      let y = 0;
      if (event.shiftKey) {
        x = event.deltaY;
        y = event.deltaX;
      } else {
        x = event.deltaX;
        y = event.deltaY;
      if (x != 0) return;
      const yd = y * dir;
      if (Math.sign(acc) != Math.sign(yd)) {
        acc = 0;
      acc += yd;
      const mod = Math.sign(acc) * Math.floor(Math.abs(acc) / n);
      // console.log(`y=${y} acc=${acc} mod=${mod}`);
      gBrowser.tabContainer.advanceSelectedTab(mod, wrap);
      acc = acc % n;

Now restart Firefox and you’re done!