브라우저의 작동 방식

최신 웹브라우저의 이면

Tali Garsiel
Tali Garsiel

머리말

WebKit 및 Gecko의 내부 운영에 관한 이 포괄적인 입문서는 이스라엘 개발자인 탈리 가르실이 수행한 많은 연구 결과입니다. 몇 년에 걸쳐 그는 브라우저 내부 기능에 관한 모든 게시된 데이터를 검토하고 웹 브라우저 소스 코드를 읽는 데 많은 시간을 할애했습니다. 그녀는 이렇게 했습니다.

웹 개발자는 브라우저 작업의 내부 기능을 학습하면 더 나은 결정을 내리고 개발 권장사항의 근거를 파악하는 데 도움이 됩니다. 다소 긴 문서이기는 하지만 자세히 살펴보는 것이 좋습니다. 도움이 되었다니 다행입니다.

폴 아이리시, Chrome 개발자 관계팀

소개

웹브라우저는 가장 널리 사용되는 소프트웨어입니다. 이 입문서에서는 AI가 보이지 않는 곳에서 어떻게 작동하는지 설명합니다. 브라우저 화면에 Google 페이지가 표시될 때까지 주소 표시줄에 google.com를 입력하면 어떤 일이 일어나는지 볼 수 있습니다.

살펴볼 브라우저

오늘날 데스크톱에서 사용되는 주요 브라우저는 Chrome, Internet Explorer, Firefox, Safari 및 Opera입니다. 휴대기기의 기본 브라우저는 Android 브라우저, iPhone, Opera Mini 및 Opera Mobile, UC Browser, Nokia S40/S60 브라우저, Chrome이며, Opera 브라우저를 제외한 모든 브라우저는 WebKit을 기반으로 합니다. 오픈소스 브라우저인 Firefox, Chrome, Safari (일부는 오픈소스임)의 예를 보여드리겠습니다. StatCounter 통계 (2013년 6월 현재)에 따르면 Chrome, Firefox, Safari가 전 세계 데스크톱 브라우저 사용의 약 71% 를 차지합니다. 모바일의 경우 Android 브라우저, iPhone 및 Chrome이 약 54% 를 차지합니다.

브라우저의 기본 기능

브라우저의 주요 기능은 선택한 웹 리소스를 서버에 요청하고 브라우저 창에 표시함으로써 제시하는 것입니다. 리소스는 일반적으로 HTML 문서이지만 PDF, 이미지 또는 다른 유형의 콘텐츠일 수도 있습니다. 리소스의 위치는 사용자가 URI (Uniform Resource Identifier)를 사용하여 지정합니다.

브라우저가 HTML 파일을 해석하고 표시하는 방법은 HTML 및 CSS 사양에 지정되어 있습니다. 다음 사양은 웹 표준 조직인 W3C (World Wide Web Consortium)에서 관리합니다. 수년 동안 브라우저는 사양의 일부만 준수했으며 자체 확장 프로그램을 개발했습니다. 이로 인해 웹 작성자에게 심각한 호환성 문제가 발생했습니다. 오늘날 대부분의 브라우저는 사양을 거의 준수합니다.

브라우저 사용자 인터페이스는 서로 공통점이 많습니다. 일반적인 사용자 인터페이스 요소는 다음과 같습니다.

  1. URI 삽입을 위한 주소 표시줄
  2. 뒤로 및 앞으로 버튼
  3. 북마크 옵션
  4. 현재 문서의 로드를 새로 고치거나 중지하기 위한 새로고침 및 중지 버튼
  5. 홈페이지로 이동하는 홈 버튼

이상하게도 브라우저의 사용자 인터페이스는 정식 사양에는 명시되어 있지 않으며, 브라우저의 사용자 인터페이스가 서로 모방하며 오랜 경험을 바탕으로 형성된 모범 사례에서 비롯된 것입니다. HTML5 사양은 브라우저에 반드시 필요한 UI 요소를 정의하지 않지만 몇 가지 일반적인 요소를 나열합니다. 그중에는 주소 표시줄, 상태 표시줄, 툴바가 있습니다. 물론 Firefox의 다운로드 관리자와 같은 특정 브라우저에만 고유한 기능이 있습니다.

대략적인 인프라

브라우저의 기본 구성요소는 다음과 같습니다.

  1. 사용자 인터페이스: 주소 표시줄, 뒤로-앞으로 버튼, 북마크 메뉴 등이 포함됩니다. 요청한 페이지가 표시되는 창을 제외한 브라우저의 모든 부분이 표시됩니다.
  2. 브라우저 엔진: UI와 렌더링 엔진 간의 작업을 마샬링합니다.
  3. 렌더링 엔진: 요청된 콘텐츠를 표시합니다. 예를 들어 요청된 콘텐츠가 HTML이면 렌더링 엔진이 HTML 및 CSS를 파싱하고 파싱된 콘텐츠를 화면에 표시합니다.
  4. 네트워킹: HTTP 요청과 같은 네트워크 호출에 플랫폼 독립적 인터페이스 뒤에 있는 여러 플랫폼에 서로 다른 구현을 사용합니다.
  5. UI 백엔드: 콤보 상자 및 창과 같은 기본 위젯을 그리는 데 사용됩니다. 이 백엔드는 플랫폼에 구애받지 않는 일반 인터페이스를 노출합니다. 그 아래에는 운영체제 사용자 인터페이스 메서드가 사용됩니다.
  6. JavaScript 인터프리터. JavaScript 코드를 파싱하고 실행하는 데 사용됩니다.
  7. 데이터 스토리지. 지속성 레이어입니다. 브라우저는 쿠키와 같은 모든 종류의 데이터를 로컬에 저장해야 합니다. 브라우저는 localStorage, IndexedDB, WebSQL, FileSystem과 같은 스토리지 메커니즘도 지원합니다.
브라우저 구성요소
그림 1: 브라우저 구성요소

Chrome과 같은 브라우저는 각 탭마다 하나씩, 여러 개의 렌더링 엔진 인스턴스를 실행한다는 점에 유의해야 합니다. 각 탭은 별도의 프로세스에서 실행됩니다.

렌더링 엔진

렌더링 엔진의 책임은 브라우저 화면에 요청된 콘텐츠를 표시하는 렌더링인 렌더링입니다.

기본적으로 렌더링 엔진은 HTML 및 XML 문서와 이미지를 표시할 수 있습니다. 플러그인 또는 확장 프로그램을 통해 다른 유형의 데이터를 표시할 수 있습니다(예: PDF 뷰어 플러그인을 사용하여 PDF 문서 표시). 그러나 이 장에서는 주요 사용 사례인 HTML 및 CSS를 사용하여 형식이 지정된 이미지를 표시하는 데 중점을 둡니다.

브라우저마다 다른 렌더링 엔진을 사용합니다. Internet Explorer는 Trident를 사용하고 Firefox는 Gecko를, Safari는 WebKit을 사용합니다. Chrome 및 Opera (버전 15부터)는 WebKit의 포크인 Blink를 사용합니다.

WebKit는 오픈소스 렌더링 엔진으로, Linux 플랫폼용 엔진으로 시작했으며 Mac과 Windows를 지원하도록 Apple에서 수정했습니다.

기본 흐름

렌더링 엔진이 네트워킹 레이어에서 요청된 문서의 콘텐츠를 가져오기 시작합니다. 이 작업은 일반적으로 8KB 청크로 수행됩니다.

이후 렌더링 엔진의 기본 흐름은 다음과 같습니다.

렌더링 엔진 기본 흐름
그림 2: 렌더링 엔진 기본 흐름

렌더링 엔진이 HTML 문서 파싱을 시작하고 '콘텐츠 트리'라는 트리의 DOM 노드로 요소를 변환합니다. 엔진은 외부 CSS 파일과 스타일 요소 모두에서 스타일 데이터를 파싱합니다. HTML의 시각적 안내와 함께 스타일 지정 정보는 또 다른 트리, 즉 렌더링 트리를 만드는 데 사용됩니다.

렌더링 트리에는 색상 및 크기와 같은 시각적 속성이 있는 직사각형이 포함됩니다. 직사각형은 화면에 표시되는 올바른 순서대로 표시되어 있습니다.

렌더링 트리를 생성한 후에는 'layout' 프로세스를 거칩니다. 즉, 화면에 표시되어야 하는 위치의 정확한 좌표를 각 노드에 제공해야 합니다. 다음 단계는 페인팅입니다. 렌더링 트리를 순회하고 UI 백엔드 레이어를 사용하여 각 노드를 페인팅합니다.

이 과정은 점진적임을 알아두어야 합니다. 더 나은 사용자 환경을 위해 렌더링 엔진은 최대한 빨리 화면에 콘텐츠를 표시하려고 시도합니다. 렌더링 트리를 빌드하고 배치하기 전에 모든 HTML이 파싱될 때까지 기다리지 않습니다. 콘텐츠의 일부는 파싱되고 표시되지만, 나머지 콘텐츠는 네트워크에서 계속 유입됩니다.

기본 흐름 예시

WebKit 기본 흐름
그림 3: WebKit 기본 흐름
Mozilla의 Gecko 렌더링 엔진 기본 흐름
그림 4: Mozilla의 Gecko 렌더링 엔진 기본 흐름

그림 3과 4를 보면 WebKit과 Gecko가 약간 다른 용어를 사용하지만 흐름은 기본적으로 같다는 것을 알 수 있습니다.

Gecko는 시각적으로 형식이 지정된 요소의 트리를 '프레임 트리'라고 부릅니다. 각 요소는 프레임입니다. WebKit은 '렌더링 트리'라는 용어를 사용하며 '객체 렌더링'으로 구성됩니다. WebKit에서는 요소 배치에 '레이아웃'이라는 용어를 사용하지만 Gecko에서는 이를 '리플로우'라고 합니다. '첨부파일'은 DOM 노드와 시각적 정보를 연결하여 렌더링 트리를 만드는 WebKit의 용어입니다. 의미상 약간의 차이는 Gecko가 HTML과 DOM 트리 사이에 추가 레이어가 있다는 것입니다. 이는 '콘텐츠 싱크'라고 하며 DOM 요소를 만드는 공장입니다. 절차의 각 부분에 대해 알아보겠습니다.

파싱 - 일반

파싱은 렌더링 엔진 내에서 매우 중요한 프로세스이므로 좀 더 자세히 살펴보겠습니다. 먼저 파싱에 관해 간단히 소개하겠습니다.

문서를 파싱한다는 것은 코드에서 사용할 수 있는 구조로 문서를 변환하는 것을 의미합니다. 파싱 결과는 일반적으로 문서의 구조를 나타내는 노드 트리입니다. 이를 파싱 트리 또는 구문 트리라고 합니다.

예를 들어 2 + 3 - 1 표현식을 파싱하면 다음 트리를 반환할 수 있습니다.

수학 표현식 트리 노드입니다.
그림 5: 수학 표현식 트리 노드

문법

파싱은 문서가 준수하는 구문 규칙(작성된 언어 또는 형식)을 기반으로 합니다. 파싱할 수 있는 모든 형식에는 어휘와 구문 규칙으로 구성된 결정론적 문법이 있어야 합니다. 이를 컨텍스트 자유 문법이라고 합니다. 인간 언어는 이러한 언어가 아니므로 기존의 파싱 기술로 파싱할 수 없습니다.

파서 - 렉서 조합

파싱은 어휘 분석과 구문 분석의 두 가지 하위 프로세스로 나눌 수 있습니다.

어휘 분석은 입력을 토큰으로 나누는 프로세스입니다. 토큰은 유효한 빌딩 블록의 모음인 언어 용어입니다. 인간 언어의 경우 해당 언어의 사전에 나오는 모든 단어로 구성됩니다.

구문 분석은 언어 구문 규칙을 적용하는 것입니다.

일반적으로 파서는 입력을 유효한 토큰으로 분할하는 렉서 (tokenizer라고도 함)와 언어 문법 규칙에 따라 문서 구조를 분석하여 파싱 트리를 생성하는 파서라는 두 가지 구성요소로 작업을 나눕니다.

Lexer는 공백 및 줄바꿈과 같은 관련 없는 문자를 제거하는 방법을 알고 있습니다.

소스 문서에서 트리 파싱으로
그림 6: 소스 문서에서 트리 파싱

파싱 프로세스는 반복적입니다. 파서는 보통 렉서에게 새 토큰을 요청하고 이 토큰을 구문 규칙 중 하나와 일치시키려고 시도합니다. 규칙이 일치하면 토큰에 해당하는 노드가 파싱 트리에 추가되고 파서는 다른 토큰을 요청합니다.

일치하는 규칙이 없으면 파서는 토큰을 내부적으로 저장하고 내부에 저장된 모든 토큰과 일치하는 규칙을 찾을 때까지 계속 토큰을 요청합니다. 규칙을 찾을 수 없으면 파서가 예외를 발생시킵니다. 이는 문서가 유효하지 않으며 구문 오류를 포함하고 있음을 의미합니다.

Translation

대부분의 경우 파싱 트리는 최종 제품이 아닙니다. 파싱은 입력 문서를 다른 형식으로 변환하는 등 번역에서 자주 사용됩니다. 컴파일을 예로 들 수 있습니다. 소스 코드를 기계어 코드로 컴파일하는 컴파일러는 먼저 이를 파싱 트리로 파싱한 다음 트리를 기계어 코드 문서로 변환합니다.

컴파일 흐름
그림 7: 컴파일 흐름

파싱 예

그림 5에서는 수학 표현식으로부터 파싱 트리를 구축했습니다. 간단한 수학적 언어를 정의하고 파싱 프로세스를 살펴보겠습니다.

구문:

  1. 언어 구문 구성요소는 표현식, 용어, 연산입니다.
  2. Google 언어에는 여러 가지 표현이 포함될 수 있습니다.
  3. 표현식은 '용어' 다음에 '연산', 또 다른 용어가 뒤따라오는 식으로 정의됨
  4. 연산은 더하기 토큰 또는 빼기 토큰입니다.
  5. 용어는 정수 토큰 또는 표현식입니다.

입력 2 + 3 - 1를 분석해 보겠습니다.

규칙과 일치하는 첫 번째 하위 문자열은 2입니다. 규칙 #5에 따르면 이 하위 문자열은 검색어입니다. 두 번째 일치는 2 + 3이며, 세 번째 규칙과 일치합니다. 즉, 검색어 다음에 연산이 있으며, 그 뒤에 다른 검색어가 이어집니다. 다음 일치 항목은 입력이 끝날 때만 이루어집니다. 2 + 3 - 1는 표현식입니다. 2 + 3가 검색어라는 것을 이미 알고 있으므로 항 뒤에 연산이 이어지고, 2 + +는 어떤 규칙과도 일치하지 않으므로 잘못된 입력입니다.

어휘 및 구문의 공식 정의

어휘는 일반적으로 정규 표현식으로 표현됩니다.

예를 들어 우리의 언어는 다음과 같이 정의됩니다.

INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -

보시다시피 정수는 정규 표현식으로 정의됩니다.

구문은 일반적으로 BNF라는 형식으로 정의됩니다. YouTube의 언어는 다음과 같이 정의됩니다.

expression :=  term  operation  term
operation :=  PLUS | MINUS
term := INTEGER | expression

문법이 맥락 없는 문법인 경우 일반 파서가 언어를 파싱할 수 있다고 말씀드렸습니다. 컨텍스트 자유 문법의 직관적인 정의는 BNF에서 완전히 표현할 수 있는 문법입니다. 공식적인 정의는 맥락 없는 문법에 관한 위키백과의 문서를 참고하세요.

파서 유형

파서에는 하향식 파서와 상향식 파서, 두 가지 유형이 있습니다. 직관적으로 설명하자면, 하향식 파서는 구문의 상위 수준 구조를 검토하고 규칙 일치를 찾으려고 한다는 것입니다. 상향식 파서는 입력으로 시작하여 하위 수준 규칙부터 높은 수준의 규칙이 충족될 때까지 점진적으로 문법 규칙으로 변환합니다.

이 두 가지 유형의 파서가 이 예를 어떻게 파싱하는지 살펴보겠습니다.

하향식 파서는 더 높은 수준의 규칙에서 시작됩니다. 즉, 2 + 3을 표현식으로 식별합니다. 그런 다음 2 + 3 - 1을 표현식으로 식별합니다 (표현식을 식별하는 프로세스가 진화하여 다른 규칙과 일치하지만 시작점이 가장 높은 규칙임).

상향식 파서는 규칙이 일치할 때까지 입력을 스캔합니다. 그러면 일치하는 입력이 규칙으로 대체됩니다. 이 작업은 입력이 끝날 때까지 계속됩니다. 부분적으로 일치하는 표현식은 파서의 스택에 배치됩니다.

스택 입력
2 + 3 - 1
term +3~1
검색어 연산 3~1
표현식 - 1명
표현식 연산 1
표현식 -

이러한 유형의 상향식 파서를 시프트 감소 파서라고 합니다. 입력이 오른쪽으로 이동 (포인터가 먼저 입력 시작을 가리키고 오른쪽으로 이동한다고 상상해 볼 수 있음)하고 점진적으로 문법 규칙으로 축소되기 때문입니다.

파서 자동 생성

파서를 생성할 수 있는 도구가 있습니다. 언어 문법(어휘 및 구문 규칙)을 사용자에게 제공하면 작동하는 파서를 생성합니다. 파서를 만들려면 파싱에 대한 깊은 이해가 필요하며 최적화된 파서를 손으로 만들기는 쉽지 않으므로 파서 생성기가 매우 유용할 수 있습니다.

WebKit은 잘 알려진 두 가지 파서 생성기를 사용합니다. 하나는 lexer를 만드는 데 Flex이고, 다른 하나는 파서를 만드는 데 Bison입니다 (Lex와 Yacc라는 이름으로 접할 수 있습니다). Flex 입력은 토큰의 정규 표현식 정의가 포함된 파일입니다. Bison의 입력은 BNF 형식의 언어 문법 규칙입니다.

HTML 파서

HTML 파서의 역할은 HTML 마크업을 파싱 트리로 파싱하는 것입니다.

HTML 문법

HTML의 어휘와 구문은 W3C 조직에서 작성한 사양에 정의되어 있습니다.

파싱 소개에서 확인한 것처럼 문법 문법은 BNF와 같은 형식을 사용하여 공식적으로 정의할 수 있습니다.

불행히도 기존의 모든 파서 주제는 HTML에 적용되지 않습니다. 재미로 한 것이 아니라 CSS 및 JavaScript를 파싱하는 데 사용됩니다. HTML은 파서에 필요한 컨텍스트가 없는 문법으로 쉽게 정의할 수 없습니다.

HTML을 정의하는 형식인 DTD (Document Type Definition)가 있지만, 컨텍스트가 없는 문법은 아닙니다.

처음 보면 이상하게 보입니다. HTML이 XML에 가깝습니다. 사용 가능한 XML 파서는 많이 있습니다. HTML의 XML 변형인 XHTML이 있습니다. 가장 큰 차이점은 무엇일까요?

차이점은 HTML 접근 방식이 더 '관대'하다는 것입니다. 즉, 특정 태그를 생략하거나 (그런 다음 암시적으로 추가됨) 시작 또는 종료 태그를 생략하는 등의 작업을 수행할 수 있습니다. XML의 까다롭고 까다로운 구문과 달리 전체적으로 'soft' 구문입니다.

이 사소한 디테일이 큰 차이를 만들어냅니다. 이것이 HTML이 널리 사용되는 가장 큰 이유이기도 합니다. HTML은 사용자의 실수를 용서해주고 웹 작성자의 편의를 높여 줍니다. 반면에, 공식적인 문법을 작성하기가 어렵습니다. 요약하자면, HTML은 문법에 맥락을 고려하지 않으므로 기존의 파서로는 쉽게 파싱할 수 없습니다. HTML은 XML 파서에서 파싱할 수 없습니다.

HTML DTD

HTML 정의가 DTD 형식입니다. 이 형식은 SGML 계열의 언어를 정의하는 데 사용됩니다. 형식에는 허용되는 모든 요소, 요소의 속성 및 계층에 대한 정의가 포함됩니다. 앞서 살펴봤듯이 HTML DTD는 컨텍스트 자유로운 문법을 형성하지 않습니다.

DTD에는 몇 가지 변형이 있습니다. 엄격 모드는 전적으로 사양만을 준수하지만, 다른 모드에서는 이전에 브라우저에서 사용한 마크업에 대한 지원을 포함합니다. 목적은 이전 콘텐츠와의 하위 호환성을 지원하는 것입니다. 현재의 엄격한 DTD는 다음과 같습니다. www.w3.org/TR/html4/strict.dtd

DOM

출력 트리('파싱 트리')는 DOM 요소 및 속성 노드의 트리입니다. DOM은 Document Object Model의 약자입니다. 인코더는 HTML 문서를 객체 방식으로 표현하고 자바스크립트와 같은 외부 세계에 대한 HTML 요소의 인터페이스입니다.

트리의 루트는 'Document' 객체입니다.

DOM은 마크업과 거의 일대일 관계입니다. 예를 들면 다음과 같습니다.

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

이 마크업은 다음 DOM 트리로 변환됩니다.

예제 마크업의 DOM 트리
그림 8: 예시 마크업의 DOM 트리

HTML과 마찬가지로 DOM은 W3C 조직에서 지정합니다. www.w3.org/DOM/DOMTR을 참고하세요. 문서 조작을 위한 일반 사양입니다. 특정 모듈은 HTML 관련 요소를 설명합니다. HTML 정의는 www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/idl-definitions.html에서 확인할 수 있습니다.

트리에 DOM 노드가 포함되어 있다고 하면 트리가 DOM 인터페이스 중 하나를 구현하는 요소로 구성된다는 의미입니다. 브라우저는 내부적으로 브라우저에서 사용하는 다른 속성이 있는 구체적인 구현을 사용합니다.

파싱 알고리즘

이전 섹션에서 본 것처럼, HTML은 일반 하향식 또는 상향식 파서를 사용하여 파싱할 수 없습니다.

이유는 다음과 같습니다.

  1. 언어의 관용적인 특성
  2. 브라우저는 기존의 오류 허용성을 갖추고 있어 잘못된 HTML에 대해 잘 알려진 사례를 지원합니다.
  3. 파싱 프로세스가 재진입입니다. 다른 언어의 경우 파싱 중에 소스가 변경되지 않지만 HTML에서는 동적 코드 (예: document.write() 호출을 포함하는 스크립트 요소)가 토큰을 더 추가할 수 있으므로 파싱 프로세스에서 실제로 입력을 수정할 수 있습니다.

일반 파싱 기술을 사용할 수 없습니다. 브라우저가 HTML을 파싱하기 위한 맞춤 파서를 만듭니다.

파싱 알고리즘은 HTML5 사양에 자세히 설명되어 있습니다. 알고리즘은 토큰화와 트리 생성이라는 두 단계로 구성됩니다.

토큰화는 입력을 토큰으로 파싱하는 어휘 분석입니다. HTML 토큰에는 시작 태그, 종료 태그, 속성 이름 및 속성 값이 있습니다.

tokenizer는 토큰을 인식하여 트리 생성자에 제공하고 다음 문자를 인식하기 위해 다음 문자를 사용하는 식으로 입력이 끝날 때까지 계속됩니다.

HTML 파싱 흐름 (HTML5 사양에서 가져옴)
그림 9: HTML 파싱 흐름 (HTML5 사양에서 가져옴)

토큰화 알고리즘

알고리즘의 출력은 HTML 토큰입니다. 알고리즘은 상태 머신으로 표현됩니다. 각 상태는 입력 스트림의 문자를 하나 이상 사용하고 이러한 문자에 따라 다음 상태를 업데이트합니다. 이러한 결정은 현재 토큰화 상태와 트리 구성 상태의 영향을 받습니다. 즉, 동일하게 소비된 문자는 현재 상태에 따라 올바른 다음 상태에 대해 다른 결과를 생성합니다. 알고리즘은 완전히 설명하기에는 너무 복잡하므로 원리를 이해하는 데 도움이 되는 간단한 예를 살펴보겠습니다.

기본 예시 - 다음 HTML 토큰화:

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

초기 상태는 '데이터 상태'입니다. < 문자가 있으면 상태는 '태그 열기 상태'로 변경됩니다. a-z 문자를 소비하면 '시작 태그 토큰'이 생성되고 상태가 '태그 이름 상태'로 변경됩니다. > 문자가 소비될 때까지 이 상태로 유지됩니다. 각 문자는 새 토큰 이름에 추가됩니다. 이 경우 생성된 토큰은 html 토큰입니다.

> 태그에 도달하면 현재 토큰이 내보내지고 상태가 다시 '데이터 상태'로 변경됩니다. <body> 태그는 동일한 단계로 처리됩니다. 지금까지 htmlbody 태그를 내보냈습니다. 이제 'Data state'로 돌아왔습니다. Hello worldH 문자를 소비하면 문자 토큰이 생성되고 내보내지며, </body><에 도달할 때까지 계속됩니다. Hello world의 각 문자에 관한 문자 토큰을 내보냅니다.

이제 '태그 열기 상태'로 돌아갑니다. 다음 입력 /을 사용하면 end tag token이 생성되고 '태그 이름 상태'로 이동합니다. 다시 >에 도달할 때까지 이 상태를 유지합니다.그런 다음 새 태그 토큰이 내보내기되고 '데이터 상태'로 돌아갑니다. </html> 입력은 이전 사례와 같이 처리됩니다.

예시 입력 토큰화
그림 10: 입력 예시 토큰화

트리 구성 알고리즘

파서가 생성되면 Document 객체가 생성됩니다. 트리 생성 단계에서 루트에 문서가 있는 DOM 트리가 수정되고 요소가 문서에 추가됩니다. tokenizer에서 내보낸 각 노드는 트리 생성자에 의해 처리됩니다. 각 토큰에 대해 사양은 관련 DOM 요소를 정의하고 이 토큰에 대해 생성됩니다. 요소가 DOM 트리에 추가되며 열린 요소 스택에도 추가됩니다. 이 스택은 중첩 불일치 및 닫히지 않은 태그를 수정하는 데 사용됩니다. 이 알고리즘은 상태 머신이라고도 합니다. 이러한 상태를 '삽입 모드'라고 합니다.

예시 입력의 트리 생성 프로세스를 살펴보겠습니다.

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

트리 생성 단계의 입력은 토큰화 단계의 토큰 시퀀스입니다. 첫 번째 모드는 '초기 모드'입니다. 'html' 토큰을 수신하면 'html 이전' 모드로 전환되고 이 모드에서 토큰이 재처리됩니다. 이렇게 하면 HTMLHTMLElement 요소가 생성되어 루트 Document 객체에 추가됩니다.

상태가 'before head'로 변경됩니다. 그러면 'body' 토큰이 수신됩니다. HTMLHeadElement는 'head' 토큰이 없지만 암시적으로 생성되어 트리에 추가됩니다.

이제 'in head' 모드와 'after head' 모드로 이동합니다. 본문 토큰이 다시 처리되고 HTMLBodyElement가 생성 및 삽입되며 모드가 '본문 내'로 전송됩니다.

이제 'Hello world' 문자열의 문자 토큰이 수신됩니다. 첫 번째 문자에는 '텍스트' 노드가 생성되어 삽입되고, 나머지 문자는 해당 노드에 추가됩니다.

본문 종료 토큰을 수신하면 'after body' 모드로 전환됩니다. 이제 html 종료 태그가 수신되며, 이 태그는 'after after body' 모드로 전환됩니다. 파일 끝 토큰을 수신하면 파싱이 종료됩니다.

예시 HTML의 트리 생성
그림 11: 예시 HTML의 트리 생성

파싱이 완료될 때의 작업

이 단계에서 브라우저는 문서를 대화형으로 표시하고 '지연' 모드에 있는 스크립트(문서가 파싱된 후 실행해야 하는 스크립트)의 파싱을 시작합니다. 그러면 문서 상태가 '완료'로 설정되고 '로드' 이벤트가 실행됩니다.

HTML5 사양에서 토큰화 및 트리 생성을 위한 전체 알고리즘을 확인할 수 있습니다.

브라우저의 오류 허용 범위

HTML 페이지에 '잘못된 구문' 오류가 발생하지 않습니다. 브라우저는 잘못된 콘텐츠를 수정하고 계속 진행합니다.

이 HTML을 살펴보겠습니다.

<html>
  <mytag>
  </mytag>
  <div>
  <p>
  </div>
    Really lousy HTML
  </p>
</html>

약 100만 개의 규칙 ('mytag'는 표준 태그가 아님, 'p' 및 'div' 요소의 잘못된 중첩 등)을 위반했지만 브라우저에서 여전히 올바르게 표시되고 불평하지 않습니다. 따라서 많은 파서 코드가 HTML 작성자 실수를 수정합니다.

오류 처리는 브라우저에서 상당히 일관되지만 놀랍게도 HTML 사양에 포함되지 않았습니다. 북마크나 뒤로/앞으로 버튼처럼 이 기능은 지난 몇 년 동안 브라우저에서 발전되어 왔습니다. 많은 사이트에서 알려진 잘못된 HTML 구조가 반복되고 있으며, 브라우저는 다른 브라우저와 동일한 방식으로 이러한 구조를 수정하려고 시도합니다.

HTML5 사양에는 이러한 요구사항 중 일부가 정의되어 있습니다. (WebKit은 HTML 파서 클래스 시작 부분에 있는 주석에서 이를 잘 요약합니다.)

파서는 토큰화된 입력을 문서에 파싱하여 문서 트리를 구축합니다. 문서의 형식이 올바르면 파싱이 간단해집니다.

안타깝게도 형식이 올바르지 않은 많은 HTML 문서를 처리해야 하므로 파서는 오류를 견딜 수 있어야 합니다.

Google에서는 최소한 다음과 같은 오류 조건을 처리해야 합니다.

  1. 외부 태그 내에서 추가하려는 요소가 명시적으로 금지되어 있습니다. 이 경우 요소를 금지하는 태그까지 모든 태그를 닫고 나중에 추가해야 합니다.
  2. 요소를 직접 추가할 수는 없습니다. 문서를 작성하는 사람이 중간에 일부 태그를 잊었거나 중간에 있는 태그는 선택사항일 수 있습니다. 다음 태그가 있을 수 있습니다. HTML HEAD BODY TBODY TR TD LI (잊어버렸나요?)
  3. 인라인 요소 내에 블록 요소를 추가하려고 합니다. 모든 인라인 요소를 다음으로 높은 블록 요소까지 닫습니다.
  4. 그래도 문제가 해결되지 않으면 Google에서 요소를 추가할 수 있을 때까지 요소를 닫거나 태그를 무시합니다.

WebKit 오류 허용 범위의 예를 몇 가지 살펴보겠습니다.

<br> 대신 </br>

일부 사이트에서는 <br> 대신 </br>를 사용합니다. WebKit은 IE 및 Firefox와 호환되기 위해 이를 <br>처럼 처리합니다.

코드:

if (t->isCloseTag(brTag) && m_document->inCompatMode()) {
     reportError(MalformedBRError);
     t->beginTag = true;
}

오류 처리는 내부적이며 사용자에게 표시되지 않습니다.

떠돌이 테이블

이탈 표란 표 셀 내부가 아닌 다른 표 안에 있는 표입니다.

예를 들면 다음과 같습니다.

<table>
  <table>
    <tr><td>inner table</td></tr>
  </table>
  <tr><td>outer table</td></tr>
</table>

WebKit은 계층 구조를 다음과 같은 두 개의 동위 테이블로 변경합니다.

<table>
  <tr><td>outer table</td></tr>
</table>
<table>
  <tr><td>inner table</td></tr>
</table>

코드:

if (m_inStrayTableContent && localName == tableTag)
        popBlock(tableTag);

WebKit은 현재 요소 콘텐츠의 스택을 사용합니다. 즉, 외부 테이블 스택에서 내부 테이블을 팝합니다. 이제 테이블이 동위가 됩니다.

중첩된 양식 요소

사용자가 양식을 다른 양식 안에 넣으면 두 번째 양식은 무시됩니다.

코드:

if (!m_currentFormElement) {
        m_currentFormElement = new HTMLFormElement(formTag,    m_document);
}

태그 계층 구조가 너무 깊은 경우

댓글은 그 자체로 의미가 있습니다.

bool HTMLParser::allowNestedRedundantTag(const AtomicString& tagName)
{

unsigned i = 0;
for (HTMLStackElem* curr = m_blockStack;
         i < cMaxRedundantTagDepth && curr && curr->tagName == tagName;
     curr = curr->next, i++) { }
return i != cMaxRedundantTagDepth;
}

잘못 배치된 html 또는 본문 종료 태그

댓글 자체가 의미가 있습니다.

if (t->tagName == htmlTag || t->tagName == bodyTag )
        return;

따라서 웹 작성자는 주의해야 합니다. WebKit 오류 허용 코드 스니펫에 예시로 표시하려는 경우가 아니라면 올바른 형식의 HTML을 작성하세요.

CSS 파싱

소개의 파싱 개념을 기억하시나요? HTML과 달리 CSS는 컨텍스트가 없는 문법이며 소개에 설명된 파서 유형을 사용하여 파싱할 수 있습니다. 실제로 CSS 사양은 CSS 어휘 및 구문 문법을 정의합니다.

몇 가지 예를 살펴보겠습니다.

어휘 문법 (어휘)은 각 토큰에 대한 정규 표현식으로 정의됩니다.

comment   \/\*[^*]*\*+([^/*][^*]*\*+)*\/
num       [0-9]+|[0-9]*"."[0-9]+
nonascii  [\200-\377]
nmstart   [_a-z]|{nonascii}|{escape}
nmchar    [_a-z0-9-]|{nonascii}|{escape}
name      {nmchar}+
ident     {nmstart}{nmchar}*

'ident'는 클래스 이름처럼 식별자를 줄인 것입니다. 'name'은 '#'로 참조되는 요소 ID입니다.

구문 문법은 BNF에 설명되어 있습니다.

ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;
selector
  : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
  ;
simple_selector
  : element_name [ HASH | class | attrib | pseudo ]*
  | [ HASH | class | attrib | pseudo ]+
  ;
class
  : '.' IDENT
  ;
element_name
  : IDENT | '*'
  ;
attrib
  : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
    [ IDENT | STRING ] S* ] ']'
  ;
pseudo
  : ':' [ IDENT | FUNCTION S* [IDENT S*] ')' ]
  ;

설명:

규칙 세트의 구조는 다음과 같습니다.

div.error, a.error {
  color:red;
  font-weight:bold;
}

div.errora.error는 선택기입니다. 중괄호 안에는 이 규칙 세트에 의해 적용된 규칙이 포함됩니다. 이 구조는 다음 정의에서 공식적으로 정의됩니다.

ruleset
  : selector [ ',' S* selector ]*
    '{' S* declaration [ ';' S* declaration ]* '}' S*
  ;

즉, 규칙 세트가 쉼표와 공백으로 구분된 선택기 또는 선택적으로 다수의 선택기입니다 (S는 공백을 나타냄). 규칙 세트에는 중괄호가 포함되며 그 안에 선언이 들어가거나 선택적으로 세미콜론으로 구분된 여러 선언이 포함됩니다. '선언'과 '선택기'는 다음 BNF 정의에 정의됩니다.

WebKit CSS 파서

WebKit은 Flex 및 Bison 파서 생성기를 사용하여 CSS 문법 파일에서 자동으로 파서를 만듭니다. 파서 소개에서 언급했듯이 Bison은 상향식 시프트 감소 파서를 만듭니다. Firefox는 수동으로 작성된 하향식 파서를 사용합니다. 두 경우 모두 각 CSS 파일은 StyleSheet 객체로 파싱됩니다. 각 개체에는 CSS 규칙이 포함됩니다. CSS 규칙 객체에는 선택기 및 선언 객체와 CSS 문법에 해당하는 기타 객체가 포함되어 있습니다.

CSS 파싱
그림 12: CSS 파싱

스크립트 및 스타일 시트의 처리 순서

스크립트

웹 모델은 동기식입니다. 작성자는 파서가 <script> 태그에 도달하는 즉시 스크립트가 파싱되고 실행되기를 기대합니다. 스크립트가 실행될 때까지 문서의 파싱이 중단됩니다. 스크립트가 외부에 있는 경우 먼저 네트워크에서 리소스를 가져와야 합니다. 이 작업도 동기식으로 수행되며 리소스를 가져올 때까지 파싱이 중단됩니다. 이 모델은 수년 동안 사용되었으며 HTML4 및 5 사양에도 지정되어 있습니다. 작성자는 'defer' 속성을 스크립트에 추가할 수 있습니다. 이 경우 문서 파싱이 중지되지 않고 문서가 파싱된 후 실행됩니다. HTML5는 스크립트를 비동기로 표시하는 옵션을 추가하여 다른 스레드에 의해 파싱되고 실행되도록 합니다.

추측 파싱

WebKit과 Firefox 모두 이 최적화를 실행합니다. 스크립트를 실행하는 동안 다른 스레드가 문서의 나머지 부분을 파싱하고 네트워크에서 로드해야 하는 다른 리소스를 찾아 로드합니다. 이렇게 하면 병렬 연결에 리소스를 로드할 수 있으며 전반적인 속도를 개선할 수 있습니다. 참고: 추측 파서는 외부 스크립트, 스타일 시트 및 이미지와 같은 외부 리소스에 대한 참조만 파싱합니다. DOM 트리를 수정하지 않고 기본 파서에 맡깁니다.

스타일 시트

반면 스타일 시트에는 다른 모델이 있습니다. 개념적으로는 스타일 시트는 DOM 트리를 변경하지 않으므로 DOM 트리를 기다리면서 문서 파싱을 중지할 필요가 없습니다. 하지만 문서 파싱 단계에서 스타일 정보를 요청하는 스크립트에 문제가 있습니다. 스타일이 아직 로드되고 파싱되지 않은 경우 스크립트가 잘못된 답변을 받게 되며 이로 인해 많은 문제가 발생합니다. 예외적인 사례처럼 보이지만 매우 일반적입니다. Firefox는 아직 로드 및 파싱 중인 스타일 시트가 있는 경우 모든 스크립트를 차단합니다. WebKit은 로드되지 않은 스타일시트의 영향을 받을 수 있는 특정 스타일 속성에 액세스하려고 하는 경우에만 스크립트를 차단합니다.

렌더링 트리 생성

DOM 트리가 생성되는 동안 브라우저는 또 다른 트리인 렌더링 트리를 생성합니다. 이 트리는 표시되는 순서대로 시각적 요소로 구성됩니다. 문서를 시각적으로 표현한 것입니다. 이 트리의 목적은 콘텐츠를 올바른 순서로 그릴 수 있도록 하는 것입니다.

Firefox는 렌더링 트리의 요소를 '프레임'이라고 부릅니다. WebKit에서는 렌더기 또는 렌더링 객체라는 용어를 사용합니다.

렌더기는 자신과 하위 요소를 배치하고 페인트하는 방법을 알고 있습니다.

렌더기의 기본 클래스인 WebKit의 RenderObject 클래스의 정의는 다음과 같습니다.

class RenderObject{
  virtual void layout();
  virtual void paint(PaintInfo);
  virtual void rect repaintRect();
  Node* node;  //the DOM node
  RenderStyle* style;  // the computed style
  RenderLayer* containgLayer; //the containing z-index layer
}

각 렌더기는 CSS2 사양에 설명된 대로 대개 노드의 CSS 상자에 해당하는 직사각형 영역을 나타냅니다. 여기에는 너비, 높이, 위치와 같은 기하학적 정보가 포함됩니다.

상자 유형은 노드와 관련된 스타일 속성의 '표시' 값에 영향을 받습니다 (스타일 계산 섹션 참고). 다음은 display 속성에 따라 DOM 노드에 만들어야 하는 렌더러 유형을 결정하는 WebKit 코드입니다.

RenderObject* RenderObject::createObject(Node* node, RenderStyle* style)
{
    Document* doc = node->document();
    RenderArena* arena = doc->renderArena();
    ...
    RenderObject* o = 0;

    switch (style->display()) {
        case NONE:
            break;
        case INLINE:
            o = new (arena) RenderInline(node);
            break;
        case BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case INLINE_BLOCK:
            o = new (arena) RenderBlock(node);
            break;
        case LIST_ITEM:
            o = new (arena) RenderListItem(node);
            break;
       ...
    }

    return o;
}

요소 유형도 고려됩니다. 예를 들어 양식 컨트롤과 표에는 특수 프레임이 있습니다.

WebKit에서는 요소가 특수 렌더기를 만들려는 경우 createRenderer() 메서드를 재정의합니다. 렌더러는 비 기하학적 정보를 포함하는 스타일 객체를 가리킵니다.

렌더링 트리와 DOM 트리의 관계

렌더러는 DOM 요소에 해당하지만 관계는 일대일 관계는 아닙니다. 비시각적 DOM 요소는 렌더링 트리에 삽입되지 않습니다. 'head' 요소를 예로 들 수 있습니다. 또한 표시 값이 'none'으로 할당된 요소는 트리에 표시되지 않습니다 (가시성이 'hidden'인 요소는 트리에 표시됩니다).

여러 시각적 객체에 해당하는 DOM 요소가 있습니다. 일반적으로 단일 직사각형으로 설명할 수 없는 복잡한 구조를 가진 요소입니다. 예를 들어 'select' 요소에는 3개의 렌더러가 있습니다. 하나는 디스플레이 영역용, 하나는 드롭다운 목록 상자용, 다른 하나는 버튼용입니다. 또한 한 줄의 너비가 충분하지 않아 텍스트가 여러 줄로 나뉘면 새로운 줄이 추가 렌더기로 추가됩니다.

여러 렌더러의 또 다른 예는 손상된 HTML입니다. CSS 사양에 따라 인라인 요소는 블록 요소만 또는 인라인 요소만 포함해야 합니다. 혼합 콘텐츠의 경우 익명 블록 렌더러가 생성되어 인라인 요소를 래핑합니다.

일부 렌더링 객체는 DOM 노드에 해당하지만 트리의 동일한 위치에 있지는 않습니다. 부동 소수점 수와 절대 위치로 배치된 요소는 흐름에서 벗어나 트리의 다른 부분에 배치되고 실제 프레임에 매핑됩니다. 자리표시자 프레임은 원래 자리에 있어야 하는 위치입니다.

렌더링 트리 및 해당 DOM 트리
그림 13: 렌더링 트리 및 해당 DOM 트리 '표시 영역'은 초기 포함 블록입니다. WebKit에서는 'RenderView' 객체가 됩니다.

트리를 구성하는 흐름

Firefox에서 프레젠테이션은 DOM 업데이트 리스너로 등록됩니다. 프레젠테이션은 프레임 생성을 FrameConstructor에 위임하고 생성자는 스타일을 결정 (스타일 계산 참고)하고 프레임을 만듭니다.

WebKit에서는 스타일을 결정하고 렌더기를 만드는 프로세스를 '첨부파일'이라고 합니다. 모든 DOM 노드에는 '연결' 메서드가 있습니다. 첨부 파일은 동기식이며 DOM 트리에 대한 노드 삽입은 새 노드 'attach' 메서드를 호출합니다.

html 및 body 태그를 처리하면 렌더링 트리 루트가 생성됩니다. 루트 렌더 객체는 CSS 사양에서 포함하는 블록, 즉 다른 모든 블록을 포함하는 최상위 블록에 해당합니다. 이 창의 크기는 표시 영역, 즉 브라우저 창 표시 영역의 크기입니다. Firefox에서는 ViewPortFrame라고 하고 WebKit에서는 RenderView라고 합니다. 문서가 가리키는 렌더링 객체입니다. 트리의 나머지 부분은 DOM 노드 삽입으로 구성됩니다.

처리 모델에 관한 CSS2 사양을 참고하세요.

스타일 계산

렌더링 트리를 빌드하려면 각 렌더링 객체의 시각적 속성을 계산해야 합니다. 이는 각 요소의 스타일 속성을 계산하면 됩니다.

이 스타일에는 다양한 출처의 스타일 시트, 인라인 스타일 요소, HTML의 시각적 속성 (예: 'bgcolor' 속성)이 포함됩니다. 스타일 시트는 일치하는 CSS 스타일 속성으로 변환됩니다.

스타일 시트의 출처는 브라우저의 기본 스타일 시트, 페이지 작성자가 제공하는 스타일 시트 및 사용자 스타일 시트입니다. 이는 브라우저 사용자가 제공하는 스타일시트입니다 (브라우저를 사용하면 즐겨 찾는 스타일을 정의할 수 있음). 예를 들어 Firefox에서는 "Firefox Profile" 폴더에 스타일시트를 배치하면 됩니다.)

스타일 계산에는 몇 가지 어려움이 있습니다.

  1. 스타일 데이터는 매우 큰 구조로, 수많은 스타일 속성을 보유하므로 메모리 문제를 일으킬 수 있습니다.
  2. 각 요소에 일치하는 규칙을 찾으면 최적화되지 않은 경우 성능 문제가 발생할 수 있습니다. 각 요소에 대한 전체 규칙 목록을 탐색하여 일치 항목을 찾는 것은 무거운 작업입니다. 선택자의 구조가 복잡할 수 있으므로 매칭 프로세스가 무의미한 것으로 확인되고 다른 경로를 시도해야 하는 유망해 보이는 경로에서 시작하게 할 수 있습니다.

    다음은 복합 선택기의 예입니다.

    div div div div{
    ...
    }
    

    즉, div 3개의 하위 요소인 <div>에 규칙이 적용됩니다. 규칙이 특정 <div> 요소에 적용되는지 확인하려고 한다고 가정해 보겠습니다. 확인할 트리 위의 특정 경로를 선택합니다. div가 두 개만 있고 규칙이 적용되지 않는지 확인하기 위해 노드 트리를 위로 이동해야 할 수 있습니다. 그런 다음 트리에서 다른 경로를 시도해야 합니다.

  3. 규칙을 적용하려면 규칙의 계층을 정의하는 매우 복잡한 하위 규칙이 필요합니다.

브라우저에서 이러한 문제가 어떻게 발생하는지 살펴보겠습니다.

스타일 데이터 공유

WebKit 노드는 스타일 객체 (RenderStyle)를 참조합니다. 이러한 객체는 일부 조건에서 노드에서 공유할 수 있습니다. 노드가 동위 요소 또는 사촌이며,

  1. 요소의 마우스 상태가 동일해야 함 (예: 하나는 :hover 중 하나는 :hover일 수 없고 다른 하나는 유효하지 않음)
  2. 두 요소 모두 ID가 없어야 합니다.
  3. 태그 이름은
  4. 클래스 속성은
  5. 매핑된 속성 집합은 동일해야 합니다.
  6. 링크 상태가 일치해야 합니다.
  7. 포커스 상태가 일치해야 합니다.
  8. 두 요소 모두 속성 선택기의 영향을 받지 않습니다. 여기에서 영향을 받는 것은 선택기 내의 어느 위치에서든 속성 선택기를 사용하는 선택기 일치 항목이 있는 것으로 정의됩니다.
  9. 요소에 인라인 스타일 속성이 있어서는 안 됩니다.
  10. 사용 중인 동위 선택기가 없어야 합니다. WebCore는 동위 선택기가 있는 경우 전역 스위치를 발생시키고, 동위 선택기가 있는 경우 전체 문서에 대한 스타일 공유를 사용 중지합니다. 여기에는 + 선택기와 :first-child, :last-child와 같은 선택기가 포함됩니다.

Firefox 규칙 트리

Firefox에는 더 쉬운 스타일 계산을 위해 두 개의 추가 트리, 즉 규칙 트리와 스타일 컨텍스트 트리가 있습니다. WebKit에도 스타일 객체가 있지만 스타일 컨텍스트 트리와 같이 트리에 저장되지 않으며 DOM 노드만 관련 스타일을 가리킵니다.

Firefox 스타일의 컨텍스트 트리.
그림 14: Firefox 스타일의 컨텍스트 트리

스타일 컨텍스트에는 최종 값이 포함됩니다. 값은 모든 일치 규칙을 올바른 순서로 적용하고 논리 값에서 구체적인 값으로 변환하는 조작을 수행하여 계산됩니다. 예를 들어 논리 값이 화면의 백분율인 경우 계산되어 절대 단위로 변환됩니다. 규칙 트리 아이디어는 정말 기발합니다. 노드 간에 이러한 값을 공유할 수 있으므로 값을 다시 계산하지 않아도 됩니다. 공간도 절약됩니다.

일치하는 모든 규칙은 트리에 저장됩니다. 경로의 하위 노드가 우선순위가 더 높습니다. 트리에는 발견된 규칙 일치의 모든 경로가 포함됩니다. 규칙 저장은 지연됩니다. 트리는 모든 노드에 대해 처음에 계산되지 않지만 노드 스타일을 계산해야 할 때마다 계산된 경로가 트리에 추가됩니다.

개념은 트리 경로를 사전의 단어로 보는 것입니다. 이 규칙 트리를 이미 계산했다고 가정해 보겠습니다.

계산된 규칙 트리
그림 15: 계산된 규칙 트리

콘텐츠 트리에서 다른 요소에 대한 규칙을 일치시켜야 하는데 일치하는 규칙 (올바른 순서)이 B-E-I인지 확인했다고 가정해 보겠습니다. 트리에 이미 이 경로가 있습니다. A-B-E-I-L 경로를 이미 계산했기 때문입니다. 이제 할 일이 줄어듭니다.

트리가 작업을 어떻게 구해 주는지 살펴보겠습니다.

구조체로 나누기

스타일 컨텍스트는 구조체로 나뉩니다. 이러한 구조체에는 테두리나 색상과 같은 특정 카테고리의 스타일 정보가 포함됩니다. 구조체의 모든 속성은 상속되거나 상속되지 않습니다. 상속된 속성은 요소에 의해 정의되지 않는 한 상위 요소로부터 상속되는 속성입니다. 상속되지 않은 속성('재설정' 속성이라고 함)은 정의되지 않은 경우 기본값을 사용합니다.

트리는 계산된 최종 값을 포함하는 전체 구조체를 트리에서 캐싱하므로 유용합니다. 하단 노드가 구조체의 정의를 제공하지 않으면 상위 노드에 캐시된 구조체를 사용할 수 있다는 개념입니다.

규칙 트리를 사용하여 스타일 컨텍스트 계산

특정 요소의 스타일 컨텍스트를 계산할 때 먼저 규칙 트리에서 경로를 계산하거나 기존 경로를 사용합니다. 그런 다음 경로에 규칙을 적용하여 새 스타일 컨텍스트에서 구조체를 채우기 시작합니다. 경로의 맨 아래 노드에서 시작하여 우선순위가 가장 높은 노드 (일반적으로 가장 구체적인 선택기)에서 시작하여 구조체가 가득 찰 때까지 트리를 순회합니다. 이 규칙 노드에 구조체에 대한 사양이 없는 경우 최적화할 수 있습니다. 완전히 지정하고 가리키는 노드를 찾을 때까지 트리를 올라가면, 이것이 최상의 최적화입니다. 전체 구조체가 공유됩니다. 이렇게 하면 최종 값과 메모리 계산을 절약할 수 있습니다.

부분 정의를 찾으면 구조체가 채워질 때까지 트리를 올라갑니다.

구조체의 정의를 찾지 못한 경우 구조체가 '상속된' 유형인 경우 컨텍스트 트리에서 상위 요소의 구조체를 가리킵니다. 이 경우 구조체도 공유 성공했습니다. 재설정 구조체인 경우에는 기본값이 사용됩니다.

가장 구체적인 노드가 값을 더하는 경우 실제 값으로 변환하기 위해 몇 가지 추가 계산을 수행해야 합니다. 그런 다음 하위 요소가 사용할 수 있도록 결과를 트리 노드에 캐시합니다.

요소에 동일한 트리 노드를 가리키는 동위 요소나 형제가 있는 경우 요소 간에 전체 스타일 컨텍스트를 공유할 수 있습니다.

한 가지 예를 보겠습니다. 이 HTML이 있다고 가정해 보겠습니다.

<html>
  <body>
    <div class="err" id="div1">
      <p>
        this is a <span class="big"> big error </span>
        this is also a
        <span class="big"> very  big  error</span> error
      </p>
    </div>
    <div class="err" id="div2">another error</div>
  </body>
</html>

다음 규칙을 준수해야 합니다.

div {margin: 5px; color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}

단순화하기 위해 색상 구조체와 여백 구조체, 두 개의 구조체만 작성해야 한다고 가정해 보겠습니다. 색상 구조체에는 멤버 한 개만 포함됩니다. 즉, 색상 여백 구조체에는 네 면이 포함됩니다.

결과 규칙 트리는 다음과 같을 것입니다 (노드는 노드 이름, 즉 노드가 가리키는 규칙의 번호로 표시됨).

규칙 트리
그림 16: 규칙 트리

컨텍스트 트리는 다음과 같이 표시됩니다 (노드 이름: 사용자가 가리키는 규칙 노드).

컨텍스트 트리입니다.
그림 17: 컨텍스트 트리

HTML을 파싱하여 두 번째 <div> 태그에 도달했다고 가정해 보겠습니다. 이 노드의 스타일 컨텍스트를 만들고 스타일 구조체를 채워야 합니다.

규칙을 일치시켜 <div>의 일치 규칙이 1, 2, 6임을 발견합니다. 즉, 요소에서 사용할 수 있는 기존 경로가 트리에 이미 있으므로 규칙 6 (규칙 트리의 노드 F)에 대해 다른 노드를 추가하기만 하면 됩니다.

스타일 컨텍스트를 만들어 컨텍스트 트리에 배치합니다. 새 스타일 컨텍스트는 규칙 트리의 노드 F를 가리킵니다.

이제 스타일 구조체를 채워야 합니다. 먼저 여백 구조체를 채워 보겠습니다. 마지막 규칙 노드 (F)는 여백 구조체에 추가되지 않으므로 이전 노드 삽입에서 계산된 캐시된 구조체를 찾아 사용할 때까지 트리를 위로 올라갈 수 있습니다. 이 값은 여백 규칙을 지정한 최상위 노드인 노드 B에서 찾을 수 있습니다.

색상 구조체에 대한 정의가 있으므로 캐시된 구조체를 사용할 수 없습니다. 색상에는 하나의 속성이 있으므로 다른 속성을 채우기 위해 트리 위로 올라가지 않아도 됩니다. 종료 값을 계산하고 (문자열을 RGB로 변환 등) 계산된 구조체를 이 노드에 캐시합니다.

두 번째 <span> 요소의 작업은 훨씬 쉽습니다. 이 규칙을 일치시켜 이전 스팬에서처럼 규칙 G를 가리키는 결론에 도달하게 됩니다. 동일한 노드를 가리키는 동위 요소가 있으므로 전체 스타일 컨텍스트를 공유하고 이전 스팬의 컨텍스트만 가리킬 수 있습니다.

상위 요소에서 상속된 규칙을 포함하는 구조체의 경우 컨텍스트 트리에서 캐싱이 이루어집니다. 즉, 색상 속성은 실제로 상속되지만 Firefox는 이를 재설정으로 취급하여 규칙 트리에 캐시합니다.

예를 들어 단락에 글꼴 규칙을 추가한 경우

p {font-family: Verdana; font size: 10px; font-weight: bold}

그러면 컨텍스트 트리에서 div의 하위 요소인 단락 요소가 상위 요소와 동일한 글꼴 구조체를 공유했을 수 있습니다. 단락에 글꼴 규칙이 지정되지 않은 경우입니다.

규칙 트리가 없는 WebKit에서는 일치하는 선언이 4번 순회됩니다. 중요하지 않은 우선순위가 높은 속성 (표시와 같이 다른 속성이 의존하므로 먼저 적용해야 하는 속성), 우선순위가 높은 중요 속성, 중요하지 않은 일반 우선순위 속성, 보통 우선순위 중요 규칙이 차례로 적용됩니다. 즉, 여러 번 나타나는 속성은 올바른 계단식 순서에 따라 확인됩니다. 마지막 승리입니다.

요약하자면, 스타일 객체 (전체 또는 내부 구조체의 일부)를 공유하면 문제 1과 3이 해결됩니다. Firefox 규칙 트리도 속성을 올바른 순서로 적용하는 데 도움이 됩니다.

쉬운 일치를 위한 규칙 조작

스타일 규칙에는 여러 가지 소스가 있습니다.

  1. 외부 스타일 시트 또는 스타일 요소에 있는 CSS 규칙. css p {color: blue}
  2. 인라인 스타일 속성(예: html <p style="color: blue" />)
  3. HTML 시각적 속성 (관련 스타일 규칙에 매핑됨) html <p bgcolor="blue" /> 마지막 두 개는 스타일 속성을 소유하고 있으며 HTML 속성은 요소를 키로 사용하여 매핑할 수 있으므로 요소와 쉽게 일치됩니다.

앞서 문제 2에서 언급한 것처럼 CSS 규칙 매칭은 더 까다로울 수 있습니다. 난이도를 풀기 위해 보다 쉽게 액세스할 수 있도록 규칙을 조작합니다.

스타일 시트를 파싱한 후 규칙은 선택기에 따라 여러 해시 맵 중 하나에 추가됩니다. 이 카테고리에는 ID별, 클래스 이름별, 태그 이름별 지도가 있으며, 이러한 범주로 적합하지 않은 항목에 대한 일반 지도가 있습니다. 선택기가 ID인 경우 규칙이 ID 맵에 추가되고, 클래스인 경우 클래스 맵에 추가됩니다.

이 조작을 통해 훨씬 더 쉽게 규칙을 일치시킬 수 있습니다. 모든 선언을 살펴볼 필요는 없습니다. 맵에서 요소에 관한 관련 규칙을 추출할 수 있기 때문입니다. 이러한 최적화를 통해 규칙의 95% 이상이 제거되므로 일치 프로세스(4.1)에서 규칙을 고려할 필요조차 없습니다.

다음 스타일 규칙의 예를 살펴보겠습니다.

p.error {color: red}
#messageDiv {height: 50px}
div {margin: 5px}

첫 번째 규칙이 클래스 맵에 삽입됩니다. 두 번째는 ID 맵에, 세 번째는 태그 맵에 들어갑니다.

다음 HTML 프래그먼트의 경우

<p class="error">an error occurred</p>
<div id=" messageDiv">this is a message</div>

먼저 p 요소의 규칙을 찾아보겠습니다. 클래스 맵에는 'p.error'에 대한 규칙이 있는 'error' 키가 포함됩니다. div 요소는 ID 맵 (키는 id임)과 태그 맵에 관련 규칙을 가집니다. 따라서 남은 유일한 작업은 키에 의해 추출된 규칙 중 실제로 일치하는 규칙을 찾는 것입니다.

예를 들어 div에 대한 규칙이 다음과 같다고 가정해 보겠습니다.

table div {margin: 5px}

키가 가장 오른쪽 선택기이므로 여전히 태그 맵에서 추출되지만 표 상위 항목이 없는 div 요소와 일치하지 않습니다.

WebKit와 Firefox가 모두 이 조작을 수행합니다.

스타일 시트 계단식 순서

스타일 객체에는 모든 시각적 속성 (모든 CSS 속성이지만 더 일반적임)에 해당하는 속성이 있습니다. 속성이 일치하는 규칙으로 정의되지 않은 경우 상위 요소 스타일 객체가 일부 속성을 상속할 수 있습니다. 다른 속성에는 기본값이 있습니다.

정의가 2개 이상일 때 문제가 시작됩니다. 문제를 해결하기 위한 계단식 순서입니다.

스타일 속성 선언은 여러 스타일 시트에 나타날 수 있으며, 스타일 시트 내에 여러 번 나타날 수 있습니다. 따라서 규칙을 적용하는 순서가 매우 중요합니다. 이를 '캐스케이드' 주문이라고 합니다. CSS2 사양에 따르면 계단식 순서는 다음과 같습니다 (낮은 순에서 높은 순).

  1. 브라우저 선언
  2. 사용자 일반 선언
  3. 작성자 일반 선언
  4. 중요 선언 작성
  5. 사용자 중요 선언

브라우저 선언은 중요도가 가장 낮으며 선언이 중요하다고 표시된 경우에만 사용자가 작성자를 재정의합니다. 순서가 동일한 선언은 특수성에 따라 정렬된 다음 지정된 순서대로 정렬됩니다. HTML 시각적 속성은 일치하는 CSS 선언으로 변환됩니다 . 우선순위가 낮은 작성자 규칙으로 처리됩니다.

특수성

선택기 특수성은 다음과 같이 CSS2 사양에 의해 정의됩니다.

  1. 출처가 선택기가 있는 규칙이 아닌 'style' 속성인 경우 1, 그렇지 않으면 0 (= a)
  2. 선택기에서 ID 속성의 수 계산 (= b)
  3. 선택기에서 다른 속성과 의사 클래스의 수 계산 (= c)
  4. 선택기에서 요소 이름 및 유사 요소의 수 계산 (= d)

(밑이 큰 숫자 시스템에서) 4개의 숫자 a-b-c-d를 연결하면 특이성이 생성됩니다.

필요한 숫자값은 카테고리 중 하나에 있는 가장 높은 수로 정의됩니다.

예를 들어 a=14인 경우 16진수를 사용할 수 있습니다. 드물지만 a=17인 경우에는 17자리 숫자 기반이 필요합니다. html body div div p... (선택기의 태그 17개... 거의 없음)와 같은 선택자를 사용하면 이후 상황이 발생할 수 있습니다.

예를 들면 다음과 같습니다.

 *             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
 li            {}  /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
 li:first-line {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul li         {}  /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
 ul ol+li      {}  /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
 h1 + *[rel=up]{}  /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
 ul ol li.red  {}  /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
 li.red.level  {}  /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
 #x34y         {}  /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
 style=""          /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

규칙 정렬

규칙이 일치하면 하위 규칙에 따라 정렬됩니다. WebKit에서는 작은 목록에 풍선 정렬을 사용하고 큰 목록에는 병합 정렬을 사용합니다. WebKit은 규칙의 > 연산자를 재정의하여 정렬을 구현합니다.

static bool operator >(CSSRuleData& r1, CSSRuleData& r2)
{
    int spec1 = r1.selector()->specificity();
    int spec2 = r2.selector()->specificity();
    return (spec1 == spec2) : r1.position() > r2.position() : spec1 > spec2;
}

점진적 프로세스

WebKit은 모든 최상위 스타일 시트(@imports 포함)가 로드되었는지 표시하는 플래그를 사용합니다. 첨부 시 스타일이 완전히 로드되지 않으면 자리 표시자가 사용되어 문서에 해당 스타일이 표시되며, 스타일 시트가 로드되면 다시 계산됩니다.

레이아웃

렌더러가 생성되어 트리에 추가되면 위치와 크기가 없습니다. 이러한 값을 계산하는 것을 레이아웃 또는 리플로우라고 합니다.

HTML은 흐름 기반 레이아웃 모델을 사용합니다. 즉, 대부분의 경우 단일 패스로 도형을 계산할 수 있습니다. '흐름' 뒷부분에 있는 요소는 일반적으로 앞부분에 있는 요소의 도형에 영향을 미치지 않으므로 레이아웃은 문서를 통해 왼쪽에서 오른쪽으로, 위에서 아래로 진행할 수 있습니다. 예외적인 경우가 있습니다. 예를 들어, HTML 테이블에는 두 번 이상의 전달이 필요할 수 있습니다.

좌표계는 루트 프레임을 기준으로 합니다. 상단 및 왼쪽 좌표가 사용됩니다.

레이아웃은 반복 프로세스입니다. HTML 문서의 <html> 요소에 해당하는 루트 렌더기에서 시작합니다. 레이아웃은 프레임 계층 구조의 일부 또는 전체를 통해 반복적으로 계속되어 필요한 각 렌더기에 대한 기하학적 정보를 계산합니다.

루트 렌더러의 위치는 0,0이고 크기는 표시 영역(브라우저 창에 표시되는 부분)입니다.

모든 렌더기에는 '레이아웃' 또는 '리플로우' 메서드가 있으며 각 렌더기는 레이아웃이 필요한 하위 요소의 레이아웃 메서드를 호출합니다.

더티 비트 시스템

모든 작은 변경에 대해 전체 레이아웃을 수행하지 않도록 브라우저는 "더티 비트" 시스템을 사용합니다. 변경되거나 추가된 렌더러는 자신과 그 하위 요소를 '더티'로 표시: 레이아웃이 필요합니다.

'dirty' 플래그와 'children are dirty'라는 두 가지 플래그가 있습니다. 이는 렌더러 자체는 괜찮을 수도 있지만 레이아웃이 필요한 하위 요소가 하나 이상 있음을 의미합니다.

전역 및 증분 레이아웃

레이아웃은 전체 렌더링 트리에서 트리거될 수 있습니다. 이것이 '전역' 레이아웃입니다. 이는 다음과 같은 이유로 발생할 수 있습니다.

  1. 글꼴 크기 변경과 같이 모든 렌더기에 영향을 미치는 전체 스타일 변경입니다.
  2. 화면 크기가 조절된 결과

레이아웃은 증분될 수 있으며 더티 렌더러만 배치됩니다 (이로 인해 추가 레이아웃이 필요한 일부 손상이 발생할 수 있음).

증분 레이아웃은 렌더기가 더티일 때 (비동기식으로) 트리거됩니다. 예를 들어 추가 콘텐츠가 네트워크에서 들어와 DOM 트리에 추가된 후 새 렌더러가 렌더링 트리에 추가됩니다.

증분 레이아웃
그림 18: 증분 레이아웃 - 더티 렌더러와 그 하위 요소만 배치

비동기 및 동기 레이아웃

증분 레이아웃은 비동기식으로 실행됩니다. Firefox는 증분 레이아웃에 대한 "리플로우 명령"을 큐에 넣고 스케줄러는 이러한 명령의 일괄 실행을 트리거합니다. WebKit에는 증분 레이아웃을 실행하는 타이머도 있습니다. 트리를 순회하고 '더티' 렌더러는 레이아웃에서 제외됩니다.

'offsetHeight'와 같은 스타일 정보를 요청하는 스크립트는 증분 레이아웃을 동기식으로 트리거할 수 있습니다.

전역 레이아웃은 일반적으로 동기식으로 트리거됩니다.

스크롤 위치와 같은 일부 속성이 변경되었기 때문에 레이아웃이 초기 레이아웃 후 콜백으로 트리거되는 경우도 있습니다.

최적화

레이아웃이 '크기 조절' 또는 렌더러 위치 변경(크기 아님)으로 인해 트리거되는 경우 렌더 크기는 캐시에서 가져오며 다시 계산되지 않습니다.

경우에 따라 하위 트리만 수정되고 레이아웃이 루트에서 시작되지 않습니다. 이는 변경사항이 로컬이고 주변 환경에는 영향을 미치지 않는 경우에 발생할 수 있습니다 (예: 텍스트 필드에 삽입된 텍스트). 그렇지 않으면 모든 키 입력이 루트에서 시작되는 레이아웃을 트리거합니다.

레이아웃 프로세스

일반적으로 레이아웃은 다음과 같은 패턴을 보입니다.

  1. 상위 렌더러에서 자체 너비를 결정합니다.
  2. 부모가 자녀를 돌보는 경우:
    1. 하위 렌더러를 배치합니다 (x 및 y를 설정함).
    2. 필요한 경우 하위 요소 레이아웃을 호출합니다. 더러워졌거나 전역 레이아웃에 있는 경우 등 다른 이유로 인해 하위 요소의 높이를 계산합니다.
  3. 상위 요소는 하위 요소의 누적 높이와 여백 및 패딩의 높이를 사용하여 자체 높이를 설정합니다. 이 값은 상위 렌더기의 상위 요소에 의해 사용됩니다.
  4. 더티 비트를 false로 설정합니다.

Firefox는 'state' 객체(nsHTMLReflowState)를 레이아웃의 매개변수로 사용합니다('리플로우'로 표시). 무엇보다도 상태에는 상위 너비가 포함됩니다.

Firefox 레이아웃의 출력은 'metrics' 객체(nsHTMLReflowMetrics)입니다. 렌더러가 계산한 높이가 포함됩니다.

너비 계산

렌더러의 너비는 컨테이너 블록의 너비, 렌더러의 스타일 '너비' 속성, 여백 및 테두리를 사용하여 계산됩니다.

예를 들어 다음 div의 너비는 다음과 같습니다.

<div style="width: 30%"/>

WebKit에서 다음과 같이 계산할 수 있습니다(RenderBox 메서드 calcWidth 클래스).

  • 컨테이너 너비는 availableWidth 및 0 컨테이너의 최대값입니다. 이 경우 availableWidth는 다음과 같이 계산되는 contentWidth입니다.
clientWidth() - paddingLeft() - paddingRight()

clientWidth 및 clientHeight는 테두리와 스크롤바를 제외한 객체의 내부를 나타냅니다.

  • 요소 너비는 '너비' 스타일 속성입니다. 컨테이너 너비의 비율을 계산하여 절댓값으로 계산됩니다.

  • 이제 가로 테두리와 패딩이 추가됩니다.

지금까지는 '선호하는 너비'를 계산했습니다. 이제 최소 및 최대 너비를 계산합니다.

기본 너비가 최대 너비보다 크면 최대 너비가 사용됩니다. 최소 너비 (브레이킹 체인지할 수 없는 가장 작은 단위)보다 작은 경우 최소 너비가 사용됩니다.

레이아웃이 필요한 경우 값이 캐시되지만 너비는 변경되지 않습니다.

줄바꿈

레이아웃 중간에 있는 렌더기가 중단해야 한다고 결정하면 렌더러가 중지되고 레이아웃의 상위 요소에 전파되어 중단되어야 합니다. 상위 요소가 추가 렌더기를 만들고 렌더러에서 레이아웃을 호출합니다.

회화

페인팅 단계에서는 렌더링 트리를 순회하고 렌더러의 'paint()' 메서드를 호출하여 화면에 콘텐츠를 표시합니다. 페인팅은 UI 인프라 구성요소를 사용합니다.

전역 및 증분

레이아웃과 마찬가지로 페인팅도 전역적으로(트리 전체가 페인트)되거나 증분될 수 있습니다. 증분 페인팅에서는 일부 렌더기가 트리 전체에 영향을 미치지 않는 방식으로 변경됩니다. 변경된 렌더러는 화면에서 직사각형을 무효화합니다. 그러면 OS에서 이를 '더티 영역'으로 간주하고 'paint' 이벤트를 생성합니다. OS는 이러한 작업을 영리하게 수행하고 여러 리전을 하나로 통합합니다. Chrome에서는 렌더기가 기본 프로세스와 다른 프로세스에 있기 때문에 더 복잡합니다. Chrome은 어느 정도 OS 동작을 시뮬레이션합니다. 프레젠테이션은 이러한 이벤트를 수신하고 메시지를 렌더링 루트에 위임합니다. 트리는 관련 렌더러에 도달할 때까지 순회합니다. 스스로 (일반적으로 하위 요소)를 다시 페인트합니다.

회화 순서

CSS2에서 페인팅 프로세스의 순서를 정의합니다. 이는 실제로 요소가 스택 컨텍스트에서 스택되는 순서입니다. 스택이 뒤에서 앞으로 칠해지므로 이 순서는 페인팅에 영향을 미칩니다. 블록 렌더기의 스택 순서는 다음과 같습니다.

  1. background color
  2. 배경 이미지
  3. border
  4. 어린이
  5. 개요

Firefox 표시 목록

Firefox는 렌더링 트리를 따라가며 페인트된 직사각형의 표시 목록을 작성합니다. 여기에는 직사각형과 관련된 렌더러가 올바른 그리기 순서 (렌더러의 배경, 테두리 등)로 포함됩니다.

이렇게 하면 다시 페인트를 위해 여러 번이 아니라 한 번만 트리를 순회하면 됩니다. 즉, 모든 배경, 모든 이미지, 모든 테두리 등을 칠합니다.

Firefox는 요소를 다른 불투명 요소 완전히 아래에 추가하는 것처럼 숨겨지는 요소를 추가하지 않음으로써 프로세스를 최적화합니다.

WebKit 직사각형 저장소

다시 그리기 전에 WebKit은 이전 직사각형을 비트맵으로 저장합니다. 그런 다음 새 직사각형과 이전 직사각형 사이의 델타만 그립니다.

동적 변경

브라우저는 변경에 대응하여 가능한 최소한의 작업을 실행하려고 합니다. 따라서 요소의 색상을 변경하면 요소만 다시 페인트할 수 있습니다. 요소 위치를 변경하면 요소, 하위 요소 및 동위 요소(예: 동위 요소)의 레이아웃과 다시 페인트가 발생합니다. DOM 노드를 추가하면 노드의 레이아웃과 다시 페인트가 발생합니다. 'html' 요소의 글꼴 크기 확대와 같은 주요 변경사항이 발생하면 캐시 무효화, 전체 트리 레이아웃 변경 및 다시 페인트가 발생합니다.

렌더링 엔진의 스레드

렌더링 엔진은 단일 스레드입니다. 네트워크 작업을 제외한 거의 모든 작업이 단일 스레드에서 발생합니다. Firefox와 Safari에서는 브라우저의 기본 스레드입니다. Chrome에서는 탭 프로세스 기본 스레드입니다.

네트워크 작업은 여러 개의 병렬 스레드에서 수행할 수 있습니다. 병렬 연결 수가 제한됩니다 (일반적으로 연결 2~6개).

이벤트 루프

브라우저 기본 스레드는 이벤트 루프입니다. 프로세스를 유지하는 무한 루프입니다. 이벤트 (예: 레이아웃 및 페인트 이벤트)를 기다렸다가 이를 처리합니다. 다음은 메인 이벤트 루프를 위한 Firefox 코드입니다.

while (!mExiting)
    NS_ProcessNextEvent(thread);

CSS2 시각적 모델

캔버스

CSS2 사양에 따르면 캔버스라는 용어는 '서식 구조가 렌더링되는 공간', 즉 브라우저에서 콘텐츠를 그리는 공간을 의미합니다.

캔버스는 공간의 각 크기에 대해 무한하지만 브라우저는 표시 영역의 크기에 따라 초기 너비를 선택합니다.

www.w3.org/TR/CSS2/zindex.html에 따르면 캔버스는 다른 캔버스에 포함되어 있으면 투명하고 그렇지 않으면 브라우저에서 정의된 색상이 지정됩니다.

CSS 상자 모델

CSS 상자 모델은 문서 트리의 요소에 대해 생성되고 시각적 형식 지정 모델에 따라 배치되는 직사각형 상자에 대해 설명합니다.

각 상자에는 콘텐츠 영역(예: 텍스트, 이미지 등)과 주변 패딩, 테두리, 여백 영역(선택사항)이 있습니다.

CSS2 상자 모델
그림 19: CSS2 박스 모델

각 노드는 이러한 상자를 0...n 생성합니다.

모든 요소에는 생성될 상자의 유형을 결정하는 'display' 속성이 있습니다.

예:

block: generates a block box.
inline: generates one or more inline boxes.
none: no box is generated.

기본값은 인라인이지만 브라우저 스타일 시트에서 다른 기본값을 설정할 수 있습니다. 예를 들어 'div' 요소의 기본 표시는 블록입니다.

기본 스타일 시트의 예는 www.w3.org/TR/CSS2/sample.html에서 확인할 수 있습니다.

포지셔닝 전략

세 가지 스키마가 있습니다.

  1. 일반: 문서 내 위치에 따라 객체가 배치됩니다. 즉, 렌더링 트리에서 해당 위치는 DOM 트리의 위치와 같으며 상자 유형과 크기에 따라 배치됩니다.
  2. 부동: 객체를 먼저 일반 흐름처럼 배치한 다음 가능한 한 왼쪽 또는 오른쪽으로 이동합니다.
  3. 절대값: 객체가 DOM 트리와 다른 위치의 렌더링 트리에 배치됩니다.

위치 지정 체계는 'position' 속성과 'float' 속성으로 설정됩니다.

  • 정적이고 상대적 원인인 정상적인 흐름
  • 절대 및 고정 원인 절대 위치

정적 배치에서는 위치가 정의되지 않으며 기본 배치가 사용됩니다. 다른 체계에서는 작성자가 위치를 상단, 하단, 왼쪽, 오른쪽으로 지정합니다.

상자의 배치 방식은 다음에 따라 결정됩니다.

  • 상자 유형
  • 상자 크기
  • 포지셔닝 전략
  • 이미지 크기 및 화면 크기와 같은 외부 정보

상자 유형

블록 상자: 블록을 형성하며 브라우저 창에 고유한 직사각형이 있습니다.

차단 상자
그림 20: 블록 상자

인라인 상자: 자체 블록이 없지만 포함 블록 내에 있습니다.

인라인 상자.
그림 21: 인라인 상자

블록은 세로로 차례로 형식이 지정됩니다. 인라인은 가로로 형식이 지정됩니다.

블록 및 인라인 서식 지정
그림 22: 블록 및 인라인 형식 지정

인라인 상자는 줄 안에 또는 '줄 상자'에 배치됩니다. 선의 높이는 최소한 가장 큰 상자의 높이와 같지만, 상자가 '기준선'으로 정렬되면 더 커질 수 있습니다. 즉, 요소의 하단이 하단이 아닌 다른 상자의 한 지점에 정렬됩니다. 컨테이너 너비가 충분하지 않으면 인라인이 여러 줄에 배치됩니다. 일반적으로 단락에서 발생하는 상황입니다.

선
그림 23: 선

포지셔닝

친척

상대 위치 - 평소와 같이 배치한 다음 필요한 델타만큼 이동합니다.

상대 위치.
그림 24: 상대 위치

부동

플로팅 상자가 선의 왼쪽이나 오른쪽으로 이동합니다. 흥미로운 점은 주위에 다른 상자가 흐르고 있다는 것입니다. HTML:

<p>
  <img style="float: right" src="images/image.gif" width="100" height="100">
  Lorem ipsum dolor sit amet, consectetuer...
</p>

다음과 같이 표시됩니다.

부동 소수점 수.
그림 25: 부동 소수점 수

절대 및 고정

레이아웃은 일반 흐름과 상관없이 정확하게 정의됩니다. 요소가 일반적인 흐름에 참여하지 않습니다. 크기는 컨테이너를 기준으로 합니다. 고정에서 컨테이너는 표시 영역입니다.

위치가 수정되었습니다.
그림 26: 고정 위치

계층화된 표현

이는 Z-색인 CSS 속성으로 지정됩니다. 이는 상자의 세 번째 차원인 'z축'을 따라 위치한 위치를 나타냅니다.

상자는 스택으로 나뉩니다 (스태킹 컨텍스트라고 함). 각 스택에서 후면 요소가 먼저 칠해지고 전면 요소는 사용자에게 더 가까이에 칠해집니다. 겹치는 부분이 있는 경우 맨 앞에 있는 요소가 이전 요소를 숨깁니다.

스택은 Z-색인 속성에 따라 정렬됩니다. 'Z-색인' 속성이 있는 상자는 로컬 스택을 형성합니다. 표시 영역에 외부 스택이 있습니다.

예:

<style type="text/css">
  div {
    position: absolute;
    left: 2in;
    top: 2in;
  }
</style>

<p>
  <div
    style="z-index: 3;background-color:red; width: 1in; height: 1in; ">
  </div>
  <div
    style="z-index: 1;background-color:green;width: 2in; height: 2in;">
  </div>
</p>

결과는 다음과 같습니다.

위치가 수정되었습니다.
그림 27: 고정 위치 지정

빨간색 div가 마크업의 녹색 div보다 앞에 있고 일반적인 흐름에서 먼저 그려졌을 수도 있지만, Z-색인 속성이 더 높기 때문에 루트 상자가 보유한 스택에서 더 앞에 있습니다.

자료

  1. 브라우저 아키텍처

    1. 그로스쿠르스, 앨런. 웹브라우저용 참조 아키텍처 (pdf)
    2. 굽타, 비니트. 브라우저의 작동 원리 - 1부 - 아키텍처
  2. 파싱

    1. Aho, Sethi, Ullman, 컴파일러: 원리, 기술 및 도구 (일명 '드래곤 북'), Addison-Wesley, 1986년
    2. 릭 젤리프. Bold and the Beautiful: HTML 5의 두 가지 새로운 초안
  3. Firefox

    1. L. 데이비드 배런, 더 빠른 HTML 및 CSS: 웹 개발자를 위한 레이아웃 엔진 내부 기능
    2. L. 데이비드 배런, 더 빠른 HTML 및 CSS: 웹 개발자를 위한 레이아웃 엔진 내부 기능 (Google 기술 강연 동영상)
    3. L. 데이비드 배론, Mozilla의 Layout Engine
    4. L. 데이비드 배론, Mozilla Style System 문서
    5. 크리스 워터슨, HTML 리플로우 관련 참고사항
    6. 크리스 워터슨, Gecko 개요
    7. 알렉산더 라르손, HTML HTTP 요청의 수명
  4. WebKit

    1. 데이비드 하얏트, CSS 구현(1부)
    2. 데이비드 하얏트, 웹 코어 개요
    3. 데이비드 하얏트, WebCore 렌더링
    4. 데이비드 하얏트, FOUC 문제
  5. W3C 사양

    1. HTML 4.01 사양
    2. W3C HTML5 사양
    3. Cascading Style Sheets 레벨 2 버전 1 (CSS 2.1) 사양
  6. 브라우저 빌드 안내

    1. Firefox https://developer.mozilla.org/Build_Documentation
    2. WebKit. http://webkit.org/building/build.html

번역

이 페이지는 일본어, 두 번 번역되었습니다.

한국어튀르키예어의 외부 호스팅 번역을 볼 수 있습니다.

감사합니다.