Home

오늘의 기억

[Web] Browser의 렌더링 과정


우리는 컴퓨터로 그리고 스마트폰에 설치된 브라우저를 이용해 네이버나 구글 같은 웹 페이지에 접속한다. 그럼 브라우저는 네트워크 통신으로 HTML과 CSS, JS 파일 등 해당 웹 페이지에 필요한 리소스들을 불러온 뒤 각종 처리 작업을 거쳐 실제 사용자가 보는 화면에 띄워준다.

이러한 작업을 렌더링 이라고 한다.

그렇다면 렌더링은 브라우저 내부에서 어떻게 동작하는걸까? 그 과정을 좀 더 자세하게 알아보자.


렌더링 엔진

렌더링 엔진의 역할은 요청 받은 내용을 브라우저 화면에 표시하는 일을 담당한다. 보통 HTML 이나 이미지 들을 표시할 수 있다.

렌더링 엔진은 브라우저마다 존재한다. 대표적으로 파이어폭스는 모질라에서 자체 제작 한 게코(Gecko) 엔진을 사용하며, 사파리와 크롬은 웹킷(Webkit) 엔진을 사용한다.

이러한 렌더링 엔진은 전부다 똑같지 않고, 엔진마다 미세한 차이가 존재한다. 때문에 우리가 같은 웹 사이트를 접속할때도 브라우저 마다 미세한 차이가 존재하는 이유가 이러한 렌더링 엔진이 브라우저마다 달라서 생기는 현상이다.


렌더링 엔진 동작 과정

1. 객체 모델 생성

1-1. 변환 (Conversion)

브라우저가 HTML 파일을 디스크나 네트워크에서 읽어온 뒤 해당 파일에 지정된 인코딩(예:UTF-8)으로 변환한다.

1-2. 토큰화 (Tokenizing)

다음으로 HTML 파일 문자열을 W3C HTML5 표준에 지정된 고유 토큰으로 변환한다. 예를들면 <html>, <body> 같이 흔히 말하는 태그에 사용되는 문자열을 토큰화 하는 것이다. 이런 각각의 토큰들은 특별한 의미와 고유한 규칙을 가진다.

1-3. 렉싱 (Lexing)

반환된 토큰은 각각의 속성 및 규칙을 정의하는 ‘객체’로 변환된다.

1-4. DOM 생성 (DOM construction)

마지막으로 생성된 객체를 트리 데이터 구조로 연결시킨다. 이 트리 데이터 구조는 부모-자식 관계도 포함되며, 우리가 알고있는 HTML이 생성되는 것이다.

여기까지 생성된 결과물이 DOM(Document Object Model) 이라 하며, 트리 구조로 되어 있어서 DOM 트리 라고도 한다.


DOM 트리


1-5. CSSOM (CSS Object Model)

브라우저가 HTML을 파싱하는 과정에서 대부분은 head 태그의 하위로 link 태그가 존재해 CSS 파일을 불러오게 된다. CSS 파일을 불러온 뒤, HTML과 마찬가지로 브라우저가 이해하고 처리할 수 있는 형식으로 변환해야 한다.

그 과정은 DOM을 생성하는 과정과 동일하다. 단 출력된 결과는 CSSOM(CSS Object Model) 이라고 부르며, DOM 트리와 마찬가지로 트리 구조로 되어있어 CSSOM 트리 라고도 부른다.


CSSOM 트리


CSSOM 트리는 하향식으로 적용된다는 규칙을 가지고 있다. 예를들면 body 요소의 하위인 경우에는 모두 body 스타일이 적용된다는 것이다.

또한 각각 브라우저마다 User-Agent Style 이라고 하는 기본 스타일이 정의되어있다. 이 스타일은 개발자가 스타일을 지정하지 않을 경우 브라우저에서 제공하는 기본 스타일이다.

2. 렌더 트리 (Render Tree)

앞서 만든 DOM 트리와, CSSOM 트리는 서로 연관이 없는 독립적인 객체이다. DOM 트리는 콘텐츠를 의미하고, CSSOM은 문서에 적용되어야 하는 스타일 규칙을 의미한다.

따라서 두 번째 단계에선 이 두 개의 트리를 결합하여 렌더 트리를 생성한다.


렌더 트리


렌더 트리는 DOM 트리의 최상위 노드부터 각각의 노드를 탐색하여 렌더링에 필요한 노드들을 CSSOM 트리와 일치시켜 생성한다.

그리고 DOM 트리를 탐색하는 과정에서 렌더링 출력에 반영되지 않는 불필요한 노드들은 건너뛰게 된다. 예를들면 <script> 태그나, <meta> 태그 같은 것들이 있다.

또한 display:none 처럼 CSS를 통해 렌더링 출력에 반영되지 않는 노드들도 실제 화면에서 렌더링이 되지 않기 때문에 렌더 트리에서 제외된다.

다만 visibility:hidden 속성은 렌더 트리에 포함된다. 그 이유는 visibility:hidden 속성은 렌더링이 되더라도 여전히 레이아웃에서 공간을 차지하기 때문이다.

3. 레이아웃 & 리플로우 (Layout & Reflow)

렌더 트리는 DOM 트리와 CSSOM 트리에 의해 정의된 스타일만 계산하였다. 하지만 기기의 뷰포트(Viewport) 내에서의 정확한 위치 및 크기는 계산되지 않았다.

예를들어 CSS에 width:50% 로 정의되어 있다고 하면, 실제 브라우저에서 표현되는 정확한 사이즈는 계산되지 않았다는 의미이다.

따라서 브라우저에 출력하기 전 실제 출력되는 정확한 위치와 크기를 계산하여야 한다. 그 단계가 레이아웃 (layout) 또는 리플로우 (reflow) 단계라고 한다. 이러한 리플로우 단계에서는 각 객체의 정확한 위치 및 크기를 계산하기 위해 렌더 트리의 루트에서부터 탐색해 실제 픽셀 값을 구하여 박스 모델 (Box Model)을 출력한다.


박스 모델 (Box Model)


박스 모델의 넓이는 뷰포트 (ICB) 기준으로 측정되고, 높이는 Contents (fonts) 기준으로 측정된다. 따라서 윈도우 사이즈를 변경하거나, 폰트를 변경하면 리플로우가 다시 발생되게 되는 것이다.

간단한 예시를 들어보자.

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

위 예시 코드에서는 두 개의 중첩된 div 가 정의되어있다. 첫 번째 div는 width 값을 뷰포트 너비의 50%로 설정하였고, 하위의 div는 width 값을 상위 노드의 50%, 즉 뷰포트의 25%로 설정하였다.


리플로우 단계 (전)


이후 리플로우 단계에서는 모든 상대적인 값들을 절대적인 값인 픽셀로 변환하는 작업을 수행한다. 전체 뷰포트의 픽셀 사이즈를 구하고, 뷰포트의 50%, 뷰포트의 25% 값을 구하여 렌더 트리에 반영한다.


리플로우 단계 (후)


이런 리플로우 단계는 브라우저의 크기를 변경하거나 폰트를 변경하면 매번 발생되게 된다.

4. 페인트 & 레스터라이징 (Paint & Rasterizing)

페인트 단계에서는 위치나 크기를 제외한 나머지 색상이나 투명도 같은 CSS 속성들을 적용한다.

최근에는 리플로우와 페인트 중간에 레이어(Layer) 단계가 새로 추가된 브라우저가 대부분이다. 그러나 이 부분은 GPU를 사용하는 최근에 나온 기술로 추후 다시 설명하겠다.

5. 합성 & 렌더 (Composite & Render)

페인트 단계까지 완료된 객체들은 실제 위치 및 크기, 색상 같은 스타일이 모두 정해졌다.

이후 마지막 합성 단계에서는 모든 객체들을 조합하여 실제 브라우저 화면을 업데이트한다.


마치며..

브라우저의 가장 기본이 되는 렌더링 과정에 대해서 알아봤다. 이 과정은 아주 예전 브라우저에서부터 진행되던 과정이라 대부분 CPU에 의존하고 진행된다. 그러나 이런 기본적인 렌더링 과정에서는 많은 문제점과 성능이 좋지 못한 부분이 존재한다.

이런 문제점들로 인해 점점 시대가 바뀌고 기술이 진화하면서 브라우저 또한 같이 진화되었다.

다음 포스트에서는 기존 렌더링 과정에서의 문제점과 성능을 악화시키는 안좋은 방법과 최신 브라우저에서는 어떤 방식으로 렌더링이 진행되는지 비교해보자.

20181011 Charyum.Park