これは JavaScript で XPCOM コンポーネントを作成するための "Hello World" 的なチュートリアルです。このチュートリアルでは、XPCOM がどのように、そしてなぜそのように動くのかということや、コードの例が何をするものなのかということは説明しません。詳細は 別の記事 を参照してください。このチュートリアルでは、コンポーネントをできる限り少数かつ簡単なステップで動かすためにすべきことを説明していきます。
警告:これは Mac 上での結果です。Windows では手順が異なるかもしれません。
実装
このコンポーネントの例では "Hello World!" という文字列を返すメソッド 1 つだけを公開します。
インタフェースの定義
作ったコンポーネントを JavaScript から、あるいは他の XPCOM コンポーネント内で使用したいとなると、公開したいインタフェースを定義しなければなりません(そのコンポーネントを JavaScript からのみ使用したい場合は、ここで説明するインタフェースを作成せずに済むように wrappedJSObject というトリックを使うことができます。ここ に例があります)。
Mozilla アプリケーションには定義済みのインタフェースがたくさんあるため、新たに定義する必要がないかもしれません。既存の XPCOM インタフェースは Mozilla のソースコードの中のさまざまな場所で閲覧することができますし、XPCOMViewer という、登録済みのインタフェースやコンポーネントを閲覧するための GUI を使うこともできます。Firefox 1.5 で動作する古いバージョンの XPCOMViewer は mozdev mirrors からダウンロードできます。
もし必要なインタフェースが見つかれば、IDL を書いたり typelib をコンパイルしたりする必要はありません。次のセクション に飛んでもかまいません。
適当な既存のインタフェースが見つからなかった場合は独自に定義する必要があります。XPCOM はインタフェースの定義に XPIDL という IDL の方言を使用します。ここに今回の HelloWorld コンポーネント用の XPIDL 定義を示します。
HelloWorld.idl
#include "nsISupports.idl"
[scriptable, uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)]
interface nsIHelloWorld : nsISupports
{
string hello();
};
作成する XPCOM コンポーネントおのおのに新しい UUID を付ける必要があることに注意してください。詳細は GUID の生成 を参照してください。
typelib のコンパイル
インタフェース定義を Mozilla アプリケーションに登録して使用するためには、バイナリ形式 (XPT) にコンパイルする必要があります。コンパイルは Gecko SDK を用いて行うことができます。Mac、Linux、Windows 版の Gecko SDK の入手方法については Gecko SDK という記事をお読みください。
このコマンドを実行して typelib をコンパイルします。ここで、<tt>{sdk_dir}</tt> は Gecko SDK を解凍したディレクトリです。
{sdk_dir}/bin/xpidl -m typelib -w -v -I {sdk_dir}/idl -e HelloWorld.xpt HelloWorld.idl
これで HelloWorld.xpt という typelib ファイルが現在の作業ディレクトリに作成されます。
コンポーネントの作成
HelloWorld.js
/***********************************************************
定数
***********************************************************/
// nsIHelloWorld.idl 内のインタフェース定義への参照
const nsIHelloWorld = Components.interfaces.nsIHelloWorld;
// すべてのコンポーネントがサポートしなければならない必須の基本インタフェースへの参照
const nsISupports = Components.interfaces.nsISupports;
// このコンポーネントを一意的に識別する UUID
// https://kruithof.xs4all.nl/uuid/uuidgen にて生成可能
const CLASS_ID = Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}");
// 説明
const CLASS_NAME = "My Hello World Javascript XPCOM Component";
// テキスト形式の一意識別子
const CONTRACT_ID = "@dietrich.ganx4.com/helloworld;1";
/***********************************************************
クラス定義
***********************************************************/
// クラスコンストラクタ
function HelloWorld() {
};
// クラス定義
HelloWorld.prototype = {
// このインタフェースで公開したい関数の定義
hello: function() {
return "Hello World!";
},
QueryInterface: function(aIID)
{
if (!aIID.equals(nsIHelloWorld) &&
!aIID.equals(nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
}
};
/***********************************************************
クラスファクトリ
このオブジェクトはグローバルスコープである Components.classes のメンバ。
コントラクト ID がキーになっている。例:
myHelloWorld = Components.classes["@dietrich.ganx4.com/helloworld;1"].
createInstance(Components.interfaces.nsIHelloWorld);
***********************************************************/
var HelloWorldFactory = {
createInstance: function (aOuter, aIID)
{
if (aOuter != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return (new HelloWorld()).QueryInterface(aIID);
}
};
/***********************************************************
モジュール定義(xpcom 登録)
***********************************************************/
var HelloWorldModule = {
registerSelf: function(aCompMgr, aFileSpec, aLocation, aType)
{
aCompMgr = aCompMgr.
QueryInterface(Components.interfaces.nsIComponentRegistrar);
aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME,
CONTRACT_ID, aFileSpec, aLocation, aType);
},
unregisterSelf: function(aCompMgr, aLocation, aType)
{
aCompMgr = aCompMgr.
QueryInterface(Components.interfaces.nsIComponentRegistrar);
aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation);
},
getClassObject: function(aCompMgr, aCID, aIID)
{
if (!aIID.equals(Components.interfaces.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
if (aCID.equals(CLASS_ID))
return HelloWorldFactory;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
canUnload: function(aCompMgr) { return true; }
};
/***********************************************************
モジュール初期化
アプリケーションがコンポーネントを登録するときにこの関数が呼び出される。
***********************************************************/
function NSGetModule(aCompMgr, aFileSpec) { return HelloWorldModule; }
インストール
拡張機能に
- HelloWorld.js と HelloWorld.xpt を {extensiondir}/components/ にコピーする
- プロファイルディレクトリから compreg.dat と xpti.dat を削除する
- アプリケーションを再起動する
Firefox に
- ソースから実行する場合、HelloWorld.js と HelloWorld.xpt を {objdir}/dist/bin/components ディレクトリにコピーする
- components ディレクトリから compreg.dat と xpti.dat を削除する
- プロファイルディレクトリから compreg.dat と xpti.dat を削除する
- アプリケーションを再起動する
コンポーネントの使用
try {
// JavaScript 内でコンポーネントを使用できるようにするには、一般にこれが必要
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var myComponent = Components.classes['@dietrich.ganx4.com/helloworld;1']
.createInstance(Components.interfaces.nsIHelloWorld);
alert(myComponent.hello());
} catch (anError) {
dump("ERROR: " + anError);
}
その他の資料
- XPCOM コンポーネントの JS での実装についての mozillazine フォーラムの 2 つのスレッド。説明、コードの例、トラブルシューティング情報もあり:
- Implementing XPCOM components in JavaScript - kb.mozillazine.org 内
- Using XPCOM in JavaScript without leaking - 必読
- コンポーネントの例
- 古い JS+XPCOM のメモ - wrappedJSObject の情報もあり