一種通用的iframe跨域通信方法

同源策略簡介

如果兩個頁面的協議、端口(如果指明瞭的話)和主機名都相同則兩個頁面擁有相同的源。同源策略阻止從一個源加載的文檔或腳本獲取或設置另一個源加載的文檔的屬性。這個策略可以追溯到 Netscape Navigator 2.0。

但很多時候兩個不同域的頁面之間需要進行通信,這就產生了跨域通信的問題了,關於跨域的文章非常多,可以參考10種方式實現跨域資源的共享,下面和大家分享一下我們項目中是如何進行雙向跨域通信的,工大家參考。

解決方案

對於雙向跨域通信來說,選擇的方案也不是很多,綜合各種因素,我們選擇FIM + window.postMessage的組合方式來解決跨域通信問題。因爲這兩種方式各有優缺點,window.postMessage,使用非常簡單,但是由於是個比較新的方法,IE6和IE7等老式瀏覽器不支持。FIM方法支持所有的瀏覽器,但比較繁瑣,而且容易產生瀏覽記錄,因此如果瀏覽器支持window.postMessage就採用window.postMessage,否則就採用FIM技術。

window.postMessage解決方案

window.postMessage是HTML5定義的一個很新的方法,這個方法可以很方便地跨window通信。由於它是一個很新的方法,所以在很舊和比較舊的瀏覽器中都無法使用,比如IE6和IE7,IE8已經支持這個方法了。

window.postMessage的使用方法比較簡單,只有兩個參數,第一個參數是要傳輸的消息,第二個參數是接收消息的域,可以用“×”來表示所有的域。發送消息的代碼如下:

  var o = document.getElementsByTagName('iframe')[0];
  o.contentWindow.postMessage('Hello world', 'http://b.example.org/');

接收消息頁面的代碼如下:

  window.addEventListener('message', receiver, false);
  function receiver(e) {
    if (e.origin == 'http://example.com') {
      if (e.data == 'Hello world') {
        e.source.postMessage('Hello', e.origin);
      } else {
        alert(e.data);
      }
    }
  }

注意這裏的綁定事件方式只針對非IE瀏覽器,IE瀏覽器需要用attachEvent方式來綁定事件。在上面的代碼裏你可以決定是否要判斷消息的來源,這裏是從安全角度考慮,防止不安全的消息。

FIM (Fragment Identitier Messaging)

FIM (Fragment Identitier Messaging)的原理是基於父窗口可以對iframe進行URL讀寫,iframe也可以父窗口的URL(注意,跨域時iframe是不可以讀取父窗口的URL的,但可以修改父窗口的URL),URL有一部分被稱 爲frag,就是#號及其後面的字符,它一般用於瀏覽器錨點定位,Server端並不關心這部分,應該說HTTP請求過程中不會攜帶frag,所以這部分的修改不會產生HTTP請求,也就是頁面不會刷新,但是會產生瀏覽器歷史記錄。FIM的原理就是改變URL的frag部分來進行雙向通信。每個window通過改變其它window的location來發送消息,並通過監聽自己的URL的變化來接收消息。這個方式的通信會造成一些不必要的瀏覽器歷史記錄,而且有些瀏覽器 不支持onhashchange事件,需要輪詢來獲知URL的改變,最後,URL在瀏覽器下有長度限制,這個制約了每次傳送的數據量。

先說父窗口向iframe發送消息,代碼如下:

  var o = document.getElementById('iframe');
  o.href = 'http://www.weakweb.com#name=boris';

這樣我們就把值傳到iframe裏面了,下面就需要考慮iframe如何監聽何時有消息傳遞過來。這裏有兩個方法:1,在iframe裏使用setInterval()輪詢來判斷當前的iframe的URL是否發生了變化,缺點是輪詢會產生性能消耗;2,在父窗口裏改變iframe的大小來觸發iframe的window.onresize事件,缺點是要改變iframe窗口的大小。當iframe發現有消息傳送過來時就可以通過locaiton.hash來讀取了。

iframe向父窗口發送消息

  parent.location.href = 'http://www.company.com#height=100';

上面提過,跨域時iframe可以寫父窗口的URL,但是不可以讀取的,所以這裏我們就通過frag的方式把消息傳遞到父窗口了,同樣,父窗口也需要監聽啥時候有消息傳進來,這裏只能用輪詢的方式了。

對FIM的一點改進

當父窗口採用修改iframe窗口大小的方式告訴iframe有消息傳遞方式時,這種方式有一個非常明顯的缺點,那就是iframe窗口的大小改變會嚴重影響用戶體驗。因此這裏採用的代理機制來處理。

我們額外創建了代理iframe叫proxyIframe吧,proxyIframe和之前的contentIframe是同一個域的,同時proxyIframe是隱藏不可見的,這樣我們先把消息傳遞到proxyIframe裏,然後在由proxyIframe發送到contentIframe,因爲這兩個iframe是同域的,所以可以很方便的傳遞。這樣做的好處是我們只修改proxyIframe的大小而不用修改contentIframe的大小,這樣不會影響用戶體驗,缺點是比之前複雜了一些。

瀏覽器的URL長度是有限制的,尤其是IE,所以我們可以通過分段傳輸來解決這個問題。

示例代碼

我寫了一個測試代碼,其中包含了三個頁面,一個父頁面和兩個iframe頁面,點擊下載

一個如何在本地測試運行上面的示例代碼的簡單方法,在C:\Windows\System32\drivers\etc\hosts文件,在裏面添加兩個域名指向127.0.0.1,這樣就可以測試跨域了。我的hosts文件如下:

  	127.0.0.1       a.com
    127.0.0.1       b.com

這裏就可以通過http://a.com/study.html來訪問父窗口,在study.html中載入http://b.com/proxyIframe.html和http://b.com/contentIframe.html頁面,就可以測試了。前提是安裝了web server,例如apache。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章