최신 웹브라우저의 배경
머리말
이 종합 입문서는 WebKit 및 Gecko의 내부 운영에 대한 이스라엘 개발업체 Tali Garsiel이 수행한 수많은 연구 결과 몇 개 이상 수년 동안 브라우저 내부에 대해 게시된 모든 데이터를 검토하고 웹 브라우저 소스 코드를 읽는 데 시간이 오래 걸립니다. 그녀는 다음과 같이 작성했습니다.
웹 개발자는 브라우저 운영의 내부를 배울 수 있습니다. 더 나은 결정을 내리고 개발 이면의 근거를 파악하는 데 도움이 됨 권장사항을 참고하세요. 이 문서는 다소 길지만, 시간을 들여 자세히 들여다볼 수 있습니다. 도움이 되어 기쁩니다.
폴 아이리시, Chrome 개발자 관계팀
소개
웹브라우저는 가장 널리 사용되는 소프트웨어입니다. 이 입문서에서는
백그라운드에서 작동합니다 google.com
를 입력하면 어떻게 되는지 살펴보겠습니다.
를 입력합니다.
알아볼 브라우저
오늘날 데스크톱에서 사용되는 주요 브라우저는 Chrome, Internet Explorer, Firefox, Safari, Opera 5개입니다. 모바일의 주요 브라우저는 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) 조직에서 관리합니다. 수년 동안 브라우저는 일부 사양만 준수했으며 자체 확장 프로그램을 개발했습니다. 이로 인해 웹 작성자에게 심각한 호환성 문제가 발생했습니다. 오늘날 대부분의 브라우저는 사양을 거의 준수합니다.
브라우저 사용자 인터페이스는 서로 많은 공통점이 있습니다. 일반적인 사용자 인터페이스 요소는 다음과 같습니다.
- URI 삽입을 위한 주소 표시줄
- 뒤로 및 앞으로 버튼
- 북마크 옵션
- 현재 문서의 새로고침 또는 로드 중지를 위한 새로고침 및 중지 버튼
- 홈페이지로 이동하는 홈 버튼
이상하게도 브라우저의 사용자 인터페이스는 정식 사양에 지정되지 않습니다. 수년 간의 경험을 통해 형성된 모범 사례와 서로를 모방하는 브라우저를 통해 비롯된 것입니다. HTML5 사양은 브라우저에 있어야 하는 UI 요소를 정의하지 않지만 몇 가지 공통 요소를 나열합니다. 여기에는 주소 표시줄, 상태 표시줄, 툴바가 있습니다. 물론 Firefox의 다운로드 관리자와 같은 특정 브라우저만의 기능도 있습니다.
상위 수준 인프라
브라우저의 주요 구성요소는 다음과 같습니다.
- 사용자 인터페이스: 여기에는 주소 표시줄, 뒤로/앞으로 버튼, 북마크 메뉴 등이 포함됩니다. 요청된 페이지가 표시되는 창을 제외하고 브라우저의 모든 부분이 표시됩니다.
- 브라우저 엔진: UI와 렌더링 엔진 간에 작업을 마샬링합니다.
- 렌더링 엔진: 요청된 콘텐츠를 표시합니다. 예를 들어 요청된 콘텐츠가 HTML인 경우 렌더링 엔진은 HTML 및 CSS를 파싱하고 파싱된 콘텐츠를 화면에 표시합니다.
- 네트워킹: HTTP 요청과 같은 네트워크 호출을 위해, 플랫폼과 상관없는 인터페이스 뒤에서 플랫폼마다 다른 구현을 사용합니다.
- UI 백엔드: 콤보 상자 및 창과 같은 기본 위젯을 그리기 위해 사용됩니다. 이 백엔드는 플랫폼별로 다르지 않은 일반 인터페이스를 노출합니다. 그 아래에는 운영체제 사용자 인터페이스 메서드가 사용됩니다.
- JavaScript 인터프리터. JavaScript 코드를 파싱하고 실행하는 데 사용됩니다.
- 데이터 스토리지. 이것은 지속성 레이어입니다. 브라우저에서 쿠키와 같은 모든 종류의 데이터를 로컬에 저장해야 할 수도 있습니다. 브라우저는 localStorage, IndexedDB, WebSQL 및 FileSystem과 같은 저장 메커니즘도 지원합니다.
![브라우저 구성요소](https://web.developers.google.cn/static/articles/howbrowserswork/image/browser-components-9cd8ff834cc9c.png?authuser=19&hl=ko)
Chrome과 같은 브라우저는 탭당 하나씩, 여러 개의 렌더링 엔진 인스턴스를 실행한다는 점에 유의해야 합니다. 각 탭은 별도의 프로세스에서 실행됩니다.
렌더링 엔진
렌더링 엔진은 렌더링을 담당합니다. 렌더링이란 브라우저 화면에 요청된 콘텐츠를 표시하는 것입니다.
기본적으로 렌더링 엔진은 HTML 및 XML 문서와 이미지를 표시할 수 있습니다. 플러그인이나 확장 프로그램을 통해 다른 유형의 데이터를 표시할 수 있음 PDF 뷰어 플러그인을 사용하여 PDF 문서를 표시할 수 있습니다. 그러나 이 장에서는 CSS를 사용하여 형식이 지정된 HTML과 이미지를 표시하는 주요 사용 사례에 초점을 맞추겠습니다.
브라우저마다 서로 다른 렌더링 엔진을 사용합니다. Internet Explorer는 Trident를, Firefox는 Gecko를, Safari는 WebKit을 사용합니다. Chrome 및 Opera (버전 15부터)는 WebKit의 포크인 Blink를 사용합니다.
WebKit는 Linux 플랫폼용 엔진으로 시작되었으며 Mac과 Windows를 지원하도록 Apple에 의해 수정된 오픈소스 렌더링 엔진입니다.
기본 흐름
렌더링 엔진이 요청된 문서의 콘텐츠를 가져오기 시작합니다. 네트워킹 계층에서 전달될 수 있습니다 이 작업은 일반적으로 8KB 단위로 이루어집니다.
다음은 렌더링 엔진의 기본 흐름입니다.
<ph type="x-smartling-placeholder">![렌더링 엔진 기본 흐름](https://web.developers.google.cn/static/articles/howbrowserswork/image/rendering-engine-basic-fl-2fba02b24e871.png?authuser=19&hl=ko)
렌더링 엔진이 HTML 문서 파싱을 시작하고 '콘텐츠 트리'라는 트리에서 요소를 DOM 노드로 변환합니다. 엔진이 외부 CSS 파일과 스타일 요소에서 스타일 데이터를 파싱합니다. HTML의 시각적 지침과 함께 스타일 지정 정보는 또 다른 트리인 렌더 트리를 만드는 데 사용됩니다.
렌더링 트리에는 색상 및 크기와 같은 시각적 속성이 있는 직사각형이 포함됩니다. 직사각형은 화면에 올바른 순서로 표시됩니다.
렌더링 트리가 생성된 후에는 'layout'을 거칩니다. 프로세스입니다 즉, 화면에 표시되어야 하는 정확한 좌표를 각 노드에 제공합니다. 다음 단계는 페인팅입니다. 렌더링 트리를 순회하고 각 노드는 UI 백엔드 레이어를 사용하여 페인팅합니다.
이 과정은 점진적으로 진행된다는 점에 유의하시기 바랍니다. 더 나은 사용자 환경을 위해 렌더링 엔진은 가능한 한 빨리 화면에 콘텐츠를 표시하려고 합니다. 모든 HTML이 파싱될 때까지 기다리지 않고 렌더링 트리의 빌드 및 레이아웃을 시작합니다. 콘텐츠의 일부는 파싱되어 표시되고 나머지 콘텐츠는 네트워크에서 계속 전달되면서 프로세스가 계속 진행됩니다.
기본 흐름의 예
<ph type="x-smartling-placeholder">![WebKit 기본 흐름](https://web.developers.google.cn/static/articles/howbrowserswork/image/webkit-main-flow-b779d50c0cf28.png?authuser=19&hl=ko)
![Mozilla의 Gecko 렌더링 엔진 기본 흐름](https://web.developers.google.cn/static/articles/howbrowserswork/image/mozillas-gecko-rendering-b18e445544965.jpg?authuser=19&hl=ko)
그림 3과 4에서 WebKit과 Gecko가 약간 다른 용어를 사용하지만 흐름은 기본적으로 동일함을 알 수 있습니다.
도마뱀은 시각적으로 형식이 지정된 요소의 트리를 '프레임 트리'라고 부릅니다. 각 요소는 프레임입니다. WebKit에서는 'Render Tree'라는 용어를 사용합니다. ‘렌더링 객체’로 구성됩니다. WebKit에서는 '레이아웃'이라는 용어를 사용함 를 사용하고 게코는 이를 '리플로우'라고 부릅니다. '첨부파일' 렌더링 트리를 만들기 위해 DOM 노드와 시각적 정보를 연결하는 WebKit의 용어입니다. 의미론적이지 않은 사소한 차이점은 Gecko가 HTML과 DOM 트리 사이에 추가 레이어를 가지고 있다는 점입니다. 이를 '콘텐츠 싱크'라고 합니다. DOM 요소를 만들기 위한 팩토리입니다. 흐름의 각 부분에 대해 알아보겠습니다.
파싱 - 일반
파싱은 렌더링 엔진 내에서 매우 중요한 프로세스이므로 조금 더 자세히 살펴보겠습니다. 파싱에 관해 간단히 소개하겠습니다.
문서를 파싱한다는 것은 코드가 사용할 수 있는 구조로 문서를 변환하는 것을 의미합니다. 파싱 결과는 일반적으로 문서의 구조를 나타내는 노드 트리입니다. 이를 파싱 트리 또는 구문 트리라고 합니다.
예를 들어 2 + 3 - 1
표현식을 파싱하면 다음 트리가 반환될 수 있습니다.
![수학 표현식 트리 노드입니다.](https://web.developers.google.cn/static/articles/howbrowserswork/image/mathematical-expression-t-6681a2511ead2.png?authuser=19&hl=ko)
문법
파싱은 문서가 준수하는 구문 규칙, 즉 문서에 사용된 언어 또는 형식을 기반으로 합니다. 파싱할 수 있는 모든 형식에는 어휘 및 구문 규칙으로 구성된 확정적인 문법이 있어야 합니다. 이를 문맥 없는 문법을 참고하세요. 인간 언어는 이러한 언어가 아니므로 기존의 파싱 기술로 파싱할 수 없습니다.
파서 - 렉서 조합
파싱은 어휘 분석과 구문 분석이라는 두 가지 하위 프로세스로 나눌 수 있습니다.
어휘 분석은 입력을 토큰으로 나누는 프로세스입니다. 토큰은 언어 어휘이며 유효한 빌딩 블록의 모음입니다. 인간 언어의 경우 해당 언어 사전에 나타나는 모든 단어로 구성됩니다.
구문 분석은 언어 구문 규칙을 적용하는 것입니다.
파서는 일반적으로 입력을 유효한 토큰으로 분할하는 렉서 (토큰나저라고도 함)와 언어 구문 규칙에 따라 문서 구조를 분석하여 파싱 트리를 구성하는 파서로 나눕니다.
렉서는 공백과 줄바꿈과 같이 관련이 없는 문자를 삭제하는 방법을 알고 있습니다.
<ph type="x-smartling-placeholder">![소스 문서에서 파싱 트리로](https://web.developers.google.cn/static/articles/howbrowserswork/image/from-source-document-par-c9c8c59da1ef2.png?authuser=19&hl=ko)
파싱 프로세스는 반복적입니다. 파서는 일반적으로 렉서에 새 토큰을 요청하고 토큰을 구문 규칙 중 하나와 일치시키려고 시도합니다. 규칙이 일치하면 토큰에 해당하는 노드가 파싱 트리에 추가되고 파서가 다른 토큰을 요청합니다.
일치하는 규칙이 없으면 파서는 토큰을 내부적으로 저장하고 내부에 저장된 모든 토큰과 일치하는 규칙이 발견될 때까지 토큰을 계속 요청합니다. 규칙이 발견되지 않으면 파서가 예외를 발생시킵니다. 이는 문서가 유효하지 않으며 구문 오류가 포함되어 있음을 의미합니다.
번역
대부분의 경우 파싱 트리는 최종 결과물이 아닙니다. 파싱은 입력 문서를 다른 형식으로 변환하는 변환에 자주 사용됩니다. 컴파일을 예로 들 수 있습니다. 소스 코드를 기계어 코드로 컴파일하는 컴파일러는 먼저 소스 코드를 파싱 트리로 파싱한 다음 트리를 기계어 코드 문서로 변환합니다.
<ph type="x-smartling-placeholder">![컴파일 흐름](https://web.developers.google.cn/static/articles/howbrowserswork/image/compilation-flow-57cfc3aa68a53.png?authuser=19&hl=ko)
파싱 예
그림 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로 완전히 표현할 수 있는 문법입니다. 공식적인 정의는 맥락 없는 문법에 관한 위키백과 자료
파서 유형
파서에는 두 가지 유형, 즉 하향식 파서와 상향식 파서가 있습니다. 직관적인 설명은 하향식 파서가 구문의 상위 수준 구조를 검토하고 규칙 일치를 찾으려고 시도한다는 것입니다. 상향식 파서는 입력으로 시작하여 낮은 수준의 규칙부터 높은 수준의 규칙이 충족될 때까지 구문 규칙으로 점진적으로 변환합니다.
두 가지 유형의 파서가 예를 파싱하는 방법을 살펴보겠습니다.
하향식 파서는 더 높은 수준의 규칙에서 시작합니다. 즉, 2 + 3
를 표현식으로 식별합니다. 그런 다음 2 + 3 - 1
를 표현식으로 식별합니다 (표현식을 식별하는 프로세스가 발전하여 다른 규칙과 일치하지만 시작점이 가장 높은 수준의 규칙임).
상향식 파서는 규칙이 일치할 때까지 입력을 스캔합니다. 그러면 일치하는 입력 내용이 규칙으로 바뀝니다. 이 작업은 입력이 끝날 때까지 계속됩니다. 부분적으로 일치하는 식은 파서의 스택에 배치됩니다.
이러한 유형의 상향식 파서를 Shift-Reduce 파서라고 합니다. 입력이 오른쪽으로 이동하고 (포인터가 먼저 입력 시작을 가리키고 오른쪽으로 이동) 문법 규칙으로 점차 줄어들기 때문입니다.
자동으로 파서 생성
파서를 생성할 수 있는 도구가 있습니다. 개발자가 그 언어의 문법, 즉 어휘와 구문 규칙을 입력하면 작동하는 파서를 생성합니다. 파서를 만들려면 파싱을 깊이 이해해야 하고 최적화된 파서를 수동으로 만들기는 쉽지 않으므로 파서 생성기가 매우 유용할 수 있습니다.
WebKit은 두 가지 잘 알려진 파서 생성기를 사용합니다. 하나는 렉서 생성을 위한 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 접근 방식이 더 '관대'하다는 점입니다. 즉, 특정 태그를 생략하거나 (이는 암시적으로 추가) 시작 또는 종료 태그를 생략하는 등의 작업을 수행할 수 있습니다. 전반적으로 '부드럽고' 문법에 맞게 설계되었습니다.
겉보기에는 작은 디테일들이 엄청난 차이를 만들어 냅니다. 한편으로는 HTML이 인기 있는 주된 이유이기도 합니다. HTML을 사용하면 실수를 용서하고 웹 작성자의 생활을 수월하게 할 수 있기 때문입니다. 반면에 공식적인 문법을 작성하기는 어렵습니다. 요약하자면 HTML은 문맥에 자유롭지 않으므로 기존의 파서로는 쉽게 파싱할 수 없습니다. HTML은 XML 파서로 파싱할 수 없습니다.
HTML DTD
HTML 정의는 DTD 형식입니다. 이 형식은 SGML 계열의 언어를 정의하는 데 사용됩니다. 형식에는 허용되는 모든 요소, 속성, 계층 구조에 대한 정의가 포함됩니다. 앞서 살펴봤듯이 HTML DTD는 문맥 없는 문법을 형성하지 않습니다.
DTD에는 몇 가지 변형이 있습니다. 엄격 모드는 사양만 따르지만, 다른 모드에는 과거에 브라우저에서 사용하던 마크업에 대한 지원이 포함됩니다. 목적은 이전 콘텐츠와의 호환성을 유지하는 것입니다. 현재의 엄격한 DTD는 다음과 같습니다. www.w3.org/TR/html4/strict.dtd
DOM
출력 트리('파싱 트리')는 DOM 요소 및 속성 노드의 트리입니다. DOM은 문서 객체 모델의 줄임말입니다. HTML 문서의 객체 표현과 JavaScript 같은 외부 세계에 HTML 요소의 인터페이스 역할을 합니다.
트리의 루트는 '문서'입니다. 객체를 지정합니다.
DOM은 마크업과 거의 일대일 관계입니다. 예를 들면 다음과 같습니다.
<html>
<body>
<p>
Hello World
</p>
<div> <img src="example.png"/></div>
</body>
</html>
이 마크업은 다음 DOM 트리로 변환됩니다.
<ph type="x-smartling-placeholder">![예제 마크업의 DOM 트리](https://web.developers.google.cn/static/articles/howbrowserswork/image/dom-tree-the-example-mar-70be67fe14c9a.png?authuser=19&hl=ko)
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 토큰에는 시작 태그, 종료 태그, 속성 이름 및 속성 값이 있습니다.
tokenizer는 토큰을 인식하여 트리 생성자에 넘겨주고 다음 토큰을 인식하기 위해 다음 문자를 사용하는 식으로 입력이 끝날 때까지 계속합니다.
<ph type="x-smartling-placeholder">![HTML 파싱 흐름 (HTML5 사양에서 가져옴)](https://web.developers.google.cn/static/articles/howbrowserswork/image/html-parsing-flow-taken-6118c51b92b56.png?authuser=19&hl=ko)
토큰화 알고리즘
알고리즘의 출력은 HTML 토큰입니다. 알고리즘은 상태 머신으로 표현됩니다. 각 상태는 입력 스트림의 문자를 하나 이상 소비하고 이러한 문자에 따라 다음 상태를 업데이트합니다. 결정은 현재 토큰화 상태와 트리 구성 상태의 영향을 받습니다. 즉, 동일한 소비된 문자라도 현재 상태에 따라 올바른 다음 상태에 대한 다른 결과가 생성됩니다. 알고리즘이 너무 복잡해서 완벽하게 설명하기에는 이 원리를 이해하는 데 도움이 되는 간단한 예를 살펴보겠습니다.
기본 예 - 다음 HTML 토큰화:
<html>
<body>
Hello world
</body>
</html>
초기 상태는 '데이터 상태'입니다.
<
문자가 있으면 상태가 '태그 열기 상태'로 변경됩니다.
a-z
문자를 사용하면 '시작 태그 토큰'이 생성되고 상태가 '태그 이름 상태'로 변경됩니다.
>
문자가 소비될 때까지 이 상태가 유지됩니다. 각 문자가 새 토큰 이름에 추가됩니다. 이 경우 생성된 토큰은 html
토큰입니다.
>
태그에 도달하면 현재 토큰이 내보내지고 상태가 다시 '데이터 상태'로 변경됩니다.
<body>
태그도 동일한 단계로 처리됩니다.
지금까지 html
및 body
태그를 내보냈습니다. 이제 'Data state'로 돌아왔습니다.
Hello world
의 H
문자를 사용하면 문자 토큰이 생성되고 내보내기되며 이는 </body>
의 <
에 도달할 때까지 계속됩니다. Hello world
의 각 문자에 대한 문자 토큰을 내보냅니다.
이제 '태그 열기 상태'로 돌아옵니다.
다음 입력 /
를 소비하면 end tag token
가 생성되고 '태그 이름 상태'로 이동됩니다. 다시 >
에 도달할 때까지 이 상태를 유지합니다.그러면 새 태그 토큰이 내보내지고 '데이터 상태'로 돌아갑니다.
</html>
입력은 이전 사례와 동일하게 처리됩니다.
![예시 입력 토큰화](https://web.developers.google.cn/static/articles/howbrowserswork/image/tokenizing-example-input-9d0cc36689681.png?authuser=19&hl=ko)
나무 구성 알고리즘
파서가 생성되면 Document 객체가 만들어집니다. 트리 생성 단계에서 루트에 문서가 있는 DOM 트리가 수정되고 요소가 트리에 추가됩니다. tokenizer에서 내보낸 각 노드는 트리 생성자에 의해 처리됩니다. 사양은 각 토큰에 대해 어떤 DOM 요소가 관련되는지 정의하고 이 토큰에 대해 생성됩니다. 이 요소는 DOM 트리에 추가되며, 열린 요소의 스택에도 추가됩니다. 이 스택은 중첩 불일치 및 닫히지 않은 태그를 수정하는 데 사용됩니다. 알고리즘은 상태 머신이라고도 합니다. 이러한 상태를 '삽입 모드'라고 합니다.
예시 입력의 트리 구성 프로세스를 살펴보겠습니다.
<html>
<body>
Hello world
</body>
</html>
트리 구성 단계에 대한 입력은 토큰화 단계의 토큰 시퀀스입니다. 첫 번째 모드는 '초기 모드'입니다. 'html' 수신 토큰은 "before html" 모드로 전환되고 해당 모드에서 토큰이 재처리됩니다. 이렇게 하면 루트 문서 객체에 추가되는 HTMLHTMLElement 요소가 생성됩니다.
상태가 'before head'로 변경됩니다. 'body' 수신됩니다 HTMLHeadElement는 'head'가 없지만 암시적으로 생성됩니다. 토큰이며 트리에 추가됩니다.
이제 "in head" 모드로 이동한 다음 "after head" 모드로 전환합니다. 본문 토큰이 다시 처리되고 HTMLBodyElement가 생성 및 삽입되며 모드가 'in body'로 전송됩니다.
'Hello world' 문자 토큰 문자열이 수신됩니다. 첫 번째를 사용하면 '텍스트'가 생성되고 삽입됩니다. 다른 문자는 해당 노드에 추가됩니다.
본문 엔드 토큰을 수신하면 "after body" 모드로 전환됩니다. 이제 html 종료 태그를 수신하게 되며 이 태그는 'after body' 모드로 이동합니다. 파일 끝 토큰을 수신하면 파싱이 종료됩니다.
<ph type="x-smartling-placeholder">![HTML 예의 트리 생성](https://web.developers.google.cn/static/articles/howbrowserswork/image/tree-construction-exampl-4e9757a851f96.gif?authuser=19&hl=ko)
파싱이 완료된 경우의 작업
이 단계에서 브라우저는 문서를 대화형으로 표시하고 '지연된' 스크립트의 파싱을 시작합니다. 모드: 문서가 파싱된 후 실행되어야 하는 모드입니다. 그러면 문서 상태가 '완료'로 설정됩니다. 'load' 이벤트가 시작됩니다.
HTML5 사양에서 토큰화 및 트리 구성을 위한 전체 알고리즘을 확인할 수 있습니다.
브라우저 오류 허용 범위
'잘못된 구문'이 표시되지 않음 오류가 발생할 수 있습니다. 브라우저는 잘못된 콘텐츠를 수정한 다음 계속 진행합니다.
다음 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 또는 본문 엔드 태그
다시 한번 말하지만 이 댓글은 의미가 있습니다.
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" 는 클래스 이름과 같은 식별자의 줄임말입니다. '이름' '#'으로 참조되는 요소 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는 공백을 나타냄). 규칙 세트에는 중괄호가 포함되며 그 안에 선언이 포함되거나 세미콜론으로 구분된 여러 개의 선언이 포함될 수도 있습니다. '선언' 및 'selector' 다음 BNF 정의에서 정의됩니다.
WebKit CSS 파서
WebKit은 Flex 및 Bison 파서 생성기를 사용하여 CSS 문법 파일에서 자동으로 파서를 만듭니다. 파서 소개에서 언급했듯이 Bison은 상향식 Shift-Reduce 파서를 만듭니다. Firefox는 수동으로 작성된 하향식 파서를 사용합니다. 두 경우 모두 각 CSS 파일이 StyleSheet 객체로 파싱됩니다. 각 객체에는 CSS 규칙이 포함됩니다. CSS 규칙 객체에는 선택자, 선언 객체, CSS 문법에 해당하는 기타 객체가 포함됩니다.
<ph type="x-smartling-placeholder">![CSS를 파싱하는 중입니다.](https://web.developers.google.cn/static/articles/howbrowserswork/image/parsing-css-4531ebee58764.png?authuser=19&hl=ko)
스크립트 및 스타일 시트 처리 순서
스크립트
웹 모델은 동기식입니다. 작성자는 파서가 <script>
태그에 도달하는 즉시 스크립트가 파싱되고 실행되기를 기대합니다.
스크립트가 실행될 때까지 문서의 파싱이 중단됩니다.
외부 스크립트인 경우 먼저 네트워크에서 리소스를 가져와야 합니다. 이 역시 동기식으로 수행되며, 리소스를 가져올 때까지 파싱이 중단됩니다.
이 모델은 오랫동안 사용되어 왔으며 HTML4 및 5 사양에서도 지정되어 있습니다.
작성자는 속성을 스크립트에 추가하여, 이 경우 문서 파싱을 중단하지 않고 문서가 파싱된 후 실행됩니다. 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 상자에 해당하는 직사각형 영역을 나타냅니다. 너비, 높이, 위치와 같은 기하학적 정보가 포함됩니다.
상자 유형은 '디스플레이'의 영향을 받습니다. 노드와 관련된 스타일 속성의 값입니다 (스타일 계산 섹션 참고). 다음은 표시 속성에 따라 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'는 요소가 포함됩니다. 또한 표시 값이 '없음'으로 할당된 요소 가 트리에 나타나지 않습니다. 반면 가시성이 '숨겨진' 요소는 트리에 나타납니다.
여러 시각적 객체에 해당하는 DOM 요소가 있습니다. 이러한 요소는 일반적으로 하나의 직사각형으로 설명할 수 없는 복잡한 구조의 요소입니다. 예를 들어 'select'는 요소에는 표시 영역, 드롭다운 목록 상자, 버튼에 대한 렌더러가 하나씩 있습니다. 또한 너비가 한 줄에 충분하지 않아 텍스트가 여러 줄로 분할된 경우 새 줄이 추가 렌더기로 추가됩니다.
여러 렌더기의 또 다른 예는 손상된 HTML입니다. CSS 사양에 따라 인라인 요소는 블록 요소만 또는 인라인 요소만 포함해야 합니다. 혼합 콘텐츠의 경우 인라인 요소를 래핑하기 위해 익명 블록 렌더러가 생성됩니다.
일부 렌더링 객체는 DOM 노드에 해당하지만 트리의 동일한 위치에 있지는 않습니다. 부동 소수점 수와 절대 위치로 지정된 요소는 흐름에서 벗어나 트리의 다른 부분에 배치되고 실제 프레임에 매핑됩니다. 자리표시자 프레임은 원래 표시되어야 하는 위치입니다.
<ph type="x-smartling-placeholder">![렌더링 트리 및 상응하는 DOM 트리](https://web.developers.google.cn/static/articles/howbrowserswork/image/the-render-tree-the-corr-f699894ef4c75.png?authuser=19&hl=ko)
트리를 구성하는 흐름
Firefox에서 프레젠테이션은 DOM 업데이트에 대한 리스너로 등록됩니다.
프레젠테이션은 프레임 생성을 FrameConstructor
에 위임하고 생성자는 스타일을 확인하고 (스타일 계산 참고) 프레임을 만듭니다.
WebKit에서는 스타일을 확인하고 렌더기를 만드는 프로세스를 '첨부파일'이라고 합니다. 모든 DOM 노드에는 '연결'이 있음 메서드를 사용하여 축소하도록 요청합니다. 첨부는 동기식이며 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)를 참조합니다. 이러한 객체는 일부 조건에서 노드에서 공유할 수 있습니다. 노드는 동위 요소 또는 사촌 노드이며 다음을 충족해야 합니다.
- 요소는 동일한 마우스 상태에 있어야 합니다 (예 :한 요소는 마우스 오버 상태이지만 다른 요소는 마우스 오버 상태가 될 수 없음).
- 두 요소 모두 ID가 없어야 합니다.
- 태그 이름은
- 클래스 특성은
- 매핑된 속성 집합은 동일해야 합니다.
- 연결 상태가 일치해야 합니다.
- 포커스 상태가 일치해야 합니다.
- 두 요소 모두 속성 선택기의 영향을 받지 않습니다. 영향을 받는 요소는 선택기 내 모든 위치에서 속성 선택기를 사용하는 선택기 일치 항목이 있는 것으로 정의됩니다.
- 요소에 인라인 스타일 속성이 없어야 합니다.
- 사용 중인 동위 선택기가 없어야 합니다. WebCore는 동위 선택기가 발생할 때 전역 스위치를 발생시키고 전체 문서가 있는 경우 스타일 공유를 사용 중지합니다. 여기에는 + 선택기와 :first-child 및 :last-child와 같은 선택기가 포함됩니다.
Firefox 규칙 트리
Firefox에는 더 쉬운 스타일 계산을 위해 규칙 트리와 스타일 컨텍스트 트리라는 두 개의 트리가 추가로 있습니다. WebKit에도 스타일 객체가 있지만 스타일 컨텍스트 트리와 같은 트리에 저장되지 않고 DOM 노드만 관련 스타일을 가리킵니다.
<ph type="x-smartling-placeholder">![Firefox 스타일 컨텍스트 트리](https://web.developers.google.cn/static/articles/howbrowserswork/image/firefox-style-context-tre-f578b75b74df7.png?authuser=19&hl=ko)
스타일 컨텍스트에는 종료 값이 포함됩니다. 모든 일치하는 규칙을 올바른 순서로 적용하고 논리적 값에서 구체적인 값으로 변환하는 조작을 수행하여 값을 계산합니다. 예를 들어 논리 값이 화면의 백분율인 경우, 논리 값이 절대 단위로 계산되어 변환됩니다. 규칙 트리 개념은 매우 기발합니다. 이렇게 하면 노드 간에 이러한 값을 공유하여 값을 다시 계산하지 않아도 됩니다. 이렇게 하면 공간도 절약됩니다.
일치하는 모든 규칙은 트리에 저장됩니다. 경로에 있는 하위 노드의 우선순위가 더 높습니다. 트리에는 발견된 규칙 일치에 대한 모든 경로가 포함됩니다. 규칙 저장은 지연됩니다. 트리는 모든 노드에 대해 처음부터 계산되지 않지만, 노드 스타일을 계산해야 할 때마다 계산된 경로가 트리에 추가됩니다.
트리 경로를 단어의 단어로 보는 것이 목표입니다. 이미 다음 규칙 트리를 계산했다고 가정해 보겠습니다.
<ph type="x-smartling-placeholder">![계산된 규칙 트리](https://web.developers.google.cn/static/articles/howbrowserswork/image/computed-rule-tree-f874f412bbaf.png?authuser=19&hl=ko)
콘텐츠 트리에서 다른 요소에 대한 규칙을 일치시켜야 하며 일치하는 규칙이 올바른 순서로 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}
단순화하기 위해 두 가지 구조체(색상 구조체와 여백 구조체)만 채워야 한다고 가정해 보겠습니다. color 구조체에는 한 가지 멤버, 즉 color 여백 구조체에는 네 면이 포함되어 있습니다.
결과 규칙 트리는 다음과 같이 표시됩니다 (노드 이름, 즉 노드가 가리키는 규칙의 수가 노드로 표시되어 있음).
<ph type="x-smartling-placeholder">![규칙 트리](https://web.developers.google.cn/static/articles/howbrowserswork/image/the-rule-tree-23f05b0dac33f.png?authuser=19&hl=ko)
컨텍스트 트리는 다음과 같습니다 (노드 이름: 노드가 가리키는 규칙 노드).
<ph type="x-smartling-placeholder">![컨텍스트 트리](https://web.developers.google.cn/static/articles/howbrowserswork/image/the-context-tree-771124b7cb80d.png?authuser=19&hl=ko)
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 규칙 트리는 또한 속성을 올바른 순서로 적용하는 데 도움이 됩니다.
쉬운 일치를 위한 규칙 조작
스타일 규칙의 몇 가지 소스는 다음과 같습니다.
- 외부 스타일 시트 또는 스타일 요소에서 CSS 규칙을 사용할 수 있습니다.
css p {color: blue}
- 인라인 스타일 속성:
html <p style="color: blue" />
- HTML 시각적 속성 (관련 스타일 규칙에 매핑됨)
html <p bgcolor="blue" />
마지막 두 개의 속성은 이 요소가 스타일 속성을 소유하고 있으며 HTML 속성은 요소를 키로 사용하여 매핑할 수 있으므로 요소와 쉽게 일치됩니다.
두 번째 문제에서 앞서 언급한 것처럼 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' 규칙이 적용되는 키 확인할 수 있습니다 div 요소는 id 맵 (키가 id임)과 태그 맵에 관련 규칙을 갖습니다. 따라서 남은 작업은 키로 추출한 규칙 중 실제로 일치하는 규칙을 찾는 것입니다.
예를 들어 div에 대한 규칙이 다음과 같다고 가정해 보겠습니다.
table div {margin: 5px}
키가 가장 오른쪽에 있는 선택기이므로 태그 맵에서 계속 추출되지만 테이블 상위 항목이 없는 div 요소와 일치하지 않습니다.
WebKit과 Firefox 모두 이 작업을 수행합니다.
스타일 시트 계단식 순서
스타일 객체에는 모든 시각적 속성에 해당하는 속성이 있습니다 (모든 CSS 속성이지만 좀 더 일반적임). 일치하는 규칙으로 속성이 정의되지 않은 경우 일부 속성이 상위 요소 스타일 객체에 의해 상속될 수 있습니다. 다른 속성에는 기본값이 있습니다.
문제는 정의가 두 개 이상일 때 시작됩니다. 즉, 문제를 해결하기 위한 단계적 정의가 나옵니다.
스타일 속성 선언은 여러 스타일 시트에 표시될 수 있으며 스타일 시트 내에 여러 번 표시될 수 있습니다. 즉, 규칙을 적용하는 순서가 매우 중요합니다. 이를 '캐스케이드'라고 합니다. 있습니다. CSS2 사양에 따르면 하향식 순서는 다음과 같습니다 (낮음에서 높음).
- 브라우저 선언
- 사용자 일반 선언
- 일반 선언 작성
- 중요 선언 작성
- 사용자 중요 선언
브라우저 선언은 가장 중요하지 않으며 선언이 중요하다고 표시된 경우에만 작성자를 재정의합니다. 순서가 동일한 선언은 특이도를 기준으로 정렬된 다음 지정된 순서대로 정렬됩니다. HTML 시각적 속성은 일치하는 CSS 선언으로 변환됩니다 . 이러한 규칙은 우선순위가 낮은 작성자 규칙으로 취급됩니다.
특수성
선택기의 특수성은 CSS2 사양에서 다음과 같이 정의됩니다.
- 선언이 'style'인 경우 1로 계산됩니다. 특성, 그렇지 않은 경우 0 (= a)
- 선택기에서 ID 속성 수 계산 (= b)
- 선택기에서 다른 속성 및 의사 클래스의 수 계산 (= c)
- 선택기에서 요소 이름 및 유사 요소 개수 계산 (= 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은 모든 최상위 스타일 시트(@import 포함)가 로드되었는지 여부를 표시하는 플래그를 사용합니다. 첨부할 때 스타일이 완전히 로드되지 않으면 자리표시자가 사용되고 문서에 표시됩니다. 스타일 시트가 로드된 후 스타일이 다시 계산됩니다.
레이아웃
렌더기를 생성하여 트리에 추가할 때 위치와 크기는 없습니다. 이러한 값을 계산하는 것을 레이아웃 또는 리플로우라고 합니다.
HTML은 흐름 기반 레이아웃 모델을 사용합니다. 즉, 대부분의 경우 단일 패스로 도형을 계산할 수 있습니다. 나중에 '흐름에 있는' 요소 은 일반적으로 흐름의 초기에 있는 요소의 도형에 영향을 미치지 않으므로 레이아웃이 문서를 통해 왼쪽에서 오른쪽, 위에서 아래로 진행할 수 있습니다. 예외가 있습니다. 예를 들어 HTML 테이블에 두 개 이상의 패스가 필요할 수 있습니다.
좌표계는 루트 프레임을 기준으로 합니다. 위쪽 및 왼쪽 좌표가 사용됩니다.
레이아웃은 반복 프로세스입니다. 루트 렌더기에서 시작하며 이는 HTML 문서의 <html>
요소에 해당합니다. 레이아웃은 프레임 계층의 일부 또는 전체를 통해 반복적으로 계속 진행되며, 레이아웃이 필요한 각 렌더기의 기하학적 정보를 계산합니다.
루트 렌더기의 위치는 0,0이며 그 크기는 브라우저 창에서 보이는 부분인 표시 영역입니다.
모든 렌더러에는 '레이아웃'이 있고 또는 '리플로우' 메서드의 경우, 각 렌더러는 레이아웃이 필요한 하위 요소의 레이아웃 메서드를 호출합니다.
더티 비트 시스템
사소한 변경 시마다 전체 레이아웃을 실행하지 않기 위해 브라우저에서는 '더티 비트' 있습니다. 변경되거나 추가된 렌더러가 자신과 그 하위 요소를 '더티': 레이아웃이 필요한 것으로 표시합니다.
플래그에는 '더티'와 '어린이가 더티'라는 두 가지 플래그가 있습니다. 즉, 렌더러 자체는 괜찮을 수 있지만 레이아웃이 필요한 하위 요소가 하나 이상 있다는 의미입니다.
전역 및 증분 레이아웃
전체 렌더링 트리에서 레이아웃을 트리거할 수 있습니다. 이는 '전역'입니다. 있습니다. 이는 다음과 같은 이유로 발생할 수 있습니다.
- 글꼴 크기 변경과 같이 모든 렌더기에 영향을 미치는 전체 스타일 변경입니다.
- 화면 크기를 조절한 결과
레이아웃은 증분식일 수 있고 더티 렌더러만 배치됩니다 (이로 인해 약간의 손상이 발생하여 추가 레이아웃이 필요할 수 있음).
렌더기가 더티인 경우 증분 레이아웃이 (비동기식으로) 트리거됩니다. 예를 들어 추가 콘텐츠가 네트워크에서 가져와 DOM 트리에 추가된 후에 새 렌더러가 렌더링 트리에 추가되는 경우입니다.
<ph type="x-smartling-placeholder">![증분 레이아웃](https://web.developers.google.cn/static/articles/howbrowserswork/image/incremental-layout-da3da0a148135.png?authuser=19&hl=ko)
비동기 및 동기 레이아웃
증분 레이아웃은 비동기식으로 실행됩니다. Firefox의 '리플로우 명령' 대기열 스케줄러는 이러한 명령어의 일괄 실행을 트리거합니다. WebKit에는 증분 레이아웃을 실행하는 타이머도 있습니다. 트리는 순회하고 '더티'합니다. 렌더러가 레이아웃을 바꾼다는 것입니다.
'offsetHeight'와 같은 스타일 정보를 요청하는 스크립트 증분 레이아웃을 동기식으로 트리거할 수 있습니다.
전역 레이아웃은 일반적으로 동기식으로 트리거됩니다.
스크롤 위치와 같은 일부 속성이 변경되어 초기 레이아웃 후에 레이아웃이 콜백으로 트리거되는 경우가 있습니다.
최적화
'크기 조정'으로 레이아웃이 트리거될 때 또는 크기가 아닌 렌더기 위치가 변경되면 렌더 크기가 캐시에서 가져오며 다시 계산되지 않습니다.
경우에 따라 하위 트리만 수정되고 레이아웃이 루트에서 시작되지 않습니다. 이러한 상황은 변경사항이 로컬에 있고 텍스트 필드에 삽입된 텍스트처럼 주변 환경에 영향을 미치지 않는 경우에 발생할 수 있습니다 (그렇지 않으면 모든 키 입력이 루트에서 시작되는 레이아웃을 트리거함).
레이아웃 프로세스
일반적으로 레이아웃은 다음과 같은 패턴을 보입니다.
- 상위 렌더기는 자체 너비를 결정합니다.
- 상위가 하위에 대해 설정되는 사항:
<ph type="x-smartling-placeholder">
- </ph>
- 하위 렌더기를 배치합니다 (x와 y 설정).
- 필요한 경우 하위 레이아웃이 더러우거나 전역 레이아웃에 있는 경우 또는 다른 이유로 하위 레이아웃을 호출하여 하위 요소의 키를 계산합니다.
- 상위 요소는 하위 요소의 누적 높이와 여백 및 패딩의 높이를 사용하여 자체 높이를 설정합니다. 이는 상위 렌더기의 상위 요소에 의해 사용됩니다.
- 더티 비트를 false로 설정합니다.
Firefox는 "상태"를 사용합니다. 객체(nsHTMLReflowState)를 레이아웃에 대한 매개변수로 사용합니다('리플로우'라고 함). 상태에는 상위 요소 너비가 포함됩니다.
Firefox 레이아웃의 출력은 '측정항목' object(nsHTMLReflowMetrics). 렌더러에서 계산한 높이를 포함합니다.
너비 계산
렌더기의 너비는 컨테이너 블록의 너비, 렌더기의 스타일인 '너비'를 사용하여 계산됩니다. 속성, 여백 및 테두리를 설정할 수 있습니다.
예를 들어 다음 div의 너비는
<div style="width: 30%"/>
WebKit에서 다음과 같이 계산합니다(RenderBox 메서드 calcWidth).
- 컨테이너 너비는 컨테이너 availableWidth 및 0의 최댓값입니다. 이 경우 availableWidth는 다음과 같이 계산되는 contentWidth입니다.
clientWidth() - paddingLeft() - paddingRight()
clientWidth 및 clientHeight는 객체의 내부를 나타냅니다. (테두리와 스크롤바 제외)
요소 너비는 '너비'입니다. style[스타일] 속성을 사용하세요. 컨테이너 너비의 백분율을 계산하여 절댓값으로 계산됩니다.
이제 가로 테두리와 패딩이 추가되었습니다.
지금까지는 '선호 너비'의 계산이 이루어졌습니다. 이제 최소 및 최대 너비가 계산됩니다.
기본 너비가 최대 너비보다 크면 최대 너비가 사용됩니다. 최소 너비 (파괴할 수 없는 가장 작은 단위)보다 작은 경우 최소 너비가 사용됩니다.
레이아웃이 필요한 경우를 위해 값이 캐시되지만 너비는 변경되지 않습니다.
줄바꿈
레이아웃 중간에 있는 렌더기가 중단해야 한다고 판단하면 렌더기가 중지되고 레이아웃의 상위 요소에 중단해야 한다고 전파합니다. 상위 요소는 추가 렌더기를 만들고 레이아웃을 호출합니다.
회화
페인트 단계에서는 렌더 트리가 순회하고 렌더러의 'Paint()'는 메서드가 호출되어 화면에 콘텐츠를 표시합니다. 페인팅은 UI 인프라 구성요소를 사용합니다.
전역 및 증분
레이아웃과 마찬가지로 페인팅도 전역적일 수 있습니다(트리 전체가 페인트되거나 증분식일 수 있음). 증분 페인팅에서는 일부 렌더러가 트리 전체에 영향을 미치지 않는 방식으로 변경됩니다. 변경된 렌더러가 화면에서 직사각형을 무효화합니다. 이로 인해 OS는 이 영역을 '더티 리전'으로 인식하게 됩니다. '페인트'를 생성하는 이벤트를 처리합니다. OS는 이를 기발하게 처리하고 여러 리전을 하나로 통합합니다. Chrome에서는 렌더러가 기본 프로세스가 아닌 다른 프로세스에 있기 때문에 더 복잡합니다. Chrome은 어느 정도 OS 동작을 시뮬레이션합니다. 프레젠테이션은 이러한 이벤트를 수신하고 메시지를 렌더링 루트에 위임합니다. 트리는 관련 렌더러에 도달할 때까지 순회합니다. 자체 (및 일반적으로 하위 요소)를 다시 페인트합니다.
페인트칠 순서
CSS2는 페인팅 프로세스의 순서를 정의합니다. 이는 실제로 요소가 스택 컨텍스트에 스택되는 순서입니다. 스택이 뒤에서 앞으로 페인트되므로 이 순서는 페인트에 영향을 미칩니다. 블록 렌더기의 스택 순서는 다음과 같습니다.
- background color
- 배경 이미지
- 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 Box 모델
CSS 상자 모델은 문서 트리의 요소에 대해 생성되고 시각적 형식 지정 모델에 따라 배치되는 직사각형 상자를 설명합니다.
각 상자에는 콘텐츠 영역 (예: 텍스트, 이미지 등)과 선택 가능한 주변 패딩, 테두리 및 여백 영역이 있습니다.
<ph type="x-smartling-placeholder">![CSS2 박스 모델](https://web.developers.google.cn/static/articles/howbrowserswork/image/css2-box-model-9c2ab852d1fb4.jpg?authuser=19&hl=ko)
각 노드는 이러한 상자를 0...n개 생성합니다.
모든 요소에는 '디스플레이' 속성이 있을 수 있습니다.
예:
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 트리의 위치와 같으며 상자 유형과 크기에 따라 배치됩니다.
- 부동 소수점 수: 객체를 먼저 정상적인 흐름처럼 배치한 다음 최대한 왼쪽이나 오른쪽으로 이동합니다.
- 절대: 객체가 렌더링 트리의 DOM 트리와 다른 위치에 배치됩니다.
위치 지정 방식은 '위치' 속성 및 'float' 속성의 값을 제공합니다.
- 정적이고 상대적인 흐름은
- 절대 및 고정 원인 절대 위치 지정
정적 위치 지정에서는 위치가 정의되지 않으며 기본 배치가 사용됩니다. 다른 스키마에서는 작성자가 위치를 지정합니다(상단, 하단, 왼쪽, 오른쪽).
상자가 배치되는 방식은 다음에 따라 결정됩니다.
- 상자 유형
- 상자 크기
- 위치 지정 체계
- 이미지 크기 및 화면 크기와 같은 외부 정보
상자 유형
블록 상자: 블록을 형성합니다. 브라우저 창에 자체 직사각형이 있습니다.
<ph type="x-smartling-placeholder">![차단 상자](https://web.developers.google.cn/static/articles/howbrowserswork/image/block-box-b5c0bff4a44d2.png?authuser=19&hl=ko)
인라인 상자: 자체 블록은 없지만 포함하는 블록 내에 있습니다.
<ph type="x-smartling-placeholder">![인라인 상자.](https://web.developers.google.cn/static/articles/howbrowserswork/image/inline-boxes-a9ff03002e7e2.png?authuser=19&hl=ko)
블록은 세로로 차례로 서식이 지정됩니다. 인라인 형식은 가로 형식으로 지정됩니다.
<ph type="x-smartling-placeholder">![블록 및 인라인 서식.](https://web.developers.google.cn/static/articles/howbrowserswork/image/block-inline-formatting-a8450c63cf457.png?authuser=19&hl=ko)
인라인 상자는 선 또는 '선 상자' 안에 배치됩니다. 선의 높이는 가장 큰 상자보다 최소 높이이지만, 상자가 '기준'에 맞춰 정렬되면 더 길 수도 있습니다. - 요소의 하단 부분이 하단이 아닌 다른 상자의 지점에 정렬됩니다. 컨테이너 너비가 충분하지 않으면 인라인이 여러 줄에 배치됩니다. 이것은 일반적으로 단락에서 일어나는 일입니다.
<ph type="x-smartling-placeholder">![선.](https://web.developers.google.cn/static/articles/howbrowserswork/image/lines-68c55a378a7ff.png?authuser=19&hl=ko)
포지셔닝
상대적
상대적 위치 지정 - 평소와 같이 배치한 후 필수 델타만큼 이동했습니다.
<ph type="x-smartling-placeholder">![상대 위치](https://web.developers.google.cn/static/articles/howbrowserswork/image/relative-positioning-fceed0670b8c5.png?authuser=19&hl=ko)
부동
부동 상자는 선의 왼쪽 또는 오른쪽으로 이동합니다. 흥미로운 점은 다른 상자가 그 주위로 흐르는 것입니다. HTML:
<p>
<img style="float: right" src="images/image.gif" width="100" height="100">
Lorem ipsum dolor sit amet, consectetuer...
</p>
다음과 같이 표시됩니다.
<ph type="x-smartling-placeholder">![부동 소수점 수.](https://web.developers.google.cn/static/articles/howbrowserswork/image/float-dd9b790210284.png?authuser=19&hl=ko)
절대 및 고정
레이아웃은 일반 흐름과 관계없이 정확하게 정의됩니다. 요소가 정상적인 흐름에 참여하지 않습니다. 크기는 컨테이너를 기준으로 합니다. 고정에서 컨테이너는 표시 영역입니다.
<ph type="x-smartling-placeholder">![고정된 위치](https://web.developers.google.cn/static/articles/howbrowserswork/image/fixed-positioning-881933fcb1a2f.png?authuser=19&hl=ko)
계층화된 표현
이는 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>
결과는 다음과 같습니다.
<ph type="x-smartling-placeholder">![고정된 위치](https://web.developers.google.cn/static/articles/howbrowserswork/image/fixed-positioning-84a46b366dc9a.png?authuser=19&hl=ko)
빨간색 div가 마크업의 녹색 div보다 앞서고 일반 흐름에서는 이전에 채색되었을 수도 있지만 Z-색인 속성이 더 높기 때문에 루트 상자에 있는 스택에서 더 앞쪽에 있습니다.
리소스
브라우저 아키텍처
- 그로스커스, 앨런. 웹브라우저용 참조 아키텍처 (pdf)
- 굽타, 비니트. 브라우저 작동 방식 - 1부 - 아키텍처
파싱
- 아호, 세티, 울만, 컴파일러: 원리, 기법 및 도구("드래곤 책"이라고도 함), Addison-Wesley, 1986년
- 릭 젤리프입니다. 굵은 글씨와 아름다움: HTML 5의 두 가지 새로운 초안
Firefox
- L. David Baron, Fast HTML and CSS: Layout Engine Internals for Web Developers
- L. David Baron, Fast HTML and CSS: Layout Engine Internals for Web Developers (Google 기술 강연 동영상)
- L. David Baron, Mozilla의 Layout Engine
- L. David Baron, Mozilla Style System 문서
- 크리스 워터슨, HTML 리플로우에 대한 참고사항
- 크리스 워터슨, Gecko 개요
- 알렉산더 라르손, HTML HTTP 요청의 수명
WebKit
- 데이비드 하얏트, CSS 구현(1부)
- 데이비드 하얏트, WebCore 개요
- 데이비드 하얏트, WebCore 렌더링
- 데이비드 하얏트, FOUC 문제
W3C 사양
브라우저 빌드 안내
번역
이 페이지는 일본어로 두 번 번역되었습니다.
- 브라우저 작동 방식 - 최신 웹브라우저 (ja) 작성자: @kosei
- ブラウザってどうやって動いてるの?(モダンWEBブラウザシーンの裏側 작성자: @ike443 @kiyoto01입니다.
다음에서 외부 호스팅 번역을 볼 수 있습니다. 한국어 및 튀르키예어.
감사합니다.