というのを作った。
最初は Opera だけで動けばいいかなと思ったんだけど、せっかくなので Greasemonkey も Chrome も対応したいと思って頑張った。
面倒だったところは、JSONP を使う必要があって、どうしてもページ側のグローバル領域に関数を定義しないといけなかった。
まあ @include で厳しく指定してるし、グローバルで何でも好きにやりたきゃやれるんだけど、そこは意地でもページのコンテクスト侵食は最低限にしようと思った。(ただし Opera では別の部分で侵食しまくり)
ここでちょっと Opera と Chrome と Greasemonkey のスクリプトのおさらい。
opera.addEventListener('BeforeEvent.load',...)
などのリスナーが使えて、これはページのスクリプトからは使われることはないので、上記のクロスドメイン GET などが使える。location.href='javascript:〜'
とするか、script.textContent='〜', document.body.appendChild(script)
とするしかない。Chrome は徹底してサンドボックス化してある。一番厄介。
さて、結論から言うと、JSONP のコールバック関数は MessageEvent を使うことにした。(os0x さんの助言と nanto_vi さんの記事が参考になりました)
MessageEvent と言っても、ページ側で window.postMessage(...)
してスクリプト側で window.onmessage = ...
(または unsafeWindow) とする方法だと Chrome では動かないと思うので (試してないけど、同じ window オブジェクトではないからこれも使えたみたい。)、 document.createEvent('MessageEvent')
して document で発火させる。document は Chrome でも Greasemonkey でもページ側と共有されるので。
ただし Opera では 10.50 になっても document.createEvent('MessageEvent')
が Not Supported Error を出すので、仕方なく document.createEvent('Event')
してオレオレプロパティを付けることにした。
つまり、ページ内で定義するのは以下のような関数。
function myCallback(obj) { if (window.opera) { var ev = document.createEvent('Event'); ev.initEvent('MyJSONPReady', true, false); ev.mydata = obj; } else { var ev = document.createEvent('MessageEvent'); ev.initMessageEvent('MyJSONPReady', true, false, JSON.stringify(obj), // data location.protocol + '//' + location.host, // origin '', // lastEventId (Server-sent Event に使われるもの) window // source ); } document.dispatchEvent(ev); }
これを文字列として script.textContent
にして、document.body.appendChild(script)
する。
受け手のほうは
document.addEventListener('MyJSONPReady', function(ev) { var data = ev.mydata || JSON.stringify(ev.data); }, false);
とやれば JSONP で要求したオブジェクトが得られるというわけ。
MessageEvent で文字列だけじゃなくてオブジェクトが送れるようになるのは最近の環境だけなので、JSON にする。Opera だけ別にしたことによって、ネイティブ JSON のない Opera 10.10 でも使える。
ハイハイ、バッドノウハウバッドノウハウ。
もっとブラウザ分岐が少ない方法があれば知りたい。(自分としてはこれで満足だけど)
"Access-Control-Allow-Origin: *"が付いている場合は、Firefox と Chrome では XMLHttpRequest で OK。
Opera では、.js で保存したスクリプト (.user.js ではなくて) の実行時のみ (それ以後のイベント内ではなくて) において、opera.addEventListener
が使える。
こんな感じ。(実際には文字コードが違うとうまく動かなかったりするけど)
(function() { var scripts = []; var callbacks = []; opera.addEventListener('BeforeScript', function(e) { var s = e.element; var index = scripts.indexOf(s); if (index >= 0) { callbacks[index].call(null, s.text); scripts.splice(index, 1); callbacks.splice(index, 1); e.preventDefault(); // スクリプトの実行をキャンセル s.parentNode.removeChild(s); // スクリプトを削除 } }, false); // クロスドメイン GET 関数 function xGet(url, callback) { var s = document.createElement('script'); s.src = url; document.body.appendChild(s); scripts.push(s); callbacks.push(callback); } // このクロージャ内では安全にクロスドメイン GET が使える window.addEventListener('load',function() { xGet('http://query.yahooapis.com/v1/public/yql?q=show%20tables&format=json', alert); }, false); })();
は動きますよ。この前のと同じ理由であまりオススメはできませんが…。
あと、scriptを挿入するとき、関数をtoStringする方法がお薦めです。文字列リテラルで書くのは面倒なので。
function myFunction(){
//
}
script.textContent = '(' + myFunction + ')();';
> どういうわけか Chrome では security violation が出た
これ、こちら(5.0.360.0 dev/WinXP )では再現しないっぽいです。Content ScriptsからYQLにアクセスできました。
http://gyazo.com/6e1d91f5e40c3f840201a70a52753a9f.png
元々、Content Scriptsはmanifest.jsonのpermissionsとは関係ありません。
あとで他のバージョンやMacでも検証してみます。
動きました。何が間違っていたんだろう。お恥ずかしい。
それにしても Chrome は同じ UserJS を何回もインストールしたら後からインストールしたほうを尊重してくれてもいいのにと思いました。
Opera だとファイル編集→Refresh display アクションでいけるので UserJS 書くのがラクでラクで。