Browser Rendering Process

soom·2020년 12월 18일
2
post-thumbnail

Browser Structure

  • User Interface: 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분
  • Browser Engine: User Interface와 Rendering Engine 사이의 동작을 제어
  • Rendering Engine: 요청한 콘텐츠를 표시, HTML을 요청하면 HTML과 CSS를 파싱 하여 화면에 표시함
  • Networking: HTTP 요청과 같은 네트워크 호출에 사용됨
  • Javascript Interpreter(또는 Engine): 자바스크립트 코드를 해석하고 실행함. 크롬에서는 V8 엔진을 사용함
  • Display Backend: 기본적인 위젯(콤보 박스 등..)을 그림
  • Data Persistence: Local Storage, 쿠키 등 클라이언트 사이드에서 데이터를 저장하는 영역

Browser Rendering Engine

브라우저마다 사용하는 렌더링 엔진들이 다르다. 렌더링 엔진이 브라우저마다 다르기 때문에, 같은 소스가 브라우저마다 다르게 그려지는 크로스 브라우징 이슈가 발생.(자바스크립트 엔진이 달라 발생하기도 한다.)

크롬 브라우저(정확히는 크로미움)는 사파리 브라우저에서 사용하는 Webkit을 사용하다가 버전 28 이후 Webkit 소스를 Fork 하여 Blink 엔진을 만들어 사용.

참고: 크로미움이란?

크롬은 크로미움 기반으로 만들어진 브라우저. 크로미움은 오픈 소스 웹 브라우저입니다. https://chromium.woolyss.com/download/ko/에서 다운로드해 브라우저로 사용 가능하다.
크로미움은 V8이라는 자바스크립트 엔진과 Blink라는 렌더링 엔진을 사용하는 브라우저이다. 크롬이 크로미움 기반으로 만들어졌다는 것은 오픈 소스인 크로미움 브라우저 코드 위에 살을 덧붙여 개발되었다는 의미이다.

Browser Rendering Abstract

브라우저의 역할은 사용자의 요청을 서버에 전달하고 그 결과를 화면에 나타내는 과정인데 렌더링은 렌더링 엔진은 HTML 문서를 파싱 하여 DOM 트리를 만들고, CSS 문서를 파싱 하여 CSSOM 트리를 만든다. DOM과 CSSOM을 이용하여 렌더 트리를 만든다.

  1. DOM(Document Object Model) 생성
    : HTML 마크업을 처리하고 DOM 트리를 빌드 합니다. (DOM 파싱)
  2. CSSOM* 생성
    : CSS 마크업을 처리하고 CSSOM 트리를 빌드 합니다. (CSS 파싱)
  3. Render Tree 생성
    : DOM 및 CSSOM을 결합하여 렌더 트리를 형성합니다. (Attachment)
  4. Render Tree 배치
    :렌더 트리에서 레이아웃을 실행하여 각 노드의 기하학적 형태를 계산합니다. (Layout)
  5. Render Tree 페인팅
    : 개별 노드를 화면에 페인트 합니다. (Painting)

렌더 트리 생성이 끝나면 Layout(Reflow라고도 합니다)이 시작된다. 이 과정은 각 노드가 화면의 정확한 위치에 표시하기 위해 위치와 크기를 계산하는 과정을 말한다. 마지막으로 계산된 위치과 크기 등의 스타일들이 실제 픽셀로 표현하는 과정이 시작된다. 이 과정을 Paint(Rasterizing)라고 한다.

이러한 과정을 통해 브라우저가 서버에 요청한 내용의 노드들을 픽셀화 시키는 것을 브라우저 렌더링 이라고 한다.

*CSSOM (CSS Object Model): CSS 개체 모델은 자바 스크립트에서 CSS의 조작을 가능하게하는 API의 집합.

Rendering Process Detail

  1. HTML을 파싱하여 DOM 노드를 만듭니다. 이 DOM 노드들을 병합하여 DOM 트리를 만듭니다.
    CSS를 파싱하여, 스타일 규칙을 만듭니다.
  2. DOM 트리와 스타일 규칙을 사용하여, Attachment라는 과정을 통해 Render 트리를 생성합니다.
  3. Render 트리를 배치(Layout)합니다.
  4. Render 트리를 화면에 그림(Painting)니다.

Overview

  1. 주소를 입력할 시 DNS서버로 가서 웹사이트의 진짜 주소(ip)를 찾아온다.
  2. 웹 사이트의 주소에서 웹 페이지를 표시하기 위해 필요한 자료를 요청한다.
  3. 웹 사이트에서 요청을 받고 승인후에 브라우저에게 정보를 전송합니다.
  4. 브라우저는 웹 사이트에서 받은 정보들을 조립하여 유저에게 보여줍니다.
  5. 이과정에서 Request(요청) 와 Response(응답)가 발생하며 마지막 4번에서 브라우저는 받은 정보를 가지고 DOM과 CSSOM 그리고 Render-Tree , Paint가 발생한다.

1. Request

Request란 클라이언트(브라우저)가 서버에게 요청하는 사항이 정리된 객체.

2. Response

Response란 앞서 Request에서 받은 요청을 처리하고 그에 해당하는 데이터를 담은 객체.

위의 두가지요청이 끝나고 나면 브라우저는 화면을 표시하기위한 정보(HTML, CSS)를 받은 상태이다.

HTML은 DOM(Document Object Model)로 변환되며 CSS는 CSSOM(CSS Object Model) 으로 변환.

이 둘은 바이트 > 문자 > 토큰 > 노드 > 객채 모델 순으로 해석.

3. DOM (Document Object Model)

앞서 1번과 2번을 통해서 받은 HTML파일은 바이트(Bytes) 상태이다.

이 바이트 상태의 정보를 다음과 같이 처리한다.

  • 변환(Conversion) : HTML의 원시 바이트(raw bytes)를 지정된 인코딩(주로 UTF-8)에 따라 문자로 변환한다.
  • 토큰화(Tokenizing) : UTF-8로 변환된 문자열을 W3C HTML5 표준에 의거하는 토큰(태그 <html>, <body>등, 꺽쇠괄호로 묶인 문자열) 로 변환합니다.
  • 렉싱(Lexing) : 각 토큰(태그)를 속성과 규칙을 가지는 객체(Nodes)로 변환한다.
  • DOM 생성 : HTML마크업이 여러 태그(일부는 다른태그안에 포함) 간의 관계를 정의하기 떄문에 트리 데이터 구조 내에 연결됩니다. 이 트리구조에는 부모태그와 자식태그의 관계도 포함됩니다.
    이 전체 프로세스의 최종 출력이 HTML페이지의 DOM(Document Object Model)이며, 브라우저는 이후 모든 페이지 처리에 이 DOM을 사용합니다.

DOM Manipulation

돔 조작(DOM Manipulation)은 웹 페이지를 수정하기 위해 매우 유용하지만, 문제점도 존재한다. 만약 자바스크립트를 사용해서 <div> 태그의 색상을 업데이트한다고 하면 해당 DOM node 객체에 접근하고 색상 속성을 업데이트하면 된다. 이때는 트리의 나머지 노드에 영향을 미치지 않는다.

그러나 트리에서 하나의 노드를 추가하거나 제거한다면, 전체 트리를 다시 정렬해야 할 수도 있다. 이것은 비용이 많이 드는 작업이며, 브라우저에서는 시간과 브라우저 리소스가 필요하다. 예를 들어 DOM에 5가지 리스트(<li></li>)가 추가된다고 하면, 하나의 리스트마다 새 노드가 DOM에 추가되어 트리가 매번 업데이트된다. 총 5개의 업데이트가 추가되는 것이다.

같은 예로 하나의 노드 추가나 삭제를 하여 웹 페이지 전체의 레이아웃에 영향을 받는 경우, 웹페이지의 일부 또는 전체가 다시 렌더링 될 수 있다. 이런 경우를 Reflow라고 한다. 다시 말하자면 대화식 사이트(interactive site)에서 업데이트한 후에 브라우저가 웹 페이지의 일부 또는 전부를 다시 처리하고 그려야 할 때를 의미한다.

과도한 Reflow를 피하기 위해서는 Dom을 너무 많이 변경하면 안되며, 브라우저에 따라 다른 요소도 브라우저에 영향을 줄 수 있다. 하지만 대부분의 Javascript 프레임 워크가 DOM을 필요한 것보다 훨씬 많이 업데이트한다. 이러한 현상은 곧 속도 저하로 이어진다.

DOM 조작은 현대적인 대화식 웹(interactive site)의 핵심이다. 10개의 항목이 포함된 목록이 있는데 하나의 항목이 수정되었다면, javascript 프레임 워크는 대부분 전체 목록을 리렌더링한다. 물론 이렇게 작은 경우라면 크게 문제가 될 일이 없겠지만, 일반적인 웹사이트에서는 많은 양의 DOM을 조작할 수 있으며 비효율적인 업데이트가 발생한다.

Virtual DOM

실제 DOM의 변경사항에 대해 DOM에서 수행해야 할 모든 변경 사항을 가상돔(virtual DOM)에서 수행한 다음 실제 DOM에 전달함으로써 위에서 언급한 계산 단계가 줄어든다. 여러 번의 변경사항이 있더라도 모든 변경 사항을 하나로 그룹화하여 한번만 수행한다.

하지만, 잘 생각해보면 가상돔 없이도 해결할 수 있는 문제다. 왜냐하면 결국 가상돔도 "Rendering"하기 위해서 고유 DOM API인 "document.createDocumentFragment()"를 사용하기 때문이다. DOM의 모든 수정 사항을 직접 그룹화 한다음에 DOM에 넘겨도 되는 것이다.

그렇다면 가상돔은 무엇을 해결하는 것일까? DOM 관리를 자동화하고 추상화하여 직접 할 필요가 없게 해주는 것이다. 또한 전체 DOM Tree를 reload하지 않기 위해 변경한 부분과 변경되지 않는 부분을 직접 할 때는 추적해야 하나 이 또한 가상돔이 자동화해주는 것이다.

마지막으로 DOM 조작 자체를 포기함으로써 DOM을 수정하는 모든 부분 간의 동기화를 피할 수 있다.

여기서 다시 한번 재고해봐야 하는 점은 나는 분명히 위 글에서 "가상돔이 더 빠르다"라는 말을 하지 않았다는 점이다. 가상돔은 개발자가 작업을 보다 쉽게 할 수 있도록 도와주는 것이지, 가상돔에서 더 빠르게 접근할 수 있는 무언가를 제공해 주는 게 아니다. 이 부분은 React의 핵심 개발자들도 vanilla js가 가상돔에서 작업하는 것보다 항상 더 빠르다고 말하는 부분과 일맥상통하는 것이다. 가상돔은 목적을 위한 수단일 뿐이다라는 점만 기억하자!

가상돔은 html 객체에 기반하여 자바스크립트 객체로 표현할 수 있다.

const vdom = {
  tagName: "html",
  children: [
    { tagName: "head" },
    {
      tagName: "body",
      children: [
        {
          tagName: "div",
          attributes: { class: "img" },
          children: [
            {
              tagName: "div",
              attributes: { class: "name" },
              textContent: "name",
            }, // end div
          ],
        }, // end div
      ],
    }, // end body
  ],
}; // end html

여기서 좋은 점은 저렇게 하나의 객체가 아닌 내가 작업하는 곳만으로 작게 쪼개서 작업 할 수 있다.

const div = {
  tagName: "div",
  attributes: { class: "img" },
  children: [
    {
      tagName: "div",
      attributes: { class: "name" },
      textContent: "name",
    }, // end div
  ],
}, // end div

가상돔은 쉽게 말해 DOM의 자바스크립트 객체로서의 표현이며, 작은 단위로 쪼개서 필요한 만큼 자주 수정할 수 있다. 그리고 변경되는 부분만 변경되게 하는 것은 직접 DOM API를 호출해도 되지만, React 같은 편리한 프레임워크를 이용해서 간단하게 해결할 수 있다.

React virtual DOM

react 애플리케이션을 렌더링할 때, 앱의 노드 트리가 메모리에 저장된다. 그 다음 트리는 다시 렌더링 환경으로 플러시(flush)된다. 그리고 앱이 업데이트되면(ex- setState) 새 Tree가 생성되고 이전 트리와 비교하여 랜더링 된 앱을 업데이트하는데 필요한 작업을 계산(=비교, diffing)하고, 실제 DOM은 변경된 내용만 업데이트 한다.

diffing - 사전 업데이트 된 virtual DOM과 업데이트 된 virtual DOM을 비교하는 과정을 diffing이라고 한다, React는 그래서 매번 두 개의 가상돔을 유지/관리한다.)

변경 사항이 생기면 ReactDOM.render()가 호출 된다. 해당 과정은 변경점을 찾는 작업과, 변경점을 실제 UI 적용하는 과정으로 나누어진다.

변경점을 찾는 작업

React는 Virtual DOM을 통하여 브라우저 DOM에 전달하기 전에 Reconciliation라는 비교 과정을 선행하기 때문에 UI에 대한 제어를 최소화 시킨다. (이는 React-Native에도 동일하게 작동한다.)
Render가 진행 될 때마다, 각 Element들은 key를 부여 받고, 해당 key를 통해 같은 Element라고 인식 되고 비교 된다.(물론, 계층이 달라지게 될 경우는 제외된다. 또, React diff 알고리즘은 필터 관련 기능에 취약하다고 한다. 많은 list를 필터하는 경우, 이것을 기억하고 해결책을 찾아보기로 기약하자.)

4. CSSOM (CSS Object Model)

브라우저는 DOM을 생성하는 동안 외부 CSS를 참조하는 <link> 태그를 만나게 되면 브라우저에 리소스를 요청합니다. CSS의 원시 바이트(raw bytes)가 문자열로 변환된 후 차례로 토큰과 노드로 변환되고 마지막으로 CSSOM(CSS Object Model)이라는 트리 구조를 만듭니다.

페이지에 CSS가 참조 될경우 DOM을 생성하는 동안 외부 스타일시트를 받아온다.
수신된 CSS는 위의 HTML파일과 같은 프로세스를 진행한다.

CSSOM이 트리구조를 가지는 이유는 HTML에서 나온 객체의 최종 스타일을 계산할때 상속되는 값도 같이 계산해야 하기 때문이다.

참고: JavaScript와 CSS

JavaScript

자바스크립트는 파서 차단 리소스(parser blocking resource)이다. 브라우저는 문서를 파싱 하다가 자바스크립트를 만나면 진행하던 파싱을 중지하고 자바스크립트 엔진에게 권한을 넘겨 자바스크립트를 파싱하고 실행한다.

자바스크립트가 실행되는 동안 문서의 파싱은 중단된다. 자바스크립트는 파싱을 중단시키기 때문에, 보통 자바스크립트를 <head> 태그가 아닌 <body> 태그가 닫히기 바로 전에 사용되도록 하는 것이 좋다.

<script> 태그에 defer 속성을 주면, 문서 파싱은 중단되지 않고 문서 파싱이 완료된 이후에 자바스크립트가 실행된다. HTML5에서 스크립트를 비동기(async)로 처리하는 속성이 추가되었다.

CSS

CSS는 렌더링 차단 리소스(render blocking resource)이다. CSS는 렌더링을 할 때 반드시 필요한 리소스이기 때문에 브라우저는 빠르게 CSS를 다운로드하는 것이 좋다. <head> 태그 안에서 정의하여 빠르게 리소스를 받을 수 있도록 해야한다.

CSSDOM 트리를 변경하지 않기 때문에 문서 파싱을 기다리거나 중단할 이유가 없다. 그러나 자바스크립트에서 스타일 정보를 요청하는 경우, CSS가 파싱 되지 않은 상태라면 스크립트 에러가 발생할 수 있다.

이런 문제를 해결하기 위해 파이어폭스는 로드 중이거나 파싱 중인 CSS가 있는 경우 모든 자바스크립트 실행을 중지한다. 반면 웹킷은 로드되지 않은 CSS 가운데 문제가 될 만한 속성이 있을 때에만 자바스크립트를 중단한다.

5. Render-Tree

CSSOM 및 DOM 트리는 결합하여 렌더링 트리를 생성.

이 렌더링 트리는 표시되는 각 요소의 레이아웃을 계산하는데 사용되고 픽셀을 화면에 렌더링하는 페인트 프로세스에 대한 입력으로 처리.

HTML과 CSS 는 각기 개별적인 DOM과 CSSOM으로 나뉘어 있다.

이것을 합쳐서 화면에 표시하는 과정은 다음과 같다.

  • DOM 트리의 루트에서 시작하여 표시되는 노드(태그) 각각을 트레버스 한다.
    일부 태그는 생략된다. (script 나 meta 등)
  • CSS를 통해 숨겨진(display:none) 도 렌더링 트리에서 생략된다.
  • 표시된 각 노드에 대해서 일치하는 CSSOM 규칙을 적용한다.
  • 표시된 노드를 콘텐츠 및 스타일과 함께 내보낸다.

DOM 트리와 렌더 트리의 관계

화면에 표시되지 않는 노드들은 렌더 트리에 포함되지 않는다. 예를 들어, <head> 태그와 같은 비시각적 DOM 노드는 렌더 트리에 추가되지 않는다.
뿐만 아니라 CSS로 인해 display 속성에 none 값이 할당된 노드들을 렌더 트리에 추가되지 않는다. 하지만, visibility:hidden은 렌더 트리에 포함된다. visibility 속성에 hidden 값이 할당된 노드는 화면에 공간을 차지하기 때문에 렌더 트리에 포함된다.

6. Layout

렌더 트리가 생성되고, 기기의 뷰포트 내에서 렌더 트리의 노드가 정확한 위치와 크기를 계산하는 과정을 Layout(혹은 Reflow)라고 한다. 모든 상대적인 측정값은 화면에서 절대적인 픽셀로 변환된다. 즉 CSS에 상대적인 값인 %로 할당된 값들은 절대적인 값은 px 단위로 변환 된다.

7. Painting

렌더 트리의 각 노드를 화면의 실제 픽셀로 나타내는 과정을 Painting(혹은 rasterizing)라고 한다. Painting 과정 후 브라우저 화면에 UI가 나타난다.

다음의 글을 참조하였습니다.

profile
yeeaasss rules!!!!

0개의 댓글