JavaScriptでXPCOMを作る ― 2005年06月13日 22時33分
FirefoxのUIはXULとJavaScriptで構成されていますが、JavaScriptの組み込みオブジェクトだけではファイル操作やウィンドウ操作などができません。そこで登場するのがXPCOM、これを用いることでOSに絡んだ操作やRDFの操作などが行えるようになります。
Firefoxに付随するXPCOMコンポーネントの多くはC++でかかれ、プラットフォーム別にコンパイル作業などが必要なのですが、XPCOMはJavaScriptを用いて作ることもでき、これならOSにあわせてコンパイルする必要はありません。そこでここでは実際にJavaScriptでXPCOMを作ってみることにします。
今回作成するXPCOMコンポーネント(nntPerson
)は人物をあらわすもので、年齢を示すage
プロパティと年齢を言うsay
メソッドを持つものとします。また、以下ではWindowsで作成することを前提として話を進めていきますので、ほかのOSで作成する場合は適宜読み替えてください。
なお、ここではXPCOMコンポーネントの呼び出し方や拡張の作成については深くは触れません。XULチュートリアル(未完の邦訳)のXPCOMの章やFirefoxまとめサイトの「拡張の作成」を参考にしてください。
下準備
Gecko SDKの入手
Geckoに関する開発を進めるためのツール集がGecko SDKです。Mozilla 1.7開発版のFTPサイトからプラットフォームにあわせたGecko SDKを入手できます。ここではWindows用ということでgecko-sdk-i586-pc-msvc.zipをダウンロード。解凍してできたgecko-sdkディレクトリをCドライブ直下にコピーします。
libIDL-0.6.dll、glib-1.2.dllの入手
Gecko SDKのツールを使うためにいくつかライブラリを追加する必要が出てきます。プラットフォームによって異なるかもしれませんが、Windowsの場合はlibIDL-0.6.3-win32-bin.zipとglib-19990228.zipを入手。解凍した中にあるlibIDL-0.6.dll、glib-1.2.dllをパスの通ったディレクトリに置きます。私は簡便のためC:\gecko-sdk\binにおきました。
UUIDの生成
拡張やインターフェースを識別するためにUUIDを生成する必要がでてきます。Web上でUUIDを生成してくれるサービスもありますが、私はMicrosoftの提供するPlatform SDKのguidgen.exeを使用しました。
作業用ディレクトリの作成
ここではC:\JSPersonを作業用ディレクトリとします。
インターフェース記述ファイルの作成
作成するコンポーネントのインターフェース、すなわちどんなプロパティやメソッドを持つのかはあらかじめFirefoxに教えておかないといけません。そこで登場するのがIDLです。IDLを使用することでコンポーネントのプロパティやメソッド、継承関係などを記述することができます。
作業用ディレクトリにcomponentsディレクトリを作成し、その中に以下の内容でnntIPerson.idlを作ります。
#include "nsISupports.idl"
[scriptable, uuid(5E458528-CE7C-4610-9872-5177DCB32E7C)]
interface nntIPerson : nsISupports
{
attribute int age;
AString say();
};
nntIPerson
インターフェースはnsISupports
インターフェースを継承し、age
プロパティとsay
メソッドを持ちます。nsISupportsを継承しているのは、どんなインターフェースもnsISupportsを継承することになっているからです。
[]内にはインターフェースを識別するためのUUIDを指定します。また、キーワードscriptable
をつけることで、作成するコンポーネントがスクリプトからアクセスできるようになります。
プロパティの宣言はattribute 型 プロパティ名
とし、読み取り専用プロパティの場合はreadonly
をattributeの前に指定します。
メソッドの宣言はC/C++/Javaのそれとよく似ています。ただし、通常の引数はin 型 引数名
の形で宣言します。
文字列型としては、string
(ASCII文字のみ)やwstring
(UTF-16文字列)ではなく、ACString
(ASCII文字のみ)やAString
(UTF-16文字列)を使ったほうが効率がいいらしいです。
タイプライブラリの作成
こうして作成したインターフェース記述ファイルですが、そのままでは使えません。Geckoがインターフェースを理解できるようコンパイルする必要があります。コンパイルに用いるのが準備で触れたGecko SDKに含まれるxpidlというツールです。コマンドプロンプトから以下のようにxpidlを実行し、nntIPerson.xptを生成してください。
> cd C:\JSPerson\components > "C:\gecko-sdk\bin\xpidl" -m typelib -w -I "C:\gecko-sdk\idl" nntIPerson.idl
インターフェースの実装
インターフェースが決まったので実際にその処理の中身をJavaScriptで書いてみます。
componentsフォルダ内にnntPerson.jsを作成しましょう。
function nntPerson() { /* 初期化処理 */ }
nntPerson.prototype = {
get age() { return this._age; },
set age(newValue) { return this._age = newValue; },
say: function ()
{
return "Hello, I'm " + this._age + " years old.";
},
QueryInterface: function (iid)
{
if (iid.equals(Components.interfaces.nntIPerson) ||
iid.equals(Components.interfaces.nsISupports))
return this;
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
return null;
},
_age: 0
}
プロパティのゲッタ、セッタはそれぞれキーワードget
、set
を用いて指定します。
QueryInterface
はnsISupportsインターフェースのメソッドです。コンポーネントが実装しているインターフェースを全て書くことになります。この場合ですとnntIPersonインターフェースはnsISupportsインターフェースを継承しているので
nsISupportsも書きます。
var nntPersonModule = {
_description: "JS Person Component";
_CID: Components.ID("{5E458528-CE7C-4610-9872-5177DCB32E7C}"),
_contractID: "@nanto.asablo.jp/jsperson;1",
...
_factory: {
createInstance: function (outer, iid)
{
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return (new nntPerson()).QueryInterface(iid);
}
},
...
};
function NSGetModule(compMgr, fileSpec) {
return nntPersonModule;
}
上に書いたのが実装を登録するためのモジュールです。CIDにはIDLで指定したUUIDコンポーネントを識別する新たなUUIDを、Contract IDにはコンポーネントを識別するための文字列を指定します。Contract IDの書式についてはIBMのXPCOM作成に関する文書をごらんください。基本的には@<ドメイン名>/<モジュール名>;<バージョン>という形になります。
テストケースの作成
次にテストケースを作成します。作業フォルダに戻って、contentフォルダを作成、さらにその下にjspersonフォルダを作成します。その中にcontents.rdfとjsperson.xulを作成します。
<?xml version="1.0" encoding="utf-8" ?>
<!-- jsperson.xul -->
<?xml-stylesheet type="text/css" href="chrome://global/skin/" ?>
<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript">
<![CDATA[
var person = Components.classes["@nanto.asablo.jp/jsperson;1"]
.createInstance(Components.interfaces.nntIPerson);
function setAge()
{
person.age = parseInt(document.getElementById("text-age").value);
}
function sayAge()
{
alert(person.say());
}
]]>
</script>
<vbox>
<textbox id="text-age" value="5" />
<hbox>
<button label="Set age" oncommand="setAge();" />
<button label="Say age" oncommand="sayAge();" />
</hbox>
</vbox>
</page>
パッケージの作成
contentフォルダをZIP圧縮し、ファイル名をjsperson.jarに変更します。作業フォルダ内にchromeフォルダを作り、jsperson.jarをその中に移動します。さらに、作業フォルダ内にinstall.rdfを以下の内容で作成します。パッケージのUUIDは新たに取得します。
<?xml version="1.0" encoding="utf-8" ?>
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<rdf:Description rdf:about="urn:mozilla:install-manifest">
<em:id>{34E20009-C0A0-49ad-82F6-5716E674C61B}</em:id>
<em:name>JSPerson</em:name>
<em:version>0.0.1</em:version>
<em:description>JavaScriptによるXPCOM作成のテスト</em:description>
<em:creator>TOYAMA Nao</em:creator>
<em:homepageURL>http://nanto.asablo.jp/blog/</em:homepageURL>
<em:targetApplication>
<rdf:Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>1.0</em:minVersion>
<em:maxVersion>1.0+</em:maxVersion>
</rdf:Description>
</em:targetApplication>
<em:file>
<rdf:Description rdf:about="urn:mozilla:extension:file:jsperson.jar">
<em:package>content/jsperson/</em:package>
</rdf:Description>
</em:file>
</rdf:Description>
</rdf:RDF>
最後にchromeフォルダ、componentsフォルダ、install.rdfをZIP圧縮し、ファイル名をjsperson-0.0.1.xpiと変更すればインストールパッケージの完成です。
実行
あとは普通の拡張と同じようにインストールして、アドレスバーにchrome://jsperson/content/と入力してください。年齢を セットするさまが見られます。なお、Firefox 1.0.4およびDeer Park Alpha 1 RCにて動作を確認しました。
参考文献
この記事の作成にあたって以下のサイトを参考にさせていただきました。
- seamonkey/xpcom/sample/ - seamonkey Cross Reference
- XPCOMのサンプルが見られます。
- IDL interface rules:
- IDLの書き方について。
- Mozilla String(文字列)ガイド
- XPCOMで使う文字列型についてのガイドです。英語版のほうが内容が新しいのですが私は日本語版のほうを参考にしました。
- xpidl - XPCOM インタフェース情報を生成するためのツール
- XPIDLの使い方について。
- dW : Components : XPCOM: 第2回: XPCOMコンポーネントの基本
- Contract IDの書式について参考にしました。
- 拡張の作成 - Firefox まとめサイト
- 拡張のパッケージングについて参考にしました。
- Element Reference - XULPlanet
- テストケースの作成にあたって参考にしました。
コメント
_ Gomita ― 2007年01月01日 04時25分
_ nanto_vi ― 2007年01月01日 23時38分
Bug 242870 – Statically link libIDL, glib with xpidl on Windows
https://bugzilla.mozilla.org/show_bug.cgi?id=242870
それから、QueryInterfaceの実装で
Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
return null;
となっている部分は
throw Components.results.NS_ERROR_NO_INTERFACE;
としたほうがいいですね。
コメントをどうぞ
※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。
※投稿には管理者が設定した質問に答える必要があります。
トラックバック
このエントリのトラックバックURL: http://nanto.asablo.jp/blog/2005/06/13/20665/tb
http://ftp.mozilla.org/pub/mozilla.org/mozilla/source/wintools.zip
また、本文中のidlで「attribute int age;」となっているのが原因でxpt作成できませんでしたが、「attribute long age;」とすることで無事成功しました。
ありがとうございました。