記一次與iframe之間的抗爭

來源:互聯網
上載者:User

標籤:click   ram   max   label   LLC   實踐   直接   100%   tac   

  iframe這個標籤之前瞭解過這個東西,知道它可以引入外來的網頁,但是實際開發中沒有用到過。這一次有一個需求是說準備要在網頁中嵌套另外一個網站,用iframe這個標籤,讓我測試一下這個可不可以在自己的網頁中對引入進來的iframe架構進行操作,操作dom和css的一些東西。讓我做出一個小案例看看可不可以,我信誓旦旦保證說可以的,我試過!!!

  就這樣交代給我之後信心滿滿的就開始了我的驗證。

  什麼是同源?

  同網域名稱、 同連接埠、 同協議  

  網上是有好多這個的解釋的,給出一張圖片。 看下面這張圖片。 引用來自  瀏覽器的同源策略

  

 

  我直接建立了一個檔案夾,在裡面寫了兩個html頁面的檔案,舉例說明是a.html和b.html,然後讓其中的一個a.html檔案中用iframe標籤的src去引入b.html檔案,在裡面去互相操作他們的css樣式和DOM元素。

  a.html

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title>    <style>        html,body{            height: 100%;        }        body{            background: pink;        }        #iframe1{            width: 400px;            height: 400px;            margin: auto;            background: blue;        }    </style></head><body>這裡是父文檔<input type="button" id="btn1" value="改變子文檔的顏色"><input type="button" id="btn2" value="刪除span1"><input type="button" id="btn3" value="改變span2的顏色"><input type="button" id="btn4" value="修改子文檔中的link標籤"><br /><br /><hr /><iframe id="iframe1" src="b.html" frameborder="0"></iframe><script>    // 只有同伺服器下 同網域名稱下才可以操作 不能更改別人的網頁。。    var oBtn1 = document.getElementById(‘btn1‘);    var oIframe1 = document.getElementById(‘iframe1‘);    function fn(){        document.body.style.background = ‘green‘;    }    oBtn1.onclick = function () {        console.log(oIframe1.contentWindow);  // ---這個東西是子文檔中的window對象        console.log(oIframe1.contentDocument);  // ---- 這個東西是子文檔中的document對象        oIframe1.contentWindow.document.body.style.background = ‘yellow‘;    };    btn2.onclick = function () {        var span1 =oIframe1.contentWindow.document.querySelector(‘.span1‘);        console.log(span1);        span1.parentNode.removeChild(span1);    };    btn3.onclick = function () {        var span2 =oIframe1.contentWindow.document.querySelector(‘.span2‘);        span2.style.color = ‘red‘;    };    btn4.onclick = function () {        var iFrameWindow = oIframe1.contentWindow;        console.log(iFrameWindow.document.getElementsByTagName(‘link‘));        var link0 = iFrameWindow.document.getElementsByTagName(‘link‘)[0];        console.log(link0.parentNode.removeChild(link0));    }</script></body></html>

 

b.html

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title>    <style>        body{            background: yellowgreen;        }    </style>    <link rel="stylesheet" href="1.css">    <link rel="stylesheet" href="2.css"></head><body>    <h1 id="h1">這裡是子文檔1</h1>    <span class="span1">span1標籤</span> <br>    <br>    <span class="span2">span2標籤</span>    <span class="span3">span3標籤</span>    <br />    <hr />    <input type="button" id="btn1" value="改變父文檔的顏色">    <script>        var oH1 = document.getElementById(‘h1‘);        var oBtn1 = document.getElementById(‘btn1‘);        oH1.onclick = function () {            alert(‘子文檔中的點擊事件,我可以改變父文檔‘);            console.log(window.parent);  // -----這個parent對象是父文檔中的 window對象        };        oBtn1.onclick = function () {            (function (window,document) {                document.body.style.background = ‘skyblue‘;            })(window.parent, window.parent.document);        };    </script></body></html>

  樣式如下

 

上面的兩個代碼中用到了一個東西,在a.html檔案中 用到了iframe標籤元素的  .contentWindow.contentDocument 這兩個東西,它們兩個分別是子文檔也就是b.html中的window對象和document對象,那麼你說知道了這兩個東西要去操作它裡面的東西還不簡單嗎。

b.html檔案中的  window.parent 這個東西是a.html的window對象,那麼它同樣也可以去操作a.html中的元素了。所以交給我的工作我感覺完成了,就去問他,這樣可以。然後我給他看了一下這個東西,後來瞭解到這兩個不是同一個網域名稱下的,這兩個網站不是在一起的,然後我就回來又來調試。

 

不同連接埠下的調試

  我經常用的編輯器是webstrom,它這個東西會自啟動一個 127.0.0.1:63342的連接埠,我又用node做了一個簡單的監聽 3000連接埠的伺服器,在網頁上面開啟了。

  還是同樣的代碼吧,只不過把ifreme上面的src改為了我3000連接埠的網頁。

  

  但是這次瀏覽器給了我一個驚喜,因為我感覺吧只有後端才會存在跨域什麼的問題,沒有想過前端的這些東西。

  它的列印出來的window對象都變了,好多都是false了,和之前在同一個頁面下面的東西都不一樣了~~

因為一看到 origin  cross-origin就感覺是跨域的那種問題。

  得了吧,去百度,google查到底怎麼辦吧。我一直相信以我現在的水平遇到的問題其他的人同樣也有人會遇到過。

  這一查不要緊,感覺看得好多文章開闢除了新的天地,真的是,在文章底部會給出參考文章,昨天我只看到了一種解決方案,並且將它付諸於實踐了,但是由於想要搞明白今天又找到了幾種解決方案,但是並沒有去實驗。

  

  還是要講一講同源對哪些行為有限制?

  隨著互連網的發展,同源策略 越來越嚴格。目前,如果非同源,共有三種行為受到限制。

  1. Cookie、localStorage和 IndexDB無法讀取

  2. DOM無法獲得

  3. AJAX 請求不能發送

  雖然這些限制是必要的,但是有時很不方便,合理的用途也會受到影響。

   

  這個問題難道就沒有辦法解決了嗎?有的

  

Cookie解決方案

  Cookie 是伺服器寫入瀏覽器的一小段資訊,只有同源的網頁才能共用。但是,兩個網頁頂層網域相同,只是次層網域不同,瀏覽器允許通過設定document.domain共用 Cookie。

  舉例來說,A網頁是http://w1.example.com/a.html,B網頁是http://w2.example.com/b.html,那麼只要設定相同的document.domain,兩個網頁就可以共用Cookie。

  document.domain = ‘example.com‘;

  現在,A網頁通過指令碼設定一個 Cookie。
  document.cookie = "test1=hello";

  B網頁就可以讀到這個 Cookie。
  var allCookie = document.cookie;
  注意,這種方法只適用於 Cookie 和 iframe 視窗,LocalStorage 和 IndexDB 無法通過這種方法,規避同源政策,而要使用下文介紹的PostMessage API。

  另外,伺服器也可以在設定Cookie的時候,指定Cookie的所屬網域名稱為頂層網域,比如.example.com。
  Set-Cookie: key=value; domain=.example.com; path=/
  這樣的話,次層網域和第三層網域名不用做任何設定,都可以讀取這個Cookie。

  上面這種方法暫時還沒有去實驗,等實驗過後再來修改一下這裡,因為自己都不知道行不行。

 

iframe

  如果兩個網頁不同源,就無法拿到對方的DOM,上面的第二個例子我已經去實驗過了,也看到報錯資訊了。

  就是父視窗運行下面的命令,如果iframe視窗不是同源,就會報錯。

document.getElementById("myIFrame").contentWindow.document// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.

  上面命令中,父視窗想擷取子視窗的DOM,因為跨域資源導致報錯。

  反之亦然,子視窗擷取主視窗的DOM也會報錯。

window.parent.document.body// 報錯

  前面講的這些實際上我第二個例子實驗過了,下面也就是我遇到問題的幾種解決方案。

  參考的他人的文章找到的,對於完全不同源的網站,目前有三種方法,來解決我遇到的問題。

 

1. 片段標識符

  片段標識符呢也就是雜湊值#,我們都知道當網址資源#前面不變,後面的部分變化的時候網頁是不會重新整理的,如果不清楚的話,可以看一下我之前寫過的一篇文章  淺談SPA  ,裡面有詳細的介紹#。

  就是父文檔和子文檔之間要互動時,就去改變hash值 也就是#後面的部分,然後兩者再相互去監聽hash變化的事件,再去自己做一些處理就好了

  

window.location.hash;  // 這個是可以擷取hash值的window.onhashchange = function(){   // 這個是hash值改變會觸發這個函數  }

  舉一個小例子可以去實驗一下

父視窗可以把資訊,寫入到子視窗的片段標識符

父視窗中的代碼

  

為了好看吧,就不用部落格園內建的那個代碼了,就這樣把子文檔的url地址給改變了吧,因為#不會重新整理網頁,而子文檔中也可以監聽到這個#值的改變,所以子文檔中

瀏覽器中列印的東西

在這裡可以看到了,我們傳遞過去的資料資訊為 #changeColor,在子頁面中可以判斷#後面的帶的東西,再去執行自己的邏輯。

 

同樣的子文檔給父文檔傳遞資料

  我們不是可以拿到父文檔的那個 window.parent嗎,就用這個去改變就可以了,但是  BUT!!!

  我在子頁面中使用的時候

btn1.onclick = function () {    console.log(parent.location);}

  

 這是什麼嘛,你父文檔都可以改子文檔了,問什麼這個還是要 block frame with啥啥啥的,我本來以為可以成功的,這個有知道解決方案的大佬可以幫幫忙嗎。嘿嘿,暫時先這樣吧,父文檔已經可以給子文檔傳遞資訊了,我的解決方案也不是這種,暫時先把這個錯誤問題放一放,以後有解決方案了,會來這裡修改的。

 

2. window.name

  瀏覽器視窗有window.name屬性,這個屬性最大的特點是,無論是否同源,只要在同一個視窗裡面,前一個視窗設定了這個屬性後,後一個網頁可以讀取它。

  父視窗先開啟一次子視窗,載入一個不同源的網頁,將網頁資訊寫入window.name屬性。

  

window.name = data;

  接著,子視窗跳回一個與主視窗同域的網址

location = ‘http://parent.url.com/xxx.html‘

  然後主視窗就可以讀取子視窗的window.name了

  

var data = document.getElementById(‘myFrame‘).contentWindow.name;

  這種方法的優點是,window.name容量很大,可以放置非常長的字串;缺點是必須監聽子視窗window.name屬性的變化,影響網頁效能。

3.跨文檔通訊API  postMessage

   我用到的解決方案是這種方法,感覺它和Vue之間的組件傳值一樣,不說話了,直接上代碼,測試吧,記得那個子文檔是 3000連接埠的頁面

父文檔

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Title</title>    <style>        body{            background: pink;        }        #iframe1{            width: 100%;            height: 400px;        }    </style></head><body><input type="button" id="btn1" value="改變子文檔的東西"><input type="button" id="btn2" value="刪除span1的顏色"><input type="button" id="btn3" value="改變span2的顏色"><br /><hr /><!--<iframe id="iframe1" src="http://localhost:3000" frameborder="0"></iframe>--><iframe id="iframe1" src="http://localhost:3000" frameborder="0"></iframe><script>    var oIframe1 = document.getElementById(‘iframe1‘);    var a = function fn(){        document.body.style.background = ‘green‘;    };    btn1.onclick = function () {        console.log(‘傳遞的資料是‘,‘messageInfo‘);        oIframe1.contentWindow.postMessage(‘changeColor‘,"http://localhost:3000");    };    btn2.onclick = function () {        //oIframe1.contentWindow.postMessage(‘changeSpan2Color‘,"http://localhost:3000");        oIframe1.contentWindow.postMessage(‘deleteSpan1‘,"http://localhost:3000")    };    btn3.onclick = function () {        oIframe1.contentWindow.postMessage(‘changeSPan2Color‘,"http://localhost:3000")    };    window.addEventListener(‘message‘,function (res) {        console.log(`這裡是父文檔`);        if(res.data == ‘GetWhiteLabel‘)        document.body.style.background = ‘yellow‘;    })</script></body></html>
  子文檔
<!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport"          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>Document</title>    <style>        body{            background: skyblue;        }    </style></head><body>    <h1>這裡是我的html頁面呢</h1>    <span class="span1">span1標籤</span>    <span class="span2">span2標籤</span> <br>    <input type="button" id="btn1" value="改變父文檔的東西">    <script>    <!---->       window.onload = function () {           let parent = window.parent;           window.addEventListener(‘message‘,function (res) {               console.log(`******************這裡是子頁面的接收到的訊息*************`);               console.log(res);               switch (res.data) {                   case ‘changeColor‘:                       document.body.style.background = ‘green‘;                       break;                   case ‘deleteSpan1‘:                       var oSpan1 = document.querySelector(‘.span1‘);                       oSpan1.parentNode.removeChild(oSpan1);                       break;                   case ‘changeSPan2Color‘:                       var oSpan2 = document.querySelector(‘.span2‘);                       oSpan2.style.color = ‘red‘;                       break;               }           });           btn1.onclick = function () {               parent.postMessage("GetWhiteLabel","*");           }       }    </script></body></html>

  還是同樣的頁面吧,實現一樣的功能。

 

 上面有的地方寫的不太好,存在一些安全問題,這個正是我現在正在做的地方,

oframe.contentWindow.postMessage(data,origin,false);  //這個是postMessage的API// 發送的資料  子文檔地址  false// 在子文檔中去監聽那個message的變化window.addEventListener("message",function(res){    console.log(res.data);  //這個東西就是發送過去的資料    // 去根據傳過來的不同的資料 再去做相應的判斷})

子文檔給父文檔傳資料的方式

parent.postMessage(‘message‘,origin,false);  // 同樣也是類似的        // 在父文檔中去監聽那個message的值    window.addEventListener("message",function(res){        console.log(res.data);  //這個東西就是發送過去的資料        // 去根據傳過來的不同的資料 再去做相應的判斷    });

 這樣做可以實現,就是有一些安全問題,就是所有人如果查看你網站的源碼的話肯定會看到這個東西的,你寫的這麼隨意,其他任何網站只要引入你的子文檔,然後就可以通過自己去寫一些東西去改變你的子文檔。

另外的一個缺點就感覺是 拓展性不好,你還需要去拿到子文檔的網站,還需要再去修改它的原始碼,感覺特別麻煩,如果有好幾十個頁面還要寫好幾十個嗎。

所以想到了一種傳值的方法,不穿那個要判斷的東西,把要修改的元素的html代碼的函數給傳過去,就是子文檔去定義一個介面去接收,父文檔把要執行的事件都傳過去,然後子文檔寫一個執行事件的介面。這裡就會出現剛才第一個那個安全問題了,更為嚴重,因為你不知道要執行的是什麼事件。

 

  現在有一個想法就是後台做一個認證,就像的那個access_token認證一樣的東西,加密,每次去操作子文檔的時候,都要去請求,然後在子頁面監聽到這個事件之後解密再兩個之間去對比,如果一樣了才去執行,這個暫時還不知道如何去下手。

 

   本文感覺特別有用的解決辦法和參考的文章有如下還有更多的沒有發,如果你遇到了同樣的問題,看了我的解決不了問題,可以去看看下面的文章,當然還可以給我留言,必回複。

  stackoverflow網頁中的問答     部落格園園友的文章 關於iframe的實踐     阮一峰大佬的  瀏覽器同源政策及其規避方法

  

  如果有更好的解決辦法還望可以告知,謝謝!

  如果你閱讀了本文章有了一些收穫,我會感到非常開心,由於能力有限,文章有的部分解釋的不到位,希望在以後的日子裡能慢慢提高自己能力,如果不足之處,還望指正。

記一次與iframe之間的抗爭

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.