Sep 01, 2010
nsIWindowWatcher::openWindow()で複数の引数をウィンドウに渡すには?
nsIWindowWatcherのopenWindow()
って何ですか
アドオンで新しいChromeウィンドウ(特権付きのウィンドウ、XPCOMとか自由に使えるやつ。ブラウザウィンドウ等、Firefoxのアプリケーションとしてのウィンドウはだいたいそう。)を開く方法はいくつかある。
window.openDialog()
を使う- nsIWindowWatcherの
openWindow()
を使う
普通にXULドキュメントの中で読み込まれてるスクリプトからやるのなら、1の方法でいい。
でも、JavaScriptコードモジュールやXPCOMコンポーネントのスクリプトのように、グローバルオブジェクトがDOMWindowじゃない場面ではこの方法が使えない。特にFirefoxが起動した直後でまだブラウザウィンドウすら開かれていないという場面では、nsIWindowMediatorのgetMostRecentWindow()
でDOMWindowを取得してそのメソッドを呼ぶ、というようなこともできない。なのでこういう時は2の方法を使わないといけない。
やりたかったこと
window.openDialog()
の引数は window.open()
と同じだ。
- 開くウィンドウのChrome URL(文字列)
- ウィンドウ名(大抵は
'_blank'
決めうち) - ウィンドウの挙動の指定(
'chrome,all,dialog=no'
とか'chrome,all,modal'
とかそういうの)
window.openDialog()
の場合はこの後に続いてさらに引数を指定することができて、第4引数以降に渡した物がそのまま、開かれたウィンドウの中でwindow.arguments
として参照できるようになっている。例えば
var w = window.openDialog(url, '_blank', 'chrome,all',
arg0, arg1, arg2);
こう指定して開かれたウィンドウでは
window.arguments[0] // => arg0の値
window.arguments[1] // => arg1の値
window.arguments[2] // => arg2の値
となる。Firefoxのブラウザウィンドウは、コマンドライン引数で渡されたURI等をこうやって受け取っている。
これと同じことをnsIWindowWatcherのopenWindow()
でやろうとして、うまくいかなかった。
openWindow()
の引数は
- 親ウィンドウ(DOMWindow)
- 開くウィンドウのChrome URL(文字列)
- ウィンドウ名(大抵は
'_blank'
決めうち) - ウィンドウの挙動の指定
- ウィンドウに渡す引数
となっていて、最初の引数が加わったことを除けば2~4はwindow.openDialog()
の第1~第3引数と同じ。問題は、開かれるウィンドウに引数を渡す方法が違うということ。window.openDialog()
と同じ感覚で引数を渡しても、期待通りに受け渡されない。
var WW = Cc['@mozilla.org/embedcomp/window-watcher;1']
.getService(Ci.nsIWindowWatcher);
// これはダメ
WW.openWindow(null, url, '_blank', 'chrome,all',
arg1, arg2, arg3);
// これもダメ
WW.openWindow(null, url, '_blank', 'chrome,all',
[arg1, arg2, arg3]);
// これは場合によってはOK
WW.openWindow(null, url, '_blank', 'chrome,all',
arg1);
Function.prototype.call()
では引数を普通に並べてFunction.prototype.apply()
では配列で渡す、という様式を真似てJavaScriptの配列を渡してみても、WindowWatcherは「なんですかコレ」って感じでこっちの意図を読み取ってはくれない。開かれたウィンドウの方でwindow.arguments
を見てみても、長さ1の配列になってて、その要素はなんだかよく分からないnsISupportsのオブジェクトになってる。
引数を1つだけ渡す場合だとうまくいくことがあるというのは、このメソッドの第5引数の型がnsISupportsだからだ。nsISupportsをを実装してるオブジェクトならWindowWatcherはちゃんと受け取ってくれて、開かれた側のウィンドウでもwindow.arguments[0].QueryInterface()
して使うことができる。
でもやりたいことはあくまで、複数の引数をウィンドウに渡して、開かれたウィンドウの側でwindow.arguments
で配列の要素としてそれぞれを受け取ることなのですよ。というか、開かれる側のウィンドウがそういう実装になっているため、どうしてもその様式で引数を渡さないといかんのです。
nsISupportsArrayとnsISupportsなんちゃらで……
XPCOMの世界で配列を扱わないといけない場面でたまーにnsISupportsArrayというのが出てくる。これは名前の通り配列のような性質を持つインターフェースで、openWindow()
の第5引数はこのインターフェースを実装してるオブジェクトも受け付けるという風にIDL定義には書いてある。
似たような感じでプリミティブ値に対応するようなXPCOMのインターフェースがいくつかあって、nsISupportsStringとかnsISupportsPRBoolとかnsISupportsPRUInt64とかそういうのがいっぱいある。これらのインスタンスをnsISupportsArrayに格納してopenWindow()
に渡してやれば、どうやら、開かれた方のウィンドウでは対応するJavaScriptのプリミティブ値としてそれらを受け取れるらしい。
が、こんな事真面目にやってらんないですよね。値の型に合わせてインターフェースを使い分けてインスタンスを作って……とか、めんどくさすぎる。
幸い、XPCOMにはnsIVariantという便利な物があって、これのsetFromVariant()
にJavaScriptの値を適当に渡してやると、あとはnsIVariant君がよろしくやってくれるのです。これを使わない手はない。
var JSArray = ['string', true, 29];
var array = Cc['@mozilla.org/supports-array;1']
.createInstance(Ci.nsISupportsArray);
JSArray.forEach(function(aItem) {
var variant = Cc['@mozilla.org/variant;1']
.createInstance(Ci.nsIVariant)
.QueryInterface(Ci.nsIWritableVariant);
variant.setFromVariant(aItem);
array.AppendElement(variant);
});
WW.openWindow(null, url, '_blank', 'chrome,all', array);
これでめでたく、複数の引数をWindowWatcherからも渡せるようになりました、と。
でもハッシュは渡せなかった
これで一件落着と思ってたんだけど、この方法が使えない場合があることが分かった。nsIVariantはプリミティブ値でもXPCOMのオブジェクトでも何でも受け渡せる万能なヤツなんだけど、nsISupportsArrayと組み合わせてWindowWatcherに渡す時は、XPCOMのインターフェースを持ってないJavaScriptのネイティブのオブジェクトはこの方法では渡せなかった。
var variant = Cc['@mozilla.org/variant;1']
.createInstance(Ci.nsIVariant)
.QueryInterface(Ci.nsIWritableVariant);
variant.setFromVariant({ prop : value });
こうやって値を設定した物をnsISupportsArrayに渡しても、開かれたウィンドウの方ではよく分からんnsISupportsのオブジェクトになってしまって、元のオブジェクトが持ってた情報を取り出せなかった。
回避方法として思いつくのは
JSON.stringify()
して渡して、受け取り側でJSON.parse()
する- nsIPropertyBagにする
くらい。JSONにできない物は2の方法でやるしかないと思う。
var bag = Cc['@mozilla.org/hash-property-bag;1']
.createInstance(Ci.nsIWritablePropertyBag);
for (var i in hash)
{
if (hash.hasOwnProperty(i))
bag.setProperty(i, hash[i]);
}
で渡して、受け取った側で
var hash = {};
var enum = window.arguments[0]
.QueryInterface(Ci.nsIPropertyBag)
.enumerator;
while (enum.hasMoreElements())
{
let item = enum.getNext()
.QueryInterface(Ci.nsIProperty);
hash[item.name] = item.value;
}
という風にしてハッシュに戻す。とか。
まとめ
ウィンドウ間の情報の引き渡しはマジ鬼門。
あと、上に書いた諸々の処理をライブラリとしてまとめておいたので、同じような事をしようとしてる人は使うといいよ。
wikieditish message: Ready to edit this entry.