최신 웹브라우저 들여다보기 (2부)

코사카 마리코

탐색 기능

이 글은 Chrome의 내부 작동 방식을 살펴보는 4부로 구성된 블로그 시리즈 중 두 번째입니다. 이전 게시물에서는 다양한 프로세스와 스레드가 브라우저의 다양한 부분을 어떻게 처리하는지 살펴보았습니다. 이 게시물에서는 웹사이트를 표시하기 위해 각 프로세스와 스레드가 통신하는 방법을 자세히 살펴봅니다.

웹 브라우징의 간단한 사용 사례를 살펴보겠습니다. 브라우저에 URL을 입력하면 브라우저가 인터넷에서 데이터를 가져와 페이지를 표시합니다. 이 게시물에서는 사용자가 사이트를 요청하고 브라우저가 페이지 렌더링을 준비하는 부분(탐색이라고도 함)에 초점을 맞추겠습니다.

브라우저 프로세스로 시작해서

브라우저 프로세스
그림 1: 상단의 브라우저 UI, 하단에 UI, 네트워크, 스토리지 스레드가 있는 브라우저 프로세스의 다이어그램

1부: CPU, GPU, 메모리 및 멀티 프로세스 아키텍처에서 다루었던 것처럼 탭 밖의 모든 것은 브라우저 프로세스에 의해 처리됩니다. 브라우저 프로세스에는 브라우저의 버튼과 입력 필드를 그리는 UI 스레드, 인터넷에서 데이터를 수신하는 네트워크 스택을 처리하는 네트워크 스레드, 파일에 대한 액세스를 제어하는 저장소 스레드와 같은 스레드가 있습니다. 주소 표시줄에 URL을 입력하면 입력은 브라우저 프로세스의 UI 스레드에서 처리됩니다.

간단한 탐색

1단계: 입력 처리

사용자가 주소 표시줄에 입력하기 시작하면 UI 스레드가 가장 먼저 '검색어 또는 URL인가요?'라고 묻습니다. Chrome에서는 주소 표시줄이 검색 입력란이기도 하므로 UI 스레드가 사용자를 파싱하여 검색엔진으로 보낼지 아니면 요청한 사이트로 보낼지 결정해야 합니다.

사용자 입력 처리
그림 1: 입력이 검색어인지 URL인지 묻는 UI 스레드

2단계: 내비게이션 시작

사용자가 Enter 키를 누르면 UI 스레드가 네트워크 호출을 시작하여 사이트 콘텐츠를 가져옵니다. 로딩 스피너가 탭의 모서리에 표시되고 네트워크 스레드가 DNS 조회와 같은 적절한 프로토콜을 통과하고 요청에 TLS 연결을 설정합니다.

탐색 시작
그림 2: mysite.com으로 이동하기 위해 네트워크 스레드와 통신하는 UI 스레드

이 시점에서 네트워크 스레드는 HTTP 301과 같은 서버 리디렉션 헤더를 수신할 수 있습니다. 이 경우 네트워크 스레드는 서버가 리디렉션을 요청하는 UI 스레드와 통신합니다. 그런 다음 다른 URL 요청이 시작됩니다.

3단계: 응답 읽기

HTTP 응답
그림 3: 실제 데이터인 Content-Type 및 페이로드가 포함된 응답 헤더

응답 본문 (페이로드)이 들어오기 시작하면 네트워크 스레드는 필요에 따라 스트림의 처음 몇 바이트를 확인합니다. 응답의 Content-Type 헤더에 데이터 유형이 표시되어야 하지만, 누락되거나 잘못되었을 수 있으므로 여기에서 MIME 유형 스니핑을 실행합니다. 이는 소스 코드에 설명된 '까다로운 비즈니스'입니다. 이 주석을 읽고 서로 다른 브라우저가 콘텐츠 유형/페이로드 쌍을 처리하는 방법을 확인할 수 있습니다.

응답이 HTML 파일인 경우 다음 단계는 데이터를 렌더기 프로세스에 전달하는 것입니다. 하지만 ZIP 파일 또는 기타 파일인 경우 다운로드 요청이므로 데이터를 다운로드 관리자에 전달해야 합니다.

MIME 유형 스니핑
그림 4: 응답 데이터가 안전한 사이트의 HTML인지 묻는 네트워크 스레드

여기에서 SafeBrowsing 검사도 진행됩니다. 도메인과 응답 데이터가 알려진 악성 사이트와 일치하는 것으로 보이면 네트워크 스레드 알림이 경고 페이지를 표시합니다. 또한 C로스 C리진 CC락 (C) 검사는 민감한 크로스 사이트 데이터가 렌더러 프로세스에 도달하지 않도록 합니다.

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

모든 검사가 완료되고 브라우저가 요청한 사이트로 이동해야 한다고 네트워크 스레드에 확신하면 네트워크 스레드는 UI 스레드에 데이터가 준비되었음을 알립니다. 그러면 UI 스레드가 웹페이지 렌더링을 실행할 렌더러 프로세스를 찾습니다.

렌더기 프로세스 찾기
그림 5: 렌더러 프로세스를 찾도록 UI 스레드에 지시하는 네트워크 스레드

네트워크 요청이 응답을 반환하는 데 수백 밀리초가 걸릴 수 있으므로 이 프로세스의 속도를 높이기 위한 최적화가 적용됩니다. 2단계에서 UI 스레드가 네트워크 스레드로 URL 요청을 보낼 때 어떤 사이트로 이동하는지 이미 알고 있습니다. UI 스레드는 네트워크 요청과 동시에 렌더기 프로세스를 사전에 찾거나 시작하려고 합니다. 이렇게 하면 모든 것이 예상대로 진행되면 네트워크 스레드가 데이터를 수신했을 때 렌더기 프로세스가 이미 대기 상태에 있는 것입니다. 탐색이 크로스 사이트로 리디렉션되는 경우 이 대기 프로세스는 사용되지 않을 수 있으며, 이 경우 다른 프로세스가 필요할 수 있습니다.

5단계: 탐색 커밋

이제 데이터와 렌더러 프로세스가 준비되었으므로 IPC가 브라우저 프로세스에서 렌더러 프로세스로 전송되어 탐색을 커밋합니다. 또한 데이터 스트림을 전달하므로 렌더러 프로세스가 HTML 데이터를 계속 수신할 수 있습니다. 브라우저 프로세스에서 렌더러 프로세스에서 커밋이 발생했다는 확인을 수신하면 탐색이 완료되고 문서 로드 단계가 시작됩니다.

이제 주소 표시줄이 업데이트되고 보안 표시기 및 사이트 설정 UI에 새 페이지의 사이트 정보가 반영됩니다. 뒤로-앞으로 버튼을 누르면 방금 이동한 사이트를 단계별로 진행할 수 있도록 탭의 세션 기록이 업데이트됩니다. 탭 또는 창을 닫을 때 탭/세션 복원을 용이하게 하기 위해 세션 기록은 디스크에 저장됩니다.

탐색 커밋
그림 6: 브라우저와 렌더러 프로세스 간 IPC(페이지 렌더링 요청)

추가 단계: 초기 로드 완료

탐색이 커밋되면 렌더러 프로세스에서 리소스를 로드하면서 페이지를 렌더링합니다. 다음 게시물에서는 이 단계에서 어떤 일이 일어나는지에 대해 자세히 알아보겠습니다. 렌더기 프로세스는 렌더링을 '완료'하고 IPC를 브라우저 프로세스로 다시 보냅니다 (모든 onload 이벤트가 페이지의 모든 프레임에서 실행되고 실행이 완료된 후). 이 시점에서 UI 스레드는 탭에서 로딩 스피너를 중지합니다.

이 시점 이후에 클라이언트 측 JavaScript가 여전히 추가 리소스를 로드하고 새 뷰를 렌더링할 수 있기 때문에 '완료'라고 합니다.

페이지 로드 완료
그림 7: 페이지가 '로드됨'을 알리기 위해 렌더러에서 브라우저 프로세스로의 IPC

간단한 탐색이 완료되었습니다! 그런데 사용자가 주소 표시줄에 다른 URL을 다시 입력하면 어떻게 될까요? 브라우저는 다른 사이트로 이동할 때도 동일한 단계를 거칩니다. 하지만 그렇게 하기 전에 현재 렌더링된 사이트에서 beforeunload 이벤트에 관심이 있는지 확인해야 합니다.

다른 페이지로 이동하거나 탭을 닫으려고 하면 beforeunload에서 '이 사이트에서 나가시겠습니까?'라는 알림을 만들 수 있습니다. JavaScript 코드를 비롯하여 탭 내부의 모든 항목이 렌더러 프로세스에 의해 처리되므로, 새 탐색 요청이 들어올 때 브라우저 프로세스는 현재 렌더기 프로세스로 확인해야 합니다.

beforeunload 이벤트 핸들러
그림 8: 브라우저 프로세스에서 렌더러 프로세스로의 IPC가 다른 사이트로 이동할 예정임을 알립니다.

탐색이 렌더기 프로세스에서 시작된 경우 (예: 사용자가 링크를 클릭하거나 클라이언트 측 JavaScript가 window.location = "https://newsite.com"를 실행함) 렌더기 프로세스는 먼저 beforeunload 핸들러를 확인합니다. 그런 다음 브라우저에서 시작된 탐색과 동일한 프로세스를 거칩니다 유일한 차이점은 탐색 요청이 렌더러 프로세스에서 브라우저 프로세스로 시작된다는 것입니다.

새 탐색이 현재 렌더링된 사이트와 다른 사이트에 적용되면 새 탐색을 처리하기 위해 별도의 렌더링 프로세스가 호출되고 unload와 같은 이벤트를 처리하기 위해 현재 렌더링 프로세스가 유지됩니다. 자세한 내용은 페이지 수명 주기 상태 개요Page Lifecycle API로 이벤트에 연결하는 방법을 참고하세요.

새 탐색 및 로드 취소
그림 9: 페이지 렌더링을 지시하고 이전 렌더러 프로세스에 언로드하도록 지시하는 브라우저 프로세스에서 새 렌더기 프로세스로의 IPC 2개

서비스 워커의 경우

탐색 프로세스의 최근 변경사항 중 하나는 서비스 워커의 도입입니다. 서비스 워커는 애플리케이션 코드에 네트워크 프록시를 작성하는 방법입니다. 이를 통해 웹 개발자는 로컬에서 캐시할 항목과 네트워크에서 새 데이터를 가져올 시기를 더 효과적으로 제어할 수 있습니다. 서비스 워커가 캐시에서 페이지를 로드하도록 설정된 경우 네트워크에 데이터를 요청할 필요가 없습니다.

서비스 워커는 렌더기 프로세스에서 실행되는 JavaScript 코드라는 점을 기억해야 합니다. 하지만 탐색 요청이 들어올 때 브라우저 프로세스는 사이트에 서비스 워커가 있는지 어떻게 알 수 있나요?

서비스 워커 범위 조회
그림 10: 서비스 워커 범위를 찾는 브라우저 프로세스의 네트워크 스레드

서비스 워커가 등록되면 서비스 워커의 범위가 참조로 유지됩니다(범위에 대한 자세한 내용은 이 서비스 워커 수명 주기 문서 참조). 탐색이 발생하면 네트워크 스레드가 등록된 서비스 워커 범위에 대해 도메인을 확인합니다. 서비스 워커가 해당 URL에 등록되어 있으면 UI 스레드는 서비스 워커 코드를 실행하기 위한 렌더러 프로세스를 찾습니다. 서비스 워커는 캐시에서 데이터를 로드하여 네트워크에서 데이터를 요청할 필요가 없거나 네트워크에서 새 리소스를 요청할 수 있습니다.

서비스 워커 탐색
그림 11: 서비스 워커를 처리하기 위해 렌더기 프로세스를 시작하는 브라우저 프로세스의 UI 스레드. 이후 렌더기 프로세스의 작업자 스레드가 네트워크에서 데이터를 요청

서비스 워커가 결국 네트워크에서 데이터를 요청하기로 결정하면 브라우저 프로세스와 렌더기 프로세스 간의 왕복이 지연될 수 있음을 알 수 있습니다. 탐색 미리 로드는 서비스 워커 시작과 동시에 리소스를 로드하여 이 프로세스의 속도를 높이는 메커니즘입니다. 이러한 요청을 헤더로 표시하여 서버가 이러한 요청에 대해 다른 콘텐츠를 전송할지를 결정할 수 있습니다. 예를 들어 전체 문서가 아닌 업데이트된 데이터만 전송할 수 있습니다.

탐색 미리 로드
그림 12: 네트워크 요청을 동시에 시작하면서 서비스 워커를 처리하기 위해 렌더기 프로세스를 시작하는 브라우저 프로세스의 UI 스레드

요약정리

이 게시물에서는 탐색 중에 발생하는 일과 응답 헤더, 클라이언트 측 JavaScript와 같은 웹 애플리케이션 코드가 브라우저와 상호작용하는 방법을 살펴보았습니다. 브라우저가 네트워크에서 데이터를 가져오기 위해 거치는 단계를 알면 탐색 미리 로드와 같은 API가 개발된 이유를 더 쉽게 이해할 수 있습니다. 다음 게시물에서는 브라우저가 페이지를 렌더링하기 위해 HTML/CSS/JavaScript를 평가하는 방법에 대해 살펴볼 것입니다.

게시물이 도움이 되었나요? 향후 게시물에 대한 질문이나 제안이 있으면 아래의 댓글 섹션이나 트위터의 @kosamari를 통해 알려주세요.

다음: 렌더러 프로세스 내부 작업