window.open과 팝업차단
왜 팝업차단이 되는걸까
* 2023. 2. 12 이 포스팅은 잘못된 정보를 다루고 있습니다. 올바른 내용을 별도로 첨부해두겠습니다.
최근 iOS 웹뷰에서 파일 다운로드를 동작시키기 위한 태스크를 진행하면서 막혔던 부분이 있었다. 웹뷰에서는 파일 다운로드가 열리지 않기 때문에 사파리 창을 띄워서 다운로드를 발생시켜야 했었는데 아무리 다운로드 버튼을 눌러도 새 창이 뜨지 않는 문제가 있었다. 고민하다가 사파리 브라우저에서 해당 페이지의 버튼을 눌러보니 팝업 차단으로 막히고 있었던 것… 웹뷰에서는 팝업이 차단 될 뿐 차단 안내가 되지 않아서 몰랐던 것이었다.
왜 차단될까?
브라우저에서 click 이벤트를 통해서 동작하는 open메서드는 신뢰할 수 있는 이벤트로 판단하고 팝업차단 없이 동작한다고 알고있었다. 그런데 찾아보니, 해당 이벤트 핸들러가 콜스택에서 사라진 이후에 비동기적으로 동작하게 되면 팝업 차단이 일어날 수 있다는 내용을 발견했다.그래서 바로 setTimeout
메서드를 사용해서 콜스택이 비워진 다음 window.open
메서드를 호출했지만 문제없이 동작했다. 테스트로 사용한 코드들은 아래와 같았고 모두 문제없이 동작했다. 그렇다면 비동기 호출이 문제가 아닌건데…
콜스택과 관계없이 AJAX응답 이후 비동기로 실행될 때 차단되는 걸로 확인했다. 글을 작성할 당시에는 브라우저에서 AJAX통신을 인식하고 차단할 거라고 예상을 못했었나보다.
// 태스크큐에 전달
const openNaverSite = () => {
setTimeout(() => window.open('https://www.naver.com', '_blank'), 0);
};
const btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
window.setTimeout(() => openNaverSite(), 0);
});
// 마이크로 태스크큐에 전달
const openNaverSite = () => {
return new Promise(res => {
window.open('https://www.naver.com', '_blank');
res(true);
});
};
const btn = document.querySelector('#btn');
btn.addEventListener('click', () => {
openNaverSite().then(() => {
window.open('https://www.naver.com', '_blank');
// 네이버 창이 두 개 연속으로 뜬다
});
});
원인
이런저런 케이스를 테스트하다가 발견한 것은 click이벤트 이후 1000ms
이후에 발생하는 window.open
메서드에 대해서 팝업 차단이 된다는 사실이었다. 때문에 비동기 로직이 1000ms
이내에 완료된다면 window.open
메서드가 정상적으로 동작하는 것이었다. 특이한 것은 크롬에서는 제한시간이 5000ms
정도로 더 넉넉했다. 구글링해서 찾은 국내 자료들은 대부분이 비동기로 호출한 함수 내에서 window.open
메서드가 호출되면 차단된다고 설명하고 있는데 실제로는 그렇지 않았다. 아마 비동기 로직의 처리 속도가 대부분 1000ms를 넘기 때문인지 아니면 이후에 정책이 바뀐건지는 알 수 없다. 아무튼 결론적은 아래와 같다.
반은 맞고 반은 틀린데, 사파리는 비동기 로직과는 상관없이 AJAX응답 이후 로직이 실행되는가 여부에 따라 팝업 차단을 하게 된다. (XHR, fetch 모두 동일) 클릭 이벤트 이후 일정 시간이 지나고 window.open이 팝업 차단으로 막히는 것은 맞다.
결론
- 사파리에서는 click이벤트 발생한지 1000ms이후에
window.open
메서드가 호출되면 팝업차단이 된다.- 크롬에서는 시간제한이 5000ms로 훨씬 넉넉하다.
실제 결론은 window.open 이벤트는 클릭액션 이후 사파리는
1000ms
, 크롬에선 이후5000ms
가 지나면 팝업차단이 이뤄지고, 사파리의 경우 클릭액션을 통한 호출이라고 하더라도 AJAX요청을 거치면 팝업차단이 된다. 비동기와는 전혀 관련이 없다.
나의 해결방법
대부분의 솔루션은 비동기 로직 이전에 window.open
을 통해 새창을 열고 메서드가 반환한 객체를 통해 새 창의 window
객체에 접근해서 location.href
의 setter를 사용해서 주소를 변경하는 식으로 안내하고 있지만 아쉽게도 웹뷰에서 새 창을 띄웠을 때 정상적으로 연결이 되지 않는 듯 했다. 다운로드 전용 페이지를 만들고 해당 페이지에 다운받을 파일의 정보를 넘긴 뒤 해당 페이지에서 비동기 로직을 처리한 뒤 자동으로 다운받게 만드는 방식으로 해결할 수 있었다.
제대로 파악을 하지 않고 글을 작성했었던 것이 아쉬운 글이다. SEO에도 적지만 노출되고 있었는데 혹시나 피해가 가지 않았을까도 걱정된다. 블로그에 댓글 기능을 켜두지 않아서 잘못된 정보를 정정받을 수 있는 기회도 놓쳤다고 생각하니 더욱 아쉽다. 댓글기능을 추가해야겠다.