【翻譯】Iframe, onload 與 document.domain

原文連接:http://www.nczonline.net/blog/2009/09/15/iframes-onload-and-documentdomain/
譯者:Demix

在web2.0的時代,愈來愈多的人開始關注使用iframe將第三方網站的內容嵌入本身的網站中。當javascript可以經過其域名進行數據交互後,iframe開始提供一系列的安全措施,使得一個嵌套於iframe中的第三方網站不可能獲取到主體網站的腳本程序。這個跨域的限制一樣也讓父級頁面沒法讀取嵌套內容的腳本。從全部的角度來講,父級頁面和被iframe包含的頁面是徹底沒有聯繫的。這個複雜的關係讓javascript對象的全部權成爲了許多有關iframe討論的話題之一。

iframe 和全部權

iframe元素自己是位於父級頁面中的,因此你能夠像一個普通元素同樣的使用和操做它。表明了iframe內容window對象是做爲一個頁面的屬性加入到iframe中的。爲了讓父級頁面可以以一種合適的方式獲取iframe的window對象,父級頁面和iframe頁面的域名應該保持一致(詳情)。

當域名吻合時,父級頁面就能夠獲取到iframe的window對象了。iframe元素擁有名爲contentDocument的屬性,這個屬性包含了iframe對象的document對象,因而咱們就可使用parentWindow這個屬性取回window對象。這已經成爲了獲取iframe的window對象的標準方法,並被絕大多數瀏覽器支持。ie8之前的瀏覽器不支持這個屬性,咱們須要用以使用其專有的contentWindow屬性。如

1  function  getIframeWindow(iframeElement){
2       return  iframeElement.contentWindow  ||  iframeElement.contentDocument.parentWindow;
3  }

補充一點,父級頁面的window對象在iframe中可以以window.parent獲取。iframe元素一樣也可使用window.frameElement來獲取本身的引用。因爲iframe被父級元素包含但卻能夠直接獲取到iframe的window對象,該方法普遍用於突破兩者的界限。

使用iframe元素的onload事件

因爲各類全部權的不一樣,嘗試肯定iframe什麼時候裝載完畢是一個頗有趣的實驗。非ie瀏覽器提供了許多有用的方法。它們讓iframe元素擁有load事件,這樣咱們就能夠肯定iframe什麼時候裝載徹底。因爲iframe元素包含於父級頁面中,你也不用擔憂跨域的限制。裝載本地數據的iframe可使用監聽裝載外部數據的iframe完成事件的相同方法。舉例以下:

1  var  iframe  =  document.createElement( " iframe " );
2  iframe.src  =   " simpleinner.htm " ;
3  iframe.onload  =   function (){
4      alert( " Iframe is now loaded. " );
5  };
6  document.body.appendChild(iframe);

上面的例子在全部非ie瀏覽器中均適用。我曾經嘗試使用attachEvent方法,不過最終發現ie並不支持在iframe上的load事件。

使用iframe的window對象的onload事件

看起來ie又要給咱們製造難題了。不過隨後,我記起來我之前沒有考慮過在iframe中引用外部文件。在個人實驗中,我曾經處理了同一域名下的內容。因爲跨域限制不存在,我可以輕易的獲取iframe對象的window對象並加上onload事件。例如:

1  var  iframe  =  document.createElement( " iframe " ),
2      iframeWindow;
3  iframe.src  =   " simpleinner.htm " ;
4  document.body.appendChild(iframe);
5  iframeWindow  =  iframe.contentWindow  ||  iframe.contentDocument.parentWindow;
6  iframeWindow.onload  =   function (){
7      alert( " Local iframe is now loaded. " );
8  };

有趣的是,你必須在iframe元素已經加到頁面中之後才能註冊事件。若是先於它,iframe的window對象將不存在,咱們也固然不可能在window對象上註冊事件。這個方法只在ie和ff下對於同域的兩個嵌套頁面有效。其餘瀏覽器不會建立window對象並將拋出異常。

定義document.domain

我試圖尋找一種能夠監聽ie裏iframe的load事件的方法以及更多的應用於其餘瀏覽器的方法,因而我繼續了個人實驗。接下來,因爲我有多個須要使用iframe讀取的不一樣二級域名的頁面,我設置了父級頁面的document.domain。將document.domain設定爲主域名可以容許這些iframe之間以及同父級頁面的通訊。例如,若是我有須要讀取一個地址爲www2.nczonline.net的iframe,在技術上上說是不被容許的。不過,若是我在父級頁面和iframe頁面中均設置了document.domain爲'nczonline.net',這兩個頁面將能夠相互通信。以下:

1  document.domain  =   " nczonline.net " ;

這個聲明消除了域名的區別,咱們能夠像處理兩個相同域名的網站同樣處理這兩個頁面。

有一個問題又產生了。在iframe徹底加載前,它將被認爲是屬於iframe標籤中聲明的src屬性標誌的頁面的。相對地址被自動加上了父級頁面的地址(www.nczonline.net)並與咱們設置的document.domain相矛盾。這意味着在比較nczonline.net和www.nczonline.net時,咱們將通不過同域檢查,因而當咱們試圖獲取iframe的window對象時,將引發javascript的報錯。iframe頁面並不會改變其關聯的domain值,直到它加載完畢,屆時改變domain值的腳本纔會執行。當iframe已經加載完畢時,一切運行完美。可是,咱們是怎麼知道iframe何時才加載完?

換個方向思考

因爲一致沒有找到一種能夠跨瀏覽器解決斷定iframe是否加載完畢的方法,我決定轉變一下個人想法。若是咱們讓iframe告訴父級頁面它已經加載完畢,而不是讓父級頁面去獲取iframe的load事件,也許可以解決問題。我但願這種方法可以與註冊一個事件句柄同樣簡單,因此我採用了下面的想法:我在iframe元素上聲明一個方法,而後,當iframe頁面加載完畢以後會執行這個函數。固然,這個方法是被聲明到iframe元素自己而不是iframe的window對象上的,後一方法在前面的研究中被證實不能兼容全部的瀏覽器。結果看起來像這樣:

1  var  iframe  =  document.createElement( " iframe " );
2  iframe.src  =   " simpleinner.htm " ;
3  iframe._myMethod  =   function (){
4      alert( " Local iframe is now loaded. " );
5  };
6  document.body.appendChild(iframe);

上面的代碼在iframe元素上聲明瞭_myMethod的方法。iframe中的頁面加入以下方法:

1  window.onload  =   function (){
2      window.frameElement._myMethod();
3  }

因爲上述代碼是在咱們聲明document.domain以後運行的,因而咱們便不用擔憂任何安全限制的問題。這種方法在同一主域名下工做得很完美。它可以兼容全部的瀏覽器,這也正是我所須要的。可是,監聽包含第三方頁面的iframe的load事件仍然在困擾我。

使用iframe的onreadystatechange

我決定研究一下ie瀏覽器關於iframe的接口文檔。若是在onload事件中聲明某些事件顯而易見不能達到咱們想要的效果,可是我以爲確定會有相似的方法。我嘗試使用attachEvent方法去增長事件句柄,可是仍然沒有用。ok,顯然ie中的iframe並不支持load事件。有其餘方法嗎?

接下來我使用了ie的一種怪異的方法——readystatechange事件。顯然它與xhr對象的readystatechange事件徹底不同。我想知道是否iframe元素也支持這個事件,它會在iframe嵌套的內容加載徹底前變成'interactive',隨後變成'complete'。同時,因爲它是註冊到iframe元素而不是iframe的window對象上,這理所固然不會存在跨域的問題。最後我整理出來的代碼以下:

 1  var  iframe  =  document.createElement( " iframe " );
 2  iframe.src  =   " simpleinner.htm " ;
 3 
 4  if  (navigator.userAgent.indexOf( " MSIE " >   - 1   &&   ! window.opera){
 5      iframe.onreadystatechange  =   function (){
 6           if  (iframe.readyState  ==   " complete " ){
 7              alert( " Local iframe is now loaded. " );
 8          }
 9      };
10  else  {
11      iframe.onload  =   function (){
12          alert( " Local iframe is now loaded. " );
13      };
14  }
15 
16  document.body.appendChild(iframe);

判斷瀏覽器是否爲ie瀏覽器稍稍有些麻煩。原本我更偏向使用判斷iframe.readystate是否存在來進行瀏覽器的檢測。可是,當試圖獲取未加入到頁面中的iframe的屬性時會拋出一個錯誤。我也嘗試使用document.readyState去判斷是否使用readystatechange,然而,已經有不少瀏覽器支持前一屬性了,因此它並非一個有效的劃分手段。

ie 對onload事件的支持

在發表這篇文章後短暫的時間裏, Christopher留言說在iframe元素上使用attachEvent是可以在ie下工做的。我發誓我以前已經嘗試過這樣的方法,可是因爲他的提示,我嘗試了另一個實驗。隨後發現,他是正確的。隨後我研讀了msdn上的文檔,最後終於發現了一段文檔說明了這個問題。它最終讓咱們的代碼變成了下面這個樣子

 1  var  iframe  =  document.createElement( " iframe " );
 2  iframe.src  =   " simpleinner.htm " ;
 3 
 4  if  (iframe.attachEvent){
 5      iframe.attachEvent( " onload " function (){
 6          alert( " Local iframe is now loaded. " );
 7      });
 8  else  {
 9      iframe.onload  =   function (){
10          alert( " Local iframe is now loaded. " );
11      };
12  }
13 
14  document.body.appendChild(iframe);

以上的代碼仍然能正常運行於全部的瀏覽器之上,並能迴避readystatechange事件與load事件潛在的衝突可能。

綜合 在一小段調研以後,咱們發現肯定一個iframe對象什麼時候加載完成的跨瀏覽器的方法是存在的。這讓咱們對iframe的監聽和錯誤控制變得容易的多。感謝全部的瀏覽器廠商看到了在iframe元素上添加這些事件的好處,而不是去依賴iframe的window對象或者認爲咱們平時並不關心iframe什麼時候完成加載。 好久沒有翻譯了,草草翻譯出上面這篇文章,錯誤必定很多。Zakas這篇文章很搞笑,寫出來幾分鐘後有人留言說裏面有錯誤,又改掉了。這裏咱們又看到寫博客的一個好處——共同成長。若是沒有後來的評論,也許Zakas會一直使用不太優雅的監聽readystatechange事件來實現。另外經過這篇文章,咱們看到了大師的細緻之處。雖然整篇文章所描述的問題也許咱們平時都會有接觸,可是又有哪個人會有這樣的細緻。PPK也如此,老道也如此,全部的大師都是如此。成功,重在細節。
相關文章
相關標籤/搜索