Goodbye Vimperator, Hello VimFx + e10s


**TL;DR:** これは Vimperator を捨てて VimFx をインストールし、e10s を有効化したらすこぶる快適になった話です。Firefox 万歳！VimFx 万歳！

前置きはここに至るまでの経緯なので読み飛ばしてもらって構いません。

## 前置き

Firefox の遅さや開発ツールの使いにくさから Google Chrome に移行して久しいですが、メモリ使用量がとんでもなく多い事やキーボードブラウジングの不完全さから昨年あたりに Firefox + Vimperator に戻ってきていました。キーボードブラウジングは cVim や Vimium 等を一通り試しましたが、Chrome の拡張の仕組み上、ウェブページがロードされないと使えなかったり、一部のタブでは動作しなかったりする点に不満と限界を感じていました。

そんな中、最近の Firefox は早くなったという記事を見かけるようになり、プロファイルを新しくして起動してみると Chrome よりも快適に使えるような気がしました。昔は KeySnail を使っていましたが、ブラウザは Emacs 風じゃなくていいかなと思い Vimperator をインストールすることにしました。こうして真のキーボードブラウジング環境を取り戻したのでした。 ~~めでたしめでたし~~

Firefox 48 から高速化のための Electrolysis (e10s) が使えるようになりました。早速有効にしたかったのですが、Vimperator が対応していないじゃないですか。なんでも、かなりアグレッシブに Firefox の UI を書き換えていたり、古いコードがたくさんあって対応が困難なようで、Fork 版の Pentadactyl も状況は同じようでした。この時点で VimFx なら e10s 対応してるよ！という情報を見ていましたが、Chrome + Vimium の苦い思い出からどうせキーボードショートカットを vim 風にしただけのものでしょと思っていました。ごめんなさい。

とりあえずキーボードブラウジングの方が大事だったので、泣く泣く e10s は忘れてそのうち対応されるだろうと Vimperator を使っていました。しかし、Firefox 51 に上げたところ `tabopen` コマンドが使えなくなって Vimperator の未来に限界を感じ、以前より Hit-a-Hint がたまに動かないこともあって、思い切って VimFx と入れ替えてみることにしました。

## Hello VimFx

[VimFx](https://github.com/akhodakivskiy/VimFx) は Vimium for Chrome の開発者が、その開発経験を踏まえて開発されたものです。Vimperator ほど Firefox に変更を加えず、Firefox に元から備わっているものは極力利用し、キーボードブラウジングに必要なものだけを取り入れています。

JavaScript で設定ファイルを記述することで、かなり高度な事ができるようになります。ブラウザの機能にアクセスするカスタムコマンドを追加でき、Vimium と比べると非常にパワフルです。Vimperator でお困りの方は一度試してみるのをおすすめします。

VimFx についてはこちらのサイトが参考になります。

- [VimFx メモ - クマーなひとときv2](http://tonkuma.hatenablog.com/entry/2016/08/04/223558)
- [VimFx メモ その2 - クマーなひとときv2](http://tonkuma.hatenablog.com/entry/2016/09/17/195647)
- [VimFx メモ その3 - クマーなひとときv2](http://tonkuma.hatenablog.com/entry/2016/12/31/120000)

## …and e10s

e10s はざっくり言うとブラウザの UI とウェブコンテンツをレンダリングするプロセスを分離するものです。Firefox 48 から利用できますが、非対応のアドオンがあると自動的に無効になります。有効になっているかは `about:support` のマルチプロセスウインドウを見ればわかります。この値が 1/1 なら有効です。

非対応なアドオンがあっても、`about:config` を開いて `browser.tabs.remote.force-enable` を true にすれば強制的に有効にすることができます。1Password のアドオンが正式には対応していなかったのでこの方法で有効にしました。

e10s はデフォルトではウェブコンテンツ用のプロセスは1つですが、`about:config` から `dom.ipc.processCount` を設定することで任意の数まで増やせます。4以上で顕著なパフォーマンス向上が見込め、8以上はメモリ使用量が多くなりすぎるとのことです。今のところ4に設定し、快適に使えています。

なお、`dom.ipc.processCount` が1以上の時の検証は十分にされていないようなので、うまく動かなければ1に戻しましょう。

## Customize VimFx

VimFx の設定ファイルは `~/.config/vimfx` に `config.js` と `frame.js` を作成します。そして `about:config` から `extensions.VimFx.config_file_directory` を `~/.config/vimfx` に設定します。設定が済んだら `gC` とタイプすると設定ファイルが読み込まれ、ブラウザの右下に通知がでます。

参考までに現状の設定ファイルを貼り付けておきます。設定方法について詳しくはドキュメントを見てください。

### config.js

```js
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const gClipboardHelper = Cc['@mozilla.org/widget/clipboardhelper;1']
      .getService(Ci.nsIClipboardHelper);
const {Preferences} = Cu.import('resource://gre/modules/Preferences.jsm', {});

const FIREFOX_PREFS = {
  'browser.startup.page': 3,
  'browser.tabs.animate': false,
  'browser.search.suggest.enabled': true,
  'browser.urlbar.suggest.searches': true,
  'browser.urlbar.maxRichResults': 20,
  'browser.tabs.remote.force-enable': true,
  'dom.ipc.processCount': 4
};

const VIMFX_PREFS = {
  'prevent_autofocus': true
};

const MAPPINGS = {
  'copy_current_url': '',
  'go_home': '',
  'stop': '<c-escape>',
  'stop_all': 'a<c-escape>',

  'history_back': 'h',
  'history_forward': 'l',

  'scroll_left': 'H',
  'scroll_right': 'L',
  'scroll_half_page_down': '<c-f>',
  'scroll_half_page_up': '<c-b>',
  'mark_scroll_position': 'mm',
  'scroll_to_mark': 'gm',

  'tab_new': 'T',
  'tab_new_after_current': 't',
  'tab_close': 'd x',
  'tab_restore': 'u',
  'tab_restore_list': 'U',
  'tab_select_previous': ',',
  'tab_select_next': '.',
  'tab_select_first_non_pinned': '^',
  'tab_select_last': '$',

  'enter_mode_ignore': 'I',
  'quote': 'i',

  'custom.mode.normal.search_selected_text': 's',
  'custom.mode.normal.copy_selection_or_url': 'yy',
  'custom.mode.normal.copy_as_markdown': 'ym',
  'custom.mode.normal.click_toolbar_pocket': 'mp'
};

const {commands} = vimfx.modes.normal;

const CUSTOM_COMMANDS = [
  [
    {
      name: 'search_selected_text',
      description: 'Search for the selected text'
    }, ({vim}) => {
      vimfx.send(vim, 'getSelection', true, selection => {
        if (selection != '') {
          vim.window.switchToTabHavingURI(`https://www.google.co.jp/search?q=${selection}`, true);
        }
      });
    }
  ],
  [
    {
      name: 'copy_as_markdown',
      description: 'Copy title and url as Markdown',
      category: 'location',
      order: commands.copy_current_url.order + 2
    }, ({vim}) => {
      let url = vim.window.gBrowser.selectedBrowser.currentURI.spec;
      let title = vim.window.gBrowser.selectedBrowser.contentTitle;
      let s = `[${title}](${url})`;
      gClipboardHelper.copyString(s);
      vim.notify(`Copied to clipboard: ${s}`);
    }
  ],
  [
    {
      name: 'copy_selection_or_url',
      description: 'Copy the selection or current url',
      category: 'location',
      order: commands.copy_current_url.order + 1
    }, ({vim}) => {
      vimfx.send(vim, 'getSelection', true, selection => {
        if (selection == '') {
          selection = vim.window.gBrowser.selectedBrowser.currentURI.spec;
        }
        gClipboardHelper.copyString(selection);
        vim.notify(`Copied to clipboard: ${selection}`);
      });
    }
  ],
  [
    {
      name: 'click_toolbar_pocket',
      description: 'Click toolbar button [Pocket]'
    }, ({vim}) => {
      vim.window.document.getElementById('pocket-button').click();
    }
  ]
];

Object.entries(VIMFX_PREFS).forEach(([name, value]) => {
  vimfx.set(name, value);
});

CUSTOM_COMMANDS.forEach(([options, fn]) => {
  vimfx.addCommand(options, fn);
});

Object.entries(MAPPINGS).forEach(([cmd, key]) => {
  if (!cmd.includes('.')) {
    cmd = `mode.normal.${cmd}`;
  }
  vimfx.set(cmd, key);
});

Preferences.set(FIREFOX_PREFS);
```

### frame.js

```js
vimfx.listen('getSelection', (willRemove, callback) => {
  let selection = content.getSelection();
  let s = selection.toString();
  if (willRemove) {
    selection.removeAllRanges();
  }
  callback(s);
});
```

### Stylish

Hint のスタイリングなどは userChrome.css か Stylish で行います。Stylish だと再起動せずに適用できるので微調整などはやりやすいです。

次の設定を入れています。

- Hint のフォントを大きくする
- Ignore モードのときにロケーションバーを赤くする
- 通知領域を黒背景にしてフォントを大きくする（参考: [Firefoxのツールチップの色合いを変えてみる - はりをきば）](http://blog.gutyan.jp/entry/2013/12/30/Firefox_Tooltip_Style)

```css
@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);

#VimFxMarkersContainer .marker {
  font-size: 14px !important;
}

/* Make location bar red while in ignore mode */
#main-window[vimfx-mode="ignore"] #urlbar {
  background: red !important;
}

statuspanel label.statuspanel-label {
  background: linear-gradient(45deg, rgba(22, 22, 22, 0.9), rgba(99, 99, 99, 0.9)) !important;
  color: whitesmoke !important;
  font-size: 14px !important;
}
```