최신 웹브라우저의 비하인드 스토리
머리말
WebKit과 Gecko의 내부 작업에 대한 이 포괄적인 입문서는 이스라엘 개발자 탈리 가르시엘이 수행한 많은 연구의 결과입니다. 몇 년 동안 브라우저 내부 구조에 관해 게시된 모든 데이터를 검토하고 웹브라우저 소스 코드를 읽는 데 많은 시간을 보냈습니다. 글쓴이는
웹 개발자는 브라우저 작업의 내부를 학습하여 더 나은 결정을 내리고 개발 권장사항의 근거를 파악할 수 있습니다. 이 문서는 다소 길지만 시간을 들여 자세히 살펴보는 것이 좋습니다. 매우 유용합니다.
폴 아이리시, Chrome 개발자 관계
소개
웹 브라우저는 가장 널리 사용되는 소프트웨어입니다. 이 입문서에서는 이들이 내부적으로 어떻게 작동하는지 설명합니다. 주소 표시줄에 google.com을 입력하고 브라우저 화면에 Google 페이지가 표시될 때까지 어떤 일이 발생하는지 살펴보겠습니다.
설명할 브라우저
현재 데스크톱에서 사용되는 주요 브라우저는 Chrome, Internet Explorer, Firefox, Safari, Opera 등 5가지입니다. 모바일의 주요 브라우저는 Android 브라우저, iPhone, Opera Mini 및 Opera Mobile, UC 브라우저, 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 (월드 와이드 웹 컨소시엄) 조직에서 유지관리합니다. 수년 동안 브라우저는 사양의 일부만 준수하고 자체 확장 프로그램을 개발했습니다. 이로 인해 웹 작성자에게 심각한 호환성 문제가 발생했습니다. 오늘날 대부분의 브라우저는 사양을 어느 정도 준수합니다.
브라우저 사용자 인터페이스는 서로 공통점이 많습니다. 일반적인 사용자 인터페이스 요소는 다음과 같습니다.
- URI를 삽입하는 주소 표시줄
- 뒤로 및 앞으로 버튼
- 북마크 옵션
- 현재 문서의 로드를 새로고침하거나 중지하는 새로고침 및 중지 버튼
- 홈페이지로 이동하는 홈 버튼
이상하게도 브라우저의 사용자 인터페이스는 공식 사양에 지정되어 있지 않으며, 수년간의 경험과 서로를 모방하는 브라우저를 통해 형성된 모범 사례에서 비롯됩니다. HTML5 사양은 브라우저에 있어야 하는 UI 요소를 정의하지 않지만 몇 가지 일반적인 요소를 나열합니다. 여기에는 주소 표시줄, 상태 표시줄, 툴바가 포함됩니다. 물론 Firefox의 다운로드 관리자와 같이 특정 브라우저에만 있는 기능도 있습니다.
상위 수준 인프라
브라우저의 주요 구성요소는 다음과 같습니다.
- 사용자 인터페이스: 주소 표시줄, 뒤로/앞으로 버튼, 북마크 메뉴 등이 포함됩니다. 요청된 페이지가 표시되는 창을 제외한 브라우저 디스플레이의 모든 부분입니다.
- 브라우저 엔진: UI와 렌더링 엔진 간의 작업을 마샬링합니다.
- 렌더링 엔진: 요청된 콘텐츠를 표시합니다. 예를 들어 요청된 콘텐츠가 HTML인 경우 렌더링 엔진은 HTML과 CSS를 파싱하고 파싱된 콘텐츠를 화면에 표시합니다.
- 네트워킹: HTTP 요청과 같은 네트워크 호출의 경우 플랫폼 독립적인 인터페이스 뒤에서 플랫폼마다 다른 구현을 사용합니다.
- UI 백엔드: 콤보 상자 및 창과 같은 기본 위젯을 그리는 데 사용됩니다. 이 백엔드는 플랫폼에 국한되지 않는 일반 인터페이스를 노출합니다. 내부적으로는 운영체제 사용자 인터페이스 메서드를 사용합니다.
- JavaScript 인터프리터. JavaScript 코드를 파싱하고 실행하는 데 사용됩니다.
- 데이터 스토리지. 이는 지속성 레이어입니다. 브라우저는 쿠키와 같은 모든 종류의 데이터를 로컬에 저장해야 할 수 있습니다. 브라우저는 localStorage, IndexedDB, WebSQL, FileSystem과 같은 저장소 메커니즘도 지원합니다.
Chrome과 같은 브라우저는 탭마다 하나씩 여러 렌더링 엔진 인스턴스를 실행한다는 점에 유의해야 합니다. 각 탭은 별도의 프로세스에서 실행됩니다.
렌더링 엔진
렌더링 엔진의 역할은… 렌더링, 즉 요청된 콘텐츠를 브라우저 화면에 표시하는 것입니다.
기본적으로 렌더링 엔진은 HTML 및 XML 문서와 이미지를 표시할 수 있습니다. 플러그인이나 확장 프로그램을 통해 다른 유형의 데이터를 표시할 수 있습니다. 예를 들어 PDF 뷰어 플러그인을 사용하여 PDF 문서를 표시할 수 있습니다. 하지만 이 장에서는 CSS를 사용하여 형식이 지정된 HTML과 이미지를 표시하는 주요 사용 사례에 중점을 둡니다.
브라우저마다 사용하는 렌더링 엔진이 다릅니다. Internet Explorer는 Trident를, Firefox는 Gecko를, Safari는 WebKit을 사용합니다. Chrome 및 Opera (버전 15부터)는 WebKit의 포크인 Blink를 사용합니다.
WebKit은 Linux 플랫폼용 엔진으로 시작하여 Apple에서 Mac과 Windows를 지원하도록 수정한 오픈소스 렌더링 엔진입니다.
기본 흐름
렌더링 엔진은 네트워크 계층에서 요청된 문서의 콘텐츠를 가져오기 시작합니다. 이는 일반적으로 8kB 청크로 수행됩니다.
그 후 렌더링 엔진의 기본 흐름은 다음과 같습니다.
렌더링 엔진은 HTML 문서를 파싱하고 요소를 '콘텐츠 트리'라는 트리의 DOM 노드로 변환하기 시작합니다. 엔진은 외부 CSS 파일과 스타일 요소 모두에서 스타일 데이터를 파싱합니다. HTML의 시각적 안내와 함께 스타일 지정 정보는 또 다른 트리인 렌더링 트리를 만드는 데 사용됩니다.
렌더 트리는 색상 및 크기와 같은 시각적 속성이 있는 직사각형을 포함합니다. 직사각형은 화면에 표시될 올바른 순서로 되어 있습니다.
렌더링 트리가 생성된 후에는 '레이아웃' 프로세스를 거칩니다. 즉, 각 노드에 화면에 표시되어야 하는 정확한 좌표를 제공합니다. 다음 단계는 페인팅입니다. 렌더링 트리가 순회되고 UI 백엔드 레이어를 사용하여 각 노드가 페인팅됩니다.
이것이 점진적인 과정이라는 점을 이해하는 것이 중요합니다. 더 나은 사용자 환경을 위해 렌더링 엔진은 가능한 한 빨리 화면에 콘텐츠를 표시하려고 합니다. 모든 HTML이 파싱될 때까지 기다리지 않고 렌더 트리를 빌드하고 레이아웃을 시작합니다. 콘텐츠의 일부가 파싱되어 표시되는 동안 네트워크에서 계속 들어오는 나머지 콘텐츠로 프로세스가 계속됩니다.
기본 흐름 예시
그림 3과 4에서 WebKit과 Gecko는 약간 다른 용어를 사용하지만 흐름은 기본적으로 동일하다는 것을 알 수 있습니다.
Gecko는 시각적으로 서식이 지정된 요소의 트리를 '프레임 트리'라고 부릅니다. 각 요소는 프레임입니다. WebKit은 '렌더 트리'라는 용어를 사용하며, 이는 '렌더 객체'로 구성됩니다. WebKit은 요소 배치에 '레이아웃'이라는 용어를 사용하고 Gecko는 이를 '리플로우'라고 부릅니다. '연결'은 DOM 노드와 시각적 정보를 연결하여 렌더 트리를 만드는 WebKit의 용어입니다. 의미론적이지 않은 사소한 차이점은 Gecko에는 HTML과 DOM 트리 사이에 추가 레이어가 있다는 것입니다. 이를 '콘텐츠 싱크'라고 하며 DOM 요소를 만드는 팩토리입니다. 흐름의 각 부분에 대해 설명합니다.
파싱 - 일반
파싱은 렌더링 엔진에서 매우 중요한 프로세스이므로 조금 더 자세히 살펴보겠습니다. 파싱에 관한 간단한 소개부터 시작하겠습니다.
문서를 파싱한다는 것은 코드가 사용할 수 있는 구조로 변환하는 것을 의미합니다. 파싱 결과는 일반적으로 문서의 구조를 나타내는 노드 트리입니다. 이를 파싱 트리 또는 구문 트리라고 합니다.
예를 들어 2 + 3 - 1 표현식을 파싱하면 다음 트리가 반환될 수 있습니다.
문법
파싱은 문서가 따르는 문법 규칙, 즉 문서가 작성된 언어 또는 형식을 기반으로 합니다. 파싱할 수 있는 모든 형식에는 어휘와 문법 규칙으로 구성된 결정론적 문법이 있어야 합니다. 이를 문맥 자유 문법이라고 합니다. 인간 언어는 이러한 언어가 아니므로 기존 파싱 기법으로 파싱할 수 없습니다.
파서 - 어휘 분석기 조합
파싱은 어휘 분석과 구문 분석이라는 두 가지 하위 프로세스로 나눌 수 있습니다.
어휘 분석은 입력을 토큰으로 분할하는 프로세스입니다. 토큰은 언어 어휘, 즉 유효한 구성요소의 모음입니다. 인간 언어에서는 해당 언어의 사전에 나오는 모든 단어로 구성됩니다.
구문 분석은 언어 구문 규칙을 적용하는 것입니다.
파서는 일반적으로 입력을 유효한 토큰으로 분할하는 어휘 분석기 (토크나이저라고도 함)와 언어 문법 규칙에 따라 문서 구조를 분석하여 파스 트리를 구성하는 파서라는 두 구성요소 간에 작업을 나눕니다.
어휘 분석기는 공백 및 줄바꿈과 같은 관련 없는 문자를 제거하는 방법을 알고 있습니다.
파싱 프로세스는 반복적입니다. 파서는 일반적으로 어휘 분석기에 새 토큰을 요청하고 토큰을 문법 규칙 중 하나와 일치시키려고 시도합니다. 규칙이 일치하면 토큰에 해당하는 노드가 파싱 트리에 추가되고 파서는 다른 토큰을 요청합니다.
일치하는 규칙이 없으면 파서는 토큰을 내부적으로 저장하고 내부적으로 저장된 모든 토큰과 일치하는 규칙을 찾을 때까지 토큰을 계속 요청합니다. 규칙이 없으면 파서에서 예외가 발생합니다. 이는 문서가 유효하지 않으며 문법 오류가 포함되어 있음을 의미합니다.
번역
대부분의 경우 파스 트리는 최종 결과물이 아닙니다. 파싱은 입력 문서를 다른 형식으로 변환하는 번역에 자주 사용됩니다. 컴파일을 예로 들 수 있습니다. 소스 코드를 기계어 코드로 컴파일하는 컴파일러는 먼저 소스 코드를 파싱 트리로 파싱한 다음 트리를 기계어 코드 문서로 변환합니다.
파싱 예시
그림 5에서는 수학 표현식에서 파싱 트리를 빌드했습니다. 간단한 수학 언어를 정의하고 파싱 프로세스를 살펴보겠습니다.
구문:
- 언어 문법 구성요소는 표현식, 용어, 연산입니다.
- 언어에는 표현식이 얼마든지 포함될 수 있습니다.
- 표현식은 '항' 다음에 '연산'이 오고 그 다음에 다른 항이 오는 것으로 정의됩니다.
- 연산은 더하기 토큰 또는 빼기 토큰입니다.
- 항은 정수 토큰 또는 표현식입니다.
입력 2 + 3 - 1를 분석해 보겠습니다.
규칙과 일치하는 첫 번째 하위 문자열은 2입니다. 규칙 #5에 따르면 이는 용어입니다.
두 번째 일치는 2 + 3입니다. 이는 세 번째 규칙인 항 다음에 연산이 오고 그 다음에 다른 항이 오는 규칙과 일치합니다.
다음 일치는 입력 끝에서만 일치합니다.
2 + 3 - 1은 표현식입니다. 2 + 3이 항이라는 것을 이미 알고 있으므로 항 다음에 연산이 오고 그 다음에 다른 항이 오기 때문입니다.
2 + +는 어떤 규칙과도 일치하지 않으므로 잘못된 입력입니다.
어휘 및 문법의 공식 정의
어휘는 일반적으로 정규 표현식으로 표현됩니다.
예를 들어 언어는 다음과 같이 정의됩니다.
INTEGER: 0|[1-9][0-9]*
PLUS: +
MINUS: -
보시다시피 정수는 정규 표현식으로 정의됩니다.
문법은 일반적으로 BNF라는 형식으로 정의됩니다. 언어는 다음과 같이 정의됩니다.
expression := term operation term
operation := PLUS | MINUS
term := INTEGER | expression
문법이 문맥 자유 문법인 경우 언어는 일반 파서로 파싱될 수 있다고 했습니다. 문맥 자유 문법의 직관적인 정의는 BNF로 완전히 표현할 수 있는 문법입니다. 공식적인 정의는 Wikipedia의 문맥 자유 문법에 관한 도움말을 참조하세요.
파서 유형
파서에는 하향식 파서와 상향식 파서의 두 가지 유형이 있습니다. 직관적으로 설명하자면 하향식 파서는 문법의 상위 수준 구조를 검사하고 일치하는 규칙을 찾으려고 합니다. 상향식 파서는 입력에서 시작하여 하위 수준 규칙부터 상위 수준 규칙이 충족될 때까지 점진적으로 문법 규칙으로 변환합니다.
두 가지 유형의 파서가 예시를 어떻게 파싱하는지 살펴보겠습니다.
하향식 파서는 상위 수준 규칙에서 시작하여 2 + 3를 표현식으로 식별합니다. 그런 다음 2 + 3 - 1를 표현식으로 식별합니다 (표현식을 식별하는 프로세스는 다른 규칙과 일치하도록 발전하지만 시작점은 가장 높은 수준의 규칙임).
상향식 파서는 규칙이 일치할 때까지 입력을 스캔합니다. 그런 다음 일치하는 입력을 규칙으로 바꿉니다. 이 작업은 입력이 끝날 때까지 계속됩니다. 부분적으로 일치하는 표현식은 파서의 스택에 배치됩니다.
이러한 유형의 상향식 파서는 입력이 오른쪽으로 이동하고 (포인터가 먼저 입력 시작을 가리키고 오른쪽으로 이동한다고 상상해 보세요) 점차 문법 규칙으로 축소되기 때문에 시프트-리듀스 파서라고 합니다.
파서 자동 생성
파서를 생성할 수 있는 도구가 있습니다. 언어의 문법(어휘 및 구문 규칙)을 입력하면 작동하는 파서가 생성됩니다. 파서를 만들려면 파싱에 대한 깊은 이해가 필요하며 최적화된 파서를 직접 만드는 것은 쉽지 않으므로 파서 생성기가 매우 유용할 수 있습니다.
WebKit은 잘 알려진 두 가지 파서 생성기를 사용합니다. 어휘 분석기를 만드는 Flex와 파서를 만드는 Bison입니다 (Lex 및 Yacc라는 이름으로 접할 수도 있음). Flex 입력은 토큰의 정규 표현식 정의가 포함된 파일입니다. Bison의 입력은 BNF 형식의 언어 문법 규칙입니다.
HTML 파서
HTML 파서의 역할은 HTML 마크업을 파싱 트리로 파싱하는 것입니다.
HTML 문법
HTML의 어휘와 문법은 W3C 조직에서 만든 사양에 정의되어 있습니다.
파싱 소개에서 살펴본 것처럼 문법 구문은 BNF와 같은 형식을 사용하여 공식적으로 정의할 수 있습니다.
안타깝게도 기존 파서 주제는 모두 HTML에 적용되지 않습니다 (재미로 언급한 것이 아니며 CSS 및 JavaScript를 파싱하는 데 사용됩니다). HTML은 파서에 필요한 문맥 자유 문법으로 쉽게 정의할 수 없습니다.
HTML을 정의하는 공식 형식인 DTD (문서 유형 정의)가 있지만 문맥 자유 문법은 아닙니다.
언뜻 보기에는 이상해 보입니다. HTML은 XML에 가깝습니다. 사용 가능한 XML 파서가 많이 있습니다. HTML의 XML 변형인 XHTML이 있는데, 큰 차이점은 무엇인가요?
차이점은 HTML 접근 방식이 더 '관대'하다는 것입니다. 특정 태그를 생략할 수 있고 (암시적으로 추가됨), 시작 또는 종료 태그를 생략할 수 있는 경우도 있습니다. 전반적으로 XML의 엄격하고 까다로운 문법과 달리 '부드러운' 문법입니다.
이러한 사소한 디테일이 큰 차이를 만듭니다. 한편으로는 이것이 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 문서의 객체 표현이자 JavaScript와 같은 외부 세계에 대한 HTML 요소의 인터페이스입니다.
트리의 루트는 'Document' 객체입니다.
DOM은 마크업과 거의 일대일 관계를 갖습니다. 예를 들면 다음과 같습니다.
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
이 마크업은 다음 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은 일반적인 하향식 또는 상향식 파서를 사용하여 파싱할 수 없습니다.
이유는 다음과 같습니다.
- 언어의 관대한 특성
- 브라우저가 잘못된 HTML의 알려진 사례를 지원하기 위해 기존의 오류 허용 범위를 가지고 있다는 사실입니다.
- 파싱 프로세스는 재진입이 가능합니다. 다른 언어의 경우 파싱 중에 소스가 변경되지 않지만 HTML에서는 동적 코드 (예:
document.write()호출이 포함된 스크립트 요소)가 추가 토큰을 추가할 수 있으므로 파싱 프로세스에서 실제로 입력을 수정합니다.
일반적인 파싱 기법을 사용할 수 없으므로 브라우저는 HTML 파싱을 위한 커스텀 파서를 만듭니다.
파싱 알고리즘은 HTML5 사양에 자세히 설명되어 있습니다. 이 알고리즘은 토큰화와 트리 구성이라는 두 단계로 구성됩니다.
토큰화는 입력을 토큰으로 파싱하는 어휘 분석입니다. HTML 토큰에는 시작 태그, 종료 태그, 속성 이름, 속성 값이 있습니다.
토크나이저는 토큰을 인식하여 트리 생성자에 제공하고 다음 문자를 사용하여 다음 토큰을 인식하는 식으로 입력이 끝날 때까지 계속합니다.
토큰화 알고리즘
알고리즘의 출력은 HTML 토큰입니다. 알고리즘은 상태 머신으로 표현됩니다. 각 상태는 입력 스트림의 하나 이상의 문자를 사용하고 해당 문자에 따라 다음 상태를 업데이트합니다. 이 결정은 현재 토큰화 상태와 트리 구성 상태의 영향을 받습니다. 즉, 현재 상태에 따라 동일한 소비 문자가 올바른 다음 상태에 대해 다른 결과를 생성합니다. 알고리즘이 너무 복잡해서 완전히 설명할 수 없으므로 원리를 이해하는 데 도움이 되는 간단한 예를 살펴보겠습니다.
기본 예 - 다음 HTML 토큰화:
<html>
<body>
Hello world
</body>
</html>
초기 상태는 '데이터 상태'입니다.
< 문자가 발견되면 상태가 '태그 열림 상태'로 변경됩니다.
a-z 문자를 사용하면 '시작 태그 토큰'이 생성되고 상태가 '태그 이름 상태'로 변경됩니다.
> 문자가 사용될 때까지 이 상태가 유지됩니다. 각 문자가 새 토큰 이름에 추가됩니다. 이 경우 생성된 토큰은 html 토큰입니다.
> 태그에 도달하면 현재 토큰이 내보내지고 상태가 다시 '데이터 상태'로 변경됩니다.
<body> 태그는 동일한 단계로 처리됩니다.
지금까지 html 및 body 태그가 생성되었습니다. 이제 '데이터 상태'로 돌아왔습니다.
Hello world의 H 문자를 사용하면 문자 토큰이 생성되고 내보내집니다. 이 과정은 </body>의 <에 도달할 때까지 계속됩니다. Hello world의 각 문자에 대해 문자 토큰을 내보냅니다.
이제 '태그 열림 상태'로 돌아왔습니다.
다음 입력 /를 사용하면 end tag token가 생성되고 '태그 이름 상태'로 이동합니다. >에 도달할 때까지 이 상태를 유지합니다. 그런 다음 새 태그 토큰이 내보내지고 '데이터 상태'로 돌아갑니다.
</html> 입력은 이전 케이스와 같이 처리됩니다.
트리 구성 알고리즘
파서가 생성되면 Document 객체가 생성됩니다. 트리 구성 단계에서 루트에 문서가 있는 DOM 트리가 수정되고 요소가 추가됩니다. 토크나이저에서 내보낸 각 노드는 트리 생성자에 의해 처리됩니다. 각 토큰에 대해 사양은 어떤 DOM 요소가 관련이 있고 이 토큰에 대해 생성될지 정의합니다. 요소가 DOM 트리와 열린 요소 스택에 추가됩니다. 이 스택은 중첩 불일치 및 닫히지 않은 태그를 수정하는 데 사용됩니다. 알고리즘은 상태 머신으로도 설명됩니다. 이러한 상태를 '삽입 모드'라고 합니다.
예시 입력의 트리 구성 프로세스를 살펴보겠습니다.
<html>
<body>
Hello world
</body>
</html>
트리 구성 단계의 입력은 토큰화 단계의 토큰 시퀀스입니다. 첫 번째 모드는 '초기 모드'입니다. 'html' 토큰을 수신하면 'before html' 모드로 이동하고 해당 모드에서 토큰이 재처리됩니다. 이렇게 하면 HTMLHtmlElement 요소가 생성되고 루트 Document 객체에 추가됩니다.
상태가 'before head'로 변경됩니다. 그런 다음 'body' 토큰이 수신됩니다. 'head' 토큰이 없더라도 HTMLHeadElement가 암시적으로 생성되어 트리에 추가됩니다.
이제 'in head' 모드로 이동한 다음 'after head'로 이동합니다. body 토큰이 재처리되고 HTMLBodyElement가 생성 및 삽입되며 모드가 'in body'로 전환됩니다.
이제 'Hello world' 문자열의 문자 토큰이 수신됩니다. 첫 번째 문자는 '텍스트' 노드를 만들고 삽입하며 다른 문자는 해당 노드에 추가됩니다.
본문 종료 토큰을 수신하면 'after body' 모드로 전환됩니다. 이제 html 종료 태그를 수신하여 'after after body' 모드로 이동합니다. 파일 끝 토큰을 수신하면 파싱이 종료됩니다.
파싱이 완료되면 실행되는 작업
이 단계에서 브라우저는 문서를 상호작용 가능으로 표시하고 '지연' 모드에 있는 스크립트(문서가 파싱된 후 실행되어야 하는 스크립트)를 파싱하기 시작합니다. 그러면 문서 상태가 '완료'로 설정되고 '로드' 이벤트가 실행됩니다.
HTML5 사양에서 토큰화 및 트리 구성에 대한 전체 알고리즘을 확인할 수 있습니다.
브라우저의 오류 허용 한도
HTML 페이지에서는 '잘못된 문법' 오류가 발생하지 않습니다. 브라우저는 잘못된 콘텐츠를 수정하고 계속 진행합니다.
예를 들어 다음 HTML을 살펴보겠습니다.
<html>
<mytag>
</mytag>
<div>
<p>
</div>
Really lousy HTML
</p>
</html>
수많은 규칙을 위반했을 것입니다 ('mytag'는 표준 태그가 아니며, 'p' 및 'div' 요소의 중첩이 잘못됨 등). 하지만 브라우저는 여전히 올바르게 표시하며 불만을 제기하지 않습니다. 따라서 파서 코드의 상당 부분은 HTML 작성자의 실수를 수정하는 것입니다.
오류 처리는 브라우저에서 상당히 일관되지만 놀랍게도 HTML 사양에 포함되지 않았습니다. 북마크 및 뒤로/앞으로 버튼과 마찬가지로 수년에 걸쳐 브라우저에서 개발된 기능입니다. 많은 사이트에서 반복되는 알려진 잘못된 HTML 구성이 있으며 브라우저는 다른 브라우저와 호환되는 방식으로 이를 수정하려고 합니다.
HTML5 사양은 이러한 요구사항 중 일부를 정의합니다. (WebKit은 HTML 파서 클래스 시작 부분의 주석에서 이를 잘 요약하고 있습니다.)
파서는 토큰화된 입력을 문서로 파싱하여 문서 트리를 빌드합니다. 문서가 올바른 형식인 경우 파싱은 간단합니다.
안타깝게도 형식이 올바르지 않은 HTML 문서를 많이 처리해야 하므로 파서는 오류를 허용해야 합니다.
최소한 다음 오류 조건을 처리해야 합니다.
- 추가되는 요소가 일부 외부 태그 내에서 명시적으로 금지됩니다. 이 경우 요소를 금지하는 태그까지 모든 태그를 닫고 나중에 추가해야 합니다.
- 요소를 직접 추가할 수 없습니다. 문서를 작성하는 사람이 중간에 태그를 잊어버렸거나 중간에 있는 태그가 선택사항일 수 있습니다. HTML HEAD BODY TBODY TR TD LI 태그가 이에 해당할 수 있습니다 (제가 빠뜨린 것이 있나요?).
- 인라인 요소 내부에 블록 요소를 추가하려고 합니다. 다음 상위 블록 요소까지 모든 인라인 요소를 닫습니다.
- 그래도 문제가 해결되지 않으면 요소를 추가할 수 있을 때까지 요소를 닫거나 태그를 무시합니다.
WebKit 오류 허용 범위의 몇 가지 예를 살펴보겠습니다.
<br> 대신 </br>
일부 사이트에서는 <br> 대신 </br>를 사용합니다. IE 및 Firefox와의 호환성을 위해 WebKit은 이를 <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 또는 body 종료 태그가 잘못 배치됨
댓글에서 분명한 답변을 확인할 수 있었습니다.
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.error 및 a.error는 선택기입니다. 중괄호 안의 부분에는 이 규칙 세트에서 적용하는 규칙이 포함됩니다.
이 구조는 다음 정의에서 공식적으로 정의됩니다.
ruleset
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
즉, 규칙 집합은 선택자이거나 쉼표와 공백으로 구분된 여러 선택자입니다 (S는 공백을 나타냄). 규칙 집합에는 중괄호가 포함되며, 중괄호 안에는 선언 또는 세미콜론으로 구분된 여러 선언(선택사항)이 포함됩니다. 'declaration' 및 'selector'는 다음 BNF 정의에 정의됩니다.
WebKit CSS 파서
WebKit은 Flex 및 Bison 파서 생성기를 사용하여 CSS 문법 파일에서 파서를 자동으로 만듭니다. 파서 소개에서 기억하시겠지만 Bison은 상향식 시프트-리듀스 파서를 만듭니다. Firefox는 수동으로 작성된 하향식 파서를 사용합니다. 두 경우 모두 각 CSS 파일은 StyleSheet 객체로 파싱됩니다. 각 객체에는 CSS 규칙이 포함됩니다. CSS 규칙 객체에는 선택자 및 선언 객체와 CSS 문법에 해당하는 기타 객체가 포함됩니다.
스크립트 및 스타일 시트의 처리 순서
스크립트
웹 모델은 동기식입니다. 작성자는 파서가 <script> 태그에 도달하면 스크립트가 즉시 파싱되고 실행될 것으로 예상합니다.
스크립트가 실행될 때까지 문서 파싱이 중지됩니다.
스크립트가 외부에 있는 경우 먼저 네트워크에서 리소스를 가져와야 합니다. 이 작업도 동기식으로 수행되며 리소스를 가져올 때까지 파싱이 중지됩니다.
이 모델은 수년 동안 사용되었으며 HTML4 및 5 사양에도 지정되어 있습니다.
작성자는 스크립트에 'defer' 속성을 추가할 수 있으며, 이 경우 문서 파싱이 중단되지 않고 문서가 파싱된 후에 실행됩니다. HTML5에는 스크립트를 비동기식으로 표시하는 옵션이 추가되어 다른 스레드에서 파싱하고 실행할 수 있습니다.
추측 파싱
WebKit과 Firefox 모두 이 최적화를 수행합니다. 스크립트를 실행하는 동안 다른 스레드가 문서의 나머지 부분을 파싱하고 네트워크에서 로드해야 하는 다른 리소스를 찾아 로드합니다. 이렇게 하면 리소스를 병렬 연결로 로드할 수 있고 전반적인 속도가 향상됩니다. 참고: 추측 파서는 외부 스크립트, 스타일 시트, 이미지와 같은 외부 리소스에 대한 참조만 파싱합니다. 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' 요소에는 디스플레이 영역용, 드롭다운 목록 상자용, 버튼용 등 세 가지 렌더러가 있습니다. 또한 너비가 한 줄에 충분하지 않아 텍스트가 여러 줄로 분할되면 새 줄이 추가 렌더기로 추가됩니다.
여러 렌더러의 또 다른 예는 손상된 HTML입니다. CSS 사양에 따르면 인라인 요소는 블록 요소만 포함하거나 인라인 요소만 포함해야 합니다. 혼합 콘텐츠의 경우 인라인 요소를 래핑하기 위해 익명 블록 렌더러가 생성됩니다.
일부 렌더링 객체는 DOM 노드에 상응하지만 트리의 동일한 위치에 있지는 않습니다. 부동 요소와 절대 위치 요소는 흐름에서 벗어나 트리의 다른 부분에 배치되고 실제 프레임에 매핑됩니다. 원래 있어야 할 위치에 자리표시자 프레임이 표시됩니다.
트리 구성 흐름
Firefox에서 프레젠테이션은 DOM 업데이트의 리스너로 등록됩니다.
프레젠테이션은 프레임 생성을 FrameConstructor에 위임하고 생성자는 스타일을 확인하고(스타일 계산 참고) 프레임을 만듭니다.
WebKit에서는 스타일을 확인하고 렌더러를 만드는 프로세스를 '연결'이라고 합니다. 모든 DOM 노드에는 'attach' 메서드가 있습니다. 연결은 동기식이며 DOM 트리에 노드를 삽입하면 새 노드 '연결' 메서드가 호출됩니다.
html 및 body 태그를 처리하면 렌더링 트리 루트가 생성됩니다.
루트 렌더링 객체는 CSS 사양에서 포함 블록이라고 부르는 것, 즉 다른 모든 블록을 포함하는 최상위 블록에 해당합니다. 크기는 표시 영역(브라우저 창 표시 영역 크기)입니다.
Firefox에서는 ViewPortFrame, WebKit에서는 RenderView라고 합니다.
문서가 가리키는 렌더링 객체입니다.
트리의 나머지 부분은 DOM 노드 삽입으로 구성됩니다.
처리 모델에 관한 CSS2 사양을 참고하세요.
스타일 계산
렌더 트리를 빌드하려면 각 렌더 객체의 시각적 속성을 계산해야 합니다. 이는 각 요소의 스타일 속성을 계산하여 수행됩니다.
스타일에는 다양한 출처의 스타일 시트, 인라인 스타일 요소, HTML의 시각적 속성('bgcolor' 속성 등)이 포함됩니다. 후자는 일치하는 CSS 스타일 속성으로 변환됩니다.
스타일 시트의 출처는 브라우저의 기본 스타일 시트, 페이지 작성자가 제공하는 스타일 시트, 사용자 스타일 시트입니다. 사용자 스타일 시트는 브라우저 사용자가 제공하는 스타일 시트입니다 (브라우저에서 원하는 스타일을 정의할 수 있음). 예를 들어 Firefox에서는 'Firefox 프로필' 폴더에 스타일 시트를 배치하여 이 작업을 수행합니다.
스타일 계산에는 몇 가지 어려움이 있습니다.
- 스타일 데이터는 수많은 스타일 속성을 보유하는 매우 큰 구조이므로 메모리 문제가 발생할 수 있습니다.
각 요소에 일치하는 규칙을 찾는 과정이 최적화되지 않으면 성능 문제가 발생할 수 있습니다. 각 요소의 전체 규칙 목록을 순회하여 일치하는 항목을 찾는 것은 부담스러운 작업입니다. 선택기는 복잡한 구조를 가질 수 있으며, 이로 인해 일치 프로세스가 유망해 보이는 경로에서 시작되지만 결국 실패하고 다른 경로를 시도해야 할 수 있습니다.
예를 들어 이 복합 선택기는 다음과 같습니다.
div div div div{ ... }규칙이 3개 div의 하위 요소인
<div>에 적용됨을 의미합니다. 지정된<div>요소에 규칙이 적용되는지 확인한다고 가정해 보겠습니다. 확인을 위해 트리의 특정 경로를 선택합니다. 노드 트리를 위로 탐색해야만 div가 두 개뿐이고 규칙이 적용되지 않는다는 것을 알 수 있습니다. 그런 다음 트리의 다른 경로를 시도해야 합니다.규칙을 적용하려면 규칙의 계층 구조를 정의하는 매우 복잡한 계단식 규칙이 필요합니다.
브라우저가 이러한 문제에 어떻게 대처하는지 살펴보겠습니다.
스타일 데이터 공유
WebKit 노드는 스타일 객체 (RenderStyle)를 참조합니다. 이러한 객체는 특정 조건에서 노드 간에 공유될 수 있습니다. 노드가 형제 또는 사촌이고 다음 중 하나에 해당합니다.
- 요소는 동일한 마우스 상태여야 합니다 (예: 한 요소는 :hover 상태이고 다른 요소는 그렇지 않은 경우).
- 두 요소 모두 ID가 없어야 합니다.
- 태그 이름이 일치해야 합니다.
- 클래스 속성이 일치해야 합니다.
- 매핑된 속성 집합이 동일해야 함
- 링크 상태가 일치해야 합니다.
- 포커스 상태가 일치해야 합니다.
- 두 요소 모두 속성 선택기의 영향을 받지 않아야 합니다. 여기서 영향을 받는다는 것은 선택기 내의 어느 위치에서든 속성 선택기를 사용하는 선택기 일치가 있는 것으로 정의됩니다.
- 요소에 인라인 스타일 속성이 없어야 합니다.
- 형제 선택기를 전혀 사용하지 않아야 합니다. WebCore는 형제 선택기가 발견되면 전역 스위치를 실행하고 형제 선택기가 있는 경우 전체 문서의 스타일 공유를 사용 중지합니다. 여기에는 + 선택자와 :first-child 및 :last-child와 같은 선택자가 포함됩니다.
Firefox 규칙 트리
Firefox에는 스타일 계산을 더 쉽게 하기 위해 규칙 트리와 스타일 컨텍스트 트리라는 두 개의 추가 트리가 있습니다. WebKit에도 스타일 객체가 있지만 스타일 컨텍스트 트리와 같은 트리에 저장되지는 않으며 DOM 노드만 관련 스타일을 가리킵니다.
스타일 컨텍스트에는 최종 값이 포함됩니다. 값은 모든 일치 규칙을 올바른 순서로 적용하고 논리적 값에서 구체적인 값으로 변환하는 조작을 수행하여 계산됩니다. 예를 들어 논리적 값이 화면의 백분율인 경우 계산되어 절대 단위로 변환됩니다. 규칙 트리 아이디어는 정말 기발합니다. 이를 통해 노드 간에 이러한 값을 공유하여 다시 계산하지 않아도 됩니다. 이렇게 하면 공간도 절약됩니다.
일치하는 모든 규칙은 트리에 저장됩니다. 경로의 하단 노드가 우선순위가 더 높습니다. 트리에는 발견된 규칙 일치의 모든 경로가 포함됩니다. 규칙은 지연 저장됩니다. 트리는 모든 노드에 대해 처음에 계산되지 않지만 노드 스타일을 계산해야 할 때마다 계산된 경로가 트리에 추가됩니다.
트리 경로를 어휘의 단어로 보는 것입니다. 이미 이 규칙 트리를 계산했다고 가정해 보겠습니다.
콘텐츠 트리의 다른 요소에 대한 규칙을 일치시켜야 하고 일치하는 규칙 (올바른 순서)이 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}
간단히 설명하기 위해 색상 구조체와 여백 구조체라는 두 가지 구조체만 작성해야 한다고 가정해 보겠습니다. 색상 구조체에는 색상이라는 하나의 멤버만 포함됩니다. 여백 구조체에는 네 개의 면이 포함됩니다.
결과 규칙 트리는 다음과 같습니다 (노드는 노드 이름으로 표시됨: 가리키는 규칙의 수).
컨텍스트 트리는 다음과 같습니다 (노드 이름: 가리키는 규칙 노드).
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에서는 일치하는 선언을 네 번 순회합니다. 먼저 중요하지 않은 높은 우선순위 속성이 적용되고 (다른 속성이 이 속성에 종속되므로 먼저 적용되어야 하는 속성, 예: display), 그다음 높은 우선순위의 중요한 속성, 그다음 일반 우선순위의 중요하지 않은 속성, 그다음 일반 우선순위의 중요한 규칙이 적용됩니다. 즉, 여러 번 표시되는 속성은 올바른 계단식 순서에 따라 처리됩니다. 마지막으로 적용된 값이 우선 적용됩니다.
요약하자면 스타일 객체 (전체 또는 내부의 일부 구조체)를 공유하면 문제 1과 3이 해결됩니다. Firefox 규칙 트리는 속성을 올바른 순서로 적용하는 데도 도움이 됩니다.
쉬운 경기를 위해 규칙 조작
스타일 규칙에는 여러 소스가 있습니다.
- 외부 스타일 시트 또는 스타일 요소의 CSS 규칙
css p {color: blue} html <p style="color: blue" />와 같은 인라인 스타일 속성- 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 속성이지만 더 일반적임)에 해당하는 속성이 있습니다. 일치하는 규칙에 의해 속성이 정의되지 않은 경우 일부 속성은 상위 요소 스타일 객체에 의해 상속될 수 있습니다. 다른 속성에는 기본값이 있습니다.
정의가 두 개 이상인 경우 문제가 발생합니다. 이 문제를 해결하기 위해 계단식 순서가 사용됩니다.
스타일 속성 선언은 여러 스타일 시트에 표시될 수 있으며 스타일 시트 내에 여러 번 표시될 수 있습니다. 즉, 규칙을 적용하는 순서가 매우 중요합니다. 이를 '계단식' 순서라고 합니다. CSS2 사양에 따르면 계단식 순서는 다음과 같습니다 (낮은 순에서 높은 순).
- 브라우저 선언
- 사용자 일반 선언
- 일반 선언 작성
- 중요 선언 작성
- 사용자 중요 선언
브라우저 선언은 가장 중요하지 않으며, 선언이 중요하다고 표시된 경우에만 사용자가 작성자를 재정의합니다. 순서가 동일한 선언은 특수성에 따라 정렬된 다음 지정된 순서에 따라 정렬됩니다. HTML 시각적 속성은 일치하는 CSS 선언으로 변환됩니다 . 우선순위가 낮은 작성자 규칙으로 처리됩니다.
특수성
선택기 특수성은 CSS2 사양에 의해 다음과 같이 정의됩니다.
- 선언이 선택자가 있는 규칙이 아닌 'style' 속성에서 온 경우 1을 계산하고, 그렇지 않으면 0을 계산합니다 (= a).
- 선택기의 ID 속성 수 계산 (= b)
- 선택기에서 다른 속성 및 의사 클래스의 수를 계산합니다 (= c).
- 선택기의 요소 이름 및 의사 요소 수 계산 (= d)
네 개의 숫자 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은 모든 최상위 스타일 시트(@import 포함)가 로드되었는지 표시하는 플래그를 사용합니다. 연결 시 스타일이 완전히 로드되지 않으면 자리표시자가 사용되고 문서에 표시되며 스타일 시트가 로드되면 다시 계산됩니다.
레이아웃
렌더러가 생성되어 트리에 추가되면 위치와 크기가 없습니다. 이러한 값을 계산하는 것을 레이아웃 또는 리플로우라고 합니다.
HTML은 흐름 기반 레이아웃 모델을 사용합니다. 즉, 대부분의 경우 한 번에 도형을 계산할 수 있습니다. '흐름에서' 뒤에 있는 요소는 일반적으로 '흐름에서' 앞에 있는 요소의 도형에 영향을 미치지 않으므로 레이아웃은 문서를 통해 왼쪽에서 오른쪽으로, 위에서 아래로 진행될 수 있습니다. 예외가 있습니다. 예를 들어 HTML 테이블에는 두 번 이상의 패스가 필요할 수 있습니다.
좌표계는 루트 프레임을 기준으로 합니다. 상단 및 왼쪽 좌표가 사용됩니다.
레이아웃은 재귀 프로세스입니다. HTML 문서의 <html> 요소에 해당하는 루트 렌더러에서 시작됩니다. 레이아웃은 프레임 계층 구조의 일부 또는 전체를 통해 재귀적으로 계속되며, 이를 필요로 하는 각 렌더러의 기하학적 정보를 계산합니다.
루트 렌더러의 위치는 0,0이고 크기는 표시 영역(브라우저 창의 표시되는 부분)입니다.
모든 렌더러에는 '레이아웃' 또는 '리플로우' 메서드가 있으며, 각 렌더러는 레이아웃이 필요한 하위 요소의 레이아웃 메서드를 호출합니다.
더티 비트 시스템
작은 변경사항이 있을 때마다 전체 레이아웃을 수행하지 않기 위해 브라우저는 '더티 비트' 시스템을 사용합니다. 변경되거나 추가된 렌더기는 자신과 하위 요소를 '더티'(레이아웃 필요)로 표시합니다.
'dirty'와 'children are dirty'라는 두 가지 플래그가 있습니다. 'children are dirty'는 렌더러 자체는 괜찮지만 레이아웃이 필요한 하위 요소가 하나 이상 있다는 의미입니다.
전역 및 증분 레이아웃
레이아웃은 전체 렌더링 트리에서 트리거될 수 있습니다. 이를 '전역' 레이아웃이라고 합니다. 이는 다음과 같은 이유로 발생할 수 있습니다.
- 글꼴 크기 변경과 같이 모든 렌더기에 영향을 미치는 전역 스타일 변경입니다.
- 화면 크기 조절로 인해
레이아웃은 점진적일 수 있으며, 더티 렌더러만 배치됩니다 (이로 인해 추가 레이아웃이 필요한 일부 손상이 발생할 수 있음).
렌더러가 더티 상태일 때 증분 레이아웃이 비동기식으로 트리거됩니다. 예를 들어 네트워크에서 추가 콘텐츠가 들어와 DOM 트리에 추가된 후 새 렌더러가 렌더 트리에 추가되는 경우입니다.
비동기 및 동기 레이아웃
증분 레이아웃은 비동기식으로 실행됩니다. Firefox는 증분 레이아웃을 위해 '리플로우 명령어'를 큐에 추가하고 스케줄러는 이러한 명령어의 일괄 실행을 트리거합니다. WebKit에는 증분 레이아웃을 실행하는 타이머도 있습니다. 트리가 순회되고 '더티' 렌더러가 배치됩니다.
'offsetHeight'와 같은 스타일 정보를 요청하는 스크립트는 증분 레이아웃을 동기식으로 트리거할 수 있습니다.
전역 레이아웃은 일반적으로 동기식으로 트리거됩니다.
스크롤 위치와 같은 일부 속성이 변경되어 초기 레이아웃 후 레이아웃이 콜백으로 트리거되는 경우도 있습니다.
최적화
'크기 조절' 또는 렌더러 위치(크기 아님) 변경으로 인해 레이아웃이 트리거되면 렌더링 크기가 캐시에서 가져와지고 다시 계산되지 않습니다.
일부 경우에는 하위 트리만 수정되고 레이아웃이 루트에서 시작되지 않습니다. 이는 변경사항이 로컬이고 주변에 영향을 미치지 않는 경우에 발생할 수 있습니다. 예를 들어 텍스트 필드에 삽입된 텍스트가 있습니다 (그렇지 않으면 모든 키 입력이 루트에서 시작하는 레이아웃을 트리거함).
레이아웃 프로세스
레이아웃은 일반적으로 다음과 같은 패턴을 갖습니다.
- 상위 렌더러가 자체 너비를 결정합니다.
- 부모가 자녀를 살펴보고 다음을 수행합니다.
- 하위 렌더러를 배치합니다 (x 및 y 설정).
- 필요한 경우(더티 상태이거나 전역 레이아웃에 있거나 다른 이유가 있는 경우) 하위 레이아웃을 호출하여 하위 요소의 높이를 계산합니다.
- 상위 요소는 하위 요소의 누적 높이와 여백 및 패딩의 높이를 사용하여 자체 높이를 설정합니다. 이는 상위 렌더러의 상위 요소에서 사용됩니다.
- 더티 비트를 false로 설정합니다.
Firefox는 '상태' 객체(nsHTMLReflowState)를 레이아웃('리플로우'라고 함)의 매개변수로 사용합니다. 상태에는 상위 요소 너비가 포함됩니다.
Firefox 레이아웃의 출력은 '측정항목' 객체(nsHTMLReflowMetrics)입니다. 렌더러에서 계산된 높이가 포함됩니다.
너비 계산
렌더러의 너비는 컨테이너 블록의 너비, 렌더러의 스타일 '너비' 속성, 여백 및 테두리를 사용하여 계산됩니다.
예를 들어 다음 div의 너비는 다음과 같습니다.
<div style="width: 30%"/>
WebKit에서 다음과 같이 계산됩니다(클래스 RenderBox 메서드 calcWidth).
- 컨테이너 너비는 컨테이너 availableWidth와 0 중 최댓값입니다. 이 경우 availableWidth는 다음과 같이 계산되는 contentWidth입니다.
clientWidth() - paddingLeft() - paddingRight()
clientWidth 및 clientHeight는 테두리와 스크롤바를 제외한 객체의 내부를 나타냅니다.
요소 너비는 '너비' 스타일 속성입니다. 컨테이너 너비의 백분율을 계산하여 절댓값으로 계산됩니다.
이제 가로 테두리와 패딩이 추가되었습니다.
지금까지는 '기본 너비'를 계산했습니다. 이제 최소 및 최대 너비가 계산됩니다.
기본 너비가 최대 너비보다 크면 최대 너비가 사용됩니다. 최소 너비 (분할할 수 없는 가장 작은 단위)보다 작으면 최소 너비가 사용됩니다.
레이아웃이 필요한 경우를 대비해 값이 캐시되지만 너비는 변경되지 않습니다.
줄바꿈
레이아웃 중간에 있는 렌더러가 중단해야 한다고 결정하면 렌더러는 중지되고 중단해야 한다는 사실을 레이아웃의 상위 요소에 전파합니다. 상위 요소는 추가 렌더러를 만들고 이에 대해 레이아웃을 호출합니다.
회화
페인팅 단계에서는 렌더 트리를 순회하고 렌더러의 'paint()' 메서드를 호출하여 화면에 콘텐츠를 표시합니다. 페인팅은 UI 인프라 구성요소를 사용합니다.
전역 및 증분
레이아웃과 마찬가지로 페인팅도 전체 트리가 페인팅되는 전역 또는 증분일 수 있습니다. 증분 페인팅에서는 일부 렌더러가 전체 트리에 영향을 미치지 않는 방식으로 변경됩니다. 변경된 렌더러는 화면에서 직사각형을 무효화합니다. 이로 인해 OS는 이를 '더티 영역'으로 인식하고 '페인트' 이벤트를 생성합니다. OS는 이를 영리하게 처리하여 여러 영역을 하나로 통합합니다. Chrome에서는 렌더러가 기본 프로세스와 다른 프로세스에 있으므로 더 복잡합니다. Chrome은 OS 동작을 어느 정도 시뮬레이션합니다. 프레젠테이션은 이러한 이벤트를 수신 대기하고 메시지를 렌더링 루트에 위임합니다. 관련 렌더기에 도달할 때까지 트리가 순회됩니다. 자체 (및 일반적으로 하위 요소)를 다시 그립니다.
그림 순서
CSS2는 페인팅 프로세스의 순서를 정의합니다. 이는 실제로 요소가 쌓기 컨텍스트에 쌓이는 순서입니다. 이 순서는 스택이 뒤에서 앞으로 그려지므로 그리기에 영향을 미칩니다. 블록 렌더러의 스택 순서는 다음과 같습니다.
- 배경 색상
- 배경 이미지
- border
- 어린이
- 개요
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 상자 모델은 문서 트리의 요소에 대해 생성되고 시각적 서식 모델에 따라 배치되는 직사각형 상자를 설명합니다.
각 상자에는 콘텐츠 영역 (예: 텍스트, 이미지 등)과 선택사항인 주변 패딩, 테두리, 여백 영역이 있습니다.
각 노드는 이러한 상자를 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에서 확인할 수 있습니다.
포지셔닝 스키마
다음과 같은 세 가지 스키마가 있습니다.
- 일반: 객체가 문서 내 위치에 따라 배치됩니다. 즉, 렌더링 트리의 위치는 DOM 트리의 위치와 같으며 상자 유형 및 크기에 따라 배치됩니다.
- Float: 객체가 먼저 일반 흐름처럼 배치된 다음 최대한 왼쪽 또는 오른쪽으로 이동합니다.
- 절대: 객체가 DOM 트리와 다른 위치의 렌더링 트리에 배치됩니다.
위치 지정 스키마는 'position' 속성과 'float' 속성으로 설정됩니다.
- static과 relative는 일반적인 흐름을 유발합니다.
- 절대 및 고정으로 인해 절대 위치 지정이 발생함
정적 위치 지정에서는 위치가 정의되지 않으며 기본 위치 지정이 사용됩니다. 다른 스키마에서는 작성자가 상단, 하단, 왼쪽, 오른쪽 위치를 지정합니다.
상자가 배치되는 방식은 다음에 의해 결정됩니다.
- Box 유형
- 상자 크기
- 포지셔닝 스키마
- 이미지 크기 및 화면 크기와 같은 외부 정보
Box 유형
블록 상자: 블록을 형성하며 브라우저 창에 자체 직사각형이 있습니다.
인라인 상자: 자체 블록이 없지만 포함 블록 내에 있습니다.
블록은 세로로 차례로 서식이 지정됩니다. 인라인은 가로로 형식이 지정됩니다.
인라인 상자는 선 또는 '선 상자' 안에 배치됩니다. 선은 가장 높은 상자만큼 높지만, 상자가 '기준'으로 정렬된 경우 더 높을 수 있습니다. 즉, 요소의 하단 부분이 하단이 아닌 다른 상자의 지점에 정렬됩니다. 컨테이너 너비가 충분하지 않으면 인라인이 여러 줄에 배치됩니다. 이것이 일반적으로 단락에서 일어나는 일입니다.
포지셔닝
상대적
상대 위치 - 평소와 같이 배치된 후 필요한 델타만큼 이동합니다.
부동
부동 상자가 선의 왼쪽 또는 오른쪽으로 이동합니다. 흥미로운 점은 다른 상자가 이 상자 주위로 흐른다는 것입니다. HTML:
<p>
<img style="float: right" src="images/image.gif" width="100" height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
다음과 같이 표시됩니다.
절대 및 고정
레이아웃은 일반적인 흐름과 관계없이 정확하게 정의됩니다. 요소가 일반 흐름에 참여하지 않습니다. 크기는 컨테이너를 기준으로 합니다. 고정된 경우 컨테이너는 표시 영역입니다.
계층화된 표현
이는 z-index CSS 속성으로 지정됩니다. 이는 상자의 세 번째 차원, 즉 'z축'을 따른 위치를 나타냅니다.
상자는 스택 (쌓기 컨텍스트라고 함)으로 나뉩니다. 각 스택에서 뒤쪽 요소가 먼저 그려지고 앞쪽 요소가 그 위에 그려져 사용자에게 더 가깝게 표시됩니다. 겹치는 경우 맨 앞의 요소가 이전 요소를 숨깁니다.
스택은 z-색인 속성에 따라 정렬됩니다. 'z-index' 속성이 있는 상자는 로컬 스택을 형성합니다. 표시 영역에는 외부 스택이 있습니다.
예:
<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>
결과는 다음과 같습니다.
마크업에서 빨간색 div가 녹색 div보다 앞에 있고 일반 흐름에서 먼저 페인트되었지만 z-index 속성이 더 높으므로 루트 상자가 보유한 스택에서 더 앞에 있습니다.
리소스
브라우저 아키텍처
- Grosskurth, Alan. 웹브라우저 참조 아키텍처 (PDF)
- Gupta, Vineet. 브라우저 작동 방식 - 1부 - 아키텍처
파싱
- Aho, Sethi, Ullman, Compilers: Principles, Techniques, and Tools('Dragon book'이라고도 함), Addison-Wesley, 1986
- 릭 젤리프 The Bold and the Beautiful: HTML 5의 새로운 초안 2개
Firefox
- L. 데이비드 바론, 더 빠른 HTML 및 CSS: 웹 개발자를 위한 레이아웃 엔진 내부
- L. 데이비드 배런, Faster HTML and CSS: Layout Engine Internals for 웹 개발자 (Google 기술 강연 동영상)
- L. 데이비드 바론, Mozilla의 레이아웃 엔진
- L. 데이비드 배런, Mozilla 스타일 시스템 문서
- 크리스 워터슨, HTML 리플로우에 관한 참고사항
- Chris Waterson, Gecko Overview
- Alexander Larsson, HTML HTTP 요청의 수명
WebKit
- 데이비드 하얏트, CSS 구현(1부)
- David Hyatt, An Overview of WebCore
- WebCore 렌더링, 데이비드 하얏트
- 데이비드 하얏트, FOUC 문제
W3C 사양
브라우저 빌드 안내
번역
이 페이지는 일본어로 두 번 번역되었습니다.
- How Browsers Work - Behind the Scenes of Modern Web Browsers (ja), @kosei
- ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏側 by @ikeike443 and @kiyoto01.
외부에서 호스팅되는 한국어 및 튀르키예어 번역을 볼 수 있습니다.
감사합니다.