XPCNativeWrapperとunsafeWindowの間で同期処理を行う

先日使い方を学んだ MessageEvent と、location.href+javascript:ハックとJSDeferred userscript版とを組み合わせ、XPCNativeWrapper と unsafeWindow の間で同期処理を行うコードを書いてみた。

まずは MessageEvent を使って前回 evalInPage 相当の処理を作る。キャンセル処理も入れたかったので UnsafeWrapper というオブジェクトにまとめることにした。前回より長くなったように見えるが、実際には MessageEvent の lastEventId が使えるようになった分、簡潔になっている。

var UnsafeWrapper = new (function() {
	var seqId = 0, waiting = {}, self = this, noop = function(){};
 
	function dispatch(data, id) {
	    var e = document.createEvent("MessageEvent");
	    e.initMessageEvent('GM_UnsafeWrapper_returned', true, false,
			       data, location.protocol + "//" + location.host,
			       id, window);
	    document.dispatchEvent(e);
	}
 
	function listen(func) {
	    document.addEventListener('GM_UnsafeWrapper_returned', func, false);
	}
 
	listen(function(e) {
		if(waiting[e.lastEventId]) {
		    waiting[e.lastEventId].call(self, JSON.parse(e.data));
		    delete waiting[e.lastEventId];
		}
	    });
 
	this.exec = function(func, args, callback) {
	    var userFunc = "("+ func +").apply(null,"+ JSON.stringify(args || []) +")";
	    waiting[seqId] = callback || noop;
	    location.href = "javascript:void "+
	    (function(dispatch, ret, id) { dispatch(JSON.stringify(ret), id) }) +
	    "("+ dispatch +","+ userFunc +"," + JSON.stringify(seqId) +")";
	    return seqId++;
	};
 
	this.cancel = function(id) {
	    delete waiting[id];
	};
    });

そして Deferred に対するアダプタを書く。とても簡単。

with(D()) {
    function unsafeExec(func, args) {
	var d = new Deferred();
	var id = UnsafeWrapper.exec(func, args, function(ret) {
		d.call(ret);
	    });
	d.canceller = function(){ UnsafeWrapper.cancel(id) };
	return d;
    };
}

これで例えば、GM_xmlhttpRequest で外部ドメインからデータを取得して、そのデータを整形して、unsafeWindow 側で表示処理を行って、そしてその結果をuserscript側で受け取って…という処理は次のように書くことができる。

with(D()) {
 
xhttp.get("http://example.com/")
    .next(function(res) {
            var data = res.responseText;
            //....
 
	    return unsafeExec(function(data) {
                    $.each(data, function(k,v) {
                      //...
                    });
                    //...
		    return result;
		},
		[data]);
	})
    .next(function(result) {
          //...
	});
 
}

より実用性を高めるのであれば、location.href で実行するコード全体を try {} で囲み、catch したエラーをJSONに変換して _failed イベントを dispatch して、Deferred の fail に転送すれば、unsafeWindow 側で起こったエラーをuserscript側のDeferred chainで捕捉できるようになる。

参考:

Leave a Reply