JSMをimportしているコード内のfunctionをJSM側から呼び出すには

みなさんは JavaScript Code Module ( 以下 JSM と略記します ) を書いていて、その JSM を import するコード側の function を JSM 側からコールしたい、といったニーズが発生したことはないでしょうか。
つまり以下のようなことをしたいケースです。

// hoge.jsm 側のコード
function hoge(){
		...
	changeXUL(); // JSM を import する側で定義されている function
		...
};
//JSM を import する側のコード
{
	Components.utils.import("resource://hoge/hoge.jsm",scopeHoge);
	let changeXUL=function(){ // JSM 側から呼び出したい function
		...
		// ブラウザの要素を DOM で変更する処理など
		...
	};
		...
}

上記コードはこのままでは動きません。hoge() 内でコールされている changeXUL() が undefined となります。
しかし hoge() から changeXUL() をコールしたいケースがないわけではありません。ここでは hoge() から changeXUL() をコールする方法を解説します。


上記の方法では hoge() から changeXUL() をコールできないことは先に述べました。hoge() から changeXUL() を呼び出すには、changeXUL() への参照を JSM 側に持つことで解決します。

最初に JSM 側に changeXUL() への参照を格納する変数を定義します。

var funcCallback;

同じく JSM 側に変数 funcCallback に changeXUL() への参照を設定する setter を定義します。

var EXPORTED_SYMBOLS=[
	"setCallback"
];
		...
function setCallback(pfunc){
	funcCallback=pfunc;
}

今度は JSM を import するコード側であらかじめ先の setter をコールするコードを書いておきます。

{
	let scopeHoge={};
	Components.utils.import("resource://hoge/hoge.jsm",scopeHoge);
		...
	let changeXUL=function(){ // JSM 側から呼び出したい function
		...
		// ブラウザの要素を DOM で変更する処理など
		...
	};
		...
	let init(){
		scopeHoge.setCallback(changeXUL); // setter のコール
	};
}

更に本来は hoge() 内に書きたかった changeXUL() の代りに changeXUL() への参照 funcCallback を書いてやります。

// hoge.jsm 側のコード
function hoge(){
		...
	funcCallback(); // changeXUL() への参照の呼び出し
		...
}

後は JSM 側で hoge() がコールされれば、changeXUL() への参照である funcCallback を通して changeXUL() のコードが実行されます。

以上のコードを整理して記述すると以下のようになります。

// hoge.jsm
var EXPORTED_SYMBOLS=[
	"setCallback"
];
var funcCallback;
		...
function setCallback(pfunc){
	funcCallback=pfunc;
}
		...
function hoge(){
		...
	funcCallback(); // changeXUL() への参照の呼び出し
		...
}
// hoge.jsm を import する側
{
	let scopeHoge={};
	Components.utils.import("resource://hoge/hoge.jsm",scopeHoge);
		...
	let changeXUL=function(){ // JSM 側から呼び出したい function
		...
		// ブラウザの要素を DOM で変更する処理など
		...
	};
		...
	let init(){
		scopeHoge.setCallback(changeXUL); // setter のコール
	};
}

以上の方法のなにが嬉しいのかといいますと、私の場合であれば JSM 内で XMLHttpRequest を非同期で利用する際、コールバック内でブラウザの要素を書き換えたい場合等に利用しています。

フォク蔵を ‘How to Improve Extension Startup Performance‘ に対応させるのに、この手法を利用しています。MPL、GPL、LGPL のトリプルライセンスでソースを公開していますので、自由に参照してください。


【追記】
dynamis さんが、よりシンプルな方法をコメントしてくださいました。ありがとうございます。
以下は dynamis さんが紹介されている “hoge(… , callback)” のように、コールバック関数を直接 JSM 内の function に引数として渡す方法のサンプルです。

// hoge.jsm
var EXPORTED_SYMBOLS=[
	"hoge"
];

function hoge(pstr,funcCallback){
		...
	funcCallback(pstr); // hoge.jsm を import している側のコードの dispStr() を実行
	                    // 'Hello Hogeヽ(°▽°、)ノ'が表示される
		...
}
{
	// JSM を import する側のコード
	let scopeHoge={};
	Components.utils.import("resource://hoge/hoge.jsm",scopeHoge);
		...
	let dispStr=function(pStr){
		alert(pStr);
	};
		...
	let sampleOrg=function(){
		...
		scopeHoge.hoge('Hello Hogeヽ(°▽°、)ノ', dispStr);
	};
		...
}

原理は最初のものと一緒ですが、ぐっとシンプルになって、コードの見通しも良くなっています。

ちなみに dynamis さんの方法と私の方法の違いは、私の方法は hoge() がJSM を import している側のコードから、ネストも含めて直接呼ばれないことを前提としている点です。
目的に合わせてそれぞれ使い分けるといいかと思います。

1 件のコメント

  1. dynamis :

    使いたい関数を受け取るための関数を別途用意する以外にはこんな感じでしょうか。

    ・hoge(… , callback) のように hoge 関数自体の引数として callback 関数を渡す(ごく普通のコールバック処理)

    ・グローバルスコープでアクセスできるブラウザが常に持っている関数なら、モジュール側から現在のブラウザウィンドウのオブジェクトを取得して参照する

    Components.classes[‘@mozilla.org/appshell/window-mediator;1′].getService(Ci.nsIWindowMediator).getMostRecentWindow(‘navigator:browser’);