wrappedJSObject

by 2 contributors:

wrappedJSObjectXPConnectラッパで利用できることがあるプロパティです。利用できる場合、それはあなたに、ラッパによって隠蔽されたJavaScriptオブジェクトへのアクセスを可能にします。

wrappedJSObjectプロパティをサポートするXPConnectラッパには2つの種類があります:

  • 内容領域のオブジェクトを操作する際にクロムのコードを保護するために使われるXPCNativeWrapper。詳細な情報はXPCNativeWrapperのページを参照してください。
  • 例えばJavaScriptによって実装されたXPCOMコンポーネントを使う時などに遭遇する、一般的なXPConnectラッパ。

この項では後者、つまり、コンポーネントが持つプロパティやメソッドのうちxpidlによってサポート済みと宣言されたインターフェースには含まれていない物を隠蔽する種類のラッパについて解説しています。

以下、XPConnectラッパが何をするものなのかという事と、wrappedJSObjectがそれを迂回するためにどのように使われるのかについて紹介しましょう。

例となるコンポーネント

wrappedJSObjectプロパティがどのように働くのかを見るためには、JavaScriptによって実装されたXPCOMコンポーネントの例が必要です。コンポーネントの作成方法の詳細についてはJavaScriptでのXPCOMコンポーネントの作成方法を参照してください。

簡単のため、コンポーネントを登録するためのコードは省略します。以下のコンポーネントが@myself.com/my-component;1というコントラクトIDで登録されているものと仮定してください。

// コンストラクタ
function HelloWorld() {
};

HelloWorld.prototype = {
  hello: function() {
    return "Hello World!";
  },

  QueryInterface: function(aIID)
  {
    if (!aIID.equals(Components.interfaces.nsISupports) &&
        !aIID.equals(Components.interfaces.nsIHelloWorld))
      throw Components.results.NS_ERROR_NO_INTERFACE;
    return this;
  }
};

XPConnectによるラッピング

それでは上記のコンポーネントへの参照を取得してみましょう。この例では getServiceを使用していますが、XPCOMから参照を取得する限りにおいては、コンポーネントは常に、XPConnectによってこれと同様にラップされます:

var comp = Components.classes["@myself.com/my-component;1"].getService();

コンポーネントの実装において定義したhello()メソッドを呼び出そうとした場合、このような結果が得られます:

> comp.hello();
TypeError on line 1: comp.hello is not a function

これは、前述した説明のとおり、compHelloWorldのインスタンスのJavaScriptオブジェクトそのものではなく、XPConnectラッパによって包まれた物であるために起こります:

> dump(comp);
[xpconnect wrapped nsISupports]

これらのラッパは、JavaScriptによって実装されたXPCOMコンポーネントを他のXPCOMコンポーネントと全く同じようにユーザに対して見せるようにするという発想に基づいています。これはそのコンポーネントの公開されたインターフェースを明確にし、コンポーネントの内部データを保護する役割も提供します。

このラッパに対してはQueryInterfaceメソッドの呼び出しが利用できますが、それはこのメソッドがnsISupportsインターフェースにおいて定義されており、ラッパ自身が、自分がラップしているオブジェクトがnsISupportsインターフェースを実装している事を知っているからです:

> comp.QueryInterface(Components.interfaces.nsIHelloWorld);
[xpconnect wrapped (nsISupports, nsIHelloWorld)]

この例に見られるように、QueryInterfaceの呼び出しは、そのラッパに対して、そのコンポーネントが他のインターフェースを実装している事を教えます。nsIHelloWorldインターフェースにおいてhelloメソッドが定義されていると仮定すると、それは以下のように呼び出せます:

> comp.hello()
Hello World!

この挙動は、そのコンポーネントに対してアクセスするために使われるべきインターフェースを明示的に定義し、コードの開発においてそれを使うよう強制する上で、良い仕組みと言えます。しかし、コンポーネントを試作する場合においてまでインターフェースの定義をいちいち書かなくてはならない(そしてそれを変更する度に再コンパイルしないといけない)のは不便です。

wrappedJSObjectの利用

XPConnectは、それによってラップされたオブジェクト自身が許可している場合、wrapper.wrappedJSObjectプロパティを用いて、ラッパを迂回してその中にあるJavaScriptオブジェクトに直接アクセスすることを許容します。

より具体的に言うと、XPConnectのソース中のコメントにあるとおり、comp.wrappedJSObjectは以下の3つの条件が満たされている場合に利用できます:

  • compが本当にJavaScriptオブジェクトをラップしたXPConnectラッパであること。JavaScriptオブジェクト以外に対するラッパはこのプロパティを持ちません。
  • ラップされたオブジェクトがwrappedJSObjectプロパティを持っており、そのプロパティが値としてJavaScriptオブジェクトを返すこと。
  • nsIXPCSecurityManagerがアクセスを許可していること。(詳細はソース中のコメントを参照してください。Mozilla拡張機能やアプリケーションにおいては大抵の場合は問題ありません。)

これは、コンポーネントを実装しているJavaScriptオブジェクトに直接アクセスできるようにするためにコンポーネントを修正する必要があるということです。例えば以下のようにします:

function HelloWorld() {
  this.wrappedJSObject = this;
};

これで、コンポーネントを直接取得できるようになりました:

var comp = Components.classes["@myself.com/my-component;1"]
                     .getService().wrappedJSObject;

これは本物のJavaScriptオブジェクトです:

> comp
[object Object]

なので、あらゆるプロパティにアクセスすることができます:

> comp.hello();
Hello World!

この機能は、試作を手軽に行うために利用できるほか、型が不定なJavaScriptの値をコンポーネントに簡単に渡すためにも利用できます。(具体的には、完全なJavaScriptのデータを共有するなど。)

ドキュメントのタグと貢献者

Contributors to this page: Piro, Mgjbot
最終更新者: Piro,