window.postMessageを使ってiframeの中のリンクを外のwindowで遷移させる

まとめ

やりたいこと

iframe の中のリンクをクリックしたら元 window で遷移してほしい。

できた

前提としては 元 window の JavaScript も iframe 内の JavaScript も自由にいじれること。

  1. 元 window で iframe の load が終わったら iframe の window に対して postMessage で自分の origin を伝える
  2. iframe 内の window で click を拾って url を window.parent.postMessage で元 window に送る
  3. 元 window は送られた url を location にぶちこむ

できた!

分かったこと

  • そもそも iframe って許可されていないとコンテンツの表示もできなくなってた
    • X-Frame-Options とか最近はセキュリティ関係のヘッダいろいろあるんだね
    • Sinatra はデフォルトセキュア寄りなので何も表示されずに悩んだ
  • window オブジェクトとか普段触らないのでアクセス方法分からなくなる
  • postMessage って自由に送れるのかと思ったら送信先の origin をちゃんと送る側が分かってないとダメ
  • iframe の src まんまみたいなもんじゃんと思ったけど、逆方向の時には工夫が必要
  • window.postMessage では object は送れない。より複雑な双方向のやりとりをするには JSON.stringify などを使ってデータ構造を持たせないと厳しそう。

やったこと

ざっくり以下のような感じ。

parent window

<!-- iframe を作る -->
<iframe name="widget" src="http://example.jp"></iframe>

<script type="text/javascript">

/** iframeのコンテンツを読み込み終わったらparentの何か送る(なんでもよい) */
document.getElementsByName('widget')[0].addEventListener('load', function(e) {
  window.frames.widget.postMessage('loaded', 'http://example.jp');
});

/** iframe内のクリックされたurlが送られてくるのでlocationを書き換える */
window.addEventListener('message', function(e) {
  if ( e.data ) {
    location = e.data
  }
}, false);

</script>

iframe の中(http://example.jp)で jQuery を使ったとする。

/** parent windowのoriginを保存 */
window.addEventListener('message', function(e) {
  widget.target = e.origin;
});

/** リンクをクリックしたらparent windowに教える */
$('a').on('click', function(e) {
  window.parent.postMessage($(e.target).attr('href'), widget.target);
});

工夫したところ、悩んだところ

iframe 内の window では parent の origin が分からないのでそのままでは postMessage の際に origin を指定できない。そこで parent 側で iframe の内容の load イベントを待って parent の情報を送ってやる。

iframe の event を拾うには iframe 要素を、iframe 内に message を送るには window オブジェクトを、使い分ける必要があってなんかメンドイ。

参考

More