공대생 정리노트

브라우저의 동작 원리 - Naver D2 요약 정리 본문

로드맵/인터넷

브라우저의 동작 원리 - Naver D2 요약 정리

woojinger 2020. 8. 23. 17:16

참고자료

https://d2.naver.com/helloworld/59361

https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/

 

How Browsers Work: Behind the scenes of modern web browsers - HTML5 Rocks

In this comprehensive primer, you will learn what happens in the browser between when you type google.com in the address bar until you see the Google page on the browser screen.

www.html5rocks.com

 


브라우저의 주요 기능

사용자가 선택한 자원(HTML, PDF, 이미지 등)을 서버에 요청하고 브라우저에 표시하는 것.

브라우저는 HTML과 CSS 명세(웹 표준화 기구인 W3C에서 정함)에 따라 HTML을 해석하여 표시한다.

 

브라우저의 기본 구조

  1. 사용자 인터페이스
  2. 브라우저 엔진 : 사용자 인터페이스와 렌더링 엔진 사이 동작 제어
  3. 랜더링 엔진 : 요청한 콘텐츠를 표시. ex) HTML과 CSS 파싱하고 화면에 표시
  4. 통신 : 플랫폼 독립적인 인터페이스. 각 플랫폼 하부에 실행
  5. UI 백엔드 : 콤보 박스와 창 같은 기본적인 장치를 그린다. OS 사용자 인터페이스 체계 사용
  6. 자바스크립트 해석기
  7. 자료 저장소 : 자료 저장. 쿠키를 저장하는 것과 같이 하드디스크에 저장

브라우저 구성 요소. 출처 : Naver D2

렌더링 엔진

요청 받은 내용을 브라우저 화면에 표시.

동작 과정

렌더링 엔진 동작 과정. 출처 : Naver D2

통신으로부터 요청한 문서의 내용을 얻는 것으로 시작. 보통 문서 내용은 8KB 단위로 전송됨.

 

렌더링 엔진은 HTML 문서를 파싱하고 "컨텐츠 트리" 내부에서 태그를 DOM(Document Object Model) 노트로 변환.

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

예를 들어 이런 코드가 있다고 하면 이것은 다음과 같은 DOM tree로 변환됨.

출처 : https://www.html5rocks.com/en/tutorials/internals/howbrowserswork/#DOM

그 다음 외부 CSS 파일과 함께 스타일 요소도 파싱. 이 스타일 정보와 HTML표시는 "렌더 트리"라는 또 다른 트리 생성

렌더 트리는 보이는 element들로 구성. 색상 또는 면적과 같은 시각적 속성이 있는 사각형을 포함하고 있고, 올바른 순서대로 contents를 그리기 위해 존재.

렌더 트리 생성이 끝나면 배치가 시작(각 노드가 화면의 정확한 위치에 표시되는 것을 의미).

그 다음에는 UI 백엔드에서 각 노드를 가로지르며 형상을 만들어 낸다.

 

렌더링 엔진은 빠르게 내용을 표시하기 위해 모든 HTML을 파싱할 때까지 기다리지 않고 배치와 그리기 과정 시작.

 

 

파싱과 DOM 트리 구축

파싱 일반

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

변환된 결과를 Parse tree, Syntax tree라고 한다.

2+3-1을 표현한 트리. 출처 : Naver D2

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

 

어휘 분석은 자료를 토큰으로 분해하는 과정이다.

구문 분석은 언어의 구문 규칙을 적용하는 과정이다.

 

문서 소스를 받으면 파서는 어휘 분석을 통해 공백과 줄 바꿈과 같은 의미 없는 문자를 제거하고 토큰으로 분해한다. 그 후 구문 분석으로 파싱 트리를 생성한다.

 

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

일치하지 않으면 토큰을 내부적으로 저장하고 토큰과 일치하는 규칙이 발견될 때까지 요청.

맞는 규칙이 없는 경우 예외로 처리(문서가 구문 오류를 포함하고 있다는 것)

변환

컴파일러는 만들어진 파싱 트리를 기계 코드 문서로 변환한다.

 

 

HTML 파서

HTML 파서는 HTML 마크업을 파싱 트리로 변환한다.

HTML은 파서가 요구하는 문맥 자유 문법에 의해 쉽게 정의할 수 없다.

그러나 파싱은 CSS와 자바스크립트를 파싱하는데 사용된다.

 

HTML은 암묵적으로 태그에 대한 생략이 가능하다. (시작, 종료 태그 생략 등..)

이러한 방식은 웹 제작을 편하게 만들어주었지만 공식적인 문법으로 작성하기 어렵게 만드는 문제가 있다.

따라서 HTML은 파싱하기 어렵고 전통적인 구문 분석이 불가능해서 문맥 자유 문법이 아니다.

 

파싱 알고리즘

HTML이 일반적인 파서로 파싱이 안되는 이유

  1. 언어의 너그러운 속성
  2. HTML 오류에 대한 브라우저 관용
  3. 변경에 의한 재파싱. HTML에서 document.write을 포함하고 있는 스크립트 태그는 토큰을 추가할 수 있어 입력 과정에서 파싱이 수정이 된다.

그래서 브라우저는 HTML 파싱을 위해 별도의 파서 생성.

알고리즘은 토큰화와 트리 구축 두 단계로 되어 있음.

 

토큰화 : 어휘 분석으로 입력값을 토큰으로 파싱. HTML에서 토큰은 시작 태그, 종료 태그, 속성 이름과 속성 값

토큰을 인지해서 트리 생성자로 넘기고 다른 토큰을 확인하기 위해 다음 문자를 확인. 이 과정을 입력 마지막까지 반복한다.

HTML 파싱 과정. 출처 : Naver D2. HTML5 명세

트리 구축 알고리즘

파서가 생성되면 문서 객체가 생성된다. 트리 구축이 진행되는 종안 문서 최상단에서는 DOM 트리가 수정되고 요소가 추가된다.

토큰화에 의해 발행된 각 노드는 트리 생성자에 의해 처리된다.

DOM 트리에 요소를 추가하는 것이 아니라면 열린 요소는 스택(임시 버퍼 저장소)에 추가.

<html>
   <body>
      Hello world
   </body>
</html>  

예를 들어보자. 아래의 순서로 트리 구축은 진행된다.

  1. 상태 : "html 이전" / html 토큰은 HTMLHtmlElement 요소를 생성하고 문서 객체의 최상단에 추가
  2. 상태 : "head 이전" / body 토큰을 받는다. head 토큰이 없더라도 HTMLHeadElement는 묵시적으로 생성되어 트리에 추가
  3. 상태 : "head 안쪽"으로 변경 후 바로 다시 "head 다음"으로 변경 / body 토큰이 처리되고 HTMLBodyElement가 생성되어 추가됐다.
  4. 상태 : "body 안쪽" / :Hello world" 문자열의 문자 토큰을 받음. 각 문자를 위한 문자 토큰을 받게 되는데, 첫 번째 토큰이 생성되고 "본문" 노드가 추가되면서 다른 문자들이 그 노드에 추가.
  5. 상태 : "body 다음". / html 종료 태그를 만나면 "body 다음 다음" 모드로 바뀌고 마지막 파일 토큰 받으면 파싱 종료

파싱 이후에는 문서 파싱 이후 실행되어야 하는 "지연"모드 스크립트를 파싱한다.

문서 상태는 "완료"가 되고 "로드" 이벤트가 발생한다.

 

 

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

스크립트

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

파서가 <script> 태그를 만나면 실행을 하고, 스크립트가 실행되는 동안 문서의 파싱은 중단된다. 

제작자가 스크립트를 "지연(defer)로 표시하면 문서 파싱은 중단되지 않고 문서 파싱이 완료된 이후에 스크립트가 실행된다. (스크립트 fetching은 병렬적)

 

예측 파싱

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

 

스타일 시트

CSS 파일이 파싱되면 스타일 시트 객체.

이론적으로는 스타일 시트는 DOM 트리를 변경하지 않기 때문에 문서 파싱을 기다리거나 중단할 이유가 없다.

그러나 스크립트가 문서를 파싱하는 동안 스타일 정보를 요청하면 문제가 된다.

따라서 파이어폭스는 로드중이거나 파싱 중인 스타일 시트가 있으면 모든 스크립트의 실행 중단

웹킷은 로드되지 않은 스타일 시트 가운데 문제가 될만한 속성이 있을 때만 스크립트 중단

 

 

렌더 트리 구축

DOM 트리가 구축되는 동안 브라우저는 렌더 트리를 구축.

표시해야할 순서와 문서의 시각적인 구성 요소로써 올바른 순서로 내용을 그려내기 위해 존재.

 

각 렌더러는 CSS 박스에 부합하는 사각형을 표시. 렌더러는 너비, 높이, 위치와 같은 기하학적 정보를 포함

 

DOM 트리와 렌더 트리의 관계

렌더러는 DOM 요소에 부합하지만 1:1 대응은 아니다. 예를 들어 head 요소와 같은 비시각적 DOM 요소는 렌더 트리에 추가되지 않고, display 속성에 none값이 할당 된 요소도 트리에 나타나지 않음

 

하나의 사각형으로 묘사할 수 없는 복잡한 구조는 여러 개의 시각 객체와 DOM 요소가 대응한다.

 

트리를 구축하는 과정

파이어폭스 : 프레젠테이션을 DOM 업데이트를 위한 리스너로 등록

웹킷 : 모든 DOM 노드에 attach 매서드가 있다. DOM 트리에 노드를 추가하면 새 노드의 attach 메소드를 호출하고, attach 메소드는 스타일을 결정하고 렌더러를 만듬

 

html 태그와 body 태그를 처리함으로써 렌더 트리 루트를 구성. 트리의 나머지 부분은 DOM 노드를 추가함으로써 구축된다.

스타일 계산

렌더 트리를 구축하려면 각 렌더 객체의 시각적 속성에 대한 계산이 필요하다.

스타일은 인라인 스타일 요소와 HTML의 시각적 속성과 같은 다양한 형태의 스타일 시트 포함.

HTML의 시각적 속성은 대응하는 CSS 스타일 속성으로 변환된다.

 

스타일 계산을 할 때의 어려움

  1. 수 많은 스타일 속성들을 수용하면서 메모리 문제 야기
  2. 최적화되어 있지 않다면 각 요소에 할당된 규칙을 찾는 것은 성능 문제를 야기
  3. 규칙을 적용하는 것은 계층 구조를 파악해야 하는 다단게 규칙 수반

이를 해결하기 위해 웹킷 노드는 스타일 객체를 참조. 스타일 객체는 일정 조건 아래 공유할 수 있다.

파이어폭스는 스타일 계산을 쉽게 처리하기 위해 규칙 트리와 스타일 문맥 트리라고 하는 두 개의 트리 더 가지고 있음

 

 

배치

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

 

HTML은 흐름 기반의 배치 모델 사용. 단일 경로를 통해 크기와 위치 정보를 계산할 수 있다는 것을 의미한다.

이런 흐름속에서 나중에 등장하는 요소는 앞서 등장한 요소의 위치와 크기에 영향을 미치지 않기에 배치는 왼쪽에서 오른쪽 혹은 위에서 아래로 흐른다.

단, 표는 하나 이상의 경로를 필요로 하기 때문에 예외가 된다.

 

배치는 <html> 요소에 해당하는 최상위 렌더러에서 시작.

모든 렌더러는 "배치" 또는 "리플로" 메서드를 갖는다. 각 렌더러는 배치해야 할 자식의 배치 메소드를 불러온다.

 

더티 비트 체제

작은 변경 때문에 전체를 다시 배치하지 않기 위해 브라우저는 더티 비트 체제 사용.

렌더러는 다시 배치할 필요가 있는 변경 요소와 그 자식을 "더티"라고 표시.

 

전역 배치와 점증 배치

배치는 렌더러 트리 전체에서 일어날 수 있는데 이것을 "전역"배치라고 함.

  • 글꼴 크기 변경과 같이 모든 렌더러에 영향을 주는 전역 스타일 변경
  • 화면 크기 변경에 의한 결과

더티 렌더러가 배치되는 경우에만 비동기적으로 점증 배치가 된다.

ex) 네트워크로부터 추가 내용을 받아 DOM 트리에 더해진 다음 새로운 렌더러가 렌더 트리에 붙을 때

 

비동기 배치와 동기 배치

점증 배치는 비동기로 실행. "offsetHeight"과 같은 스타일 정보를 요청하는 스크립트는 동기적으로 점증 배치를 실행하기도 함

전역 배치는 보통 동기적으로 실행된다.

 

최적화

배치가 "크기 변경" 또는 렌더러 위치 변화 때문에 실행되는 경우 렌더러의 크기는 다시 계산하지 않고 캐시로부터 가져온다.

배치과정

  1. 부모 렌더러가 자신의 너비를 결정
  2. 부모가 자식을 검토
    1. 자식 렌더러를 배치(자식의 x, y 설정)
    2. (부모와 자식이 더티하거나 전역 배치 등의 이유로) 필요하다면 자식 배치를 호출하여 자식의 높이 계산
  3. 부모는 자식의 누적된 높이와 여백, 패딩을 사용하여 자신의 높이를 설정. 이 값을 부모 렌더러의 부모가 사용
  4. 더티 비트 플래그 제거

그리기

화면에 내용을 표시하기 위한 렌더 트리가 탐색되고 렌더러의 "paint" 메서드 호출. 

전역과 점증

배치와 마찬가지로 전역 또는 점증 방식으로 수행한다.

 

점증

점증 그리기에서 일부 렌더러는 전체 트리에 영향을 주지 않는 방식으로 변경된다.

이때 변경된 렌더는 화면 위의 사각형을 무효화 하고 OS는 이것을 "더티" 영역으로 보고 "paint" 이벤트 발생시킨다.

 

그리기 순서

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

동적 변경

브라우저는 변경에 대해 최소한의 동작으로 반응하려고 노력한다.

  • 요소 색깔 변경 : 해당 요소 리페인팅만 발생
  • 요소 위치 변경 : 요소, 자식, 형제의 리페인팅과 재배치 발생
  • DOM 노드 추가 : 노드의 리페인팅과 재 배치 발생

html 요소의 글꼴 크기를 변경하는 것과 같은 큰 변경은 캐시를 무효화하고 트리 전체의 배치와 리페인팅이 발생한다.

 

렌더링 엔진의 스레드

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

통신의 경우 병렬 연결의 수는 2~6개로 제한된다.

 

이벤트 순환

브라우저의 주요 스레드는 이벤트 순환으로 처리 과정을 유지하기 위해 무한 순환된다. 배치와 그리기 같은 이벤트를 위해 대기하고 이벤트를 처리.

'로드맵 > 인터넷' 카테고리의 다른 글

HTTP 2, HTTP 3  (0) 2021.05.16
HTTP - Mozilla document 요약  (0) 2020.08.21
인터넷, DNS  (0) 2020.08.21
Comments