kimjeongwonnabout

window.open과 팝업차단

왜 팝업차단이 되는걸까

최근 iOS 웹뷰에서 파일 다운로드를 동작시키기 위한 태스크를 진행하면서 막혔던 부분이 있었다. 웹뷰에서는 파일 다운로드가 열리지 않기 때문에 사파리 창을 띄워서 다운로드를 발생시켜야 했었는데 아무리 다운로드 버튼을 눌러도 새 창이 뜨지 않는 문제가 있었다. 고민하다가 사파리 브라우저에서 해당 페이지의 버튼을 눌러보니 팝업 차단으로 막히고 있었던 것… 웹뷰에서는 팝업이 차단 될 뿐 차단 안내가 되지 않아서 몰랐던 것이었다.

왜 차단될까?

브라우저에서 click 이벤트를 통해서 동작하는 open메서드는 신뢰할 수 있는 이벤트로 판단하고 팝업차단 없이 동작한다고 알고있었다. 그런데 찾아보니, 해당 이벤트 핸들러가 콜스택에서 사라진 이후에 비동기적으로 동작하게 되면 팝업 차단이 일어날 수 있다는 내용을 발견했다. 그래서 바로 setTimeout 메서드를 사용해서 콜스택이 비워진 다음 window.open메서드를 호출했지만 문제없이 동작했다. 테스트로 사용한 코드들은 아래와 같았고 모두 문제없이 동작했다. 그렇다면 비동기 호출이 문제가 아닌건데…

// 태스크큐에 전달
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를 넘기 때문인지 아니면 이후에 정책이 바뀐건지는 알 수 없다. 아무튼 결론적은 아래와 같다.

결론

  • 사파리에서는 click이벤트 발생한지 1000ms이후에 window.open메서드가 호출되면 팝업차단이 된다.
    • 크롬에서는 시간제한이 5000ms로 훨씬 넉넉하다.

나의 해결방법

대부분의 솔루션은 비동기 로직 이전에 window.open을 통해 새창을 열고 메서드가 반환한 객체를 통해 새 창의 window객체에 접근해서 location.href의 setter를 사용해서 주소를 변경하는 식으로 안내하고 있지만 아쉽게도 웹뷰에서 새 창을 띄웠을 때 정상적으로 연결이 되지 않는 듯 했다. 다운로드 전용 페이지를 만들고 해당 페이지에 다운받을 파일의 정보를 넘긴 뒤 해당 페이지에서 비동기 로직을 처리한 뒤 자동으로 다운받게 만드는 방식으로 해결할 수 있었다.