前端單兵基本教練 - 使用 postMessage 與 IFrame 跨網站網頁互動
0 |
昨天講 Chrome 92 起禁止 IFrame 內嵌跨網站網頁 alert/confirm/prompt 一事時提到,IFrame 內嵌跨網站網頁時原本就有諸多限制,基於安全考量,禁止被內嵌的第三方網頁存取母網頁,也不允許母網頁存取第三方網頁的 DOM、JavaScript 物件,甚至想看一下 location.href 也不准。
十年前我想出一種讓第三方網頁再 IFrame 內嵌母網頁同來源網頁的奇技淫巧,但隨時代演進,如果不必考慮 IE6/7/8/9/10,要與 IFrame 或 window.open() 開啟的跨網站網頁溝通有更方便的選擇 - window.postMessage() API。
postMessage() 允許兩個不同來源的 Window 物件(IFrame 內嵌或 window.open() 開啟)彼此溝通,提供指定及檢查對方來源的方法維護安全性。其原理是網頁可以呼叫第三方網頁 window 物件的 postMessage() 方法,傳入資料參數 data (可以是字串,也可以是任何 JavaScript 物件),並指定 targetOrigin (對方包含 Scheme、Host、Port 的 URL, 例如:ℎttp://child.utopia.net),瀏覽器會負責把關,確定對方是指定來源網站上的網頁才予以放行。(註:targetOrigin 輸入 "*" 代表不限定對象,但強烈不建議這麼做)
至於要接收資料的一方,則要實作 window 的 message 事件,事件參數物件包含 source 屬性可供檢核傳送資料來源(也是用 ℎttp://parent.utopia.net 這種 URL 形式),data 則為資料物件,另外有個 source 指向來源 Window 物件,可用呼叫對方 Window 物件的 postMessage() 回傳訊息,達成雙方溝通。
直接看範例比較好懂。
被內嵌的網頁 cross-origin.html 如下,在 window message 事件中,先檢查 e.orgin 排除不是來自 ℎttp://parent.utopia.net 的資料,取出 e.data 物件加以顯示,e.source 是來源視窗物件,反向呼叫它的 postMessage() 傳回 ACK 訊息。
<!DOCTYPE html>
<html>
<body>
<div id=u style='font-size:10pt'></div>
<script>
document.getElementById('u').innerText = location.href;
</script>
<ul id=m></ul>
<script>
window.addEventListener('message', function(e) {
//務必檢查呼叫來源,只開放特定網域呼叫,以免被惡意利用
if (e.origin !== 'http://parent.utopia.net')
return;
//傳送的資料物件
let data = e.data;
let mmss = data.time.toISOString().substr(14, 5);
let li = document.createElement('li');
li.innerText = mmss + ' ' + data.msg;
document.getElementById('m').appendChild(li);
//傳送 postMessage() 的來源網頁物件,可用來於向對方回傳 postMessage()
let srcWin = e.source;
//回傳資料物件給送訊息的來源
srcWin.postMessage('ACK for ' + mmss, e.origin);
});
</script>
</body>
</html>
parent.html 如下,有個按鈕觸發傳送訊息給 IFrame 內嵌網頁物件,postMessage() 傳入 { time: new Date(), msg: 'msg from parent'} 並指定對方必須來自 ℎttp://child.utopia.net;由於對方也會用 postMessage() 回傳,故 parent.html 也要用 addEventListener() 實作 message 事件接收 ACK 字串顯示出來。
<!DOCTYPE html>
<html>
<body>
<div id=u style='font-size:9pt'></div>
<script>
document.getElementById('u').innerText = location.href;
</script>
<div style='padding: 6px'>
<button id=b>postMessage()</button>
<span id=s></span>
</div>
<iframe id=f src='http://child.utopia.net/aspnet/iframetest/cross-origin.html'></iframe>
<script>
document.getElementById('b').addEventListener('click', function() {
document.getElementById('f').contentWindow.postMessage(
//要傳遞的資料,可以是字串也可以是物件
{ time: new Date(), msg: 'msg from parent'},
//指定接受資料的對象,雖然可以用 * 全面開放,但不建議
'http://child.utopia.net');
});
window.addEventListener('message', function(e) {
//務必檢查呼叫來源,只處理來自特定網域的呼叫,以免被惡意利用
if (e.origin !== 'http://child.utopia.net')
return;
document.getElementById('s').innerText = e.data;
});
</script>
</body>
</html>
實際運作結果如下,輕鬆實現跨網站網頁溝通,很簡單吧?
Tips of how to use window.postMessage to pass data between cross-origin windows.
Comments
Be the first to post a comment