webview와 iframe
- webview
- 웹뷰
- iframe
- WKWebView
- 인앱 브라우저
한 줄 결론
webview는 네이티브 앱이 웹 문서를 띄우려고 앱 안에 심는 창이고, iframe은 이미 떠 있는 웹 문서 안에 다른 문서를 끼워 넣는 HTML 요소다. 둘은 담는 층이 달라서 대체재가 아니며, 한 화면에 동시에 존재하는 것이 정상이다.
질문 바로잡기
webview로 띄울지 iframe으로 띄울지 고르려는 질문은 성립하지 않는다. 둘 다 "남이 만든 웹 페이지를 내 화면 한 칸에 박아 넣는다"는 결과가 같아서 고르는 문제처럼 보이지만, 그 화면이 무엇 안에 있느냐에 따라 쓸 수 있는 쪽이 이미 정해져 있다. 네이티브 앱 화면에는 애초에 iframe을 놓을 자리가 없고, 웹 페이지 안에서는 WebView 클래스를 인스턴스화할 방법이 없다.
결과가 같아 보여도 둘이 놓이는 자리는 서로 다르다. 그래서 먼저 물어야 할 것은 각각이 무엇 안에 들어 있고 무엇을 담는가다.
무엇으로 갈리는가: 담는 층
webview와 iframe은 무엇이 무엇을 담는지로 갈린다. 둘은 서로 다른 칸에 앉고, 그 사이에는 웹 문서 하나가 통째로 끼어 있다. 그래서 둘을 같은 줄에 놓고 비교하면 항상 어긋난다.
webview는 네이티브 앱의 뷰 계층에 속하는 부품이다. Android 문서는 WebView를 "웹 페이지를 액티비티 레이아웃의 일부로 표시하게 해주는 View 클래스의 확장"이라고 정의하고, Apple은 WKWebView를 "인앱 브라우저처럼 상호작용형 웹 콘텐츠를 표시하는 객체"라고 정의한다. 두 정의문 모두 주어가 네이티브 클래스다. webview가 이 자리에 앉는 이유는 그것을 만드는 주체가 HTML 파서가 아니라 Kotlin이나 Swift 코드이기 때문이다.
iframe은 웹 문서 안의 요소다. MDN은 iframe을 "중첩된 브라우징 컨텍스트를 나타내며, 다른 HTML 페이지를 현재 페이지 안에 임베드하는 HTML 요소"로 정의한다. iframe이 존재하려면 그것을 담을 문서가 이미 파싱되고 있어야 한다. 문서 없이는 iframe이 생길 수 없다는 이 의존 관계가 iframe을 webview보다 한 칸 아래에 고정한다.
webview와 같은 칸에 앉는 것은 브라우저의 탭이다. 탭도 webview도 문서를 처음 열어주는 최상위 자리이고, 그 안에서 문서가 iframe을 몇 개 만들든 상관하지 않는다. webview와 나란히 놓고 비교할 대상을 찾는다면 브라우저 탭이나 인앱 브라우저를 놓아야 한다.
판별은 만드는 주체를 보면 된다. HTML 파서가 태그를 읽어서 만들어 준 것이면 iframe이고, 앱 코드가 생성자를 호출해 뷰 트리에 붙인 것이면 webview다. 이 기준은 문서에 없는 새 사례에도 그대로 적용된다.
왜 이렇게 나뉘는가
브라우저 엔진은 문서를 렌더링하는 기계이고, 그 기계를 어디에 두느냐가 이 구분의 출발점이다. iframe은 이미 돌고 있는 엔진 안에서 자식 브라우징 컨텍스트를 하나 더 만드는 일이다. webview는 그 엔진 자체를 앱이 부품으로 들고 와서 화면 한 칸에 박는 일이다. 앞의 것은 엔진 안에서 문서를 나누는 작업이고, 뒤의 것은 엔진을 앱 안으로 가져오는 작업이라 애초에 층이 다르다.
앱이 탭을 쓰지 않고 굳이 엔진만 떼어 오는 이유는 브라우저 UI가 필요 없기 때문이다. Android 문서는 WebView가 "주소창이나 내비게이션 컨트롤 같은 완전한 웹 브라우저의 기능을 포함하지 않으며, 기본적으로 웹 페이지를 보여주는 일만 한다"고 못 박는다. 앱은 이용약관이나 사용자 가이드처럼 서버에서 바꿔 끼울 수 있는 화면을 자기 UI 안에 자연스럽게 넣고 싶을 뿐, 앱 한가운데에 브라우저를 띄우고 싶은 것이 아니다. 껍데기를 버리고 렌더링 능력만 가져오려니 클래스 형태가 된 것이다.
층이 다르다는 사실은 신뢰 모델에서 가장 크게 드러난다. iframe은 같은 웹 세계 안에 있으므로 동일 출처 정책이 기본으로 걸리고, 그 위에 sandbox로 스크립트나 폼 제출을 끄고 allow로 카메라와 마이크 권한을 열어주는 식으로만 조절할 수 있다. 웹 표준이 허용한 것 이상을 자식 문서에 줄 방법은 없다. 반면 webview는 앱이 호스트라서 addJavascriptInterface() 같은 통로로 네이티브 코드를 웹 쪽에 그대로 노출할 수 있다. Android 문서가 "WebView 안의 HTML을 신뢰할 수 없다면 공격자가 원하는 코드를 실행시킬 수 있으므로, HTML과 JavaScript를 전부 직접 작성한 경우가 아니면 쓰지 말라"고 경고하는 것은 이 통로가 웹 밖으로 나가는 문이기 때문이다.
컨테이너와 문서를 따로 세면 이 층위가 손에 잡힌다. webview는 그 자체로 문서가 아니라 최상위 문서 하나가 들어올 빈 자리이고, loadUrl()을 부르기 전까지 안에는 아무 문서도 없다. iframe 요소도 자식 문서 하나가 들어올 자리라는 점에서 똑같다. 그래서 한 webview 안의 문서 개수는 최상위 문서 하나에 그 문서가 만든 iframe 개수를 더한 값이고, webview나 탭 같은 자리는 이 셈에 들어가지 않는다.
이름과 자리가 어긋나는 경우
같은 webview라는 이름도 누가 쓰느냐에 따라 앉는 자리가 다르다. 이름만 보고 자리를 정하면 틀리고, 만드는 주체를 봐야 자리가 정해진다.
Android와 iOS에서 webview는 네이티브 뷰 클래스를 가리킨다. WebView는 View를, WKWebView는 UIView를 상속하므로 레이아웃 코드에서 버튼이나 이미지 뷰와 같은 방식으로 다뤄진다. 이 관점에서 webview는 명백히 웹 문서 바깥의 물건이다.
Electron에서 webview는 DOM 태그다. <webview src="...">를 읽어 요소를 만드는 것은 렌더러의 HTML 파서이므로, 만드는 주체 기준으로는 iframe 계열이다. Electron 문서도 이 태그가 "격리된 프레임과 프로세스에서 외부 웹 콘텐츠를 표시"하며, 내부적으로 Out-of-Process iframe(OOPIF)을 써서 크로스 도메인 iframe처럼 포커스와 이벤트가 동작한다고 밝힌다.
다만 별도 프로세스에서 돌고 앱과의 상호작용이 전부 비동기라서 일반 iframe보다 격리가 한 단계 강하다. 웹 문서 안에서 iframe과 webview를 나란히 놓고 고르는 장면은 Electron에서만 생기고, "둘은 형제"라는 인상도 대부분 이 사례에서 나온다. Electron은 현재 이 태그를 권장하지 않으며 iframe이나 WebContentsView를 대안으로 제시하므로, 새 코드에서 이 예외를 만들 일은 줄어들고 있다.
React Native의 WebView는 JSX 문법으로 쓰지만 컴파일되면 네이티브 뷰가 된다. 생김새가 태그라서 Electron과 같아 보이지만, 그것을 해석하는 것이 HTML 파서가 아니라 React Native의 브릿지라는 점이 다르다. 여기서도 만드는 주체를 보면 webview 쪽 칸으로 정해진다.
경계 사례
하이브리드 앱에서 웹뷰로 띄운 결제 페이지 안에 PG사 iframe이 들어 있는 경우는 흔하다. 이때 "웹뷰냐 iframe이냐"를 고르려 들면 답이 안 나오는데, 층이 다른 물건이 각자 제 자리에 있는 것이라 애초에 고를 일이 아니기 때문이다. 정확히 말하면 웹뷰가 결제 문서를 담고, 그 문서가 다시 iframe을 담고 있다.
Chrome Custom Tabs나 SFSafariViewController로 앱에서 링크를 여는 경우는 판정이 가장 헷갈린다. 화면은 앱 안에서 뜨지만 이것들은 webview가 아니라 브라우저다. 앱 프로세스가 그 안의 문서에 접근할 수 없고 쿠키와 세션을 브라우저와 공유하며, addJavascriptInterface() 같은 통로 자체가 없다. Android 문서가 남의 사이트로 나가는 링크는 WebView 대신 이쪽으로 넘기라고 안내하는 이유가 바로 이 격리다.
실제로 쓰는 문장
화면 구성을 설명할 때
"이 화면은 웹뷰로 띄웠고, 그 안의 결제창은 iframe입니다"라고 말하면 층위가 그대로 드러난다. 하나를 골랐다는 뜻이 아니라 둘이 겹쳐 있다는 사실을 전달하므로, 문제를 어디서 봐야 하는지도 같이 정해진다.
문제 위치를 가를 때
"웹뷰 문제인지 iframe 문제인지 갈라야 한다"보다 "웹뷰 바깥 문제인지 문서 안 문제인지 갈라야 한다"가 정확하다. 앞 문장은 둘을 형제로 놓지만, 뒤 문장은 네이티브 브릿지, 쿠키, User-Agent처럼 웹뷰가 책임지는 영역과 동일 출처 정책과 sandbox처럼 문서 안에서 벌어지는 일을 구분해 준다.
링크를 여는 방식을 정할 때
"웹뷰 대신 iframe을 쓰자"는 성립하지 않고, "이 링크는 웹뷰로 열지 말고 인앱 브라우저로 넘기자"가 성립하는 문장이다. webview의 대안은 브라우저를 여는 방식이고, 그렇게 말해야 신뢰 경계를 바꾸자는 제안이라는 의도가 전달된다.
외부 페이지를 임베드할 때
"iframe으로 임베드하고 sandbox와 allow로 권한을 좁히겠다"처럼 iframe의 조절 수단까지 붙여 말하는 것이 좋다. iframe은 권한을 넓힐 수단이 없고 좁힐 수단만 있다는 성질이 이 문장에 그대로 드러난다.
근거
- MDN,
<iframe>: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe - Android Developers, Build web apps in WebView: https://developer.android.com/develop/ui/views/layout/webapps/webview
- Apple Developer, WKWebView: https://developer.apple.com/documentation/webkit/wkwebview
- Electron, webview Tag: https://www.electronjs.org/docs/latest/api/webview-tag