postMessageておもしろいスな。
なぜにこんなことを。
クロスドメインでAjax的なことをしようとしたら、怒られまくって困った。
いろいろ調べてみたら、postMessageがスマートで面白いのでメモ。
JSONPでもできなくはなかったけど、イマイチ面倒なのと面白くなかったので。
ちなみに、これを調べたきっかけはHerokuとAmazonのS3でやり取りするため。
はじめに
postMessageは、安全にクロスドメイン通信をするためのメソッドだそうで。もちろんJavaScriptでやる。
よくわかる解説:
https://developer.mozilla.org/ja/DOM/window.postMessage
やろうとしたこと
Herokuで実装したJavaScriptのAPIと、AmazonS3においたHTML(JavaScript)とでおしゃべりしたい。
AmazonS3のHTMLは実際にはiframeの子として読み込まれる。
なんか変な設計だけど、いろいろと事情がありまして。。。
Cacooたのしい。
結果こうなった
HerokuのJavaScriptにはpostMessageとreceiverを追加。
AmazonS3においたHTML(JavaScript)はいじらずに済むよう、あともろもろの理由でラッパーをかますことに。
AmazonS3にラッパー用フレームを置いて、そこでpostMessageとreceiverを実装した。
なにをしたか
Heroku側のJavaScriptに以下のようなコードを追加した
Event.observe(window, 'message', receiveMessage, false); function receiveMessage(evt){ if(evt.origin === '<%= AWS_URL %>'){ if(evt.data == 'geturl'){ frames[0].postMessage('data_url=<%= @data.url %>', '<%= AWS_URL %>'); return true; } if(evt.data == 'initialize'){ frames[0].postMessage('progress=<%=raw @data.progress.to_json %>', '<%= AWS_URL %>'); return true; } if(evt.data.match(/terminate=/)){ <%= remote_function( :url => {:controller => "activities", :action => "terminate"}, :type => :synchronous, :with => "'val=' + RegExp.rightContext" ) %> location.reload(); return true; } } }
なんかいろいろ見えてるけど気にしない;
postMessageを受けとったら、function receiveMessage(evt)が実行される。
つまり、Amazonから呼ばれた時の動作を作る。
引数evtについての詳細は解説を読むとして。
origin ・・・・・呼び出し元のウィンドウのアドレス? data ・・・・・・・送られてきたメッセージ
かならず呼び出し元をチェックする。
frames[0].postMessage()は、このウィンドウ内のフレームにpostMessageしている。
AmazonS3には以下のようなJSを追加した。
Event.observe(window, 'message', receiveMessage, false); function receiveMessage(evt){ if(evt.origin === 'https://localhost'){ if(evt.data.match(/data_url=/)){ $('inner').setAttribute('src', RegExp.rightContext); return true; } if(evt.data.match(/progress=/)){ progress = RegExp.rightContext.evalJSON(); return true; } if(evt.data.match('terminate')){ Terminate(); return true; } } } function Terminate(){ win.postMessage('terminate=' + Object.toJSON(progress), 'https://localhost'); return true; } var win; if(opener){ win = opener; }else if(parent){ win = parent; } win.postMessage('geturl', 'https://localhost');
同じようにイベントとfunctionを追加して、HerokuのJavaScriptからメッセージを受け取るように。
はー。JavaScriptだけでこんだけ出来るってすごいなー。
IEでハマったとこ
window.openerがとれん。仕方ないのでiframeにしてwindow.parentで妥協した。
このせいで(おかげで?)あいまいだった仕様が確定したことは内緒。
そしてゴミコードが残ってるのも内緒。
ジブンヨウメモ RailsでIEが動かんとき IEばくはつしろ!
IEがdeleteしてくれない
rails 3.0.7でつくってたら見事に。
対処 => rails.jsを入れ替えてみる。https://github.com/rails/prototype-ujs/
参考 => http://tag3.at.webry.info/201101/article_3.html
〜〜〜 2011/08/08 追記
prototype.jsも入れ替える。
http://prototypejs.org/2010/10/12/prototype-1-7-rc3-support-for-ie9