SyntaxHighlighter

2014年11月30日日曜日

Open TortoiseSVN for Google Chrome の Native Messaging Host 対応

Open TortoiseSVN for Google Chrome の Native Messaging Host 対応

前に書いたように、Google Chrome には、登録済みのローカルファイルを実行する仕組みがあり、Native Messaging と呼ばれています。
今回は、この具体的な作り方について書きます。

Native Messaging を使う Google Chrome 拡張機能

Chrome 拡張で Native Messaging を使うには、大きく分けて以下の対応が必要です。
なお、公式のページに、まとまった情報があります。

  • JSON 形式でやりとりする実行ファイルの準備
  • 実行ファイルのマニフェストファイルの準備
  • Chrome 拡張機能から、上記の実行ファイルの呼び出し

JSON 形式でやりとりする実行ファイル

やりとりするデータ形式

Native Messaging では、JavaScript 側と実行ファイル側が JSON 形式でデータをやりとりします。
JavaScript 側から渡すデータは JSON 形式にシリアライズされ、実行ファイルの標準入力から渡されます。
逆に、実行ファイル側からは、標準出力に JSON 形式でシリアライズしたデータを出力することで、JavaScript 側にデータを渡すことができます。

ただし、JSON データの長さ 4byte が、後にくる文字列の前に配置されます。

例えば、{"a":1234} というオブジェクトを、実行ファイル側から渡すことを考えます。
この JSON データの長さは 10 (0Ah) byte です。この場合、0A 00 00 00という 4byte が出力され、その後に JSON データが続きます。
サイズを示す先頭の 4byte は、Native Byte Order で渡すので、大抵の PC では Little Endian になります。

データ列の例
0A000000'{''"''a''"'':''1''2''3''4''}'

そのため、受け取る側はまず最初の 4byte を読み、そのサイズ分のデータを続けて読むことになります。

逆に実行ファイル側から出力する場合も同様です。

注意点

Windows 特有の注意点として、「標準入力と標準出力をバイナリモードで開く必要がある」ということがあります。

Windows では、ファイルを処理する際に「テキストモード」と「バイナリモード」があります。
説明は省きますが、標準入力や標準出力は「テキストモード」になっているので、その状態で \n を出力すると \r\n に自動で変換されることになります。
先の例では、0Ah\n なので、以下のようなバイト列になります。

データ列が変換される例
0D0A000000'{''"''a''"'':''1''2''3''4''}'

結果として、データを受け取る Chrome 側は 00000A0Dh つまり 2573byte の JSON データが送られてくると判断し、正しく動かなくなります。

そのため、C 言語であれば、以下のような Windows 依存のコードを実行しておくことで、標準入力や標準出力をバイナリモードにしておく必要があります。

#include <io.h>
#include <fcntl.h>

void set_binary_mode()
{
    _setmode(_fileno(stdin), _O_BINARY);
    _setmode(_fileno(stdout), _O_BINARY);
}

これらに注意して、好きな実行ファイルを作ります。

Open TortoiseSVN for Google Chrome の場合、 open_tortoise_svn_host.exe がその実行ファイルですが、この中では JSON を扱うために json11 を使わせてもらいました。
このライブラリは C++11 向けに書かれており、少し変更するだけで Visual Studio 2013 の C++ コンパイラでもコンパイルできます。

実行ファイルのマニフェストファイルの準備

作成した実行ファイルは、そのままでは Chrome から呼び出すことはできません。
呼び出せるようにするために、マニフェストファイルを作り、そのマニフェストファイルを Chrome に知らせる必要があります。

マニフェストファイル

公式のページにあるように、マニフェストファイルは以下のように定義します。

{
  "name": "com.my_company.my_application",
  "description": "My Application",
  "path": "chrome_native_messaging_host.exe",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://knldjmfmopnpolahpmmgbagdohdnhkik/"
  ]
}

上記のようなマニフェストファイルを任意のファイル名で作成します。
name は、この Native Messaging 機能の名前を任意で決めて指定します。
path には実行ファイルの名前を指定します。実行ファイルは絶対パスで指定しますが、Windows ではマニフェストファイルがあるディレクトリからの相対パスでも構いません。
type は、現状 stdio しか指定できません。これは上で説明したような JSON 形式のデータを標準入力と標準出力でやりとりすることを意味しています。
allowed_origins には、この Native Messaging Host にアクセスしてよい拡張機能の名前を指定します。この名称は、拡張機能一覧のページ (chrome://extensions/) で ID をチェックすることで分かります。

というか、公式のページの説明を見ましょう。

マニフェストファイルの登録

続いて、作成したマニフェストファイルを Chrome に知らせる必要があります。

これも公式のページに書いてあるように、Windows の場合はレジストリで指定します。
HKEY_CURRENT_USER\SOFTWARE\Google\Chrome\NativeMessagingHosts\com.my_company.my_application にマニフェストファイルの絶対パスを書き込めばよいです。
HKEY_LOCAL_MACHINE 以下でもよいですが、HKEY_CURRENT_USER の下の方が管理者権限が不要なので楽でしょう。

Chrome 拡張機能から、上記の実行ファイルの呼び出し

Native Messaging には、実行ファイルが常駐するタイプと、毎回起動するタイプがあります。
Open TortoiseSVN for Google Chrome は後者のタイプです。

この場合、下記の公式ページの例のように sendNativeMessage を用いて、送信した JSON データと、受け取った JSON データを処理するためのコールバック関数を引数として渡すことで、好きな処理をすることができます。

chrome.runtime.sendNativeMessage('com.my_company.my_application',
  { text: "Hello" },
  function(response) {
    console.log("Received " + response);
  });

面倒になってきたので後半は省略しましたが、作成するのは簡単なのでローカルの実行ファイルと Chrome を連携させたい場合は利用するとよい機能だと思います。

Google Chrome の Native Messaging Host

Google Chrome の Native Messaging Host

Chrome 向けの Open TortoiseSVN を Native Messaging 対応したので、そのまとめです。
今回は、Native Messaging の概要についてです。

TechCrunch の記事にあるように、Chrome では NPAPI がブロックされます。
Open TortoiseSVN for Google Chrome では NPAPI を使っていたので、これを機に Native Messaging 版に変更しました。

Native Messaging

Native Messaging とは、 Google Chrome の Extension の機能の一つで、ローカルの実行ファイルを直接起動してやりとりするための仕組みです。
今回は、この機能を使ってローカルの TortoiseSVN を起動しています。

Native Messaging の概要

詳しくは Google Chrome の解説のページを見ると分かりますが、基本的には以下の通りです。

  • JavaScript から、あらかじめ登録済みのローカルの EXE を実行できる
  • その EXE と JSON 形式でメッセージのやりとりができる

なお、EXE と書きましたが、 BAT などでも問題ありません。

さらに、「EXE を実行してすぐに終了する」形式と「EXE を実行し、サービスのように常駐させてやりとりする」の二つの呼び出し形式がサポートされています。
また、JSON 形式でやりとりをする、という以外には、EXE の内容に制限はありません。

NPAPI プラグインとの違い

「ローカルの EXE (DLL) を呼び出し、JavaScript の制限外のことができる」という点では、NPAPI と似ています。
実際、これまで Open TortoiseSVN では、 NPAPI プラグインを使って、 JavaScript だけではできない「ローカルファイルを実行」を実現していました。

しかし、いくつか大きな違いがあります。

  • Native Messaging の場合、やりとりするデータは JSON に限られる
  • Native Messaging の場合、必ず別プロセスで起動される。

JavaScript のオブジェクトに比較的自由にアクセスできる NPAPI と異なり、Native Messaging では JSON 形式で明示的にやりとりしたデータしか EXE 側には渡らないので、意図せず JavaScript 側のグローバルオブジェクトにアクセスされたりせず、従来の JavaScript のセキュリティモデルは崩れません。
また、EXE を別プロセスで呼ぶため、クラッシュに悩まされることもなくなります。

PPAPI プラグインとの違い

一方で、NPAPI の置き換えとして Google が推進している PPAPI と比べると、PPAPI では呼べる API やローカルリソースへのアクセスに制限があるので、単純にできることが異なります。
基本的には PPAPI はサンドボックス化されており、NPAPI のように、任意の OS Native な API を呼べるわけではありません。

とても大雑把に言うと、NPAPI プラグインでできていたことのうち、「C/C++ で高速に動作するものを作りたい (が、ローカルリソースへの自由なアクセスは不要)」という部分は PPAPI に、「ローカルリソース (OS Native な API などを含む) に制限なくアクセスしたい」という部分が Native Messaging に分割され、整理されたということだと思います。

次回は Native Messaging を利用するための方法について、簡単に書きます。