Iframe跨窗口通信原理詳解
同源
同源策略會限制 窗口(window) 和 frame 之間的通信,因此首先要知道同源策略。
同源策略目的是保護用戶信息免遭信息盜竊:加入小王有兩個打開的頁面:一個是 shop.com,一個是 email.com。小王不希望 shop.com 的腳本可以讀取 mail 的郵件,這時同源策略就起作用了。
如果兩個 URL 具有相同的協(xié)議,域,和端口,則稱它們是同源的。
以下幾個URL是同源的:
以下是不同源的:
- https://site.com
- http://bbs.site.com
- site.com:8080
- http://site.org
同源策略規(guī)定:
- 如果我們有對另一個窗口的引用(window.open || iframe),并且該窗口是同源的,那么我們就具有對該窗口的全部訪問權(quán)限。見代碼:2-1 、2-2
- 如果不是同源的,我們就不能訪問窗口中的內(nèi)容:變量,文檔,任何東西。唯一例外是location:我們可以修改它,使用它進行重定向。但是我們無法讀取 location 。因此,我們無法看到用戶當前所處的位置,也就不會泄露任何信息。
iframe
iframe 標簽承載了一個單獨的嵌入的窗口,它有自己的 document 和 window
iframe.contentWindow 來獲取 中的 window
iframe.contentDocument 來獲取 中的 document , 是 iframe.contentWindow.document 的簡寫。
當我們訪問嵌入的窗口中的東西時,瀏覽器會檢查 iframe 是否具有相同的源。如果不是,則會拒絕訪問(對 location
進行寫入是一個例外,它是會被允許的)。
代碼 2-1 : (在 同源 情況下)
<!-- 1.html 內(nèi)容 --> <!-- http://127.0.0.1:8000/1.html --> <body> 我是 1.html, 下面嵌套 2.html <iframe src="http://127.0.0.1:8000/2.html" ></iframe> <script> function hello () { console.log('this is 1.html') } var iframe = document.getElementsByTagName('iframe')[0]; console.log('contentWindow ??', iframe.contentWindow); // 能訪問 console.log('contentDocument ??', iframe.contentDocument); // 能訪問 // 注意訪問方式, 需要在 onload 后才能取到值 console.log( iframe.contentWindow.hello() ) // Uncaught TypeError: iframe.contentWindow.hello is not a function iframe.onload = function(){ console.log( iframe.contentWindow.hello() ) // this is 2.html // 輸出 Location 對象, 依然要在 iframe.onload 中訪問 console.log('contentWindow.location ??', iframe.contentWindow.location) // iframe.contentWindow.location.host : 127.0.0.1:8000 // xxx.hash: "" // xxx.host: "127.0.0.1:8000" // xxx.hostname: "127.0.0.1" // xxx.href: "http://127.0.0.1:8000/2.html" // xxx.origin: "http://127.0.0.1:8000" // xxx.pathname: "/2.html" // xxx.port: "8000" // xxx.protocol: "http:" // ... // 有相同的源 我們可以進行任何操作 iframe.contentDocument.body.innerHTML('<p>hi, i am ur father !</p>'); iframe.contentDocument.getElementsByTagName('p'); }) iframe.contentWindow.location = 'http://www.360doc.com'; // 可以直接修改 iframe 地址, 不受同源策略的限制。 有的網(wǎng)站不支持被iframe引用, 所以會報錯。 注意區(qū)分錯誤信息。 </script> </body> <!-- 2.html 內(nèi)容 --> <!-- http://127.0.0.1:8000/2.html --> <body> 我是 2.html <script> function hello () { console.log('this is 2.html') } </script> </body>
iframe.onload
vs iframe.contentWindow.onload
iframe.onload
事件(在 <iframe>
標簽上)與 iframe.contentWindow.onload
(在嵌入的 window 對象上)基本相同。當嵌入的窗口的所有資源都完全加載完畢時觸發(fā)。
……但是,我們無法使用 iframe.contentWindow.onload
訪問不同源的 iframe。因此,請使用 iframe.onload
。
window:document.domain
但是,如果窗口的二級域相同,例如 bbs.site.com
,nav.site.com
和 site.com
(它們共同的二級域是 site.com
),我們可以使瀏覽器忽略該差異,使得它們可以被作為“同源”的來對待,以便進行跨窗口通信。
為了做到這一點,每個這樣的窗口都應該執(zhí)行下面這行代碼:
document.domain = 'site.com';
這樣就可以了。現(xiàn)在它們可以無限制地進行交互了。但是再強調(diào)一遍,這僅適用于具有相同二級域的頁面。
已棄用,但仍有效
document.domain
屬性正在被從 規(guī)范 中刪除??绱翱谕ㄐ牛ㄏ旅鎸⒑芸旖忉尩剑┦墙ㄗh的替代方案。
也就是說,到目前為止,所有瀏覽器都支持它。并且未來也將繼續(xù)支持它,而不會導致使用了 document.domain
的舊代碼出現(xiàn)問題。
代碼 2-1 : (在 不同源 情況下)注意端口
<!-- 1.html 內(nèi)容 --> <!-- http://127.0.0.1:8000/1.html --> <body> 我是 1.html, 下面嵌套 2.html <!-- 端口不同, 不同源 --> <iframe src="http://127.0.0.1:8001/2.html" ></iframe> <script> function hello () { console.log('this is 1.html') } var iframe = document.getElementsByTagName('iframe')[0]; console.log('contentWindow ??', iframe.contentWindow); // 可以獲取對內(nèi)部 window 的引用 console.log('contentDocument ??', iframe.contentDocument); // 空的 document 對象 iframe.onload = function(){ console.log( iframe.contentWindow.hello() ) // Uncaught DOMException: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a cross-origin frame. // 無法讀取 iframe 中頁面的 URL console.log( iframe.contentWindow.location ) // Location {then: undefined, Symbol(Symbol.toStringTag): undefined, Symbol(Symbol.hasInstance): undefined, Symbol(Symbol.isConcatSpreadable): undefined, replace: ?} console.log(iframe.contentWindow.location.href) // Uncaught DOMException: Blocked a frame with origin "http://127.0.0.1:8000" from accessing a cross-origin frame. }) iframe.contentWindow.location = 'http://www.360doc.com'; // 可以直接修改 iframe 地址, 不受同源策略的限制,不受同源策略的限制,不受同源策略的限制。 </script> </body> <!-- 2.html 內(nèi)容 --> <!-- http://127.0.0.1:8001/2.html --> <body> 我是 2.html <script> function hello () { console.log('this is 2.html') } </script> </body>
iframe:錯誤文檔陷阱
當一個 iframe 來自同一個源時,我們可能會訪問其 document
,但是這里有一個陷阱。它與跨源無關(guān),但你一定要知道。在創(chuàng)建 iframe 后,iframe 會立即就擁有了一個文檔。但是該文檔不同于加載到其中的文檔!
因此,如果我們要立即對文檔進行操作,就可能出問題,因為那是錯誤的文檔。
正確的文檔在 iframe.onload
觸發(fā)時肯定就位了。但是,只有在整個 iframe 和它所有資源都加載完成時,iframe.onload
才會觸發(fā)。
看一下下面這段代碼:
let oldDoc = iframe.contentDocument; iframe.onload = function() { let newDoc = iframe.contentDocument; // 加載的文檔與初始的文檔不同! alert(oldDoc == newDoc); // false };
window.frames
獲取 <iframe>
的 window 對象的另一個方式是從命名集合 window.frames
中獲取:
- 通過索引獲?。?code>window.frames[0] —— 文檔中的第一個 iframe 的 window 對象。
- 通過名稱獲取:
window.frames.iframeName
—— 獲取name="iframeName"
的 iframe 的 window 對象。
例如:
// <iframe src="/" style="height:80px" name="win" id="iframe"></iframe> alert(iframe.contentWindow == frames[0]); // true alert(iframe.contentWindow == frames.win); // true
一個 iframe 內(nèi)可能嵌套了其他的 iframe。相應的 window
對象會形成一個層次結(jié)構(gòu)(hierarchy)。
可以通過以下方式獲?。?/p>
window.frames
—— “子”窗口的集合(用于嵌套的 iframe)。
window.parent
—— 對“父”(外部)窗口的引用。
window.top
—— 對最頂級父窗口的引用。
例如:
window.frames[0].parent === window; // true
我們可以使用 top
屬性來檢查當前的文檔是否是在 iframe 內(nèi)打開的:
if (window === window.top) { alert('不是在 iframe 中打開的'); } else { alert('在 iframe 中打開的'); }
“sandbox” iframe 特性
sandbox
特性(attribute)允許在 <iframe>
中禁止某些特定行為,以防止其執(zhí)行不被信任的代碼。它通過將 iframe 視為非同源的,或者應用其他限制來實現(xiàn) iframe 的“沙盒化”。
對于 <iframe sandbox src="...">
,有一個應用于其上的默認的限制集。但是,我們可以通過提供一個以空格分隔的限制列表作為特性的值,來放寬這些限制,該列表中的各項為不應該應用于這個 iframe 的限制,例如:<iframe sandbox="allow-forms allow-popups">
。
換句話說,一個空的 "sandbox"
特性會施加最嚴格的限制,但是我們用一個以空格分隔的列表,列出要移除的限制。
以下是限制的列表:
allow-same-origin
默認情況下,"sandbox"
會為 iframe 強制實施“不同來源”的策略。換句話說,它使瀏覽器將 iframe
視為來自另一個源,即使其 src
指向的是同一個網(wǎng)站也是如此。具有所有隱含的腳本限制。此選項會移除這些限制。
allow-top-navigation
允許 iframe
更改 parent.location
。
allow-forms
允許在 iframe
中提交表單。
allow-scripts
允許在 iframe
中運行腳本。
allow-popups
允許在 iframe
中使用 window.open
打開彈窗。
查看 官方手冊 獲取更多內(nèi)容。
iframe 通信:postMessage onmessage
postMessage
接口允許窗口之間相互通信,無論它們來自什么源。
因此,這是解決“同源”策略的方式之一。它允許來自于 marh.com
的窗口與來自于 qq.com
的窗口進行通信,并交換信息,但前提是它們雙方必須均同意并調(diào)用相應的 JavaScript 函數(shù)。這可以保護用戶的安全。
這個接口有兩個部分。
1 postMessage
想要發(fā)送消息的窗口需要調(diào)用接收窗口的 postMessage
方法。換句話說,如果我們想把消息發(fā)送給 win
,我們應該調(diào)用 win.postMessage(data, targetOrigin)
。
參數(shù)
data
要發(fā)送的數(shù)據(jù)。可以是任何對象,數(shù)據(jù)會被通過使用“結(jié)構(gòu)化序列化算法(structured serialization algorithm)”進行克隆。IE 瀏覽器只支持字符串,因此我們需要對復雜的對象調(diào)用 JSON.stringify
方法進行處理,以支持該瀏覽器。
targetOrigin
指定目標窗口的源,以便只有來自給定的源的窗口才能獲得該消息。
// <iframe src="http://127.0.0.1:8080/2.html" name="example" /> let win = window.frames.example; win.postMessage("message", "http://127.0.0.1:8080");
2 onmessage
為了接收消息,目標窗口應該在 message
事件上有一個處理程序。當 postMessage
被調(diào)用時觸發(fā)該事件(并且 targetOrigin
檢查成功)。
event 對象具有特殊屬性:
data
從 postMessage
傳遞來的數(shù)據(jù)。
origin
發(fā)送方的源,例如 http://javascript.info
。
source
對發(fā)送方窗口的引用。如果我們想,我們可以立即 source.postMessage(...)
回去。
要為 message
事件分配處理程序,我們應該使用 addEventListener
,簡短的語法 window.onmessage
不起作用。
window.addEventListener("message", function(event) { console.log(event) if (event.origin != 'http://http://127.0.0.1:8080') { // 來自未知的源的內(nèi)容,我們忽略它 return; } if (window == event.source) { // chrome 下, 頁面初次加載后會觸發(fā)一次 message 事件, event.source 是 window 對象 // 此時 event.source.postMessage 會形成死循環(huán) // 因此,要跳過第一次的初始化觸發(fā)的情況 return } console.log( "received: " + event.data ); // 可以使用 event.source.postMessage(...) 向回發(fā)送消息 event.source.postMessage('i am 2.html') }, source);
跨窗口的 cookie
<!-- 1.html 內(nèi)容 --> <!-- http://127.0.0.1:8000/1.html --> <body> 我是 1.html, 下面嵌套 2.html <!-- 端口不同, 不同源 --> <iframe src="http://127.0.0.1:8001/2.html" ></iframe> <script> if (!document) { document.cookie = 'name=1'; document.cookie = 'old=10'; } console.log('1.html', document.cookie) </script> </body> <!-- 2.html 內(nèi)容 --> <!-- http://127.0.0.1:8000/2.html --> <body> 我是 2.html <script> document.cookie = 'name=2'; document.cookie = 'year=2020'; console.log('2.html', document.cookie) </script> </body>
第一次渲染輸出:
1.html name=1; old=10 ,
2.html old=10; name=2; year=2020
刷新頁面輸出:
1.html old=10; year=2020; name=2 ,
2.html old=10; year=2020; name=2
我們可以得出以下結(jié)論:
- iframe 嵌套的 2.html 設(shè)置的 cookie 我們可以從 1.html 中獲取
- iframe 中設(shè)置的 cookie 會覆蓋 1.html cookie 中 Name相同的值( 不同源也是同樣的效果 )
以上就是Iframe跨窗口通信原理詳解的詳細內(nèi)容,更多關(guān)于Iframe跨窗口通信的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS觸摸屏網(wǎng)頁版仿app彈窗型滾動列表選擇器/日期選擇器
這篇文章主要介紹了觸摸屏網(wǎng)頁版仿app彈窗型滾動列表選擇器/日期選擇器的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-10-10詳解微信小程序如何實現(xiàn)類似ChatGPT的流式傳輸
這篇文章主要為大家介紹了微信小程序如何實現(xiàn)類似ChatGPT的流式傳輸示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03JavaScript中使用toLocaleString數(shù)字格式化處理詳解
這篇文章主要為大家介紹了JavaScript中使用toLocaleString數(shù)字格式化處理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08TypeScript與JavaScript對比及打包工具比較
這篇文章主要為大家介紹了TypeScript與JavaScript對比及打包工具比較,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03