Cross Frame: 與不同網域的 Frame 做互動

此篇文章最近更新時間為2010-10-06 23:52:56 目前共有7篇留言

關於作者 - JosephJ

任職於 Faria。喜好戶外運動、2008 年 5 月完成「跑步環島」。對於新技術跟程式碼有著強烈的偏執狂。

為何要用不同網域的 Iframe?

通常較具規模的網站,會考慮這樣的安全性問題:當要使用到第三方所提供的 JavaScript 時,得放置在與頁面完全不同網域的 Iframe 中。例如在 Yahoo! 工作時,生活+UrMap 合作的地圖就是放在另一個網域的 Iframe 中的。若不這樣子做,UrMap 網站的 JavaScript 可以隨時竄改 Yahoo! 網頁的內容、甚至危害到使用者。

用不同網域的 Iframe 來放置第三方程式碼的情形,簡單來說就是:「我不信任你的程式、但我需要你的程式成為我網頁中的一部分功能」(除了 Iframe,另外還有 ADSafe、Caja、Flash 等方式可以做到,但後三者都有一定的門檻。)

Same Origin Policy

將第三方網頁或程式碼、用不同網域的 Iframe 放到你的網頁上是一件非常簡單的事情 (<iframe src="xxx"></iframe> 人人都會用)。但問題在於當兩者網域不同時,JavaScript 是沒有辦法透過 parent, window.frames, top, opener 等方式存取的。這就是所謂 Same Origin Policy 的限制。

不能互動,就缺乏了彈性與應用的機會。例如剛剛提到 Yahoo! 生活+ 與 UrMap 的合作,生活+ 這邊會希望在頁面上點選了「餐廳」的連結時、就用 AJAX 取得伺服器的餐廳經緯度資訊、傳遞到放置地圖不同網域的 Iframe 中、Iframe 接到訊息後就以 UrMap API 動態更新網頁地圖上的座標點。而這樣的行為也會因 Same Origin Policy 無法直接達成。

Cross Document Messaging

WHATWG 提出了 Cross Document Messaging(也是 HTML 5 的 Web Messaging)來解決此一問題。簡單來說,你可以丟任何 String 到任何你網頁上的 Window 物件,但是該 Window 物件要不要處理此 String,就是它自己的判斷(會一併提供來源 Window 的 domain)。範例程式碼如下:

A 網頁 (http://yahoo.com/a.html) 放置了一個 name=urmap 的 iframe:

<iframe name="urmap" src="http://yahoomashup.com/urmap.html"></iframe>

A 網頁想要傳遞 Hello World 的訊息給此 Iframe:
<script>
/* 我是 yahoo.com */
window.frames['urmap'].postMessage("Hello World!", "*");
</script>

Iframe 裡的 B 網頁 (http://yahoomashup.com/urmap.html) 可以這樣接到資料:

/* 我是 yahoomashup.com */
window.onmessage = function (e) {
    if (e.origin == "yahoo.com") { // 當來自 yahoo.com 時...
        alert(e.data); // 顯示 "Hello World!"
    }
}

好消息是目前大多數的主流瀏覽器支援此方式,包括 Firefox 3, Google Chrome, Opera, IE8, IE9 皆可。壞消息則是 IE6 與 IE7 沒有此功能。

Iframe In Iframe Hack

如果此主題有任何 A-Grade 瀏覽器無法使用,那也沒寫這篇文章的必要。只提供半套作法就遜掉了(這也是我對大多數 HTML 5 的新技術感到冷感的原因,IE[6-8] must die 也只是開發者肝苦下喊爽的產物,現實世界是不能不管 IE 的使用者的)其實早在 2007 年就有一些非正規 Cross Frame 的解法。這篇文章是目前解說最詳細的作法:Cross-Domain Communication with IFrames

其實原理相當簡單,就是當 A 網頁要傳遞訊息給 B 網頁的(在 Iframe 中)時,先動態產生一個與 B 網頁同網域的 Iframe,此動態 Iframe 內的 JavaScript 即可利用 window.frames 取得 B 網頁及做任何修改。另外還可透過修改生成 Iframe 中的 URL hash (#)、讓 JavaScript 可以依照不同參數做動作。這個作法的缺點是,你必須另外在接收端的網域放置一支 Proxy 的 HTML 檔、作為動態 Iframe 的網址。

YAHOO.util.CrossFrame 函式庫

雖然原理很簡單,但是要能夠有系統的溝通、寫程式就是一大挑戰了、中間的小細節其實是很多的。幸好 Yahoo! 的前端工程師 Julien Lecomte 用 YUI 2 寫了一個 CrossFrame Utility

YAHOO.util.CrossFrame.send(
    "http://www.y.com/proxy.html",
    "frames['mashup']",
    "message"
);

YAHOO.util.CrossFrame.onMessageEvent.subscribe(
    function (type, args, obj) {
        var message = args[0];
        var domain = args[1];
        // Do something with the incoming message...
    }
);

看到了嗎?這邊其實就是用 YAHOO.util.CrossFrame.send() 取代 HTML5 的 window.postMessage()、用 YAHOO.util.CrossFrame.onMessageEvent 取代 window.onmessage 事件。單一 API 介面、所有瀏覽器都能用!

用 YUI 3 改寫為 Y.CrossFrame

因為公司內用的是 YUI 3,有時間的話當然希望改寫。另外就是 Julien 當時寫那篇文章的時間點是 2007 年,postMessage 只有 Opera 有實作,所以 Julien 對於 Opera 以外瀏覽器都採用 Iframe in Iframe 的作法,效能差上許多。也因此我改為以 postMessage 為主、Iframe in Iframe 為輔的作法較有效率

Y.CrossFrame.postMessage(
    "Hello World!",       // 要傳遞的訊息
    "frames['urmap']",  // 要傳遞的視窗(請用 String)
    {proxy:"http://yahoomashup.com/b.html"}  //  不支援 postMessage 的瀏覽器會用到的 proxy 網址
);

Y.Global.on("crossframe:message", function (message, domain, url) {
alert(message);
});

目前測試 IE6, IE7, IE8, FF3, Chrome, Opera 都沒有問題(模擬器像是 IE Tester 無法使用)。希望對您有所幫助 :)

相關連結



Comments

  1. lizn 2011-01-29 14:29:53
    刚才写的内容只用空格分开了,阅读效果不好啊

    注意到你回复时总是换行隔开句子,这样阅读起来舒服多了

    果然是优秀的前端人员哈哈 向你致敬
  2. lizn 2011-01-29 14:17:36
    哦对 我还常用这种功能 竟然也没有想想他们是如何实现的 惭愧 其实我还是一个菜鸟 正努力学习前端技术 苦于现在的技术实在太多 又发展太快 无人指引的情况下自学进度很慢呐 幸好有你们这样的优秀人才还在写博客 能从中学到许多东西 感谢您的回复
    快新年了 新春快乐:)
  3. josephj 2011-01-28 19:33:13
    哈哈,會用 window.opener 是因為裕波提到有人說我不知道此方法
    的確我是不知道啊, 就去查了一下做了整合 :D

    嗯... Open Platform 的登入介面是比較常見的案例
    最有名的例子就是 Facebook Login Button
    http://developers.facebook.com/docs/reference/plugins/login

    而且我們的項目也是得提供第三方網站以 window.open 的方式做登入
    所以它還是有其價值存在的 :)
  4. lizn 2011-01-28 17:12:01
    cool ! 我也查了IE6 7 的opener漏洞,这样就很方便了。window.open的方式应该很少用吧
  5. josephj 2011-01-28 11:00:20
    IE8 針對 Iframe 也是支援 postMessage 沒有問題的 ;)

    不過我現在 IE6, IE7 應該也可以了
    針對 IE6, IE7 已經使用 window.opener 的漏洞直接用 Function 而非 URL 的方式來存取

    目前問題比較大的應該是 window.open 的傳遞
    因為 IE6, 7 的 opener 物件被我破壞了
    IE8 的 window.open 不支援 postMessage,所以他是走 GET URL 的方式來存取
    如果你不需 window.open 做傳遞
    長度目前應該是主流 A-Grade 瀏覽器都可以使用的
  6. lizn 2011-01-28 09:32:34
    hello 我测试了你的demo IE8也原生支持了postMessage吗 我在textarea中粘贴了超过10k个字符 都传递过去了 URL长度限制不是2048字符吗 还是说你做了分批处理 我还没有阅读您的源码 想先问一下
  7. 裕波 2010-10-08 16:54:51
    学习了,尝试一下
暱稱: 必填。
Email: 非必填。若填寫為不公開欄位,僅供站長參考聯繫。
內容: 必填。限 255 個字元以內。
驗證碼:
送出

Facebook Comment