2 분 소요

[TIL]

WEB - 브라우저

브라우저 - 파싱과 DOM 트리 구축

CSS 파싱

HTML과는 다르게 CSS는 문맥 자유 문법이다. (어휘와 문법을 갖고 있다.)

CSS 파싱

CSS 어휘는 토큰화를 위해 정규표현식으로 정의되어 있다.
구문 문법은 일반적으로 문맥 자유 언어를 표현하는 방법인 BNF로 구성되어 있다.

웹킷은 flex, bison 파서 생성기를 사용, 파이어폭스는 직접 만든 하향식 파서 사용
두개 모두 CSS 파일을 스타일 시트 오브젝트로 파싱하고, 각 오브젝트는 css 규칙으로 나뉜다.

스타일 시트는 DOM 트리를 변경하지 않기 때문에 문서 파싱을 신경쓰지 않아도 된다.
하지만 스크립트가 실행될 때 스타일 정보를 요청하면 잘못된 결과를 반환한다.

그래서 파이어폭스에서는 이런 스타일 시트가 있으면 스크립트 실행을 중단 시키고, 웹킷에서는 아직 로드되지 않은 시트에서 영향을 줄만한 속성에 접근하려 할 때에만 중단한다.

스크립트와 스타일 시트의 진행 순서

스크립트

웹은 파싱과 실행이 동시에 수행되는 동기화 모델이다.

script 태그를 만나면 즉시 파싱하고 실행되는데, 스크립트가 실행되는 동안은 문서의 파싱이 중단된다. 스크립트가 외부에 있는 경우는 자원을 받아올 때까지 파싱이 중단된다.
HTML5에너는 defer와 async가 도입되면서 스크립트가 파싱이 완료되어도 실행하지 않고 문서 파싱이 완료된 이후에 스크립트가 실행되도록 한다.

예측 파싱

웹킷과 파이어폭스는 예측 파싱과 같은 최적화를 지원한다.
스크립트가 실행되는 동안 다른 스레드에서 네트워크로부터 다른 자원을 찾아 내려받고, 문서의 나머지 부분을 파싱한다.

예측 파서는 DOM 트리를 수정하지 않는다. 예측 파서는 외부 스크립트, 외부 스타일 시트와 외부 이미지와 같이 참조된 외부 자원을 파싱한다.

렌더 트리 구축

DOM 트리가 구축되는 동안 브라우저는 렌더 트리를 구축한다.
표시해야 할 순서와 시각적인 구성 요소로써 올바른 순서로 내용을 그려낼 수 있도록 한다.

렌더 트리를 구축하는 것을 웹킷에서는 renderer(렌더러) / render object(렌더 객체) 라고 부르고, 파이어폭스에서는 frame(형상)이라고 한다.

DOM 트리와 렌더 트리의 관계

렌더러는 DOM 요소에 부합하지만, 1:1로 대응하는 관계는 아니다.
head 요소는 비시각적 DOM 요소이기 때문에 렌더 트리에 추가되지 않는다.
또한 display none 일 경우 해당 요소는 트리에 나타나지 않는다. (visibility : hidden 을 가진 요소는 트리에 반영된다.)

또한, select 요소처럼 ‘표시 영역, 드롭다운 목록, 버튼’을 위한 3개의 렌더러가 있거나, 줄바꿈 처리될 때 새 줄은 별도의 렌더러로 추가된다.

어떤 렌더 객체는 DOM 노드에 대응하지만, 트리의 동일한 위치에 있지 않을 수도 있다.
float 처리 요소, 혹은 position 속성이 absolute일 경우 흐름에서 벗어나 트리의 다른 곳에 배치된 상태로 렌더된다.

트리를 구축하는 과정

스타일 속성 계산

웹킷에서는 스타일을 결정하고 렌더러를 만드는 과정을 “attachment/어태치먼트”라고 부른다.

이는 DOM 트리에 있는 엘리먼트의 스타일 속성을 계산하는 것이다.
태그의 인라인 스타일 속성이나 스타일 시트를 로드하여 스타일을 계산한다.

렌더 트리 구축

html 태그와 body 태그를 처리함으로써 렌더 트리 루트(루트 오브젝트)를 구성한다.

이 루트 렌더 객체(최초의 블록)를 파이어폭스에서는 ViewPortFrame, 웹킷에서는 RenderView라고 한다.

이제 하위 오브젝트들은 나머지 DOM 노드들로 구성한다.

배치

렌더러가 생성되어 트리에 추가될 때, 크기와 위치 정보가 없이 계산하는 것을 배치 / 리플로우라고 부른다.

배치는 반복되며 HTML 문서의 html 요소에 해당하는 최상위 렌더러에서 시작한다.
렌더러는 배치 / 리플로우 메서드를 갖는데, 각 렌더러는 배치해야할 자식의 배치 메소드를 불러온다.

화면의 왼쪽 위부터 해당하는 DOM node를 그려나간다.

그리기

그리기 단계에서는 화면에 내용을 표시하기 위한 렌더 트리가 탐색되고, 렌더러의 paint 메서드가 호출된다.

painting은 stacking contexts 스택에 명령을 쌓아 실행한다. 따라서, LIFO 정책을 따라 나중에 들어온 것이 먼저 실행된다.

그리기 순서:

  1. 배경 색
  2. 배경 이미지
  3. 테두리
  4. 자식
  5. 아웃라인

실행 순서:

  1. 아웃라인
  2. 자식
  3. 테두리
  4. 배경 이미지
  5. 배경 색

리페인팅 전에 웹킷은 기존의 사각형을 비트맵으로 저장하여 새로운 사각형과 비교하고 차이가 있는 부분만 다시 그린다.

동적 변경

브라우저는 변경에 대해 가능한 최소한의 동작으로 반응하려고 한다.
따라서, 요소의 색깔이 바뀌면 해당 요소의 리페인팅만 발생한다. (모든 CSS를 다 파싱하지 않는다.)
요소의 위치가 바뀌면 요소와 자식, 형제의 리페인팅과 재배치(리플로우)가 발생한다.
html 요소의 글꼴 크기를 변경하는 것과 같은 큰 변경은 캐시를 무효화하고 트리 전체의 배치와 리페인팅이 발생한다.

렌더링 엔진의 스레드

렌더링 엔진은 통신을 제외한 거의 모든 경우에 단일 스레드로 동작한다.

댓글남기기