본문 바로가기

NAVER D2 정리/Web

최신 브라우저의 내부 살펴보기2 - 내비게이션 과정에서 일어나는 일

원문 작성일: 2019.03.26

원문: https://d2.naver.com/helloworld/9274593

 

내비게이션 과정에서 일어나는 일

이 글에서는 웹 사이트를 표시하기 위해 각 프로세스와 스레드가 어떻게 통신하는지 좀 더 깊게 알아본다.

 

브라우저의 주소 표시줄에 URL을 입력하면 브라우저가 인터넷에서 데이터를 가져와서 페이지를 표시한다.

간단한 이 동작에서 사용자가 사이트를 요청하고 브라우저가 페이지 렌더링을 준비하는 과정(이 글에서는 이 과정을 '내비게이션'이라고 하겠다)에 초점을 맞춰 살펴보겠다.

 

(역주: '내비게이션'을 (사이트 간의)이동'이라 생각하면 이 글을 이해하기 좀 더 쉬울 것이다)

 

브라우저 프로세스에서 시작한다

브라우저 프로세스에는 UI 스레드와 네트워크 스레드, 스토리지 스레드 등이 있다.

UI 스레드는 브라우저의 버튼과 입력란을 그린다.

네트워크 스레드는 인터넷에서 데이터를 가져오기 위해 네트워크 스택을 다룬다.

스토리지 스레드는 파일에 대한 접근을 제어한다.

주소 표시줄에 URL을 입력하면 브라우저 프로세스의 UI 스레드가 입력을 처리한다.

브라우저 UI와 브라우저 프로세스. 브라우저 프로세스에는 UI 스레드와 네트워크 스레드, 스토리지 스레드가 있다.

 

간단한 내비게이션

[1단계: 입력 처리]

주소 표시줄에 타이핑을 시작하면 UI 스레드는 먼저 입력되는 내용이 검색어(search query)인지 URL인지 확인한다.

Chrome에서 주소 표시줄은 검색창이기도 하다. UI 스레드는 입력되는 내용을 파싱해서 검색 엔진으로 이동할지 요청한 사이트로 이동할지 결정해야 한다.

입력되는 내용이 검색어인지 URL인지 확인하는 UI 스레드

(역주: 사용자가 입력한 문자열이 '검색어'라면 문자열을 사용자가 선택한 검색 엔진의 URL과 조합해 새로운 URL 형태로 변환한다. 이런 변환을 위해 Chrome은 검색 엔진의 URL 주소를 관리한다)

 

(역주: 입력된 문자열이 검색어인지 URL인지 판별하는 것은 어려운 일이기 때문에 브라우저는 이를 도와줄 서버와 통신하거나 DNS Lookup을 실행하기도 한다. 네이버에서 개발한 브라우저인 Whale의 '사이트 곧장 가기' 기능도 이 단계에서 실행되는 기능이다)

 

 

[2단계: 내비게이션 시작]

사용자가 Enter 키를 누르면 사이트의 콘텐츠를 가져오기 위해 UI 스레드가 네트워크 호출을 시작한다.

로딩 스피너가 탭의 모서리에 표시되고, 네트워크 스레드는 요청에 대한 DNS Lookup 및 TLS 연결 설정과 같은 적절한 프로토콜을 거쳐 요청을 처리한다.

mysite.com으로 이동하기 위해 네트워크 스레드와 통신하는 UI 스레드

이때 네트워크 스레드가 HTTP 301과 같은 서버 리디렉션 헤더를 수신할 수도 있다.

그런 경우에는 네트워크 스레드가 UI 스레드와 통신해 서버가 리디렉션을 요청했다는 것을 알린다.

그런 다음 새로운 URL 요청이 시작된다.

 

 

[3단계: 응답 읽기]

응답 본문인 페이로드가 들어오기 시작하면 네트워크 스레드는 필요에 따라 스트림의 처음 몇 바이트를 확인한다.

페이로드가 어떤 형식의 데이터인지는 응답 헤더의 Content-Type 헤더가 알려주지만 정보가 없거나 잘못된 정보가 있을 수 있다.

그래서 이때 MIME 스니핑을 실행해 데이터의 실제 형식을 알아낸다. Chromium 소스 코드의 주석에 적힌 것처럼 데이터의 실제 형식을 알아내는 것은 '까다로운 작업(tricky business)이다. 이 주석을 보면 브라우저가 얼마나 다양한 방법으로 Content-Type 헤더와 페이지 로드를 처리하는지 알 수 있을 것이다.

Content-Type을 포함하는 응답 헤더와 실제 데이터인 페이로드

응답이 HTML 파일이라면 데이터를 렌더러 프로세스에 전달하는 단계로 넘어간다.

하지만 응답이 ZIP 형식 파일이나 다른 형식의 파일이라면 다운로드 요청이므로 다운로드 매니저에 데이터를 전달하는 단계로 넘어가야 한다.

 

(역주: 좀 더 정확하게 구분하자면 '렌더러 프로세스가 다룰 수 있는 데이터 형식일 때'와 '렌더러 프로세스가 다룰 수 없는 데이터 형식일 때'로 구분하는 것이 좋다. 예를 들어 PDF 파일을 반환하는 주소로 접근했을 때 다운로드로 넘어가지 않고 브라우저에 문서를 바로 표시하는 경우를 생각할 수 있다)

 

응답 데이터가 안전한 사이트의 HTML 문서인지 확인하는 네트워크 스레드

이 단계는 또한 Safe Browsing의 검사가 실행되는 단계이다.

도메인과 응답 데이터가 악성 사이트로 알려진 사이트와 일치하는 것 같다면 네트워크 스레드는 경고 페이지를 표시하라고 알린다. 이에 더해서 CORB(Cross-Origin Read Blocking) 기능이 서로 다른 사이트(Cross-site)의 민감한 데이터가 렌더러 프로세스에서 실행되지 않게 검사한다.

 

(역주: 네이버의 Whale은 Safe Browsing 검사 단계에서 네이버가 구축한 악성 페이지 데이터도 함께 확인한다)

 

 

[4단계: 렌더러 프로세스 찾기]

모든 검사가 끝나고 요청된 사이트로 이동해야 한다고 네트워크 스레드가 확신하게 되면 네트워크 스레드는 UI 스레드에 데이터가 준비되었음을 알린다. 그러면 UI 스레드는 웹 페이지의 렌더링을 수행할 렌더러 프로세스를 찾는다.

스레드에게 렌더러 프로세스를 찾으라고 알리는 네트워크 스레드

네트워크 요청이 응답을 받기까지 수백 밀리초가 걸릴 수 있기 때문에 이 과정을 더 빨리 진행하기 위한 최적화가 적용되어 있다. 2단계에서 UI 스레드가 네트워크 스레드로 URL 요청을 보낼 때 UI 스레드는 이미 어느 사이트로 이동할지 알고 있다. UI 스레드는 렌더러 프로세스를 먼저 찾거나 네트워크 요청과 동시에 렌더러 프로세스를 시작한다.

이런 방식에서는 모든 것이 예상대로 잘 진행된다면 네트워크 스레드가 데이터를 받을 때 이미 렌더러 프로세스는 준비 상태에 있게 된다. 만약 다른 사이트로 리디렉션이 이루어져 다른 프로세스가 필요하게 되면 미리 준비한 프로세스가 사용되지 않을 수도 있다.

 

 

[5단계: 내비게이션 실행]

  1. 데이터와 렌더러 프로세스가 준비되었으므로 내비게이션을 실행하도록 브라우저 프로세스에서 렌더러 프로세스로 IPC 메시지를 전송한다.
  2. 렌더러 프로세스가 HTML 데이터를 계속 수신할 수 있도록 브라우저 프로세스는 데이터 스트림을 전달한다.
  3. 렌더러 프로세스에서 내비게이션이 실행되었다는 것을 브라우저 프로세스가 확인하고나면 내비게이션이 완료되고 문서 로딩 단계가 시작된다.

이 시점에 주소 표시줄이 업데이트되고 보안 표시와 사이트 설정 UI도 새 페이지의 사이트 정보를 반영해 갱신된다.

탭에 대한 세션 기록이 업데이트되어 뒤로 가기 버튼과 앞으로 가기 버튼도 방금 이동한 사이트를 반영해 작동한다.

탭이나 창을 닫은 이후 탭과 세션을 복원할 수 있게 세션 기록이 디스크 드라이브에 저장된다.

브라우저 프로세스와 렌더러 프로세스 간의 IPC 통신. 페이지 렌더링을 요청한다.

 

[추가 단계: 초기 로드 완료]

내비게이션이 실행되면 렌더러 프로세스는 계속 리소스를 로딩하고 페이지를 렌더링한다.

이 단계에서 일어나는 일은 다음 글에서 자세하게 다루겠다.

렌더러 프로세스가 렌더링을 '끝내면' 브라우저 프로세스로 IPC 메시지를 보낸다(이 시점은 페이지의 모든 프레임에서 onload 이벤트의 실행까지 끝낸 이후이다).

그러면 UI 스레드는 탭에서 로딩 스피너의 작동을 중지한다.

 

'끝낸다(finish)'라고 표현한 이유는 클라이언트 사이드의 JavaScript가 여전히 추가적인 리소스를 로드하거나 이후에 새로운 뷰를 렌더링할 수도 있기 때문이다.

렌더러 프로세스가 브라우저 프로세스에 페이지가 '로딩되었다'는 것을 알리기 위한 IPC 통신

 

다른 사이트로 내비게이션

간단한 내비게이션이 완료되었다.

그런데 사용자가 주소 표시줄에 다른 URL을 다시 입력하면 어떻게 될까?

브라우저 프로세스는 동일한 단계를 거쳐 다른 사이트로 이동을 처리한다.

하지만 그 전에 현재 렌더링된 사이트에서 beforeunload 이벤트를 확인해야한다.

 

beforeunload 이벤트는 탭을 닫거나 이동하려고 할 때 "이 사이트를 떠나시겠습니까?"라는 경고창을 만들 수 있다.

JavaScript 코드를 포함해 탭 안의 모든 것은 렌더러 프로세스에 의해 처리되므로 브라우저 프로세스는 새로운 내비게이션 요청이 들어오면 현재 렌더러 프로세스를 확인해야 한다.

 

(주의: beforeunload 이벤트 핸들러를 아무때나 추가하면 안 된다. 내비게이션을 시작하기 전에 이벤트 핸들러를 실행해야하기 때문에 대기 시간(latency)이 늘어난다. 이 이벤트 핸들러는 필요한 경우에만 추가해야 한다. 예를 들어 페이지에 입력한 데이터가 손실될 수 있음을 경고해야 하는 경우에 beforeunload 이벤트 핸들러를 추가한다)

 

(역주: beforeunload 이벤트 핸들러는 경우에 따라 실행되지 않을 수도 있다. chrome://flags/#enable-fast-unload 설정이 활성화되었다면 beforeunload 이벤트 핸들러가 실행되지 않을 가능성이 더 높다)

브라우저 프로세스가 렌더러 프로세스에 다른 사이트로 이동한다는 것을 알려주는 IPC 통신

사용자가 링크를 클릭하거나 클라이언트 사이드 JavaScript에서 window.location="https://newsite.com" 코드를 실행하는 것과 같이 렌더러 프로세스에서 내비게이션이 시작되면 렌더러 프로세스는 먼저 beforeunload 이벤트 핸들러를 확인한다. 이후에는 브라우저 프로세스가 내비게이션을 시작했을 때와 동일한 과정을 거친다. 유일한 차이점은 내비게이션 요청이 렌더러 프로세스에서 시작되어 브라우저 프로세스로 넘어간다는 점이다.

 

현재 렌더링된 사이트와 다른 사이트로 이동하는 새로운 내비게이션이 발생하면 별도의 렌더러 프로세스가 새로운 내비게이션을 처리한다. 현재 렌더링된 사이트를 처리한 렌더러 프로세스는 unload와 같은 이벤트를 처리하기 위해 유지된다. 더 자세한 내용은 "Page Lifecycle API"(웹 페이지 라이프사이클 API)에서 'Overview of Page Lifecycle states events'(웹 페이지 라이프사이클의 상태와 이벤트 개요)를 참고한다. 이벤트를 어떻게 후킹하는지도 알 수 있다.

 

브라우저 프로세스가 새로운 렌더러에는 프로세스에게 페이지를 렌더링하라고 알리는 IPC 통신과 브라우저 프로세스가 이전 렌더러 프로세스에 웹 페이지를 떠나라고 알리는 IPC 통신

 

서비스 워커

최근에 서비스 워커가 도입되며 내비게이션 과정에도 변화가 생겼다.

서비스 워커는 애플리케이션의 코드에 네트워크 프록시를 작성할 수 있는 수단이다.

서비스 워커를 통해 무엇을 로컬 캐시에 저장할지, 언제 네트워크에서 새 데이터를 가져올 지 제어할 수 있다.

서비스 워커가 캐시에서 페이지를 로드하도록 설정되었다면 네트워크에서 데이터를 가져오도록 요청할 필요가 없다.

 

기억해야 할 중요한 점은 서비스 워커가 렌더러 프로세스에서 실행되는 JavaScript 코드라는 점이다.

그렇다면 내비게이션 요청이 들어왔을 때 브라우저 프로세스는 서비스 워커가 있다는 것을 어떻게 알 수 있을까?

서비스 워커의 범위를 찾는 브라우저 프로세스의 네트워크 스레드

서비스 워커가 등록되면 서비스 워커의 범위는 참조(reference)로 유지된다.

내비게이션이 발생하면 네트워크 스레드는 도메인을 등록된 서비스 워커의 범위와 비교한다.

해당 URL에 등록된 서비스 워커가 있으면 UI 스레드는 서비스 워커 코드를 실행하기 위해 렌더러 프로세스를 찾는다.

서비스 워커는 네트워크에 데이터를 요청하지 않고 캐시에서 데이터를 가져올 수 있다.

또는 네트워크에 새 리소스를 요청할 수도 있다.

 

서비스 워커를 처리할 렌더러 프로세스를 시작하는 브라우저 프로세스의 UI 스레드. 렌더러 프로세스의 워커 스레드가 네트워크에서 데이터를 요청한다.

 

내비게이션 프리로드

브라우저 프로세스와 렌더러 프로세스 사이를 왕복해야 하는 상황에서 서비스 워커가 네트워크에서 데이터를 요청하기로 하면 지연이 발생하게 됨을 알 수 있다. 내비게이션 프리로드는 서비스 워커의 시작과 병렬로 리소스를 로딩해 내비게이션 과정의 속도를 높이는 메커니즘이다. 이 요청은 헤더에서 표시되어 서버가 이러한 요청에 대해 다른 콘텐츠를 보낼 수 있게 한다. 예를 들어 전체 문서를 보내지 않고 업데이트된 데이터만 보낼 수 있다.

서비스 워커를 처리할 렌더러 프로세스를 시작하면서 동시에 병렬로 네트워크 요청을 시작하는 브라우저 프로세스의 UI 스레드