웹 브라우저, 인터넷 브라우저 또는 웹 탐색기는 웹 서버에서 이동하며(navigate) 쌍방향으로 통신하고 HTML 문서나 파일을 출력하는 그래픽 사용자 인터페이스 기반의 응용 소프트웨어이다. 웹 브라우저는 대표적인 HTTP 사용자 에이전트의 하나이기도 하다.
-위키백과
웹 페이지를 가져와 표시하기 위해 http라는 통신 규약을 이용해 웹 서버와 통신하는 어플리케이션이다.
이번 글에서는 크롬 브라우저 위주로 설명한다.
브라우저의 주요 기능은 방금 말했듯이 사용자가 선택한 자원을 서버에 요청하고 그 결과를 브라우저에 표시하는 것이다. 자원은 보통 html 문서지만 pdf나 이미지 등이 되기도 한다. 자원의 주소는 uri에 의해 정해진다.
브라우저는 html과 css 명세에 따라 html 파일을 해석해서 표시하는데 이 명세는 웹 표준화 기구인 w3c에서 정한다.
표준 명세에서 다음과 같은 기능을 제시한다.
위 예시 말고도 다양한 기능을 제공한다.
브라우저의 기본 구조는 아래와 같다.
브라우저 주소창에 무언가 입력했을 때 일어나는 일을 보며 위의 구성요소들이 각각 어떤 역할을 하는지 살펴보자.
위에서 브라우저 엔진이 브라우저 밖의 보이지 않는 부분들을 담당한다고 했다. 브라우저 엔진은 버튼이나 입력창을 그리는 UI 스레드, 인터넷에서 데이터를 수신하기 위해 통신 스택을 건드리는 네트워크 스레드, 파일 접근을 위한 스토리지 스레드 등을 가지고 있다.
따라서 주소창에서 url을 입력하는 순간 브라우저 엔진의 UI 스레드가 동작한다.
사용자가 주소창에 입력을 하게 되면 UI 스레드는 가장 먼저 '검색어인지 UI인지'부터 판단한다. 크롬에서는 주소창이 검색창도 겸하기 때문에 유저가 입력한 문구를 검색 엔진에 보낼 지, 요청한 페이지로 연결할 지 결정해야 한다.
사용자가 주소를 입력하고 엔터를 치면 UI 스레드가 사이트의 컨텐츠를 받기 위해 네트워크 요청을 초기화한다.
이 시점에 네트워크 스레드는 http 301같은 서버의 리다이렉션 헤더를 수신할 수도 있다. 그럴 경우 네트워크 스레드는 UI 스레드에게 서버가 리다이렉션을 요청했음을 알린다. 그러면 UI 스레드는 새로운 url 요청을 초기화한다.
- 네트워크 초기화 :
데이터 오브젝트나 변수의 초기 값 할당을 의미한다. 여기선 '네트워크 요청을 시작한다'와 같은 맥락이다.
응답이 들어오기 시작하면 네트워크 스레드가 헤더의 content-type을 확인한다.
응답이 html 파일이면 다음으로 랜더링 엔진에 데이터를 전달한다. 그 외 zip 또는 다른 형식의 파일일 경우 다운로드 요청이라는 뜻으로 다운로드 매니저에 데이터를 넘긴다.
이 시점에 안전 브라우징 체크도 수행한다. 만약 도메인과 응답 데이터가 이미 알려진 악성 사이트와 일치한다면 네트워크 스레드는 경고 페이지를 보여준다. 또한, Cross Origin Read Blocking(CORB) 체크를 해서 민감한 cross-site 데이터가 렌더링 엔진에 도달하는 것을 막는다.(위험한 주소가 숨겨져 있을 수 있기 때문에)
- CORB :
웹에서 cross origin의 xml, html, json 등의 데이터를 읽어오지 못하게 브라우저에서 막는 동작을 말한다.
cors 정책을 위반했을 때 막는 행위 자체를 말하는 듯 하다. 이에 대해서는 다음 포스팅에서 자세하게 다루도록 하자.
위의 확인 작업이 끝나고 네트워크 스레드가 검색이 아닌 요청된 사이트로 이동해야 함을 확신하면, UI 스레드에게 데이터가 준비되었음을 알린다. 그리고 UI 스레드는 웹 페이지 랜더링을 할 랜더링 프로세스를 찾는다.
이제 다음과 같은 과정을 수행한다.
문서 로딩 단계에서
내비게이션이 실행되면 렌더링 프로세스는 계속 리소스를 로딩하고 페이지를 렌더링한다. 이 렌더링 프로세스의 동작에 대해서는 조금 후에 보도록 하자. 렌더링 프로세스가 렌더링을 끝내면 브라우저 프로세스로 IPC 메시지를 보낸다.(클라이언트 사이드의 js가 여전히 추가적인 리소스를 로드하거나 이후에 화면을 렌더링할 수 있다.)
랜더링 프로세스는 탭 내부에서 발생하는 모든 작업을 담당한다. 랜더링 프로세스의 메인 스레드가 개발자가 구현한 대부분의 코드를 처리한다. 웹 페이지를 효율적이고 부드럽게 렌더링하기 위해 별도의 컴포지터 스레드와 래스터 스레드가 실행된다.
즉, 랜더링 프로세스의 주 역할은 서버로부터 받은 html, css, js를 사용자와 상호작용할 수 있는 웹페이로 변환하는 것이다.
랜더링 프로세스가 탐색을 위해 커밋 메세지를 받고 html 데이터를 받기 시작할 때(네비게이션 수행) 메인 스레드는 문자열(html)을 파싱해서 DOM(document object model)으로 변환하기 시작한다.
DOM은 브라우저 내부적으로 웹 페이지를 표현하는 방법일 뿐만 아니라 웹 개발자가 js를 통해 상호작용할 수 있는 데이터 구조이자 API이다.
웹사이트는 일반적으로 이미지, css, js와 같은 외부 리소스를 사용한다. 이러한 파일은 네트워크나 캐시에서 로딩해야 한다. DOM을 구축하기 위해 파싱하는 동안 이런 리소스를 만날 때마다 하나하나 요청할 수도 있지만 속도를 높이기 위해 '프리로드 스캐너'가 동시에 실행된다. html 문서에 img, link 같은 태그가 있으면 프리로드 스캐너는 html parser가 생성한 토큰을 확인하고 브라우저 프로세스의 네트워크 스레드에 요청을 보낸다.
html 파서는 script 태그를 만나면 문서의 파싱을 일시 중단한 다음 js 코드를 로딩하고 파싱해 실행한다. js는 DOM을 조작할 수 있기 때문이다. 따라서 html parser는 js의 실행이 끝날때까지 기다려야 한다.
html parsing이 끝나도 웹 페이지 구성은 끝나지 않는다. css가 아직 반영되지 않았다.
메인 스레드는 css를 파싱하고 각 DOM 노드에 해당되는 계산된 스타일을 확정한다. 계산된 스타일은 css 선택자로 구분된 요소에 적용될 스타일에 관한 정보이다.
css를 개발자가 작성하지 않았더라도 dom 노드에는 스타일이 적용되어 있다. 각 브라우저마다 제공하는 기본 스타일이다.
이제 렌더링 프로세스가 문서의 구조와 각 노드의 스타일을 알게 되었지만 여전히 페이지를 렌더링하기에는 부족하다. 각 노드에 대한 스타일은 알지만 그것들이 어떤 식으로 배치되는지는 모르기 때문이다.
레이아웃은 요소의 기하학적인 속성을 찾는 과정이다. 메인 스레드는 DOM과 계산된 스타일을 훑으며 레이아웃 트리를 만든다. 레이아웃 트리는 x, y좌표, 박스 영역의 크기와 같은 정보를 가지고 있다. 레이아웃 트리는 DOM 트리와 비슷한 구조이지만 웹 페이지에 보이는 요소에 관한 정보만 담고 있다.(display : none이 적용된 요소는 포함되지 않는다. 그러나 visiablity: hidden이 적용된 것은 포함된다.)
여전히 완성된 페이지를 만들 수 없다. 무엇부터 그려야 하는지 순서가 정해지지 않았다. 예를 들어 z-index같은 속성을 고려하지 않고 html에 작성된 순서로 적용된다면 문제가 될 것이다.
페인트 단계에서 메인 스레드는 페인트 기록을 생성하기 위해 레이아웃 트리를 순회한다. 페인트 기록은 1. 배경 2. 텍스트 3. 직사각형과 같이 페인팅 과정을 기록한 것이다.
이제 브라우저는 문서의 구조와 각 요소의 스타일, 요소의 기하학적 속성, 페인트 순서까지 알고 있다. 다음은 무엇이 필요할까? 이제 앞의 정보들을 가지고 스크린의 픽셀로 바꾸는 것을 래스터화(rasterizing)이라고 한다.
가장 단순한 방식의 래스터화는 아마 뷰포트 안쪽을 래스터화하는 방식일 것이다. 사용자가 웹 페이지를 스크롤하면 이미 래스터화한 프레임을 움직이고 나머지 빈 부분을 추가로 래스터화한다. 이 방식은 크롬이 처음 출시되었을 때 사용한 방식이지만 최근에는 합성(compositing)이라는 보다 정교한 방법을 사용한다.
합성은 웹 페이지의 각 부분을 레이어로 분리해 별도로 래스터화하고 컴포지터 스레드라고 하는 별도의 스레드에서 웹 페이지로 합성하는 기술이다. 스크롤되었을 때 레이어는 이미 래스터화되어 있으므로 새 프레임을 합성하기만 하면 된다. 애니메이션 역시 레이어를 움직이고 합성하는 방식으로 만들 수 있다. 해당 레이어는 개발자 도구의 Layers panel에서 볼 수 있다.
앞의 레이어 트리가 생성되고, 페인트 순서까지 결정되면 메인 스레드는 다음과 같은 과정을 실행한다.
컴포지터 스레드는 서로 다른 래스터 스레들에 대해 우선순위를 정할 수 있어 뷰포트 안이나 근처의 것들이 먼저 래스터화될 수 있다. 또한 레이어는 줌인같은 동작을 처리하기 위해 여러 해상도별로 타일 세트를 가지고 있다.
이후 타일이 레스터화되면 컴포지터 스레드는 '합성 프레임'을 생성하기 위해 타일의 정보를 모은다. 이 타일의 정보를 '드로 쿼드(draw quads)'라고 부른다.
- 드로 쿼드 :
메모리에서 타일의 위치와 웹 페이지 합성을 고려해 타일을 웹 페이지의 어디에 그려야 하는지에 관한 정보를 가지고 있다.- 합성 프레임 :
웹 페이지의 프레임을 나타내는 드로 쿼드의 모음
이후
합성의 장점은 메인 스레드와 별개로 작동할 수 있다는 점이다. 컴포지터 스레드는 js의 실행이나 스타일 계산을 기다리지 않아도 된다. 그러나 레이아웃이나 페인트를 다시 계산해야 할 경우 메인스레드가 관여해야 한다.
여기까지가 사용자가 주소창에 url을 입력했을 때 결과가 화면에 보이기까지 브라우저의 동작 과정이다.