Skip to content
Ace-Who edited this page Jan 8, 2023 · 52 revisions

Please share useful custom commands here!

See documentation/config-file.md for how to use this.

Tip: Here’s a way to find how many built-in Firefox commands are implemented:

  1. Go to about:config and make sure that devtools.chrome.enabled is set to true.

  2. Open the browser console.

  3. Run the following code:

    Array.prototype.map.call(document.querySelectorAll('command'), command => `${command.id}: ${command.getAttribute('oncommand')}`).join('\n')

    That prints a list of command names and the code they use.

Copy as markdown link

vimfx.addCommand({
  name: 'copy_markdown',
  description: 'Copy as markdown link',
  category: 'location',
}, ({vim}) => {
  let url = vim.window.gBrowser.selectedBrowser.currentURI.spec
  let title = vim.window.gBrowser.selectedBrowser.contentTitle
  let fmt = "["+title+"]("+url+")"
  gClipboardHelper.copyString(fmt)
  vim.notify("Copied String: "+ fmt)
});

Open new tab and focus Search Bar

let {commands} = vimfx.modes.normal
vimfx.addCommand({
  name: 'tab_new_and_focus_search_bar',
  description: 'Open new tab and focus Search Bar',
  category: 'tabs',
  order: commands.tab_new.order + 1,
}, args => {
  commands.tab_new.run(args)
  commands.focus_search_bar.run(args)
})

Zoom

vimfx.addCommand({
  name: 'zoom_in',
  description: 'Zoom in',
}, ({vim}) => {
  vim.window.FullZoom.enlarge()
})

You can also replace enlarge with reduce to zoom out, and with reset to reset to 100%.

Bookmark page

vimfx.addCommand({
  name: 'bookmark_page',
  description: 'Bookmark page',
}, ({vim}) => {
  let {window} = vim
  window.PlacesCommandHook.bookmarkCurrentPage(true, window.PlacesUtils.bookmarksMenuFolderId)
})

The above is equivalent to the Firefox default shortcut C-d.

Search bookmarks / Search open tabs

let {commands} = vimfx.modes.normal
vimfx.addCommand({
  name: 'search_tabs',
  description: 'Search tabs',
  category: 'tabs',
  order: commands.focus_location_bar.order + 1,
}, (args) => {
  let {vim} = args
  let {gURLBar} = vim.window
  gURLBar.value = ''
  commands.focus_location_bar.run(args)
  // Change the `*` on the text line to a `%` to search tabs instead of bookmarks.
  gURLBar.value = '* '
  gURLBar.onInput(new vim.window.KeyboardEvent('input'))
})

You may also want to read more about handy location bar features.

Open a specific URL

vimfx.addCommand({
  name: 'goto_downloads',
  description: 'Downloads',
}, ({vim}) => {
  vim.window.gBrowser.loadURI('about:downloads')
})

Replace loadURI with loadOneTab to open in a new tab instead.

Here are some URLs you might find interesting (they should be self-explanatory):

  • about:downloads
  • about:preferences
  • about:addons

Like about: pages? Go to about:about to list them all!

Increment/Decrement the last number in the URL

let {commands} = vimfx.modes.normal
vimfx.addCommand({
  name: 'go_increment',
  description: 'Increment the last number in the URL',
  category: 'location',
  order: commands.go_to_root.order + 1,
}, ({vim, count = 1}) => {
  let url = vim.browser.currentURI.spec
  let newUrl = url.replace(/(\d+)(?=\D*$)/, match =>
    Math.max(0, Number(match) + count)
  )
  if (newUrl === url) {
    vim.notify('Cannot increment/decrement URL')
  } else {
    vim.window.gBrowser.loadURI(newUrl)
  }
})
vimfx.addCommand({
  name: 'go_decrement',
  description: 'Decrement last number in the URL',
  category: 'location',
  order: commands.go_increment.order + 1,
}, ({vim, count = 1}) => {
  commands.go_increment.run({vim, count: -count})
})

Search for the selected text

vimfx.addCommand({
  name: 'search_selected_text',
  description: 'Search for the selected text',
}, ({vim}) => {
  let {messageManager} = vim.window.gBrowser.selectedBrowser
  vimfx.send(vim, 'getSelection', null, selection => {
    let inTab = true // Change to `false` if you’d like to search in current tab.
    vim.window.BrowserSearch.loadSearch(selection, inTab)
  })
})

frame.js:

vimfx.listen('getSelection', (data, callback) => {
  let selection = content.getSelection().toString()
  callback(selection)
})

Web console

vimfx.addCommand({
  name: 'web_console',
  description: 'Web console',
}, ({vim}) => {
  vim.window.gDevToolsBrowser.selectToolCommand(vim.window.gBrowser, 'webconsole')
})

The above is equivalent to the Firefox default shortcut C-S-k. Alternatively, you can use the following to toggle the developer tools instead:

vim.window.gDevToolsBrowser.toggleToolboxCommand(vim.window.gBrowser)

Move tab to index

This command moves the current tab before tab number count.

vimfx.addCommand({
  name: 'tab_move_to_index',
  description: 'Move tab to index',
  category: 'tabs',
  order: commands.tab_move_forward.order + 1,
}, ({vim, count}) => {
  if (count === undefined) {
    vim.notify('Provide a count')
    return
  }
  let {window} = vim
  window.setTimeout(() => {
    let {selectedTab} = window.gBrowser
    if (selectedTab.pinned) {
      vim.notify('Run from a non-pinned tab')
      return
    }
    let newPosition = window.gBrowser._numPinnedTabs + count - 1
    window.gBrowser.moveTabTo(selectedTab, newPosition)
  }, 0)
})

Tip: Add the following to custom styling to make it easier to work out which number to type. It turns the tab close buttons into numbers until you hover the tab:

#TabsToolbar {
  counter-reset: tabs-counter;
}

.tab-close-button:not([pinned]) {
  visibility: hidden;
  display: block;
  color: inherit;
  counter-increment: tabs-counter;

  &::before {
    content: counter(tabs-counter);
    visibility: visible;
    font-weight: normal;
    font-style: normal;
  }
}

Close tab and select previous/next tab

Known as d and D in Vimperator.

vimfx.addCommand({
  name: 'tab_close_and_focus_previous',
  description: 'Close tab and focus previous.',
  category: 'tabs',
}, ({vim}) => {
  let {gBrowser} = vim.window
  let previousTabIndex = gBrowser.tabContainer.selectedIndex - 1
  gBrowser.removeCurrentTab()
  gBrowser.selectTabAtIndex(previousTabIndex)
})

vimfx.addCommand({
  name: 'tab_close_and_focus_next',
  description: 'Close tab and focus next.',
  category: 'tabs',
}, ({vim}) => {
  let {gBrowser} = vim.window
  let nextTabIndex = gBrowser.tabContainer.selectedIndex
  gBrowser.removeCurrentTab()
  gBrowser.selectTabAtIndex(nextTabIndex)
})

Close tabs to the left

let {commands} = vimfx.modes.normal
vimfx.addCommand({
  name: 'tab_close_to_start',
  description: 'Close tabs to the left',
  category: 'tabs',
  order: commands.tab_close_to_end.order + 1,
}, ({vim}) => {
  let {gBrowser} = vim.window
  gBrowser.tabs.slice(gBrowser._numPinnedTabs, gBrowser.selectedTab._tPos)
    .forEach(tab => gBrowser.removeTab(tab))
})

Close window

let {commands} = vimfx.modes.normal
vimfx.addCommand({
  name: 'window_close',
  description: 'Close window',
  order: commands.follow_in_private_window.order + 1,
}, ({vim}) => {
  let {window} = vim
  if (window.gBrowser.tabs.length > 1) {
    if (window.confirm('There are multiple tabs open. Close anyway?')) { 
      window.close()
    }
  } else {
    window.close()
  }
})

(Related: Issue #402.)

Minimize Window

let {commands} = vimfx.modes.normal
vimfx.addCommand({
  name: 'window_minimize',
  description: 'Minimize window',
  order: commands.follow_in_private_window.order + 1,
}, ({vim}) => {
  vim.window.minimize()
})

Click the NoScript button

vimfx.addCommand({
  name: 'noscript_click_toolbar_button',
  description: 'NoScript',
}, ({vim}) => {
  vim.window.document.getElementById('noscript-tbb').click()
})

Play a link with an external media player

const {classes: Cc, interfaces: Ci} = Components

const mpv_path = '/usr/bin/mpv'
const mpv_options = '--video-unscaled=yes'

vimfx.addCommand({
  name: 'play_with_mpv',
  description: 'Play the focused link with MPV'
}, ({vim}) => {
  vimfx.send(vim, 'getCurrentHref', null, url => {
    let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile)
    file.initWithPath(mpv_path)

    let process = Cc['@mozilla.org/process/util;1'].createInstance(Ci.nsIProcess)
    process.init(file)

    let args = mpv_options.split(' ')

    if (url.includes('youtube.com')) {
      // Parse url params to an object like:
      // {"v":"g04s2u30NfQ","index":"3","list":"PL58H4uS5fMRzmMC_SfMelnCoHgB8COa5r"}
      let qs = (function(a) {
        if (a == '') return {}
        let b = {}
        for (let i = 0; i < a.length; ++i) {
          let p = a[i].split('=', 2)
          if (p.length == 1) {
            b[p[0]] = ''
          } else {
            b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, ' '))
          }
        }
        return b
      })(url.substr(1).split('&'))

      if (qs['list'] && qs['index']) {
        // Example args: ['--video-unscaled=yes', '--ytdl-raw-options=format=best']
        // So check for ytdl-raw-options.
        let ytdlRawOptionsIndex = -1
        for (let i = 0; i < args.length; i++) {
          if (args[i].includes('ytdl-raw-options')) {
            ytdlRawOptionsIndex = i
            break
          }
        }

        if (ytdlRawOptionsIndex > -1) {
            args[ytdlRawOptionsIndex] += `,yes-playlist=,playlist-start=${qs['index']}`
        } else {
            args.push(`--ytdl-raw-options=yes-playlist=,playlist-start=${qs['index']}`)
        }
      }
    }

    args.push(url)

    // process.run(false, args, args.length)
    process.runAsync(args, args.length)
  })
})

frame.js:

vimfx.listen('getCurrentHref', (data, callback) => {
  let {href} = content.document.activeElement
  callback(href)
})

Sorry for hairy code :)

Restart Firefox

const {classes: Cc, interfaces: Ci} = Components;

vimfx.addCommand({
    name: 'restart',
    description: 'Restart Firefox'
}, (args) => {
    let canceled = Cc["@mozilla.org/supports-PRBool;1"]
        .createInstance(Ci.nsISupportsPRBool);

    Services.obs.notifyObservers(canceled, "quit-application-requested", "restart");

    if (canceled.data) return false; // somebody canceled our quit request

    // restart
    Cc['@mozilla.org/toolkit/app-startup;1'].getService(Ci.nsIAppStartup)
        .quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);

    return true;
});