- Published on
브라우저는 어떻게 동작하는가? - 모던 웹브라우저가 동작하는 방법
- Author
- Name
- yceffort
🚧작성중 🚧
원문-How Browsers Work: Behind the scenes of modern web browsers
이 글을 요약/번역한 더 좋은 글이 네이버 d2에 있습니다. 개인적인 공부 차원에서 이 원문을 fully 한글로 번역하고 있습니다.
소개
웹 브라우저는 가장 널리 쓰이는 소프트웨어다. 이 글에서는, 브라우저가 어떻게 동작하는지 소개할 것이다. 이 글을 읽고 나면, google.com
을 타이핑 한 시점 부터 구글페이지가 브라우저에 보이기 까지, 어떤 일련의 과정이 있는지 알게 될 것이다.
목차
- 소개
- 이 글에서 소개하는 브라우저
- 브라우저의 주요 기능
- 브라우저의 상위 레벨 구조
- 렌더링 엔진
- 렌더링 엔진들
- 주요 흐름
- 주요 흐름 예제
- 파싱과 DOM 트리 구축
- 파싱: 일반
- 문법들
- Parser-Lexer 조합
- 변환
- 파싱 예제
- 어휘와 구문에 대한 공식적인 정의
- 파서의 종류
- 자동으로 파서 생성
- HTML 파서
- HTML 문법 정의
- 문맥으로 부터 자유롭지 못함
- HTML DTD
- DOM
- 파싱 알고리즘
- 토큰화 알고리즘
- 트리구조 알고리즘
- 파싱이 끝났을 경우 액션
- 브라우저 에러 처리
- CSS 파싱
- 웹킷 css 파서
- 스크립트와 스타일시트를 프로세싱하는 순서
- 스크립트
- 예측 파싱
- 스타일 시트
- 파싱: 일반
- 렌더 트리 구축
- 돔 트리와 렌더 트리와의 관계
- 트리를 구축하는 순서
- 스타일 연산
- 스타일 데이터 공유
- 파이어폭스 규칙 트리
- 구조체로 분리
- 규칙 트리를 활용하여 스타일 컨택스트 연산
- 쉬운 매칭을 위한 규칙 다루기
- 다단계 순서에 따라 규칙 적용하기
- 스타일 시트 다단계 순서
- 특정성
- 규칙정렬
- 점진적 프로세스
- 레이아웃
- Dirty bit system
- 글로벌 및 incremental 레이아웃
- 동기 및 비동기 레이아웃
- 최적화
- 레이아웃 프로세스
- 너비 계산
- 라인 브레이킹
- 페인팅
- 글로벌 및 incremental
- 페인팅 순서
- 파이어폭스 디스플레이 리스트
- 웹킷 사각형 저장소
- 다이나믹 변화
- 렌더링 엔진의 스레드
- 이벤트 루프
- CSS2 비쥬얼 모델
- 캔버스
- CSS box model
- 포지셔닝 스킴
- 박스 타입
- 포지셔닝
- relative
- floats
- absolute & fixed
- 층표현
- 리소스
1. 소개
1-1. 이 글에서 소개하는 브라우저
오늘날에는 5개의 메인 브라우저가 존재한다: 크롬, 익스플로러, 파이어폭스, 사파리 그리고 오페라. 모바일에서, 안드로이드 브라우저, 아이폰, 오페라 미니, 오페라 모바일, UC 브라우저 등등이 존재하는데, 웹킷을 베이스로 하는 오페라를 제외하고는 대부분이 크롬을 기반으로 하고 있다. 여기에서는 오픈소스 브라우저인 파이어폭스, 크롬, 사파리 (부분적으로 오픈소스)를 예로 들 것이다. stat counter에 따르면 2013년 6월 기준 이 세 브라우저가 차지하는 글로벌 데스크톱 브라우저 비중이 71%에 육박한다. 모바일에서는, 안드로이드 브라우저, 아이폰과 크롬 베이스 프라우저가 54%정도를 차지한다.
2019년 7월 현재 세 브라우저의 시장 점유율은 83% 정도를 차지한다.
1-2. 브라우저의 주요 기능
브라우저의 주요 기능은 사용자가 선택한 웹리소스를 서버에 요청하고, 브라우저 윈도우에 디스플레이하여 표현하는 것이다.일반적으로 리소스는 HTML 문서지만, 여기에는 PDF, 이미지, 혹은 기타 다른 유형이 있을 수도 있다. 이런 리소스의 위치는 사용자가 사용하는 URI(Uniform Resource Identifier)에 의해서 정해진다.
브라우저가 HTML 파일을 해석하고 표시하는 방법은 HTML과 CSS 명세에 따라서 정해진다. 이러한 명세는 웹 표준화 기구인 W3C(World Wide Web Consortium) 에서 정해진다. 수년 간 일부 브라우저는 사양의 일부만을 준수하고 자체 익스텐션을 개발했다. 이로인해 웹 개발자들 사이에서 심각한 호환성 문제가 발생했다. 오늘날 대부분의 브라우저는 이러한 명세를 거의 지킨다.
샤앙을 어긴 브라우저는... ^^
브라우저의 UI는 서로 대부분의 공통점을 가지고 있다. 이러한 공통점들을 예로 들자면
- URI를 넣을 수 있는 주소창
- 뒤로가기/앞으로가기 버튼
- 북마크 옵션
- 현재 문서를 새로 고치거나 멈출 수 있는 새로고침/멈춤 버튼
- 사용자의 홈페이지로 가는 홈버튼
이상하게도 이러한 공통점들은, 공식적인 명세로 지정된 것이 아님에도 불구하고 수년 동안 형성된 좋은 관행과 서로를 모방하는 브라우저의 특징에서 비롯된 것이다. HTML5 명세는 브라우저가 가져야하는 UI 요소를 정의하고 있지는 않지만, 일부 공통된 요소들을 나열한다. 그 중에는 주소 표시줄, 상태 표시줄, 도구 표시줄 등이 있다. 물론 파이어폭스나 크롬의 다운로드 관리자와 같은 특정 브라우저에만 있는 기능도 있다.
1-3 브라우저의 상위레벨 구조
- 유저 인터페이스: 주소창, 앞/뒤 버튼, 북마크 메뉴 등, 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분을 칭한다.
- 브라우저 엔진: 유저 인터페이스와 렌더링 엔진 사이의 동작을 제어
- 렌더링 엔진: 요청한 콘텐츠를 표시한다. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시한다.
- 네트워킹: HTTP 요청과 같은 네트워크 통신에 사용된다. 여기는 플랫폼 독립적인 인터페이스이고 각 플랫폼 하부에서 실행된다.
- UI 백엔드: 콤보 박츠, 창과 같은 기본적인 장치를 그린다. 플랫폼에서 명시하지 않은 일반적인 인터페이스로, OS 사용자 인터페이스 체계를 사용한다.
- 자바스크립트 인터프리터: 자바스크립트 코드를 파싱하고 실행을 위해 사용한다.
- 데이터 스토리지: 자료를 저장하는 계층이다. 쿠키를 저장하는 것과 같이, 모든 종류의 자원을 하드디스크에 저장할 때 사용한다. HTML5 명세에는 브라우저가 지원해야하는 웹 데이터 베이스가 정의 되어 있다.
크롬과 같은 경우에는 각 탭마다 별도의 렌더링 엔진을 사용한다. 각 탭은 다른 프로세스에서 실행된다.
2. 랜더링 엔진
렌더링 엔진의 역할은... 말그대로 렌더링이다. 렌더링은 여기서 요청받은 콘텐츠를 브라우저 화면에 보여주는 역할이다.
기본적으로 렌더링엔진은 HTML, XML, 그리고 이미지를 표시할 수 있다. 플러그인이나 익스텐션을 활용한다면, 다양한 데이터 타입, 예를 들어 PDF 등 도 표시할 수 있다. 그러나, 이번 챕터에서는 일반적인 활용 예제인 CSS로 포맷된 HTML과 이미지를 표시하는 법에 대해서 다룰 것이다.
2-1. 렌더링 엔진들
브라우저 마다 서로다른 렌더링 엔진을 사용하고 있다. 익스프롤러는 Trident를, 파이어폭스는 Gecko를 사용하며 사파리는 Webkit을 사용한다. 그리고 크롬과 15버전 부터 오페라는 Webkit에서 포크된 Blink를 사용한다.
Webkit은 오픈소스 렌더링 엔진으로, 리눅스 플랫폼에서 사용될 엔진으로 만들어졌다가 애플에 의해서 맥과 윈도우도 지원하게 되었다. 자세한 것은 webkit.org를 참조하면 된다.
2-2. 주요 흐름
렌더링 엔진은 통신을 통해 요청한 문서의 내용을 얻는 것부터 시작한다. 보통 문서내용은 8kb 단위로 전송된다. 렌더링 엔진의 기본적인 동작과정은 아래와 같다.
- DOM 트리 구축을 위한 HTML 파싱
- 렌더 트리 구축
- 렌더 트리 배치
- 렌더 트리 그리기
렌더링 엔진은 HTML 문서를 파싱하기 시작하며, 콘텐츠 트리 내부에서 태그를 DOM 노드로 변환한다.그리고 엔진은 CSS파일과 스타일 요소를 파싱하기 시작한다. 스타일 정보와 HTML 표시 규칙은 '렌더트리' 라고 부르는 또다른 트리를 생성한다.
렌더 트리는 색상 또는 면적과 같은 시각적 속성이 있는 사각형을 포함하고 잇는데, 정해진 순서대로 화면에 표시된다.
렌더 트리 구축 이후에는, 레이아웃 프로세스로 넘어간다. 이 말은, 각 노드가 화면의 정확한 위치에 표시되는 것을 의미한다. 다음은 UI 백엔드에서 렌더 트리의 각 노드를 가로지르며 모양을 만들어 내는 그리기 과정이다.
2-3. 주요 흐름 예제
웹킷의 주요 흐름
모질라 게코의 렌더링 엔진 주요 흐름
그림에서 보다시피, 웹킷과 게코가 약간 다른 용어를 쓰고 있지만 기본적인 흐름은 동일하다.
게코는 시각적으로 처리되는 렌더 트리를 프레임트리 라고 부르고, 각 요소를 프레임이라고 부르는 반면, 웹킷은 렌더 객체로 구성되어 있는 렌더 트리라는 용어를 사용한다. 웹킷은 요소를 배치하는데 레이아웃이라는 용어를 사용하지만, 개코는 리플로우라는 용어를 사용한다. attachment는 웹킷이 렌드 트리를 생성하기 위해 DOM노드와 시각정보를 연결하는 과정을 의미한다. 반면에 게코는 HTML과 DOM트리 사이에 콘텐츠 싱크라고 부르는 과정을 두는데, 이는 DOM 요소를 생성하는 과정으로 웹킷과 비슷하여 큰 의미 있는 차이점으로 보지는 않는다.
3. 파싱과 DOM 트리 구축
3-1. 파싱 일반
파싱은 렌더링 엔진에서 아주 중요한 작업이라서, 파싱에 대해서 아주 깊게 다룰 예쩡이다. 파싱에 대한 짧은 소개와 함께 시작한다.
문서를 파싱한다는 것은 문서 구조를 읽을 수 있는 코드로 변환한다는 것을 의미한다. 파싱의 결과는 보통 노드의 트리로 나타나는데, 이 노드의 트리는 문서의 구조를 나타낸다. 이것을 파스트리 또는 신택스 트리 라고 한다.
예를 들어, 2+3-1
은 아래와 같은 트리구조로 나타낼 수 있다.
3-1-1. 문법들
파싱은 문서에 작성된 언어 또는 형식의 규칙을 따른다. 파싱할 수 있는 모든 형식은 정해진 용어와 구문 규칙에 따라야 한다. 이것을 문맥 자유 문법이라고 한다. 인간의 언어는 이런 모습과는 다르기 때문에 기계적으로 파싱이 불가능하다.
3-1-2. Parser-Lexer 조합
파싱은 두가지 서브 프로세스로 나눌수 있다. 렉시컬 분석과 신택스 분석.
렉시컬 분석은 입력값을 토큰으로 나누는 과정이다. 토큰은 유효하게 구성된 단위의 집합이라고 볼 수 있다. 인간의 언어에서는 사전적으로 뜻이 있는 단어들을 의미한다.
신택스 분석은 언어를 신택스 규칙에 적용하는 것이다.
파서는 보통 두가지 일을 하는데 렉서 (토크나이저라고도 한다)는 입력값을 유효한 토큰 값으로 나누는 일을 하고, 파서는 언어 규칙에 따라 문서구조를 분석하여 파싱트리를 생성한다. 렉서는 공백, 줄바꿈 같은 의미 없는 문자를 제거한다.
D2에서는 렉서를 어휘분석으로, 파서는 구문분석으로 정의했다.
파싱과정은 반복된다. 파서는 렉어세 새로운 토큰이 있는지 질의하고, 토큰을 신택스 규칙에 맞추려고 한다. 만약 맞는 규칙이 있다면, 토큰에 해당하는 노드가 파싱트리에 추가되고, 파서는 또다른 토큰을 요청하게 된다.
만약 일치하는 규칙이 없다면, 파서는 토큰을 내부에 저장하고 토큰과 일치하는 규칙이 발견될때 까지 요청한다. 맞는 규칙이 계속해서 없다면 예외처리를 하는데, 이는 문서가 유효하지 않고 신택스 오류가 있다는 것을 의미한다.
3-1-3. 변환
대부분의 경우 파스 트리가 마지막 결과물이 아니다. 파싱은 보통 변환과정에서 사용되는데, 이 과정은 입력된 문서를 다른 형식으로 변환하는 과정을 의미한다. 이와 같은 예로 컴파일이 있다. 소스 코드를 기계 코드로 만드는 컴파일러는, 파싱트리 생성후 기계 코드 문서로 변환한다.
3-1-4. 파싱 예제
이전 그림에서 수학식을 파스 트리로 만들어 보았다. 간단한 수학 언어를 정의하고, 파싱과정을 살펴보자.
언어: 이 수학언어에는 정수, 더하기, 빼기가 있다.
신택스:
- 기본적인 요소로는 표현식, 항, 연산자가 있다
- 언어에 포함되는 표현식 갯수에 제한이 없다
- 항 뒤에 연산자, 그뒤에 또다른 항이 나오는 형태로 정의한다
- 연산자는 더하기 토큰 또는 빼기 토큰이다
- 정수 토큰 또는 하나의 표현식은 항이다.
이제 아까 예제인 2+3-1
을 분석해보자.
규칙에 맞는 첫 번째 문자열은 2다. 규칙 5에 따르면 이것은 하나의 항으로 볼 수 있다. 두 번째로 맞는 것은 2+3 인데 이것은 항 뒤에 연산자와 또 다른 항이 등장한다는 세 번째 규칙과 일치한다. 입력 값의 마지막 부분까지 진행하면 또 다른 일치를 발견할 수 있다. 2+3은 항과 연산자와 항으로 구성된 하나의 새로운 항이라는 것을 알고 있기 때문에 2+3-1은 하나의 표현식이 된다. 2++은 어떤 규칙과도 맞지 않기 때문에 유효하지 않은 입력이 된다.
3-1-5. 어휘와 구문에 대한 공식적인 정의
어휘는 보통 정규표현식을 활용한다.
예를 들어 이 수학언어는 아래처럼 표현할 수 있을 것이다.
INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -
보시다시피, 정수도 정규표현식으로 정의했다.
신택스는 보통 BNF라고 부르는 형식을 따라서 정의한다.
expression := term operation term
operation := PLUS | MINUS
term := INTEGER | expression
문법이 문맥 자유 문법이라면 언어는 정규 파서로 파싱할 수 있다. 문맥 자유 문법은 완전히 BNF로 표현 가능한 문법으로 보면 된다.
3-1-6. 파서의 종류
파서는 top-down 파서와 bottom up 파서 이렇게 두가지로 나누어진다. 직관적으로 설명하자면, top-down 파서는 위에서 부터 상위구조에서 부터 일치하는 부분을 찾기 시작하지만, bottom-up의 경우에는 밑에서 부터 점차 높은 수준으로 찾는다.
두 종류 파서가 예제를 어떻게 파싱하는지 살펴보자.
top-down 파서는 상위 구조에서 부터 시작한다. 2+3
에 해당하는 표현식을 찾는다. 그리고 2+3-1
를 찾을 것이다. 표현식을 찾는 과정은, 다른 규칙을 점진적으로 계속해서 찾는 방식인데 가장 높은 수준의 규칙을 먼저 찾는 것을 시작한다.
반면에 bottom-up은 입력값이 규칙에 맞을때 까지 찾아서 맞는 입력값 규칙으로 바꾸는데, 이는 입력값의 끝까지 진행된다. 부분적으로 일치하는 표현식은 파서의 스택에 쌓인다.
Stack | input |
---|---|
2 + 3 - 1 | |
항 | + 3 - 1 |
항 연산자 | 3 -1 |
표현식 | -1 |
표현식 연산자 | 1 |
표현식 |
bottom up 파서는 shift-reduce 파서라고도 불리우는데, 왜냐하면 입력값이 오른쪽으로 이동하면서 신택스 규칙으로 남는 것이 점차 감소하기 때문이다.
3-1-7. 파서 자동 생성
파서를 생성해 줄 수 있는 도구를 파서 생성기라고 한다. 이 생성기에 문법을 제공하고, 어휘와 신택스 규칙을 적용하여 파서를 생성한다. 파서를 작성하려면 파싱에 대한 깊은 이해가 필요하며, 수작업으로 최적화된 파서를 제공하는 것은 쉽지 않으므로 파서 생성기가 유용할 수 있다.
Webkit의 경우에는 flex라고 불리우는 lexer와 bison이라고 불리우는 파서 생성기를 사용한다. Flex는 토큰의 정규 표현식 정의를 포함하는 파일을 입력값으로 받고, bison은 BNF형식의 언어 신택스 규칙을 입력 받는다.