브라우저는 어떻게 동작할까?

YU·2021년 7월 2일
1

브라우저의 주요 기능

브라우저의 주요 기능은 사용자가 선택한 자원을 서버에 요청하고 브라우저에 표시하는 것이다. 자원은 보통 HTML 문서지만 PDF나 이미지 또는 다른 형태일 수 있다. 자원의 주소는 URI(Uniform Resource Identifier)에 의해 정해진다.

브라우저의 기본 구조

  1. 사용자 인터페이스
    • 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등.
      요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분이다.
  2. 브라우저 엔진
    • 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.
    • 예를들어 만약 여러분들이 사용자 인터페이스 레이어에 있는 새로고침 버튼을 눌렀다면, 브라우저 엔진은 이를 이해하고 새로고침 명령을 수행합니다.
  3. 렌더링 엔진
    • 요청한 콘텐츠를 표시합니다.
    • 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시합니다.
    • Chrome과 Opera, Edge는 Blink를, Firefox는 Gecko를, Internet Explorer는 Trident를, Safari는 WebKit을 사용합니다.
  4. 통신(네트워크)
    • HTTP 요청과 같은 네트워크 호출에 사용됩니다.
    • 외부의 리소스를 얻어오거나 서버에 요청을 보낼 때 사용되는 계층입니다.
  5. UI 백엔드
    • 브라우저가 동작하고 있는 운영체제의 인터페이스를 따르는 UI들을 처리합니다.
      얼럿(alert)이나 셀렉트 박스(select)가 운영체제 별로 다르게 동작하는 것을 쉽게 확인할 수 있습니다.
  6. 자바스크립트 해석기
    • 자바스크립트 코드를 해석하고 실행.
  7. 자료 저장소
    • 이 부분은 자료를 저장하는 계층이다. 쿠키를 저장하는 것과 같이 모든 종류의 자원을 하드 디스크에 데이터를 저장하는데 사용됩니다.

렌더링 엔진

렌더링 엔진의 역할은 요청 받은 내용을 브라우저 화면에 표시하는 일이다.

동작 과정

렌더링 엔진은 통신으로부터 요청한 문서의 내용을 얻는 것으로 시작하는데 문서의 내용은 보통 8KB 단위로 전송된다.

다음은 렌더링 엔진의 기본적인 동작 과정이다.

파싱과 DOM 트리 구축

파싱 일반

문서 파싱은 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것을 의미한다.

파싱은 어휘 분석구문 분석이라는 두 가지로 구분할 수 있다.

  1. 어휘 분석은 자료를 토큰으로 분해하는 과정이다.
    • 토큰은 유효하게 구성된 단위의 집합체로 용어집이라고도 할 수 있는데
      인간의 언어로 말하자면 사전에 등장하는 모든 단어에 해당된다.
  2. 구문 분석은 언어의 구문 규칙을 적용하는 과정이다.

파싱 과정은 반복된다. 파서는 보통 어휘 분석기로부터 새 토큰을 받아서 구문 규칙과 일치하는지 확인한다. 규칙에 맞으면 토큰에 해당하는 노드가 파싱 트리에 추가되고 파서는 또 다른 토큰을 요청한다.

규칙에 맞지 않으면 파서는 토큰을 내부적으로 저장하고 토큰과 일치하는 규칙이 발견될 때까지 요청한다. 맞는 규칙이 없는 경우 예외로 처리하는데 이것은 문서가 유효하지 않고 구문 오류를 포함하고 있다는 의미다.

이러한 파스 트리를 이용해서 렌더를 바로 할 수 있을까?

파서 트리는 최종 결과물이 아니다. 파싱은 보통 문서를 다른 양식으로 변환하는데 컴파일이 하나의 예가 된다. 소스 코드를 기계 코드로 만드는 컴파일러는 파싱 트리 생성 후 이를 기계 코드 문서로 변환한다.

HTML 파싱

HTML 파서는 HTML 마크업을 파싱 트리로 변환하고
파싱 트리는 DOM 요소와 속성 노드의 트리로서 출력 트리가 된다.
=> 브라우저는 파스 트리를 이용해 DOM(Document Object Model) 트리를 새로 만든다.

파스 트리는 토큰화된 문자열을 단순하게 구조화한 트리에 불과했지만, DOM 트리는 우리가 실제로 상호작용할 수 있는 HTML 엘리먼트로 이루어진 트리입니다.
따라서 우리가 실제로 JavaScript로 상호작용할 수 있는 부분은 DOM 트리입니다.

DOM은 마크업과 1:1의 관계를 맺는다. 예를 들면 아래와 같은 마크업이 있다.

<html>
  <body>
    <p>Hello World</p>
    <div><img src="example.png" /></div>
  </body>
</html>  

이것은 아래와 같은 DOM 트리로 변환할 수 있다.

CSS 파싱

일반적으로 CSS을 링크하는 코드가 HTML 코드 내에 삽입되어 있기 때문에, HTML을 파싱하는 도중에 CSS 파싱이 시작됩니다. 네트워크를 통해 먼저 받아온 코드부터 해석을 실행할 수 있는 HTML 파서와는 달리, CSS 파서는 전체 파일을 모두 다운로드할 때까지 파싱을 시작할 수 없습니다.

전체 CSS 파일을 다운로드 한 후 CSS 파싱 과정이 끝나게 되면, 코드에서 명세한 내용과 순서를 바탕으로 DOM과 같은 트리를 구성하는데 이를 CSSOM(CSS Object Model) 트리라 부릅니다. 이 트리에는 스타일, 규칙, 선택자 등의 정보가 노드에 들어가게 됩니다.

렌더 트리

한편, 위에서 이야기한 DOM 트리가 구성되는 동안 브라우저는 렌더 트리(Render Tree)를 구성하기 시작합니다. 동의어로는 프레임 트리(Frame Tree)라고도 합니다.

렌더 트리는 기본적으로 화면에 나타나는 요소들을 결정하는 트리입니다. 즉, 어떠한 요소들이 보여야 하는지, 어떤 스타일이 적용되어야 하는지, 그리고 어떤 순서로 나타낼 것인지를 명세하는 트리죠.

렌더 트리는 DOM 트리와 CSSOM 트리를 조합하여 만들어지고, 이 때 화면에 그려지지 않는 요소들은 트리에 나타나지 않습니다. 가령 head, script 같은 태그나 display: none 스타일이 적용된 엘리먼트가 있겠죠. 이러한 태그는 시각적으로 나타낼 것이 없기 때문에 렌더 트리에 그려지지 않습니다. 즉 렌더 트리는 DOM 트리와 정확하게 1:1로 매칭이 되지는 않습니다.

레이아웃 또는 리플로우 (렌더 트리 배치)

렌더 트리 구성이 끝나면 레이아웃 단계가 이어집니다.
모질라에서는 이 과정을 리플로우(reflow)라고 부르기도 합니다.

시각적으로 나타낸 영상

한편, 레이아웃은 계산의 범위에 따라 전역적 레이아웃(Global Layout)증분적 레이아웃(Incremental Layout)으로 구분할 수 있습니다.

전역적 레이아웃은 말 그대로 화면 전체의 레이아웃을 계산하는 것입니다. 가령 새로운 폰트를 적용하거나, 폰트 사이즈가 바뀌거나, 뷰포트의 사이즈 변경 같은 경우가 있을 때 전체 레이아웃을 다시 계산합니다. offsetHeight 같은 일부 DOM 관련 JavaScript API에 접근을 하는 경우에도 전역적 레이아웃이 다시 계산되기도 합니다.

이러한 전역적 레이아웃 단계는 모든 렌더 트리 노드에 대해 기하학적인 계산을 수행하기 때문에, 노드가 많아지게 된다면 그 속도가 느려지게 됩니다. 따라서 브라우저에서는 자체적인 최적화 로직을 탑재하고 있습니다.

그 중 하나가 바로 더티 비트 시스템(Dirty bit system)입니다. 더티 비트 시스템은 특정 엘리먼트의 레이아웃이 변경이 되었을 때, 렌더 트리를 처음부터 탐색하면서 레이아웃을 계산하지 않고 특정한 부분만 다시 계산하여 리소스의 낭비를 줄이는 최적화 방법입니다.

증분적 레이아웃은 이러한 더티 비트 시스템을 활용합니다. 레이아웃 과정에서 렌더 트리를 재귀적으로 탐색을 하다가 더티한 엘리먼트들, 즉 레이아웃의 변경이 발생해야 하는 엘리먼트들을 만나게 되면, 그 계산을 즉시 수행하는 것이 아니라 스케쥴러를 통해 비동기로 일괄 작업(batch)을 진행합니다. 이를 통해 연산의 횟수와 범위를 줄일 수 있습니다.

하지만 아주 복잡한 레이아웃의 경우에는 브라우저 단에서의 최적화만으로는 충분하지 않기 때문에, 프론트엔드 개발자 역시 레이아웃 과정의 연산을 최소화하도록 신경을 써야 합니다. 때문에 브라우저처럼 행동하는 것이 필요합니다. DOM의 레이아웃과 관련된 값을 직접 읽어오거나 변화를 주는 JavaScript 코드를 작성해야 한다면, 그러한 구문들을 최대한 묶어야 합니다.

페인트

페인트 단계는 말 그대로 레이아웃 단계를 통해 화면에 배치된 엘리먼트들에게 색을 입히고 레이어의 위치를 결정하는 단계입니다. 이 단계 역시 루트 오브젝트로부터 재귀적으로 실행이 됩니다. 또한 레이아웃과 마찬가지로 페인팅에도 전역적 페인팅증분적 페인팅이 있습니다.

즉, 문서가 클수록 브라우저가 수행해야 하는 작업도 더 많아지며, 스타일이 복잡할수록 페인팅에 걸리는 시간도 늘어납니다. 예를 들어, 단색은 페인트하는 데 시간과 작업이 적게 필요한 반면, 그림자 효과는 계산하고 페인트 하는데 시간과 작업이 더 필요합니다.

페인팅에는 그 순서가 있는데, 이는 z-index 축을 이용한 쌓임 맥락(Stacking context)과도 일맥상통합니다. 때문에 z-index가 낮은 순서대로 먼저 페인팅이 됩니다.

한편 블록 단위에서의 페인팅 순서는 CSS 페인팅 명세에 따르면 다음과 같습니다.

  1. background-color
  2. background-image
  3. border
  4. children
  5. outline

따라서 만약 background-colorbackground-image 가 함께 세팅되어 있고, background-image로 설정한 외부 리소스의 크기가 크다면 background-color 를 먼저 보게 될 것이고, 나중에 이미지가 완전히 로드된 후 background-image로 교체가 될 것입니다.

참고 자료

https://d2.naver.com/helloworld/59361,
https://wormwlrm.github.io/2021/03/27/How-browsers-work.html

profile
Web Developer

0개의 댓글