2010-02-25

JSONPを使いたいが、Operaの挙動がおかしい

http://www.example.com/が、以下のような内容を返すとする。

callback({
    "foo" : "bar"
}) ;

このとき、script要素を動的に生成することによって、same originを回避しつつ、しかもJSONデータをJavascriptから利用することが可能になる。

function callback(data) {}

var jsonp = document.createElement("script") ;
jsonp.setAttribute("type", "application/javascript") ;
jsonp.setAttribute("src", "http://www.example.com/") ;

document.head.appendChild(jsonp) ;
// callback()が呼ばれる。

このテクニックは、JSONPと呼ばれている。

実際のコードでは、HTML5のdocument.headに対応してないブラウザのため、別の方法でhead要素へのDOMを取得しているが、それは話の本題ではないので、省略する。

ここまではいい。しかし、このscript要素は、無駄であるので、できれば、消したい。

document.head.appendChild(jsonp) ;
// callback()が呼ばれる。
document.head.removeChild(jsonp) ;

わたしは予想では、これは動くはずであった。なぜならば、JavascriptによるDOM操作は、暗黙のうちに非同期になってくれたりはしないからだ。ひとたび、appendChild()が呼ばれたならば、DOMに追加され、読み込みが終わり、関連するすべての処理がおわるまで、Javascriptのコードは実行を続けることができないはずである。

これは、時として、ユーザーをブロックする原因にもなるが、この場合は、都合がいい仕様である。

Chrome, Safari, Firefoxでは、私の期待通りに、問題なくcallback()が呼ばれた。

しかし、Operaでは、動かなかった。

Operaでは、callback()は呼ばれるものの、その引数であるdataが、nullになっていた。removeChild()を呼び出さなければ、nullではなく、ちゃんと、JSONオブジェクトを参照しているのだ。

相変わらず、Operaの実装は分からない。最適化のために、script要素がDOMに追加された場合の、その中身の実行のタイミングを遅らせるなどの、妙なことをしているのではあるまいか。

実は、もう一つ、removeChild()を呼び出した場合の不都合が存在した。それは、ブラウザの「戻る」機能を使って、ページを戻した場合、動的に追加したscript要素内のコードが実行されないのだ。これは、すべてのブラウザに当てはまる。

結局、removeChild()を呼び出すのは、諦める事にした。script要素が存在することで、CPU時間やメモリなどのリソースの消費が、目に見えるほど増えることはない。

3 comments:

  1. 昔から appendChild した script が同期というのは保証されてなかったと記憶しています。
    試してみると、Firefox ではいつも同期、Safari と Chrome では src がキャッシュされてる場合は同期で、そうじゃない場合は非同期 (つまり removeChild した場合は callback が呼ばれない) で、Opera はいつも非同期みたいでした。

    IE 以外では script.onload が使えます。
    http://d.hatena.ne.jp/os0x/20080827/1219815828

    余談ですが、document.write にもおもしろい話があります。
    http://webreflection.blogspot.com/2009/12/documentwriteshenanigans.html

    ReplyDelete
  2. そうか、onloadを使えばよかったのか。
    さっそく改良。

    ReplyDelete
  3. いまどきdocument.writeがどうしたと思ったら、これはすばらしい。

    ReplyDelete

You can use some HTML elements, such as <b>, <i>, <a>, also, some characters need to be entity referenced such as <, > and & Your comment may need to be confirmed by blog author. Your comment will be published under GFDL 1.3 or later license with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.