記一次與iframe之間的抗爭

来源:https://www.cnblogs.com/z937741304/archive/2018/09/30/9728160.html
-Advertisement-
Play Games

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


  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的實踐     阮一峰大佬的  瀏覽器同源政策及其規避方法

  

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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • mysql安裝(rpm) 1.卸載系統自帶的 mariadb lib [root@centos linux ~] rpm qa|grep mariadb mariadb libs 5.5.44 2.el7.centos.x86_64 [root@centos linux ~] rpm e maria ...
  • 在將項目集成到 Jenkins 後,經常會出現不穩定的構建,Jenkins 控制台輸出的錯誤信息為: Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed) 。 經過調查,問 ...
  • 本文分為三部分, 第一部分簡單介紹如何使用Espresso, 第二部分分析如何處理諸如非同步, 依賴註入, 程式結構對UI測試的影響以及提供解決辦法, 第三部分提供源碼以及一些Reference的地址. ...
  • 用Koala實現less的實時編譯 1、下載Koala(Koala可以實現實時編譯) 2、把CSS文件夾(如index.css,index.less)拖到Koala中 3、點擊到需要編譯的index.css,如: 4、點擊Compile即可實現實時編譯。Koala會在底部最小化運行。 less的語法 ...
  • 進來的小伙伴可以先自己思考一下 對於還屬於小白的我來說掃了一眼這些代碼的反應是:“這都是啥?” 但是我也比較喜歡鑽研~ 仔細看了第二眼的反應是:“這回調函數也太回調了吧!” 又看了第三眼差不多也理解了一星半點,寫出解題邏輯的同時也讓自己理解的更深刻一點 答案輸出:1 3 5 6 4 2; 1. 2. ...
  • 涉及知識點:(1)原型的引入(2)構造函數、原型對象和實例對象之間的關係(3)__proto__和prototype的理解 直接舉例:在自定義構造函數創建對象時,因為創建的對象使用的不是同一個方法,所以創建對象越多,就會開闢大量空間造成記憶體浪費。 驗證:在<script>標簽中寫如下代碼,瀏覽器中打 ...
  • 前端工程化 背景 前端工程化的概念近兩年來被廣泛的提及,究其原因,是前端工程師所負責的客戶端功能邏輯在不斷複雜化。PC網站、手機應用、桌面應用、微信小程式,前端開發的應用領域越來越廣,前端工程師這個職位也不再是幾年前被戲稱的“切圖仔”,在這種背景下,前端工程化應運而生。 聊到前端工程化,必然會有一些 ...
  • jQuery DataTable 刪除數據後重新載入 問題描述: 利用jQuery Datatable和artTemplate組合來做的表格。但是當刪除數據時,需要重新載入table里的數據。但是問題是datatable並沒有直接的重新渲染,反而給數據累加上了。 解決辦法: 經過查看高人的blog, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...