Greasemonkey: prototype.jsでクロスドメインAjaxを可能にする

以前、「Greasemonkeyでprototype.jsやscript.aculo.usを使う方法」を紹介しました。しかし、Greasemonkey内でprototype.jsが利用できるようになった恩恵を十分に受けられていませんでした。つまり、Greasemonkeyで提供されているGM_xmlhttpRequest()が可能にする「異なるドメインとの非同期通信」からの恩恵です。そこで今回は、Greasemonkey内で使えるようになったprototype.jsをさらにパワーアップし、クロスドメインAjaxができるようにしてみたいと思います。

問題

厄介な問題は、GM_xmlhttpRequest()はFirefoxInternet Explorerなどで提供されているXMLHttpRequestとインタフェースが全く違うということです。

GM_xmlhttpRequest()は単なる関数です。例えば、以下のように利用します。(詳しい説明は「GM_xmlhttpRequest Dive Into Greasemonkey」を参照してください。)

GM_xmlhttpRequest({
    method: 'POST',
    url: 'http://example.com/test',
    data: 'p=foo&q=buz',
    onload: function(responseDetails) {
        alert('Response: ' + responseDetails.responseText);
    }
});

一方、XMLHttpRequestはクラスです。コンストラクタを呼び出し、インスタンスを生成してから利用します。例えば、Firefoxの場合、以下のように利用します。

var req = new XMLHttpRequest();
req.open("POST", 'http://example.com/test', true);
req.onreadystatechange = function() {
    if (req.readyState == 4) {
        alert('Response: ' + req.responseText);
    }
}
req.send('p=foo&q=buz');

このように両者は大きくインタフェースが異なるため、ちょっとしたHackでは、prototype.jsAjax機能をGM_xmlhttpRequest()で交換するのは難しいことがわかります。

解決

上で述べた問題は、インタフェースに問題があるだけです。機能的には問題はありません。ですから、GM_xmlhttpRequest()にラッパーをかませてやることで、XMLHttpRequestとほぼ等価なインタフェースを作ることができます。XMLHttpRequestが持っているopen()、send()、onstatechange()、readyStateといったメソッドやプロパティをそのラッパーで実装して、あたかもXMLHttpRequestと同じように振舞わせればOKです。

あとは、prototype.jsで定義されているAjax.getTransport()が、そのラッパーのインスタンスを返すように細工すれば、うまくいくはずです。次のコードはprototype.jsで定義されているコードの一部です。

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
      function() {return new XMLHttpRequest()}
    ) || false;
  },

  activeRequestCount: 0
}

このコードにあるAjax.getTransport()を、以下のコードで実装を上書きしてしまいます。ここでTransportは、先ほど説明したラッパーです。Transportのコンストラクタが呼び出されると、XMLHttpRequestと互換性のあるインタフェースを持ったインスタンスが生成されます。

Ajax.getTransport = function() {
    return new Transport();
};

Cross-Domain Ajax Kit

問題とその解決のエッセンスがわかったのは良いですが、いちいち実装するのは面倒です。そこで、小さなキットを作りました。このキットを利用すれば、今まで説明してきたことを簡単に解決できます。(MITライセンスですので、自由に利用して下さい。)

このキットを正常に動作させるには、Greasemonkeyスクリプトで以下のようなコードが必要になります。Transportクラスがある通常のJavaScriptの世界では、GM_xmlhttpRequest()は存在せず、Greasemonkeyスクリプトの世界にしかGM_xmlhttpRequest()が存在しないためです。要は橋渡しが必要なのです。ちょっとdirtyな感じがしますが、しょうがないですね。詳しくはデモのコードを見てください。

var l = unsafeWindow;
l.Transport.prototype.GM_xmlhttpRequest = function(param) {
    GM_xmlhttpRequest(param);
}

キットのダウンロードは以下からできます。

デモ

Greasemonkeyスクリプトをインストールすると、任意のサイトで検索ボックスが表示されるようになります。この検索ボックスは、prototype.jsで定義されているAjax.Updater()を利用して、Googleに検索クエリーを発行し、その結果を画面に表示します。

実用的であるかどうかは別にして、それほど手間をかけずに、Greasemonkey内でprototype.jsAjax機能がクロスドメイン対応にパワーアップした点に注目して下さい。

デモのGreasemonkeyスクリプトのインストールは以下からできます。

応用の可能性

prototype.jsAjax機能がクロスドメイン対応にできたということは、prototype.jsAjax機能を基礎としているscript.aculo.usのin-place edit機能も自動的にクロスドメイン対応になったということを意味します。

これで、夢広がりんぐです。