上記2つのGMスクリプトは自分で作ったものですが結構お気に入りで、よく使っていました。ところが、最近ブラウザをOperaに変えたので、使えなくなっちゃいました。
これは非常に不便なので、Operaでも動くように改良してみました。
まずは完成品をどぞ。
新規にOpera専用バージョンを作ったのではなく、以前作ったやつをFirefoxでもOperaでもどっちでも動くように直しました。また、URLに「#」を含んだ場合の処理の不具合も直ってますので、今までFirefoxで使っていた人もインストールしなおして欲しい。Operaの人は、「ツール」「設定」「詳細設定」「コンテンツ」「JavaScript オプション」「ユーザー JavaScript ファイル」のところに好きなディレクトリを指定して、そのディレクトリに上記のuser.jsファイルを配置して下さい。
使い方は、この記事の冒頭に貼ったリンク先を参照。
ここからは(人によっては)難しい話。修正点はおおきく2箇所。
これは簡単。冒頭に
var w = (typeof unsafeWindow != 'undefined') ? unsafeWindow : window;
と書いておいて、コード中でunsafeWindowを呼んでるところを全部wに書き換えればいい。
GM_xmlhttpRequestはいわゆるAjax風な通信をする機能ですが、これをOperaに対応させるには、
ということになると思います(違ったらゴメン))。今回は、アクセス先はYahoo! Pipesなので、2番目のやり方でいけます。
JSONPの呼び出しは、以前「複数のJSONPを連続で呼び出して最後にコールバック関数を呼ぶ」のときに作ったJSONLoaderクラスを使ってみました。コピペで貼るだけ。
あとは、
GM_xmlhttpRequest({
method: 'GET',
url: 'http://(略)',
onload: function(res){
var obj = eval('(' + res.responseText + ')');
(略)
}
});
こんな感じの呼び出し部分を
var json_uri = 'http://(略)'
var callback = function(obj){
(略)
};
if(typeof GM_xmlhttpRequest == 'undefined'){
var jsonp_uri = json_uri + '&callback=';
var loader = new JSONLoader();
loader.config = [{ uri: jsonp_uri }];
loader.callback = function(results){ callback(results[jsonp_uri]); };
loader.load();
}else{
GM_xmlhttpRequest({
method: 'GET',
url: json_uri,
onload: function(res){ callback(eval('(' + res.responseText + ')')); }
});
}
上記のように書き換えます。GM_xmlhttpRequestがundefinedのときだけJSONPを使うようにしています。なのでFirefoxでは今までどおりGM_xmlhttpRequestを使います。
別に、わざわざブラウザを振り分けないで常にJSONPを使うようにしてもいいと思います。
ここまででうまく行ったように見えましたが、まだ問題があった。OperaはJSONPを非同期に呼び出せないのです。つまり、JSONPのための script要素を挿入すると、処理が終わるまで待たされる。それで我慢できることもあるかと思いますが、今回のuser.jsはYahoo! Pipesを使っているので待ち時間が長く、できれば非同期でやりたい。
そこで、Operaでも非同期リクエストが並列処理できる img-JSONP(TAKESAKO @ Yet another Cybozu Labs)で解説されてる方法を使ってみた。
こんな感じの流れ。すばらしいアイディア。感動しました。みんなすごすぎるよ。
さて、これを実装するため、JSONLoaderクラスを以下のように直しました。
var JSONLoader = function(){};
JSONLoader._count = 0;
JSONLoader.prototype.load = function(){
var jsonp_callback_name = 'JSONLoader_callback_' + (JSONLoader._count++);
w[jsonp_callback_name] = this.callback;
var img = document.createElement('img');
img.src = this.uri + jsonp_callback_name;
img.onerror = function(e){
var script = document.createElement('script');
script.setAttribute('src', this.src);
script.setAttribute('type', 'text/javascript');
script.setAttribute('charset', 'utf-8');
document.getElementsByTagName('head')[0].appendChild(script);
};
img.width = 0;
img.height = 0;
document.body.appendChild(img);
};
このJSONLoaderクラスは他のところでもそのまま使えると思います。Operaで非同期JSONPをやりたい場合はぜひ。
修正のついでに、「複数のJSONPを呼び出して最後にコールバック」の機能を削除しました。今回は1個のJSONPを呼ぶだけなので。これによって、JSONLoader利用側もちょっと変わりました。
var json_uri = 'http://(略)'
var callback = function(obj){
(略)
};
if(typeof GM_xmlhttpRequest == 'undefined'){
var loader = new JSONLoader();
loader.uri = json_uri + '&callback=';
loader.callback = callback;
loader.load();
}else{
GM_xmlhttpRequest({
method: 'GET',
url: json_uri,
onload: function(res){ callback(eval('(' + res.responseText + ')')); }
});
}
複数のJSONPを呼び出す機能を削除したので、利用側のコードがシンプルになった。
これらの修正をした結果、Operaでも問題なく動作するようになりました。もしかしたらCreammonkeyとかSleipnirでも動くのかな?試してない。
URLに「#」を含んだ場合の処理で、user.js側に不具合があって今回直しましたが、サーバ側つまりYahoo! Pipesで作ったAPI側にも不具合がありました。そっちも直したので、今後は「#」のあるURLでも正しく動作するかと思います。いままでこの問題を放置していてすみませんでした。