Vim で書いているスクリプトを Firefox 上で実行する


鰺坂です。

これは TECHSCORE Advent Calendar 2017 (略してテットカ) の12日目の記事です。

Firefox Quantum

先月の 14 日、Firefox のバージョン 57 (Quantum) がリリースされました。
かなり高速化されたとのことですが、旧式のアドオンが全く使えなくなっています。
その結果、Firefox の UI が完全に変える Vimperator・KeySnail・Pentadactyl といったアドオンは全滅です。
これらアドオンはアドオン自身が(プラグインと呼ばれていましたが)アドオンシステムをもっており、当然のことながらこれらも使用できません。

あのプラグインを再び

私は Vimperator を使っていて、さらにそのプラグイン auto_source.js も使っていました。
auto_source.js は、指定した JavaScript ファイルを監視して変更があれば Firefox のコンテクストで実行するというものです。
今回は、これと似たような事ができる Web Extension を書いてみようという事になります。

既存のアドオンに有りそうな気はしますが、勉強を兼ねるということであえて探さないことにします。
これが上手くいけば、開発者ツールのコンソールで長いコードを入力して(入力者が)爆発することがなくなるはずです。

仕様

元のプラグインとは異なりますが、
「Vim 上でコマンド入力すると、Firefox のアクティブなタブで Vim のバッファのスクリプトが実行される」
という仕様にします。
要は Vim で書いているスクリプトを任意のタイミングで実行できれば良いからです。

実装方針

Firefox にコマンドを発行するなどしてスクリプトを直接実行できれば良いのですが、それは(自分が知る限り)出来ません。
そこで、WebSocket + TCP な中継サーバー作成し、このサーバーを使って Vim から Firefox へスクリプトを送りこむことにします。

つまり、以下の三つで構成されるシステムとなります。

  • Firefox のアドオン
  • Vim スクリプト
  • 中継サーバー

Firefox の WebSocket

これは言うまでもありませんね。
今回作るアドオンは WebSocket で中継サーバーに常時接続することにします。
これにより Vim からのスクリプトを待ち受けます。

Vim の channel

最近の Vim には channel という機能があり、

let channel = ch_open('localhost:8765')

のようにすることで TCP サーバーへ接続できます。
しかも、モードを指定することで JSON で送受信ができるのです。
今回は、この channel を使って中継サーバーへスクリプトを送りこみます。

中継サーバー

中継サーバーは Ruby の em-websocket という Gem を使って書きます。
EventMachine というイベント駆動ネットワークライブラリーをベースとした WebSocket サーバーライブラリーとのことです。
(TCP サーバーは EventMachine 自体に含まれているようです)

実装

Web Extension・Ruby・Vim のコードについての詳細は省きますが、後で参考サイトをいくつかあげます。
ソースコード全体については vim2fx.zip を参照してください。

プロトコル

以下のような JSON でやりとりすることにします。
script プロパティにスクリプトが入っているというシンプルなものです。
スクリプトを直接でも良いですが、オブジェクト内に入れることで拡張性を一応担保しておくことにします。

Web Extension

Web Extension では、manifest.json で background スクリプトを指定することでブラウザ起動中ずっと動作するコードを書けます。
ただ、background スクリプトから直接アクティブなタブでスクリプトを実行することはできません。
そこで、アクティブなタブにメッセージを送信して、それをタブ側で受信してスクリプトを実行することにします。
といっても、chrome.tabs.query でアクティブなタブを探し、chrome.tabs.sendMessage でメッセージを送信するだけです。

タブ側では、chrome.runtime.onMessage.addListener で受信関数を登録します。
この関数内でメッセージ内のスクリプト eval してしまいます。

Ruby / EventMachine

Vim の channel で JSON 送信すると [1, {"script": "window.alert(1)"}] のように先頭に数値が入った配列で届きます。
なので、後ろのオブジェクト本体だけを取りだしています。
TCP サーバーのポートは 2018、WebSocket サーバーは 2017 とします。
数値に意味はないので、好きなポートで構いません。
EM.start_server で TCP サーバーを、EM::WebSocket.start で WebSocket サーバーを起動します。
EM::Channel を使って、Vim からのメッセージを TCP サーバーから WebSocket サーバーへ転送します。

Ruby のバージョンは 2.4.2、Gem は最新のを使っています。

Vim

前述の通り、channel を使って TCP サーバーにスクリプトを送信します。

実行してみる

vim2fx.rb が上で示した Ruby スクリプトです。
これをまずは実行しておきます。

そして、Firefox で about:debugging で一時的なアドオンとして読みこみ、適当な普通のページを開いているタブに変更します。
(about:debugging などのような特別なページや addons.mozilla.org 上では、アドオンのスクリプトが動作しません。)

この状態で Vim で何かしらスクリプトを書き Fx コマンドを実行すれば、Firefox で開いているタブでスクリプトが実行されます。

問題なく実行されれば完成です。

パーミッション

動かすコードによっては追加のパーミッションが必要になるので manifest.json に追記が必要になります。
内容が内容なので、全てのパーミッションを設定しちゃっても良いかもしれません。

まとめ

いつでも Vim から Firefox へスクリプトを送りこみ実行することができるようになりました。
Fx コマンドを定義したので、 autocmd やマッピングで任意のタイミングで実行するようにもできます。
また、中継サーバーは単に JSON を受けとるに過ぎないので、jq などを併用してシェルスクリプトからも実行できます。

ただ、今回のシステムではスクリプトの実行結果を受け取ることはできません。
気が向いたら、それも出来るようにしようかと思います。

以前の Vimperator 環境のようにはいきませんが少しは取り戻せた気がします。

参考サイト

要点のみで、全て説明できてはいないので参考になるサイトを挙げておきます。

堂島猫


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です