안녕하세요. 이번 시간에는 iframe 안에 들어있는 페이지와 통신하는 방법에 대해 알아보겠습니다. 요즘 iframe 관련 작업을 하고 있어서 겸사겸사 포스팅도 해봅니다.
보통 아이프레임은 다른 웹페이지를 현재 페이지에 넣기 위해 사용합니다. 써드 파티 웹앱이나 애드센스 같은 것들이 보통 아이프레임이고, 제 홈페이지 우측에 있는 페이스북도 아이프레임입니다.
단순히 아이프레임을 다른 웹페이지를 보여주는 용도로만 사용한다면 별로 특별할 건 없지만, 아이프레임은 부모 페이지(아이프레임을 포함하고 있는 페이지)와 소통할 수 있습니다. 예를 들면 아이프레임에서 부모 페이지의 로컬 스토리지에 접근할 수도 있고, 반대로 부모 페이지에서 아이프레임의 자원을 사용할 수도 있습니다.
먼저 페이지 두 개를 만들어봅시다.
parent.html
<html>
<head>
</head>
<body>
<div>부모</div>
<iframe id="iframe" src="./child.html"></iframe>
<script>
localStorage.setItem('dummy', 'zerocho');
</script>
</body>
</html>
child.html
<html>
<head>
</head>
<body>
<div>자식</div>
<script>
window.parent.localStorage.getItem('dummy');
</script>
</body>
</html>
parent.html이 child.html을 아이프레임을 통해 불러왔습니다. 먼저 parent.html에서 child.html로 접근하는 방법은
document.getElementById('iframe').contentWindow;
입니다. child.html의 window 객체로 접근합니다. 반대의 경우는
window.parent;
입니다. child.html에서 부모 페이지의 로컬스토리지에 접근해보겠습니다. 다음 부분이 그 부분입니다.
window.parent.localStorage.getItem('dummy');
하지만 에러가 발생합니다. 허용되지 않은 origin이라고 뜹니다. 직접 접근하는 것을 막는 최소한의 보안입니다.
Uncaught DOMException: Blocked a frame with origin "null" from accessing a cross-origin frame.
현재 파일 시스템에서 접근하는 것이라 origin이 null로 나오고 에러가 발생하지만, 실제 서버에서도 호스트 주소가 다른 경우에는 위의 에러가 발생합니다.
그래도 부모 창에 접근이 필요한 경우가 있습니다. 이 때 사용하는 것이 postMessage입니다. IE8부터 지원하기 때문에(IE는 부분 지원) 모든 브라우저에서 사용할 수 있습니다.
먼저 parent.html에 메세지를 받을 이벤트 리스너를 등록합니다. 자식으로부터 메시지가 오면 로컬스토리지를 읽어서 다시 자식에게 보내줍니다.
<script>
window.addEventListener('message', function(e) {
console.log(e.data); // { hello: 'parent' }
var item = localStorage.getItem('dummy');
console.log(item); // zerocho
document.getElementById('iframe').contentWindow.postMessage(item, '*');
});
</script>
이제 child.html에서 부모로 메세지를 보냅니다. localStorage 부분은 지우고 새로 스크립트를 넣어줍니다. 부모로 메시지를 보내는 코드와 부모로부터 메시지를 받는 코드입니다.
<script>
window.onload = function() {
window.parent.postMessage({ hello: 'parent' }, '*');
};
window.addEventListener('message', function(e) {
console.log(e.data); // zerocho
});
</script>
첫 번째 인자는 데이터, 두 번째 인자는 origin입니다. 현재 모든 origin을 허용하기 위해 *(애스터리스크)를 넣었는데요. 실 사용 시에는 부모 윈도우의 주소를 넣으시면 됩니다.
부모의 콘솔에 e.data와 item 정보가 뜹니다. 이것을 document.getElementById('iframe').contentWindow.postMessage(item, '*');
여기서 child.html로 다시 보냅니다.
이처럼 아이프레임은 부모 페이지와 소통할 수 있습니다. e 객체에 data 외에도 여러 가지 정보들이 담겨 있습니다. 받을 때는 e.origin
으로 올바른 origin에서 메세지가 왔는지 확인하고, 보낼 때도 * 대신 제대로 된 주소를 넣어 보냅시다. *를 사용하면 어디로 메시지가 갈 지 모릅니다. 정보를 누가 가로챌 수도 있다는 뜻입니다.
window.addEventListener('message', function(e) {
if (e.origin === '메시지를 보낸 곳의 주소') {
// e.data를 사용한 동작 수행
}
});
어떤 윈도우가 나에게 메시지를 보낼지 모르기 때문에 항상 메시지의 origin을 확인하세요. 이렇게 부모와 소통할 수 있게 되면 편하지만, 출처를 알 수 없는 아이프레임을 넣게 된다면 사이트 보안에 심각한 위협(정보를 빼가거나 코드를 심음)이 될 수 있기 때문에 항상 잘 모르는 스크립트 파일이나 아이프레임은 넣기 전 주의를 기울일 필요가 있습니다.
별개로 postMessage 메소드는 팝업과도 소통할 수 있기 때문에 알아두시면 좋습니다.
부모페이지에서 자식페이지에게 데이터를 넘겨주기위해 브라우저의 로컬스토리지-쿠키에 데이터를 저장하는 경우에도 발생하는데,
이 경우도 cross-origin 에러에 해당하나요?