XPCNativeWrapperの外側でスクリプトを評価する関数。Firebugも同じことをしている。unsafeWindowを触らないので安全。
function evalInPage(fun) { location.href = "javascript:void (" + fun + ")()"; }
これは面白い!Function.prototype.toString が関数のソースコードを返すことを利用した、巧妙にして簡潔なハック。
次のように引数を渡せるように改良すればさらに強力になる(引数に渡せるのはJSON化可能な値のみ)。
function evalInPage(func, args) { var argStr = JSON.stringify(args || []); location.href = "javascript:void "+ func +".apply(null,"+ argStr +")"; }
GM_xmlhttpRequest を使って別ドメインから得たデータを、unsafeWindow 上のライブラリを使って表示する、といったコードが安全かつ自然に書けるようになる。
// データの取得は Greasemonkey で行う GM_xmlhttpRequest({ method:"GET", url: "http://example.com/api", onload: function(response) { var data = response.responseText; //... evalInPage(render, [data]); } }); // データの表示は unsafeWindow 上で行う // jQueryなど unsafeWindow 上のライブラリが使える function render(data) { $.each(data, function(k,v){ //... }) }
これだけでも十分便利なのだが、「返り値が利用できない」「Firefoxのlocation.hrefは非同期で実行される」といった難点があるらしい。それを回避するためにunsafeExec on JSDeferred – 枕を欹てて聴くにおいてはJSDeferredというライブラリを使う方法が紹介されているのだが、コードを見る限り unsafeWindow に対するアクセスが必要になるようだ。
unsafeWindow にアクセスせずにGreasemonkeyに制御を戻すには、DOM Eventを使うのが良いと思う。以下、DOMNodeInserted イベントを使って実装してみた(返り値として利用できるのはJSON化可能な値のみ)。
追記: Constellationさんからコメントをいただきました。DOMNodeInsertedなどのイベントを使わなくても、直接createEvent/dispatchEventでイベントを生成・データを送受信する方法があるようです。 詳細はこのページの Constellationさんのコメントや、『Greasemonkey スクリプトとイベントで通信: Days on the Moon』など参照してください。
function getUniqId() { do { var id = "_tmp" + String(Math.random()).slice(2); } while(document.getElementById(id)); return id; } function evalInPage(func, args, callback) { var argStr = JSON.stringify(args || []), userFunc = "("+ func +").apply(null,"+ argStr +")"; if(!callback) { location.href = "javascript:void "+ userFunc; return; } var div = document.createElement('div'), id = getUniqId(); div.id = id; div.style.display = 'none'; document.body.appendChild(div); div.addEventListener('DOMNodeInserted', function(e){ callback(JSON.parse(e.target.nodeValue)); div.parentNode.removeChild(div); }, false); location.href = "javascript:void "+ (function(ret, id) { document.getElementById(id) .appendChild(document.createTextNode(JSON.stringify(ret))); }).toString() + ".call(null,"+ userFunc +"," + JSON.stringify(id) +")"; }
JSDreferredを使う方法と同じく、戻り値を受け取るコールバック関数を指定する。
evalInPage(function(obj, num) { //この中のコードは unsafeWindow で実行される return { answer: obj.a + num }; }, [{a:99}, 1], function(ret) { //この中のコードは Greasemonkey で実行される alret(ret.answer); });
多分まじめに作り込めば一種のRPCシステムみたいになると思う。
DOMNodeInserted Eventを使ってやるのは多分にcostがかかるので, Eventのdispatchを使うのはどうでしょうか.
unsafeWindowとの分離が必要なGMに限れば,
page側
var data = JSON.stringify(someobj);
var ev = document.createEvent(‘MessageEvent’);
ev.initMessageEvent(‘GM_dispatch’, true, false, data, location.protocol+”//”+location.host, “”, window);
target.dispatchEvent(ev);
GM側
window.addEventListener(‘GM_dispatch’, function(ev){
var target = ev.target;// HTMLElementをtargetを介して伝える
var data = JSON.parse(ev.data);// MessageEvent の data
}, false);
といった具合です.
コメントありがとうございます!
initMessageEventで検索したら以下のページなどが引っかかりました。
HTML5に向けて進展している分野なのですね。
http://nanto.asablo.jp/blog/2008/06/26/3596261
とても勉強になりました。
もっと調べてみようと思います。
[...] これがXPCNativeWrapperとunsafeWindowの間でデータを送受信する | へびにっき [...]
[...] 先日使い方を学んだ MessageEvent と、location.href+javascript:ハックとJSDeferred [...]