Mac OS XでネイティブAPIをGoogle Chromeの拡張機能から実行
先日のWWDCでAppleはSafariの拡張機能を公開し、またSafari Developer Programを開始しました。 既にGoogleはChromeブラウザで拡張機能を提供していますし、にわかにFirefoxの独壇場だった拡張機能市場が盛り上がりを見せています。
実際、僕はSafariがデフォルトブラウザなのですが、たまにChromeを使って、Chrome上からURLをSafariに送りたくなったりします。 しかし、ちょっと探してみてもそのような拡張機能はありません。なければ作るしかないわけですね!
ただ、これを実現するには他のアプリケーションをGoogle Chromeから起動しなくてはいけませんし、そうするためにはMac OS XのネイティブAPIを呼び出す必要があるわけです。ひょっとしたらもっと賢い方法があるかもしれないのですけど。Google Chromeは幸いネイティブコードを拡張機能から実行する手立てを用意してくれています。そのためにはFlashプレイヤーのようなNPAPIに対応したプラグインを作成して拡張機能のバックグランドページに読み込みます。この記事ではNPAPIプラグインを利用してネイティブコードをエクステンションから呼び出す方法を紹介します。
まず始めに
ソースコードはgithubのレポジトリで公開しています。 すでにgitを利用している場合はgithubからレポジトリをcloneしてください。gitがない場合は、githubのDownload SourceボタンからZipかTarをダウンロードして解凍してください。
% git clone git://github.com/niw/open_with_default
実験的APIを使うための準備
この拡張機能はリンクの右クリックメニューを拡張します。そのためには実験的なAPI
を有効にする必要がありますが、これは普通のリリース版では使えません。
まず、Google ChromeをDevChannelで提供されているものに更新します。
その後、Google Chromeを起動する際に--enable-experimental-extension-apis引数を追加します。
たとえば、ターミナル.appから次のように実行します。
% open /Applications/Google\ Chrome.app --args --enable-experimental-extension-apis
ビルドしてテスト!
ダウンロードしたOpenWithDefault.xcodeprojをXcodeで開いて、メニューからビルドを選択します。展開済みの拡張機能がExtensionフォルダに作成されます。
上記の通り、DevChannelからGoogle Chromeをダウンロードして起動します。
「拡張機能」のページを開いて、「デベロッパーメニュー」を有効にします。
「パッケージ化されていない拡張機能を読み込みます…」という変な日本語のボタンを押してExtensionフォルダを選び拡張機能をGoogle Chromeに読み込みます。
「Open with Default」という項目がリンクの右クリックメニューに追加されます。選ぶとデフォルトのブラウザ、Safariなどでそのリンクを開くことができます。
NPAPIプラグインを拡張機能に読み込む
基本的なGoogle Chrome拡張機能の作り方は割愛します。NPAPIプラグインは、Flashプレイヤーと同じなので、<embed>タグでbackground.htmlに読み込みます。typeはNPAPIプラグインを作る際に指定します。
<body onload="init()">
<embed type="application/x-open-with-default" id="pluginId">
</body>
このように読み込んだプラグインに対して、idを使ってJavaScriptからは以下のように呼び出しをします。
var plugin = document.getElementById("pluginId");
plugin.open("http://twitter.com/");
このopenは作ったプラグインで提供するメソッドになります。
NPAPIプラグインを作成する
NPAPI(Netscape Plugin Application Programming Interface)はウェブブラウザのプラグインを作るための伝統的なCのAPIです。 プラグインは任意の関数をブラウザのJavaScriptに提供できるので、そこを経由してネイティブAPIを拡張機能から呼び出すことができるというわけです。
Mac OS Xでは.pluginで終わるBundleに特別なInfo.plistを記述することで作成します。以下にこの拡張機能のInfo.plistから重要な部分を掲載します。
<key>WebPluginMIMETypes</key>
<dict>
<key>application/x-open-with-default</key>
<dict>
<key>WebPluginTypeDescription</key>
<string>A Plugin for Chrome Extension</string>
</dict>
</dict>
<key>WebPluginName</key>
<string>${PRODUCT_NAME}</string>
<key>WebPluginDescription</key>
<string>A Plugin for Chrome Extension</string>
ここではembedタグのtypeと同じMIMEタイプを指定する必要があります。この場合は、application/x-open-with-defaultです。
またプラグインを初期化するためにいくつかの必要な関数を定義します。
NPError NP_Initialize(NPNetscapeFuncs* browserFuncs) {
// NPAPIで提供されるAPIをプラグインから呼び出すために保存しておく
npnfuncs = browserFuncs;
return NPERR_NO_ERROR;
}
NPError NP_GetEntryPoints(NPPluginFuncs* pluginFuncs) {
// このプラグインを登録するために必要な関数を指定
pluginFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
pluginFuncs->size = sizeof(pluginFuncs);
pluginFuncs->newp = NPP_New;
pluginFuncs->destroy = NPP_Destroy;
pluginFuncs->getvalue = NPP_GetValue;
return NPERR_NO_ERROR;
}
void NP_Shutdown(void) {
// 何もしないけれども、必要です
}
このNP_Shutdownは何もしないですが、これら3つの関数はすべて必要です。NP_GetEntryPointsでは、今回必要な3つの関数、newpとdestroy、getvalueを登録します。
newpとdestroyはこのプラグインがページにロード、アンロードされた際に呼ばれます。getvalueはJavaScriptからこのプラグインが呼ばれたときに必要になります。
JavaScriptから呼べるようにする
getvalueで指定した関数、この場合NPP_GetValueの中で上で説明したようなJavaScriptの呼び出しをしたときに使われるオブジェクトのインスタンスを生成します。
// もしまだインスタンス化していなかったら、作る
if(!plugin_instance) {
plugin_instance = npnfuncs->createobject(instance, &plugin_class);
npnfuncs->retainobject(plugin_instance);
}
*(NPObject **)value = plugin_instance;
plugin_classはJavaScriptから見えるオブジェクトの定義です。またインスタンス化したあとに参照カウントを増やす必要があります。destroyで指定した関数、つまりNPP_Destroyでこの参照カウントを減らすことでNPAPIはプラグインがアンロードされたときにこのオブジェクトに確保したメモリを開放することができます。
このオブジェクトに対して関数、たとえばopenのような呼び出しをした場合、このサンプルではplugin_classで定義したplugin_invokeが関数名と引数とともに呼ばれます。
この中でネイティブの世界でできることがなんでも出来ます。もちろん、デフォルトのブラウザでURLを開く、といったことも。
関数名はNPIdentifier型、引数や戻り値はNPVariant型の配列で提供されます。
これらの値は次の通り通常のUTF-8の文字列などに変換することができます。
// NPIdentifierからUTF-8の文字列を得る
NPUTF8 *name = npnfuncs->utf8fromidentifier(methodName);
// NPVariantからUTF-8の文字列を得る
if(NPVARIANT_IS_STRING(args[0])) {
NPString str = NPVARIANT_TO_STRING(args[0]);
// str.UTF8Charactersとstr.UTF8Lengthを使ってなにかする
}
ネイティブAPIを使った拡張機能
NPAPIプラグインをつかった拡張機能を作るのは、確かに面倒ですがとても強力です。 たとえば、辞書.appを引くような拡張をGoogle Chromeに追加するといようにGoogle Chromeに欠けている機能を追加できます。 この拡張機能はたいしたことをしていませんが、サンプルとして素晴らしい拡張機能を作る際の参考になればと思います。
Great example for NPAPI.
Currently trying to create NPAPI plugin for Windows 7 Chrome to max-height (WIN+Shift+Up arrow) the created window (OnCreated).
great post.Thanks