프로그래밍/CS

[프론트엔드] 브라우저 렌더링 과정 정리 | 렌더링 엔진, CRP

choar 2022. 9. 20. 17:47
반응형

[프론트엔드] 브라우저 렌더링 과정 정리 | 렌더링 엔진, CRP

 

브라우저 렌더링 과정에서 일어나는 일들을 프론트엔드 위주로 자세히 정리해보았다.

 

브라우저

먼저 브라우저의 아키텍처를 살펴보자.

(⚠️ 브라우저마다 다른 아키텍처로 이루어져 있으므로 각 브라우저의 동작과 완벽하게 일치하지 않을 수 있다.)

 

브라우저의 하이 레벨 아키텍처

 

브라우저의 주요 구성 요소는 다음과 같다.

  1. User Interface : 주소 표시줄, 뒤로/앞으로 가기 버튼, 홈 버튼, 북마크 버튼아 등이 포함된다. 요청한 페이지가 표시되는 창을 제외한 브라우저의 모든 부분을 표시한다.
  2. Browser engine : UI와 렌더링 엔진 사이의
  3. Rendering engine : 요청된 콘텐츠 표시를 담당한다. 예를 들어, 요청된 콘텐츠가 HTML인 경우 렌더링 엔진은 HTML, CSS를 파싱하고 파싱한 콘텐츠를 화면에 표시한다.
  4. Networking : HTTP/HTTPS 네트워크 요청을 처리한다.
    * 네트워크에 특정 웹페이지에 요청을 하고 응답을 받아오는 과정도 추후 포스팅 예정이다.
  5. JavaScript Interpreter : 자바스크립트 코드를 파싱 및 실행하는데 사용된다.
  6. UI Backend : 기본 위젯을 그리는 데 사용된다. UI Backend는 특정 플랫폼이 아닌 OS의 방법을 사용한다.
  7. Data Persistence : 쿠키, localStorage, IndexedDB, WebSQL, FileSystem 같이 로컬에 저장되어 좀 더 오래 유지되어야만 하는(persistence) 스토리지 메커니즘을 지원하는 영역이다.

모던 브라우저는 대부분 각각의 탭을 독립적인 프로세스로 처리하여 별도의 렌더링 인스턴스를 유지(process per tab)합니다. 프로세스를 탭마다 분할함으로써 한 탭의 렌더링 프로세스가 응답하지 못할 경우 다른 탭에 영향을 주지 않으므로 더 좋은 사용성을 제공할 수 있습니다. 또한 보안과 샌드박싱 면에서도 이점을 지닙니다. 운영체제가 프로세스의 권한을 제한하는 방법을 제공하기 때문에 브라우저는 특정 기능에서 특정 프로세스를 샌드박싱할 수 있습니다.

🌟 파싱(parsing)이란?
구문 분석이라고도 불리며, 일련의 문자열을 의미 있는 토큰(token)으로 분해하고 토큰 간의 위계 관계를 분석해 구조를 결정하는 것이다. 자세한 과정은 '렌더링 과정' 1번에서 서술하였다.
🌟 샌드박싱(sandboxing)이란? (ref)
샌드박스란 외부로부터 들어온 프로그램이 보호된 영역에서 동작해 시스템이 부정하게 조작되는 것을 막는 보안 형태이다. 호스트 머신이나 운영 체제에 손상을 입히지 않고 확인되지 않거나 신뢰할 수 없는 서드파티, 공급자, 사용자, 웹사이트로부터 잠재적으로 테스트되지 않거나 신뢰하지 못하는 프로그램이나 코드를 실행하기 위해 종종 사용된다. 고도로 제어되는 환경을 제공한다는 측면에서 샌드박스는 가상화의 특정 예시로 간주할 수 있다. 샌드박스는 소프트웨어가 호스트 장치에 위험을 주지 않게 하면서 바이러스나 기타 악성 코드를 포함할 수 있는 미검증된 프로그램을 테스트하기 위해 종종 사용된다.

 

렌더링 과정

  1. 사용자가 URL을 입력하고 엔터를 누른다.
  2. 브라우저 프로세스에서 제일 먼저 작업이 시작된다.
    (브라우저 프로세스에는 UI 스레드, 네트워크 스레드, 스토리지 스레드 등이 존재한다.)
  3. UI 스레드는 해당 텍스트가 검색어인지 URL인지 확인한다.
    1. 검색어인 경우 : 검색 엔진 URL에 검색어를 결합해 페이지를 이동시킨다.
    2. URL인 경우 : 네트워크 호출을 수행한다.
  4. 이때부터 브라우저에 로딩이 되고 있다는 표시가 나타나게 된다.
    동시에 네트워크 스레드는 적절한 프로토콜로 요청을 처리한다.
  5. 응답이 도착하기 시작하면 응답이 어떤 타입인지 판단한다.
    1. 응답이 렌더러 프로세스가 다룰 수 있는 HTML, PDF 등인 경우 : 데이터를 렌더러 프로세스로 전달한다.
      (⚠️ PDF : 일부 브라우저에 따라 지원하지 않을 수 있음)
    2. 응답이 다운로드 파일 형식일 경우 : 데이터를 다운로드 매니저에게 전달한다.
      이 단계에서 해당 사이트가 악성 페이지인지 검사하며, 악성 페이지일 경우 더 이상 처리하지 않고 작업을 중단한다.
  6. 모든 검사가 끝나면, 네트워크 스레드는 UI 스레드에게 작업이 준비되었다고 알려준다.
  7. UI 스레드는 웹 페이지를 렌더링할 렌더러 프로세스를 찾는다.
  8. 데이터와 렌더러 프로세스가 준비되면 본격적으로 페이지의 이동이 시작된다.
    이 시점에 렌더러 프로세스는 HTML 데이터를 수신해 문서를 로딩하며, 브라우저의 주소 표시줄, 사이트 제목과 같은 관련 UI들도 갱신된다. 이후에 탭을 이동할 수 있도록 세션 기록이 저장된다.
  9. 렌더링이 완료되면 렌더러 프로세스는 브라우저 프로세스로 로딩이 완료되었음을 알리고 UI 스레드의 로딩 표시를 중지한다.
🌟 세션(session)이란? (ref)
일반적으로 세션은 컴퓨터 시스템의 관리자(또는 OS, 서버)가 자신의 자산을 이용하는 것을 허락한 사용자(컴퓨팅)를 인식한 일정한 기간을 가리키는 것으로 광범위하게 이해될 수 있다.

 

 

렌더링 엔진의 기본 흐름

 

여기서 렌더러 프로세스의 작업을 더 구체적으로 살펴보자.

1. 파싱, 렌더 트리 생성

파싱(Parsing)은 구문 분석이라고도 불리며, 일련의 문자열을 의미 있는 토큰(token)으로 분해하고 토큰 간의 위계 관계를 분석해 구조를 결정하는 것이다.

파싱 과정
· 변환 : 문서를 가져와서 지정된 인코딩 방식으로 읽는다.
· 토큰화 : 표준에 맞춰 지정된 태그들을 토큰화한다.
· 렉싱(lexical analysis) : 토큰을 해당 속성 및 규칙을 정의하는 노드로 만든다.
· 트리 생성 : root 노드를 기준으로 생성된 노드들의 계층을 연결한다.

(좌) HTML 파싱 과정 (우) 토큰화 과정

 

1-1. HTML 파싱

렌더러 프로세스의 메인 스레드가 HTML을 파싱해 DOM(Document Object Model)으로 변환한다.

파싱의 최종 결과로 DOM 트리가 만들어지며, 최상위 노드(root)는 <html>이다.

DOM은 일종의 인터페이스로 해당 요소를 나타내는 노드, 노드의 속성을 나타내는 프로퍼티, 이를 조작할 수 있는 여러 메서드를 담아 구조화한 객체로 표현한다. DOM을 통해 자바스크립트 같은 여러 프로그래밍 언어로 해당 요소에 접근해 해당 구조나 내용, 스타일을 변경할 수 있다. DOM이 없다면 자바스크립트는 웹 페이지에 대한 정보는 얻을 수 없다.

<html>
  <body>
    <p>
      Hello World
    </p>
    <div> <img src="example.png"/></div>
  </body>
</html>
// DOM API 사용 예시
// p 태그의 내용이 변경된다.
let paragraph = document.querySelector('p');
paragraph.textContent = "Hi :)";

⚠️ <img>, <link> 같은 태그를 만나면 미리 브라우저 프로세스의 네트워크 스레드로 요청을 보낸다.

⚠️ <script> 태그를 만나면 브라우저는 HTML 파싱을 중단한다. 자바스크립트에는 문서 전체의 구조를 바꿀 수 있는 메서드, 프로퍼티가 존재하기 때문이다. 자바스크립트 파싱이 끝난 후 HTML 파싱이 재개된다. 만약 스크립트 파일에서 문서 조작이 이루어지지 않는다면 <script> 태그에 async나 defer 속성을 지정해, HTML 파싱이 중단되지 않도록 설정할 수 있다.

💡 async, defer 속성? (ref)
· async : HTML 문서가 완전히 다운로드되지 않은 상태라도 병렬적으로 스크립트가 로드 및 평가된다.
· defer : 스크립트가 모두 로드 및 평가된 이후에 DOMContentLoaded 이벤트가 발생하도록 한다.

DOM 트리

 

1-2. CSS 파싱

HTML 파싱 과정에서 <link> 태그를 만나면, 해당 CSS 리소스를 가져온다.

HTML과 비슷하게 CSS 역시 토큰화, 노드 생성을 거쳐 CSSOM(CSS Object Model)이라는 트리 구조를 가진다.

CSSOM은 자바스크립트에서 CSS를 조작할 수 있게 해주는 API 집합이다.

// CSSOM API 사용 예시
document.body.style.background = 'lightblue';

CSSOM 트리

 

1-3. 렌더 트리(Render Tree) 만들기

렌더 트리는 DOM 트리와 CSSOM 트리가 결합되어 생성된다.

각 노드는 렌더 객체로 이루어져 있으며 렌더 객체는 보이는 노드만을 포함한다.

렌더 트리 생성 과정
1. <html> 태그와 <body> 태그를 처리하며 렌더 트리 루트를 구성한다.
2. DOM 트리를 순회하며 최상위 노드(<html>)부터 보여지지 않는 노드(<link>, <script>, <meta> 등)를 생략한다.
3. display: none처럼 CSS로 숨겨지는 노드 또한 트리에서 생략한다.
4. float나 position 같은 속성을 사용했을 경우 흐름에서 벗어나 실제 그려지는 위치로 렌더 객체가 이동한다.
5. 화면에 나타나는 노드에 CSSOM 규칙을 찾아 일치하는 스타일을 적용한다.

렌더 트리

 

2. 레이아웃(Layout)

레이아웃은 각 요소의 상대적인 위치, 크기를 찾는 과정이다.

레이아웃은 렌더 트리의 최상위 노드인 <html>부터 시작하며 이후 자식 렌더 객체들의 레이아웃을 배치하며 반복적으로 발생한다.

레이아웃은 흐름 기반의 배치 모델을 사용한다. 요소가 나타나는 순서대로 "왼쪽"에서 "오른쪽"으로, "위"에서 "아래"의 방향으로 진행됩니다. 일반적으로 "이후"의 요소는 "이전"의 요소에 영향을 주지 않지만 display: table / flex / grid를 사용할 경우 여러 흐름이 생성되어 영향을 줄 수 있다.

레이아웃이 발생할 경우 부모, 자식 요소들의 크기를 재계산해야 하고 하위 요소들에 모두 영향을 주기 때문에 부하가 매우 크다. 따라서 빠르게 웹 페이지를 렌더링하기 위해서는 불필요한 레이아웃을 발생시키지 않는 것이 중요하다.

 

💡 초기 레이아웃 이후 DOM 노드가 추가되거나 변경되었을 경우 이 변경사항은 어떤 영향을 미칠까?

변경된 부분을 계산하기 위해 전체 노드를 대상으로 레이아웃이 발생한다면 너무 많은 낭비일 것이다. 레이아웃은 일부의 변경으로 인해 전체를 다시 배치하지 않기 위해 더티 비트(Dirty Bit) 방식을 사용한다. 렌더 객체는 다시 배치할 필요가 있는 변경 요소, 추가된 노드, 자식 노드를 "더티(Dirty)"라고 표시한다.

  • 글로벌 레이아웃 : 전체의 레이아웃이 필요한 경우
    • font-family, font-size처럼 전역 스타일이 변경되거나 창이 리사이즈되었을 때 발생
  • 로컬 레이아웃 : 레이아웃 일부만 변경이 이뤄져야 하는 경우
레이아웃 과정
1. 부모 노드가 자식 노드의 너비를 결정한다. 너비는 블록의 너비, 렌더 객체의 스타일 속성, 박스 모델을 고려해 계산된다.
2. 자식 렌더링 객체를 배치한다.
  - 자식 렌더링 객체들의 x, y 값을 지정한다.
  - 부모 혹은 자식 객체가 "더티"한 경우 재계산이 필요하다. 자식 렌더링 객체의 layout() 메서드를 호출해 높이를 계산한다.
3. 자식 렌더링 객체의 높이를 더해 부모 렌더 객체의 높이를 계산한다.
4. 레이아웃 중인 렌더 객체의 "더티" 플래그를 제거한다.

 

3. 페인트(Paint)

렌더 트리를 만들며 DOM 요소의 위치, 스타일 등을 계산했다. 하지만 같은 위치에 그려지는 여러 노드가 있다면 어떤 순서로 그려야 할까? 만약 z-index, float, position 같은 CSS 프로퍼티를 이용해 레이어의 순서를 변경한다면, 렌더 트리의 순서가 레이어의 순서와 일치하지 않을 것이다.

페인트 단계에서는 렌더 트리를 순회하며 레이어를 만들고, 레이어의 배경, 테두리, 텍스트, 그려지는 순서, 레이어 간의 순서 등 그려지는 과정을 기록한다.

 

4. 합성(Compositing)

그려질 요소들의 순서, 스타일, 위치 등의 정보를 화면의 픽셀로 변환하는 것래스터화(rasterizing)라고 한다.

합성은 각 레이어를 분리해서 래스터화한 뒤 브라우저에서 페이지의 크기, 뷰포트에 맞게 합성해 화면으로 나타내는 과정이다.

 

지금까지 살펴본 HTML, CSS, JavaScript를 화면의 픽셀로 나타내기 위한 과정을 주요 렌더링 경로(Critical Rendering Path, CRP)라고 부른다. 

 

🌟 내용에 오류가 있다면 댓글 달아주시면 감사하겠습니다.


References

『기초부터 완성까지, 프런트엔드』 7, 8장

https://web.dev/howbrowserswork/

https://developer.chrome.com/blog/inside-browser-part1/

반응형