Lingr chatroom に埋め込む JSON と JSONP

http://www.legendes.jp/lingr/ のサイコロ用のブラウザ拡張では、FireFox では JSON を利用し、Internet Explorer では JSONP を利用している。ブラウザ拡張用にこれらのインターフェスを追加した当初は、どくにどちらを利用するというわけでもなく、ASP.NET 側では JSON, JSONP 両方の呼び出しを可能にしていたのだが、結果的にこのような形になり、双方を利用することとなった。
サイコロを振るためのデータを取り出すため、Lingr.com からコチラの legendes..jp に向けてリクエストを発行しなければならないのだが、ここでクロスドメイン問題が発生するため XMLHttpRequest を単純に利用することはできない。そこで、JSONP による解決方法が手軽に取れそうだと、FireFox 用のブラウザ拡張の Greasemonkey user-script も、最初は JSONP を利用するように作成しなんとなく動作している状態になっていた。

複数の SCRIPT タグと JSONP

FireFox では、複数の SCRIPT タグがレンダリング対象に存在すると、そのすべての SCRIPT タグのソースコードを読み込み完了するまで、スクリプトエンジンは新しいスクリプトを実行しないようになっているようだ。また、レンダリングエンジンは読み込み終わったスクリプトをメモリ上で保持しているため、レンダリングと読み込みは完了しているがまだスクリプトエンジンにわたっていない、という状態のスクリプトを保持している SCRIPT タグを破棄しても、その SCRIPT タグで読み込まれたスクリプトは、きっちりスクリプトエンジンによって実行される。
JSONP を利用する場合、外部スクリプトとして JSON データと実行コードを含んだスクリプトを、動的に SCRIPT タグで読み込むことで外部データとその処理ロジックを駆動させるのだが、上記の仕様によって読み込みが完了した JSONP スクリプトが、いつまでたっても実行されないという症状が発生しうるのである。

FireFox における問題点

Lingr では、http://blog.japan.cnet.com/kenn/archives/003149.html で解説されているように、observe と呼ばれる Long-lived connection によって JSONP ライクな処理が利用されている。これが今回の問題を発生させる要因となった。
埋め込まれたサイコロの挿入ボタンを押すと、必要なデータを取り出す JSONP リクエストが SCRIPT タグとして追加され、JavaScript のロードが開始される。非常に小さなコードなのでロードは即座に終了する……通常ならば、この時点でロードされたスクリプトが実行され、サイコロの挿入が実施されるはずなのだが、前述のような FireFox の動作のため、observe 側の SCRIPT タグの読み込みが完了するまで、このサイコロの挿入スクリプトはいつまでたっても実行されないのである。
Lingr の技術解説にあるように、誰か*1Lingr で発言を行うと、はじめてこの読み込みは完了し、その発言がブラウザに届くと同時に、やっとロードされていたサイコロ挿入スクリプトが実行される、ということになってしまった。そして、サイコロの挿入ボタンを押した回数だけ、ロード済みのスクリプトはメモリ上に貯まっていくため、複数回のボタン使用で、いっきに複数回分のスクリプトが実行される……というような状態になってしまった。

FireFox における解決方法

似たような問題は、Opera で単一の SCRIPT タグでも発生していたらしく、id:secondlife:20060906:1157515075 なんかが見つかり、対策として IFRAME を利用するということがあり、おそらく FireFox でも動作すると思われる。
とりあえず、今回は Greasemonkey からの呼び出しであることを利用して、Greasemonkey の cross domain xmlHttpRequest を利用して、JSON データのみを取得するように変更することで対応した。

Internet Explorer では

複数の SCRIPT タグを利用していて、片一方の読み込みが完了していなくても、スクリプトの読み込みが完了した時点で実行が始まるようです。これが、IE でもおなじ状態になっていたら、IE では GM_xmlHttpRequest のような機能がないので困ってしまっていたところだ。*2

*1:含む自分

*2:TrixieTurnabout を導入すれば対応している