運用 postMessage 解決 iframe 與父層溝通的問題

Eason Lin
5 min readJan 2, 2021

--

最近在支援公司某項專案時,碰到了需要在頁面導入不同源頁面的狀況,在導入過程中由於不知道其高度,直覺上就打算使用 DOM Selector 去取 iframe 內的元素:

const iframe = document.querySelector("iframe");
iframe.onload = () => {
const iframeInner = iframe.contentWindow;
console.log(iframeInner);
};

查了一下才知道,基於同源政策,如果透過 iframe 去引入不同源的頁面,我們是不能透過 JavaScript 去存取其中的任何東西的。

光就高度而言,用 F12 直接去看該頁面的高度,並直接用 CSS 給一個固定高度好像也行吧?但 iframe 頁面的高度如果在未來的某個版本改變了,導入 iframeparent 頁就也必須跟著改,那有沒有什麼方法,可以讓 iframe 在被引入時傳遞一些 parent 頁所需資訊的方法嗎?

postMessage

如果 iframesrc 為不同源(httphttps、不同的 Port、不同的 Domain),彼此之間就可以使用 window.postMessage 來溝通。

假設我現在有一個 Port5501parent.htmlPort5500iframe.html,我們可以在 iframe.html 中輸入:

parent.postMessage("some message", "http://127.0.0.1:5501/parent.html");

在 parent.html 中輸入:

window.addEventListener("message", (e) => {
console.log(e.data);
});

應該可以在 parent.html 中看見 iframe.html 傳入的 ‘some message’ 字串。

postMessage 第一個參數 message 除字串外也可以傳入其他型別的值,如果要傳遞高度:

parent.postMessage({ height: 550 }, "http://127.0.0.1:5501/parent.html");

parent 就可以將其設定在 iframe 的高度:

window.addEventListener("message", (e) => {
iframe.style.height = e.data.height + "px";
});

安全性問題

看到這裡可能會發現,透過這樣的傳遞方式雖然方便,但在安全性一定也會有問題。例如這個 parent.html 接受數個不同源 iframe,就可能導致一些問題。我們可以透過 event.originmessagefunction 決定要不要做事情:

window.addEventListener("message", (e) => {
const origin = e.origin;
if (origin === "http://127.0.0.1:5500") {
iframe.style.height = e.data.height + "px";
} else {
// ...
}
});

同理,在 iframe.html 中,postMessage 也能夾帶 origin 去決定哪個源可以接受要傳遞的值。

其他運用

說來比較可惜,由於目前支援的專案,其不同源的 iframe 中內容會基於使用者的一些操作去和後端伺服器交換資訊,如果使用 postMessage,就可以讓 iframe 把內容傳遞給 parent(也就是該專案本體),再由 parent 去和後端伺服器交換資訊後決定要做什麼事情,相較讓 iframe 中的頁面去做會方便許多,彈性一定也會更大。接觸到 postMessage 的時機點已經算是專案接近尾聲的時候了,但在規劃階段我們就已經商議出其他替代性的做法,因此就沒辦法在實務上用到這個 API 了。

關於 iframeparent 運用 postMessage 傳遞資訊的筆記就記錄到這邊,內文如果有任何錯誤,還請不吝指出,謝謝大家。

References:

--

--