1. 程式人生 > >記一次與iframe之間的抗爭

記一次與iframe之間的抗爭

之間 讓我 yellow span 問答 報錯信息 dom 更改 selector

  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之間的抗爭