이전 브라우저 탐색에 대한 내용은 아래 글 참고바랍니다.
2022.08.20 - [Frontend] - Chrome 웹 브라우저 내부 아키텍처 및 동작 살펴보기 (2부)
Renderer Process는 웹 콘텐츠를 처리한다
Renderer Process는 탭 내부에서 발생하는 모든 일을 담당합니다. Renderer Process에서 Main Thread는 사용자에게 보내는 대부분의 코드를 처리합니다.
web worker 또는 service work를 사용하는 경우, JavaScript의 일부가 work의 thread에 의해 처리됩니다. compositor 및 raster thread는 Renderer Process 내부에서 실행되어, 페이지를 효율적이고 원활하게 렌더링합니다.
Renderer Process의 핵심 작업은 HTML, CSS 및 JavaScript를 사용자가 상호 작용할 수 있는 웹 페이지로 변환하는 것입니다.
파싱(Parsing)
DOM 구성
메인 스레드는 텍스트 문자열(HTML)을 구문 분석하고, 분석 후 DOM(Document Object Model)으로 변환합니다. DOM은 웹 개발자가 JavaScript를 통해 상호 작용할 수 있는 데이터 구조 및 API뿐 아니라 페이지에 대한 브라우저의 내부 표현입니다.
HTML 문서를 DOM으로 구문 분석하는 것은 HTML 표준에 의해 정의됩니다.
HTML 표준: https://html.spec.whatwg.org/
하위 리소스 로드
웹 사이트는 일반적으로 이미지, CSS 및 JavaScript와 같은 외부 리소스를 사용합니다. 이러한 파일은 네트워크 또는 캐시에서 로드해서 가져옵니다.
메인 스레드는 DOM을 구축하기 위해 파싱을 하다 발견한 대로 하나씩 요청할 수 있지만, 속도를 높이기 위해 "프리로드 스캐너(preload scanner)"를 동시에 실행합니다.
HTML 문서에 <img> 또는 <link>가 있다면, "프리로드 스캐너"는 HTML 파서에 의해 생성된 토큰을 보고, Browser Process에서 Network Thead에 네트워크 요청을 보냅니다.
Javascript 구문 분석은 블록킹 될 수 있다
HTML 파서는 <script>태그를 찾으면 HTML 문서의 파싱을 일시 중지하고 JavaScript 코드를 로드, 파싱 및 실행합니다.
document.write()와 같이 JavaScript에서는 DOM 구조를 변경할 수 있기 때문에, 문서의 모양이 바뀔 수 있기 때문입니다. 따라서, HTML 파서는 JavaScript가 실행될 때까지 기다린 뒤에 HTML 문서 파싱을 다시 시작합니다.
리소스를 로드할 방법을 브라우저에 알려주기
만약 내 javascript 코드에서 document.write()와 같이 DOM 구조를 변경하는 코드를 사용하지 않는다면, <srcipt> 태그에 async, defer 속성을 사용하여, HTML 파싱을 블록킹 하지 않고 비동기적으로 로드, 파싱 및 실행할 수 있습니다.
- async 속성이 명시된 경우: 브라우저가 페이지를 파싱되는 동안에도 스크립트가 실행됨
- async 속성은 명시되어 있지 않고 defer 속성만 명시된 경우: 브라우저가 페이지의 파싱을 모두 끝내면 스크립트가 실행됨
- async 속성과 defer 속성이 모두 명시되어 있지 않은 경우 브라우저가 페이지를 파싱하기 전에 스크립트를 가져와 바로 실행시킴
스타일 계산
CSS에서 페이지 Element의 스타일을 지정할 수 있기 때문에 DOM이 있는 것만으로는 페이지가 어떻게 생겼는지 알 수 없습니다. 메인 스레드는 CSS를 구문 분석하고 각 DOM 노드에 대해 계산된 스타일을 결정합니다.
Layout
Renderer Process는 문서의 구조와 각 노드의 스타일을 알고 있지만 페이지를 렌더링하기에 충분하지 않습니다. 아래 예시와 같이 친구에게 빨간색 원과 파란색 사각형이 있다고 알려줘도 좌표 및 크기를 알 수 가 없습니다.
메인 스레드는 DOM 및 계산된 스타일을 살펴보고 xy 좌표 및 크기와 같은 정보가 있는 레이아웃 트리를 생성합니다.
여기서 display: none 의 경우는 layout tree의 일부에 포함이 안되지만, visibility: hidden은 layout tree에 포함이 됩니다. 비슷하게 p::before { content:"Hi!" }의 경우도 DOM에는 없지만 layout tree에는 포함이 됩니다.
페이지 레이아웃을 결정하는 것은 어려운 작업입니다. 위에서 아래로의 블록 흐름과 같은 가장 단순한 페이지 레이아웃조차도 단락의 크기와 모양에 영향을 미치기 때문에 글꼴의 크기와 줄 바꿈 위치를 고려해야 합니다. 그러면 다음 단락이 있어야 하는 위치에 영향을 줍니다.
페인트
DOM, 스타일 및 레이아웃을 갖는 것만으로는 여전히 페이지를 렌더링하기에 충분하지 않습니다.
그림을 재현하려고 한다고 가정해 보겠습니다. 요소의 크기, 모양 및 위치를 알고 있지만 여전히 요소를 칠하는 순서를 판단해야 합니다.
z-index를 예로 들 수 있습니다. 이 경우 HTML에 작성된 요소 순서대로 페인팅하면 잘못된 형태로 렌더링이 됩니다.
이 페인트 단계에서 메인 스레드는 layout tree를 생성하고, paint records를 생성합니다. 페인트 기록은 "배경을 먼저 그린 다음 텍스트를 그린 다음 직사각형"과 같은 페인팅 과정에 대한 메모를 남깁니다.
렌더링 파이프라인을 업데이트하는 데 비용이 많이 든다
렌더링 파이프라인에서 파악하는 가장 중요한 것은 각 단계에서 이전 작업의 결과를 사용하여 새 데이터를 생성한다는 것입니다.
예를 들어 레이아웃 트리에서 변경 사항이 있는 경우 문서의 영향을 받는 부분에 대해 페인트 순서를 다시 생성해야 합니다.
Element에 애니메이션을 적용하는 경우 브라우저는 모든 프레임 사이에서 이러한 작업을 실행해야 합니다. 대부분의 디스플레이는 초당 60회(60fps) 화면을 새로 고칩니다. 모든 프레임에서 화면을 가로질러 사물을 움직일 때 애니메이션은 사람의 눈에 부드럽게 나타납니다. 그러나 애니메이션이 그 사이의 프레임을 놓치면 페이지가 "버벅거림"으로 나타납니다.
렌더링 작업이 화면 새로 고침을 따라가고 있더라도 이러한 계산은 기본 스레드에서 실행되므로 애플리케이션이 JavaScript를 실행할 때 블록킹이 될 수 있습니다.
requestAnimationFrame()를 사용하여, JavaScript 작업을 작은 덩어리로 나누고, 모든 프레임에서 실행되도록 할 수 있습니다
합성
이제 브라우저는 문서의 구조, 각 요소의 스타일, 페이지의 기하학 및 페인트 순서를 알고 있으므로 페이지를 그릴 수 있습니다. 이 정보를 화면의 픽셀로 바꾸는 것을 래스터화라고 합니다.
간단한 방법은 뷰포트 내부의 부분을 래스터하는 것입니다. 사용자가 페이지를 스크롤하면 래스터된 프레임을 이동하고 추가 래스터로 누락된 부분을 채웁니다.
위 방법이 Chrome이 처음 출시되었을 때 래스터화를 처리한 방식입니다. 그러나 최신 Chrome브라우저는 합성이라고 하는 보다 정교한 프로세스를 실행합니다.
합성이란?
합성은 페이지의 일부를 레이어로 분리하고 개별적으로 래스터화한 다음, 합성기 스레드라는 별도의 스레드에서 페이지로 합성하는 기술입니다. 스크롤이 발생하면 레이어가 이미 래스터화되어 있으므로 새 프레임을 합성하기만 하면 됩니다. 애니메이션은 레이어를 이동하고 새 프레임을 합성하여 동일한 방식으로 얻을 수 있습니다.
레이어로 나누기
어떤 Elemenet가 어떤 레이어에 있어야 하는지 알아보기 위해, 메인 스레드는 layout tree를 통해 레이어 트리를 만듭니다(이 부분은 DevTools 성능 패널에서 "레이어 트리 업데이트"라고 함).
슬라이드인 사이드 메뉴와 같이 별도의 레이어여야 하는 페이지의 특정 부분에 레이어가 표시되지 않으면, will-changeCSS의 속성을 사용하여 브라우저에 알려줄 수 있습니다.
모든 요소에 레이어를 제공하고 싶을 수도 있지만, 너무 많은 레이어에 걸쳐 합성하면 매 프레임 페이지의 작은 부분을 래스터화하는 것보다 작업 속도가 느려질 수 있으므로 애플리케이션의 렌더링 성능을 측정하는 것이 중요합니다. 참고: https://web.dev/stick-to-compositor-only-properties-and-manage-layer-count/
메인 스레드의 raster 및 composite
레이어 트리가 생성되고 페인트 순서가 결정되면 메인 스레드는 해당 정보를 compositor thread에 커밋합니다. 그런 다음 compositor thread는 각 레이어를 raster합니다. 레이어는 페이지의 전체 길이만큼 클 수 있으므로, compositor thread는 레이어를 타일로 나누고 각 타일을 raster thread로 보냅니다. raster thread는 각 타일을 raster하고 GPU 메모리에 저장합니다.
compositor thread는 다른 raster thread의 우선 순위를 지정하여 viewport 내의 항목을 먼저 raster할 수 있습니다. 또한 layer에는 확대 동작과 같은 작업을 처리하기 위해 다양한 해상도에 대한 여러 타일링이 있습니다.
타일이 raster되면 compositor thread는 draw quads 라는 타일 정보를 수집하여 compositor frame을 생성합니다.
- Draw quads: 메모리 내 타일의 위치, 페이지 합성을 고려하여 타일을 그릴 페이지 내 위치 등의 정보를 담고 있다
- Compositor frame: 페이지의 프레임을 나타내는 "Draw quads" 모음
그런 다음 compositor frame이 IPC를 통해 browser process에 제출됩니다. 이 시점에서 브라우저 UI 변경을 위해 UI thread에서 또는 확장을 위해 다른 renderer process에서 다른 compositor frame을 추가할 수 있습니다. 이러한 compositor frame은 GPU로 전송되어 화면에 표시됩니다. 스크롤 이벤트가 발생하면 compositor thread는 다른 compositor frame을 생성하고, GPU로 보냅니다.
합성의 이점은 메인 스레드를 포함하지 않고 수행된다는 것입니다. 합성 스레드는 스타일 계산이나 JavaScript 실행을 기다릴 필요가 없습니다. 이러한 이유로 부드러운 성능을 위해 애니메이션만 합성 하는 것이 가장 좋은 것으로 간주되는 이유입니다. 레이아웃이나 페인트를 다시 계산해야 하는 경우 메인 스레드가 관련되어야 합니다. 참고: https://web.dev/animations-guide/
Reference
https://developer.chrome.com/blog/inside-browser-part3/
'Frontend' 카테고리의 다른 글
Chrome 웹 브라우저 내부 아키텍처 및 동작 살펴보기 (2부) (0) | 2022.08.20 |
---|---|
Chrome 웹 브라우저 내부 아키텍처 및 동작 살펴보기 (1부) (0) | 2022.08.18 |
yarn berry 환경에서 WebStorm, Actions on save 사용하기 (prettier, eslint) (0) | 2022.05.26 |
이미지 포맷 종류(jpg, jpeg, png, gif, svg) (0) | 2021.12.16 |
세션(Session)과 쿠키(Cookie🍪) (0) | 2021.03.25 |