3

前端單兵基本教練 - 使用 postMessage 與 IFrame 跨網站網頁互動

 2 years ago
source link: https://blog.darkthread.net/blog/window-postmessage/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
使用 postMessage 與 IFrame 跨網站網頁互動-黑暗執行緒

昨天講 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>

實際運作結果如下,輕鬆實現跨網站網頁溝通,很簡單吧?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK