브라우저 동작
오픈소스 렌더링 엔진의 소스코드를 분석하여 어떻게 브라우저가 동작하는지 파악한 이스라엘 개발자가 게시한 글을 번역한 기술글(https://d2.naver.com/helloworld/59361)을 공부하면서 작성한 글
기술글이 2019년도에 작성된 글이기 때문에 내용이 옛날 내용이라는 댓글 발견(흐름은 그대로니 웹킷이나 파이어폭스가 어떻게 동작하는것보다 브라우저의 기본구조나 각 동작과정에 대해 이해하는 정도가 좋을듯!!)
[ 브라우저의 주요 기능 ]
브라우저의 주요 기능은 사용자가 선택한 자원을 서버에 요청하고 브러우저에 표시하는 것
자원은 보통 HTML문서지만 PDF나 이미지 또는 다른 형태일 수 있음. 자원의 주소는 URI에 의해 정해짐
브라우저는 HTML과 CSS 명세에 따라 HTML 파일을 해석해서 표시하는데 이 명세는 웹 표준화 기구인 W3C(World Wide Web Consortium)에서 정해짐(과거에는 웹 제작자가 호환성 문제를 겪었지만 최근에는 대부분의 브라우저가 이 표준 명세를 따름)
브라우저의 사용자 인터페이스 일반적 요소
- URI를 입력할 수 있는 주소 표시 줄
- 이전 버튼과 다음 버튼
- 북마크
- 새로 고침 버튼과 현재 문서의 로드를 중지할 수 있는 정지 버튼
- 홈 버튼
브라우저의 사용자 인터페이스는 표준 명세가 없음에도 서로의 장점을 모방하면서 현재에 이르게 됨
HTML5 명세는 주소 표시줄, 상태표시줄, 도구 모음과 같은 일반적인 요소를 제외하고 브라우저의 필수 UI를 정의하지 않았음(파이어폭스의 다룬로드 관리자와 같이 브라우저에 특화된 기능도 있음)
[ 브라우저의 기본 구조 ]
- 사용자 인터페이스: 주소 표시줄, 이전/다음 버튼 등 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분
- 브라우저 엔진: 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어
- 렌더링 엔진: 요청한 콘텐츠를 표시(HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시)
- 통신: HTTP 요청과 같은 네트워크 호출에 사용됨(플랫폼 독립적인 인터페이스이고 각 플랫폼 하부에서 실행됨)
- UI 백엔드: 콤보 박스와 창 같은 기본적인 장치를 그림(플랫폼에서 명시하지 않은 일반적인 인터페이스로서 OS 사용자 인터페이스 체계를 사용)
- 자바스크립트 해석기: 자바스크립트 코드를 해석하고 실행
- 자료 저장소: 데이터를 저장하고 필요할 때 검색하고 수정하는 곳(쿠키는 그 중 하나의 형태이며 HTML5 명세는 이러한 데이터 저장소를 관리하기 위한 새로운 도구인 '웹 데이터 베이스'를 도입)
- 웹 데이터 베이스: 브라우저에서 지원되는 데이터베이스 시스템(기존 쿠키와는 다르게 보다 더 많은 양의 데이터를 저장할 수 있고 더 복잡한 쿼리를 실행할 수 있음. 오프라인 상태에서도 작동할수 있으며 보다 효율적인 데이터 관리가 가능함)
- 크롬은 대부분의 브러우저와 달리 각 탭마다 별도의 렌더링 엔진 인스턴스를 유지하여 각 탭은 독립적인 프로세스로 처리됨 멀티프로세스 아키텍처를 적요함으로써 안전성과 성능 ↑ 메모리 효율↓, 만약 작업 프로세스가 응답을 안하면 애플리케이션의 다른 부분을 중지하지 않아도 응답하지 않는 렌더러 프로세스만 종료 가능함 하지만 탭 별로 프로세스를 다 부여해 버려 메모리를 많이 잡아먹는 단점이 존재함
[ 렌더링 엔진 ]
▶ 렌더링 엔진 동작 과정
렌더링 엔진은 통신으로부터 요청한 문서의 내용을 얻는 것으로 시작하는데 문서의 내용은 보통 8KB 단위로 전송됨
렌더링 엔진은 HTML 문서를 파싱하고 '콘텐츠 트리' 내부에서 태그를 DOM 노드로 변환
그 다음 외부 CSS 파일과 함께 포함된 스타일 요소도 파싱함
스타일 정보와 HTML 표시 규칙은 '렌더 트리'라고 부르는 또 다른 트리를 생성함
렌더 트리는 색상 또는 면적과 같은 시각적 속성이 있는 사각형을 포함하고 있는데 정해진 순서대로 화면에 표시됨
렌더 트리 생성이 끝나면 배치가 시작되는데 각 노드가 화면의 정확한 위치에 표시되는 것을 의미함(Layout)
그 후 UI 백엔드에서 렌더 트리의 각 노드를 가로지르며 형상을 만들어 내는 그리기 과정(Paint)
일련의 과정들이 점진적으로 진행된다는 것을 아는 것이 중요한데 렌더링 엔진은 좀 더 나은 사용자경험(UX)을 위해 가능하면 빠르게 내용을 표시하는데 모든 HTML을 파싱할때까지 기다리지 않고 배치와 그리기 과정을 시작함
즉, 네트워크로부터 나머지 내용이 전송되기를 기다리는 동시에 받은 내용의 일부를 먼저 화면에 표시하는 것
여기서 어태치먼트(attachment)는 웹킷이 렌더 트리를 생성하기 위해 DOM 노드와 시각 정보를 연결하는 과정
▶ 파싱과 DOM 트리 구축
파싱 일반
'문서 파싱'은 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것을 의미
파싱결과는 보통 문서 구조를 나타내는 노드 트리인데 파싱 트리(parse tree) 또는 문법 트리(syntax tree)라고 부름
문법
파싱은 문서에 작성된 언어 또는 형식의 규칙에 따르는데 파싱할 수 있는 모든 형식은 정해진 용어와 구문 규칙에 따라야 함 이것을 '문맥 자유 문법'이라고 함. 인간의 언어는 이런 모습과 다르기 때문에 기계적으로 파싱이 불가능
파서-어휘 분석기 조합
파싱은 어휘 분석과 구문 분석이라는 두가지로 구분
'어휘 분석'은 자료를 토큰으로 분해하는 과정
토큰은 유효하게 구성된 단위의 집합체(=용어짐)임
예를들어 인간의 언어로 치면 사전에 등장하는 모든 단어에 해당
'구문분석'은 언어의 구문 규칙을 적용하는 과정
파서는 보통 두가지 일을 하는데 자료를 유효한 토큰으로 분해하는 어휘분석기(=토큰 변환기)가 있고 언어 구문 규칙에 따라 문서 구조를 분석함으로써 파싱 트리를 생성하는 파서가 있음
어휘 분석기는 공백과 줄 바꿈 같은 의미 없는 문자를 제거
파싱 과정은 반복됨. 파서는 보통 어휘 분석기로부터 새 토큰을 받아서 구문 규칙과 일치하는지 확인
규칙에 맞으면 토큰에 해당하는 노드가 파싱 트리에 추가되고 파서는 또 다른 토큰을 요청
규칙에 맞지 않으면 파서는 토큰을 내부적으로 저장하고 토큰과 일치하는 규칙이 발견될 때까지 요청
맞는 규칙이 없는 경우 예외로 처리하는데 해당하는 문서가 유효하지 않고 구문 오류를 포함하고 있다는 의미
변환
파서 트리는 최종 결과물이 아님
파싱은 보통 문서를 다른 양식으로 변환함(ex. 컴파일)
소스코드를 기계 코드로 만드는 '컴파일러'는 파싱 트리 생성 후 이를 기계 코드 문서로 변환
어휘와 구문에 대한 공식적인 정의
어휘는 보통 정규 표현식으로 표현함(정수는 정규 표현식으로 정의)
INTEGER : 0|[1-9][0-9]*
PLUS : +
MINUS : -
구문은 보통 BNF라고 부르는 형식에 따라 정의
expression := term operation term
operation := PLUS | MINUS
term := INTEGER | expression
문법이 문맥 자유 문법이라면 언어는 정규 파서로 파싱할 수 있음
문맥 자유 문법을 쉽게 말하면 완전히 BNF로 표현 가능한 문법
파서의 종류
파서는 기본적으로 하향식 파서와 상향식 파서가 있음
'하향식 파서'는 구문의 상위 구조로부터 일치하는 부분을 찾기 시작하는데 반해 '상향식 파서'는 낮은 수준에서 점차 높은 수준으로 찾음
상향식 파서는 입력 값의 오른쪽으로 이동하면서(입력 값의 처음을 가리키는 포인터가 오른쪽으로 이동) 구문 규칙으로 갈수록 남는 것이 점차 감소하기 때문에 '이동-감소 파서'라고 부름
파서 자동 생성
파서를 생성해 줄 수 있는 도구를 '파서 생성기'라고 함
언어에 어휘나 구문 규칙 같은 문법을 부여하면서 동작하는 파서를 만들어줌
웹킷은 잘 알려진 두 개의 파서 생성기를 사용함
어휘 생성을 위한 플랙스(Flex)와 생성을 위한 바이슨(Bison)
플렉스는 토큰의 정규 표현식 정의를 포함하는 파일을 입력받고 바이슨은 BNF 형식의 언어 구문 규칙을 입력 받음
▶ HTML 파서
HTML 파서는 HTML 마크업을 파싱 트리로 변환함
문맥 자유 문법이 아닌 이유
일반 파싱처럼 문법은 BNF와 같은 형식을 이용하여 공식적으로 정의할 수 있지만 모든 전통적인 파서는 HTML에 적용할 수 없음(HTML은 파서가 요구하는 문맥 자유 문법에 의해 쉽게 정의할 수 없음 - 파싱은 CSS와 JS를 파싱하는데 사용)
HTML 정의를 위한 공식적인 형식으로는 DTD(문서 형식 정의, XML 문서 구조를 정의하는데 사)가 있지만 이것은 문맥 자유 문법이 아님
HTML과 XML은 유사함 하지만 XML에 비해 HTML이 더 너그러운 특징이 있음
HTML은 암묵적으로 태그에 대한 생략이 가능하며 가끔 시작 또는 종료 태그 등을 생략함
즉, XML에 반하여 HTML은 유연한 문법임
웹 제작자의 실수를 너그럽게 넘어가는 것이야 말로 HTML이 인기 있었던 이유 하지만 한편으로는 공식적인 문법으로 작성하기 어렵다는 문제가 있음
정리하면 HTML은 파싱하기 어렵고 정통적인 구문 분석이 불가능 하기 때문에 문맥 자유 문법이 아니라는것
XML 파서로도 파싱하기 쉽지 않음
HTML DTD
HTML의 정의는 DTD 형식 안에 있는데 SGML(문서용 마크업 언어를 정의하기 위한 메타 언어) 계열 언어의 정의를 이용한 것. 이 형식은 허용되는 모든 요소와 그들의 속성 그리고 중첩 구조에 대한 정의를 포함함
DTD는 여러 변종이 있음. 엄격한 형식은 명세만을 따르지만 다른 형식은 낣은 브라우저에서 사용된 마크업을 지원함(낡은 마크업을 지원하는 이유는 오래된 콘텐츠에 대한 하위 호환성 때문)
DOM
'파싱 트리'는 DOM 요소와 속성 노드의 트리로서 출력 트리가 됨
DOM은 문서 객체 모델(Document Object Model)의 준말
HTML 문서의 객체 표현이고 외부를 향하는 JS와 같은 HTML 요소의 연결 지점
트리의 최상위 객체는 문서(Document)
DOM은 마크업과 1:1의 관계를 맺음
HTML과 마찬가지로 DOM은 W3C에 의해 명세가 정해져 있음
문서를 다루기 위한 일반적인 명세인데 부분적으로 HTML 요소를 설명하기도 함
트리가 DOM 노드를 포함한다고 말하는 것은 DOM 접점의 하나를 실행하는 요소를 구성한다는 의미
브라우저는 내부의 다른 속성들을 이용하여 이를 구체적으로 실행함
파싱 알고리즘
HTML은 일반적인 하향식 또는 상향식 파서로 파싱이 안됨
- 언어의 너그러운 속성
- 잘 알려져 있는 HTML 오류에 대한 브라우저의 관용
- 변경에 의한 재파싱(일반적으로 소스는 파싱하는 동안 변하지 않지만 HTML에서 document.write을 포함하고 있는 스크립트 태그는 토큰을 추가할 수 있기 때문에 실제로는 입력 과정에서 파싱이 수정됨)
일반적인 파싱 기술을 사용할 수 없기 때문에 브라우저는 HTML 파싱을 위해 별도의 파서를 생성
파싱 알고리즘은 토큰화와 트리 구축 이렇게 두 단계로 되어있음
'토큰화'는 어휘 분석으로서 입력 값을 토큰으로 파싱(HTML에서 토큰은 시작태크, 종료태크, 송성 이름과 속성 값)
토큰화는 토큰을 인지해서 트리 생성자로 넘기고 다름 토큰을 확인하기 위해 다음 문자를 확인함
그리고 입력의 마지막까지 이 과정을 반복함
토큰화 알고리즘
알고리즘의 결과물은 HTML 토큰. 알고리즘은 상태 기계(State Machine)라고 볼 수 있음
각 상태는 하나 이상의 연속된 문자를 입력받아 이 문자에 따라 다음 상태를 갱신함
그러나 결과는 현재의 토큰화 상태와 트리 구축 상태의 영향을 받는데 같은 문자를 읽어 들여도 현재 상태에 따라 다음 상태의 결과가 다르게 나온다는 것을 의미
트리 구축 알고리즘
파서가 생성되면 문서 객체가 생성됨
트리 구축이 진행되는 동안 문서 최상단에서는 DOM 트리가 수정되고 요소가 추가됨
토큰화에 의해 발행된 각 노드는 트리 생성자에 의해 처리됨
각 토큰을 위한 DOM 요소의 명세는 정의되어 있음
DOM 트리에 요소를 추가하는 것이 아니라면 열린 요소는 스택(임시 버퍼 저장소)에 추가됨
이 스택은 부정확한 중첩과 종료되지 않은 태그를 교정함
알고리즘은 상태 기계라고 설명할 수 있고 상태는 '삽입 모드'라고 부름
파싱이 끝난 이후의 동작
브라우저는 문서와 상호작용할 수 있게 되고 문서 파싱 이후에 실행되어야 하는 '지연'모드 스크립트를 파싱하기 시작
문서 상태는 '완료'가 되고 '로드' 이벤트가 발생함
브라우저의 오류 처리
HTML 페이지에서 '유효하지 않는 구문'이라는 오류를 볼 수 없는데 이는 브라우저가 모든 오류 구문을 교정하기 때문
◎ 파서의 오류 처리
- 어떤 태그의 안쪽에 추가하려는 태그가 금지된 것일 때 일단 허용된 태그를 먼저 닫고 금지된 태그는 외부에 추가
- 파서가 직접 요소를 추가해서는 안됨(문서 제작자에 의해 뒤늦게 요소가 추가될 수 있고 생략 가능한 경우도 있음 - HTML, HEAD, BODY, TBODY, TR, TD, LI 태그가 이런 경우에 해당)
- 인라인 요소 안쪽에 블록 요소가 있는 경우 부모 블록 요소를 만날 때까지 모든 인라인 태그를 닫음
- 이런 방법이 도움이 되지 않으면 태그를 추가하거나 무시할 수 있는 상태가 될 때까지 요소를 닫음
◎ 웹킷이 오류를 처리하는 예시
- <br> 대신 </br>을 사용하는 경우(<br>로 간주)
- 어긋난 표(표 안에 또 다른 표가 th또는 td 셀 내부에 없는 경우, 표의 중첩을 분해하여 형제 요소가 되도록 처리)
- 중첩된 폼 요소(안쪽의 폼은 무시)
- 태그 중첩이 너무 깊을 때(최대 20개의 중첩만 허용하고 나머지는 무시)
- 잘못 닫힌 html 또는 body 태그(깨진 html 지원, 문서가 끝나기 전에 body를 닫아버리는 경우 브라우저는 body를 닫지 않고 대신 종료를 위해 end()를 호출)
CSS 파싱
HTML과는 다르게 CSS는 문맥 자유 문법임(CSS명세)
어휘 문법은 각 토큰을 위한 정규표현식으로 정의되어 있으며 구문 문법은 BNF로 설명되어 있음
웹킷 CSS 파서
웹킷은 CSS 문법 파일로부터 자동으로 파서를 생성하기 위해 플렉스와 바이슨 파서 생성기를 사용함
바인슨은 상향식 이동 감소 파서를 생성(파이어 폭스는 직접 작성한 하향식 파서를 사용)
두 경우 모두 각 CSS 파일은 스타일 시트 객체로 파싱되고 각 객체는 CSS 규칙을 포함함
CSS 규칙 객체는 선택자와 선언 객체 그리고 CSS 문법과 일치하는 다른 객체를 포함
스크립트와 스타일 시트의 진행 순서
스크립트
웹은 파싱과 실행이 동시에 수행되는 동기화(synchronous)모델
파서가 script 태그를 만나 스크립트가 실행되는 동안 문서의 파싱은 중단됨
스크립트가 외부에 있는 경우 우선 네트워크로부터 자원을 가져야와야 하는데 이 또한 실시간으로 처리되고 자원을 받을 때까지 파싱은 중단됨
개발자는 스크립트를 지연(defer)으로 표시할 수 있는데 지연으로 표시하게 되면 문서 파싱은 중단되지 않고 문서 파싱이 완료된 이후에 스크립트가 실행됨
HTML5는 스크립트를 비동기(asynchronous)로 처리하는 속성을 추가했기 때문에 별도의 맥락에 의해 파싱되고 실행
예측 파싱
웹킷과 파이어폭스는 예측 파싱과 같은 최적화 지원
스크립트를 실행하는 동안 다른 스레드는 네트워크로부터 다른 자원을 찾아 내려받고 문서의 나머지 부분을 파싱
자원을 병렬로 연결하여 받을 수 있고 전체적인 속도를 개선
예측 파서는 DOM 트리를 수정하지 않고 메인 파서의 일로 넘김
예측 파서는 외부 스크립트, 외부 스타일 시트와 외부 이미지와 같이 참조된 외부 자원을 파싱함
스타일 시트
스타일 시트는 다른 모델을 사용함
이론적으로 스타일 시트는 DOM 트리를 변경하지 않기 때문에 문서 파싱을 기다리거나 중단할 이유가 없음
그러나 스크립트가 문서를 파싱하는 동안 스타일 정보를 요청하는 경우라면 문제가 됨
스타일이 파싱되지 않은 상태라면 스크립트는 잘못된 결과를 내놓기 때문에 많은 문제를 야기시킬 수 있음
파이어폭스의 경우 아직 로드 중이거나 파싱 중인 스타일 시트가 있는 경우 모든 스크립트의 실행을 중단하며 웹킷은 로드되지 않은 스타일 시트 가운데 문제가 될만한 속성이 있을때에만 스크립트를 중단함
▶ 렌더 트리 구축
DOM 트리가 구축되는 동안 브라우저는 렌더 트리를 구축함
표시해야 할 순서와 문서의 시각적인 구성 요소로써 올바른 순서로 내용을 그려낼 수 있도록 하기 위한 목적
이 구성요소를 브라우저마다 다르게 부르는데 웹킷은 '렌더러(renderer)' 또는 '렌더 객체(render object)'라는 용어를 사용
렌더러는 자신과 자식 요소를 어떻게 배치하고 그려내야 하는지 알고 있음
렌더러는 너비, 높이 그리고 위치와 같은 기하학적 정보를 포함함
박스 유형의 경우 노드와 관련된 'display' 스타일 속성의 영향을 받음
요소 유형 또한 고려해야 하는데 예를 들면 폼 콘트롤과 표는 특별한 구조임
요소가 특별한 렌더러를 만들어야 한다면 웹킷은 createRenderer 메서드를 무시하고 비기하학 정보를 포함하는 스타일 객체를 표시함
DOM 트리와 렌더 트리의 관계
렌더러는 DOM 요소에 부합하지만 1:1로 대응하는 관계는 아님
head 요소와 같은 비시각적 DOM 요소는 렌더 트리에 추가되지 않으며 display속성에 none 값이 할당된 요소는 트리에 나타나지 않음(visibility 속성에 hidden 값이 할당된 요소는 트리에 나타남)
여러 개의 시각 객체를 대응하는 DOM 요소도 있는데 이것들은 보통 하나의 사각형으로는 묘사할 수 없는 복잡한 구조
select 요소의 경우 표시영역, 드롭다운 목록, 버튼 표시를 위한 3개의 렌더러가 있음
또한 한줄에 충분히 표시할 수 없는 문자가 여러 줄로 바뀔 때 새 줄은 별도의 렌더러로 추가됨
깨진 HTML의 경우, CSS 명세에 의하면 인라인 박스는 블록 박스만 포함하거나 인라인 박스만을 포함해야 하는데 인라인과 블록박스가 섞인 경우 인라인 박스를 감싸기 위한 익명의 블록 렌더러가 생성됨
어떤 렌더 객체는 DOM 노드에 대응하지만 트리의 동일한 위치에 있지 않음
float 처리된 요소는 position 속성 값이 absolute로 처리된 요소는 흐름에서 벗어나 트리의 다른 곳에 배치된 상태로 형상이 그려짐(대신 자리 표시자가 원래 있어야 할 곳으로 배치됨)
트리를 구축하는 과정
파이어폭스에서 프레젠테이션은 DOM 업데이트를 위한 리스너로 등록됨
프레젠테이션은 형상 만들기를 FrameConstructor에 위임하고 FrameConstructor는 스타일(스타일 계산 참고)을 결정하고 형상을 만듬
웹킷에서는 스타일을 결정하고 렌더러를 만드는 과정을 "어태치먼트(attachment)" 라고 부름
모든 DOM 노드에는 "attach" 메서드가 있음
어태치먼트는 동기적인데 DOM 트리에 노드를 추가하면 새 노드의 "attach" 메서드를 호출
html 태그와 body 태그를 처리함으로써 렌더 트리 루트를 구성함
루트 렌더 객체는 CSS 명세에서 포함 블록(다른 모든 블록을 포함하는 최상위 블록)이라고 부르는 것과 일치함(파이어폭스는 ViewPortFame이라고 웹킷은 RenderView라고 부름)
이것이 문서가 가리키는 렌더 객체. 트리의 나머지 부분은 DOM 노드를 추구함으로써 구축됨
▶ 스타일 계산
렌더 트리를 구축하려면 각 렌더 객체의 시각적 속성에 대한 계산이 필요한데 이것은 각 요소의 스타일 속성을 계산함으로써 처리됨
스타일은 인라인 스타일 요소와 HTML의 시각적 속성과 같은 다양한 형태의 스타일 시트를 포함하는데 HTML의 시각적 속성들은 대응하는 CSS 스타일 속성으로 변환됨
최초의 스타일 시트는 브라우저가 제공하는 기본 스타일 시트인데 페이지 제작자 또는 사용자도 이를 제공할 수 있음
브라우저는 사용자가 선호하는 스타일을 정의할 수 있도록 지원함
스타일을 계산하는 일에는 몇가지 어려움이 따름
- 스타일 데이터는 구성이 매우 광범위한데 수 많은 스타일 속성들을 수용하면서 메모리 문제를 야기할 수 있음
- 최적화되어 있지 않다면 각 요소에 할당된 규칙을 찾는 것은 성능 문제를 야기할 수 있음(각 요소에 할당된 규칙 목록을 전체 규칙으로부터 찾아내는 것은 과중한 일임)
- 규칙을 적용하는 것은 계층 구조를 파악해야 하는 꽤나 복잡한 다단계 규칙을 수반
스타일 정보 공유
웹킷 노드는 스타일 객체(RenderStyle)를 참조하는데 이 객체는 일정 조건 아래 공유할 수 있음
노드가 형제이거나 또는 사촌일 때 공유하며 다음과 같은 조건일 때 공유할 수 있음
- 동일한 마우스 반응 상태를 가진 요소여야 함
- 아이디가 없는 요소.
- 태그 이름이 일치
- 클래스 속성이 일치
- 지정된 속성이 일치
- 링크(link) 상태가 일치
- 초점(focus) 상태가 일치
- 문서 전체에서 속성 선택자의 영향을 받는 요소가 없어야 함(영향이라 함은 속성 선택자를 사용한 경우 - ex. input[type=text]{...})
- 요소에 인라인 스타일 속성이 없어야 함(ex. <p style="...">...</p>).
- 문서 전체에서 형제 선택자를 사용하지 않아야 함(웹 코어는 형제 선택자를 만나면 전역 스위치를 열고 전체 문서의 스타일 공유를 중단함 - 형제 선택자는 + 선택자와 :first-child 그리고 :last-child를 포함함)
파이어폭스 규칙 트리
파이어폭스는 스타일 계산을 쉽게 처리하기 위해 규칙 트리와 스타일 문맥 트리라고 하는 두 개의 트리를 더 가지고 있음
웹킷도 스타일 객체를 가지고 있지만 스타일 문맥 트리처럼 저장되지 않고 오직 DOM 노드로 관련 스타일을 처리함
'스타일 문맥'에는 최종 값이 저장되어 있음
값은 올바른 순서 안에서 부합하는 규칙을 적용하고 논리로부터 구체적인 값으로 변환함으로써 계산됨
예를 들어 논리적인 값이 화면의 백분율(%) 이라면 이 값은 계산에 의해 절대적인 단위(px)로 변환됨(따라서 노드 사이에서 이 값을 공유함으로써 그것들을 다시 계산하는 일을 방지함)
부합하는 모든 규칙은 트리에 저장하는데 경로의 하위 노드가 높은 우선순위를 갖음
규칙 저장은 느리게 처리됨
트리는 처음부터 모든 노드를 계산하지 않지만 노드 스타일이 계산될 필요가 있을 때 계산된 경로를 트리에 추가함
▶ 배치
렌더러가 생성되어 트리에 추가될 때 크기와 위치 정보는 없는데 이런 값을 계산하는 것을 배치 또는 리플로라고 부름
HTML은 흐름 기반의 배치 모델을 사용하는 이것은 보통 단일 경로를 통해 크기와 위치 정보를 계산할 수 있다는 것을 의미
일반적으로 흐름속에서 나중에 등장하는 요소는 앞서 등장한 요소의 위치와 크기에 영향을 미치지 않기 때문에 배치는 왼쪽에서 오른쪽으로 또는 위에서 아래로 흐름(단, 표는 크기와 위치를 계산하기 위해 하나 이상의 경로를 필요로 하기 때문에 예외가 됨)
좌표계는 기준점으로부터 상대적으로 위치를 결정하는데 좌단(X축)과 상단(Y축) 좌표를 사용함
배치는 반복되며 HTML 문서의 <html> 요소에 해당하는 최상위 렌더러에서 시작됨
배치는 프레임 계층의 일부 또는 전부를 통해 반복되고 각 렌더러에 필요한 크기와 위치를 계산함
최상위 렌더러의 위치는 0,0이며 브라우저 창이 보이는 영역에 해당하는 뷰포트 만큼의 면적을 갖음
모든 렌더러는 '배치' 또는 '리플로' 메서드를 갖는데 각 렌더러는 배치해야 할 자식의 배치 메소드를 불러옴
더티 비트 체제
소소한 변경 때문에 전체를 다시 배치하지 않기 위해 브라우저는 '더티 비트' 체제를 사요함
렌더러는 다시 배치할 필요가 있는 변경 요소 또는 추가된 것과 그 자식을 '더티'라고 표시
"더티"와 "자식이 더티" 이렇게 두 가지 플래그가 있음
자식이 더티하다는 것은 본인은 괜찮지만 자식 가운데 적어도 하나를 다시 배치할 필요가 있다는 의미
전역 배치와 점증 배치
배치는 렌더러 트리 전체에서 일어날 수 있는데 이것을 '전역' 배치라고 함
- 글꼴 크기 변경과 같이 모든 렌더러에 영향을 주는 전역 스타일 변경
- 화면 크기 변경에 의한 결과
배치는 더티 렌더러가 배치되는 경우에만 점증되는데 추가적인 배치가 필요하기 때문에 약간의 손실이 발생할 수 있음
점증 배치는 렌더러가 더티일때 비동기적으로 일어남
예를들어 네트워크로부터 추가 내용을 받아서 DOM 트리에 더해진 다음 새로운 렌더러가 렌더 트리에 붙을 경우
비동기 배치와 동기 배치
점증 배치는 비동기로 실행됨
파이어폭스는 점증 배치를 위해 "리플로 명령"을 쌓아 놓고 스케줄러는 이 명령을 한꺼번에 실행함
웹킷도 점증 배치를 실행하는 타이머가 있는데 트리를 탐색하여 "더티" 렌더러를 배치함
"offsetHeight" 같은 스타일 정보를 요청하는 스크립트는 동기적으로 점증 배치를 실행
전역 배치는 보통 동기적으로 실행됨
때때로 배치는 스크롤 위치 변화와 같은 일부 속성들 때문에 초기 배치 이후 콜백으로 실행됨
최적화
배치가 크기 변경 또는 렌더러 위치 변화 때문에 실행되는 경우 렌더러의 크기는 다시 계산하지 않고 캐시로부터 가져옴
어떤 경우는 하위 트리만 수정이 되고 최상위로부터 배치가 시작되지 않는 경우도 있음
이런 경우는 입력 필드에 텍스트를 입력하는 경우와 같이 변화 범위가 한정적이어서 주변에 영향을 미치지 않을 때 발생함
만약 입력 필드 바깥쪽에 텍스트가 입력되는 경우라면 배치는 최상단으로부터 시작될 것
배치 과정
- 부모 렌더러가 자신의 너비를 결정
- 부모가 자식을 검토
- 자식 렌더러를 배치(자식의 x와 y를 설정)
- (부모와 자식이 더티하거나 전역 배치 상태이거나 또는 다른 이유로) 필요하다면 자식 배치를 호출하여 자식의 높이를 계산
- 부모는 자식의 누적된 높이와 여백, 패딩을 사용하여 자신의 높이를 설정(이 값은 부모 렌더러의 부모가 사용)
- 더티 비트 플래그를 제거
너비 계산
렌더러의 너비는 포함하는 블록의 너비, 그리고 렌더러의 너비와 여백, 테두리를 이용하여 계산됨
배치할 필요가 있지만 너비가 고정된 경우 값은 캐시에 저장
줄 바꿈
렌더러가 배치되는 동안 줄을 바꿀 필요가 있을 때 배치는 중단되고 줄 바꿀 필요가 있음을 부모에게 전달
부모는 추가 렌더러를 생성하고 배치를 호출
▶ 그리기
그리기 단계에서는 화면에 내용을 표시하기 위한 렌더 트리가 탐색되고 렌더러의 'paint' 메서드가 호출됨
그리기는 UI 기반의 구성 요소를 사용함
전역과 점증
그리기는 배치와 마찬가지로 전역 또는 점증 방식으로 수행됨
점증 그리기에서 일부 렌더러는 전체 트리에 영향을 주지 않는 방식으로 변경됨
변경된 렌더러는 화면 위의 사각형을 무효화 하는데 OS는 이것을 '더티 영역'으로 보고 paint 이벤트를 발생시킴
OS는 몇 개의 영역을 하나로 합치는 방법으로 효과적으로 처리
크롬은 렌더러가 별도의 처리 과정이기 때문에 조금 더 복잡함
크롬은 OS의 동작을 어느 정도 모방함
프레젠테이션은 이런 이벤트에 귀 기울기고 렌더 최상위로 메시지를 전달함
그러면 트리는 적절한 렌더러에 이를 때까지 탐색되고 스스로(보통 자식과 함께) 다시 그짐
그리기 순서
CSS 2는 그리기 과정의 순서를 정의했음
이것은 실제로 요소가 stacking contexts에 쌓이는 순서
스택은 뒤에서 앞으로 그려지기 때문에 이 순서는 그리기에 영향을 미침
- 배경 색
- 배경 이미지
- 테두리
- 자식
- 아웃라인
동적 변경
브라우저는 변경에 대해 가능한 한 최소한의 동작으로 반응하려고 노력함
그렇기 때문에 요소의 색깔이 바뀌면 해당 요소의 리페인팅만 발생
요소의 위치가 바뀌면 요소와 자식 그리고 형제의 리페인팅과 재배치가 발생함
DOM 노드를 추가하면 노드의 리페인팅과 재 배치가 발생함
"html" 요소의 글꼴 크기를 변경하는 것과 같은 큰 변경은 캐시를 무효화하고 트리 전체의 배치와 리페인팅이 발생함
렌더링 엔진의 스레드
렌더링 엔진은 통신을 제외한 거의 모든 경우에 단일 스레드로 동작함
통신은 몇 개의 병렬 스레드에 의해 진행될 수 있는데 병렬 연결의 수는 보통 2개에서 6개로 제한됨
이벤트 순환
브라우저의 주요 스레드는 이벤트 순환으로 처리 과정을 유지하기 위해 무한 순환됨
배치와 그리기 같은 이벤트를 위해 대기하고 이벤트를 처리함
▶ CSS2 시각 모델
캔버스
CSS2 명세는 캔버스를 "서식 구조가 표현되는 공간" 이라고 설명함
브라우저가 내용을 그리는 공간인 것
캔버스 공간 각각의 면적은 무한하지만 브라우저는 뷰포트의 크기를 기초로 초기 너비를 결정함
CSS2 명세에 따르면 캔버스는 기본적으로 투명하기 때문에 다른 캔버스와 겹치는 경우 비쳐 보이고, 투명하지 않을 경우에는 브라우저에서 정의한 색이 지정됨
CSS 박스 모델
CSS 박스 모델은 문서 트리에 있는 요소를 위해 생성되고 시각적 서식 모델에 따라 배치된 사각형 박스를 설명함
각 박스는 콘텐츠 영역(문자, 이미지 등)과 선택적인 패딩과 테두리, 여백이 있음
각 노드는 이런 상자를 0에서 n개 생성
모든 요소는 만들어질 박스의 유형을 결정하는 "display" 속성을 갖음
- block - 블록 상자를 만든듬
- inline - 하나 또는 그 이상의 인라인 상자를 만듬
- none - 박스를 만들지 않음
기본 값은 인라인이지만 브라우저의 스타일 시트는 다른 기본 값을 설정함
예를 들면 "div" 요소의 display 속성에 대한 기본 값은 block
위치 결정 방법
- Normal: 객체는 문서 안의 자리에 따라 위치가 결정됨(렌더 트리에서 객체의 자리가 DOM 트리의 자리와 같고 박스 유형과 면적에 따라 배치됨을 의미)
- Float: 객체는 우선 일반적인 흐름에 따라 배치된 다음 왼쪽이나 오른쪽으로 흘러 이동
- Absolute: 객체는 DOM 트리 자리와는 다른 렌더 트리에 놓임
위치는 "position" 속성과 "float" 속성에 의해 결정
- static과 relative로 설정하면 일반적인 흐름에 따라 위치가 결정
- absolute와 fixed로 설정하면 절대적인 위치
position 속성을 정의하지 않으면 static이 기본 값이 되며 일반적인 흐름에 따라 위치가 결정됨
static 아닌 다른 속성 값(relatice, absolute, fixed)을 사용하면 top, bottom, left, right 속성으로 위치를 결정할 수 있음
박스가 배치되는 방법은 다음과 같은 방법으로 결정됨
- 박스 유형(display, inline ...)
- 박스 크기(width, height ...)
- 위치 결정 방법(position, float)
- 추가적인 정보 - 이미지 크기와 화면 크기 등
박스 유형
- 블록 박스: 브라우저 창에서 사각형 블록을 형성
- 인라인 박스: 블록이 되지 않고 블록 내부에 포함
블록은 다른 블록 아래 수직으로 배치되고 인라인은 수평으로 배치됨
인라인 박스는 라인 또는 "라인 박스" 안쪽에 놓임
라인은 적어도 가장 큰 박스만큼 크지만 "baseline" 정렬일 때 더 커질 수 있음(포함하는 너비가 충분하지 않으면 인라인은 몇 줄의 라인으로 배치되는데 이것은 보통 문단 안에서 발생함)
위치 잡기
상대적인 위치
상대적인 위치 잡기는 일반적인 흐름에 따라 위치를 결정한 다음 필요한 만큼 이동
플로트
플로트 박스는 라인의 왼쪽 또는 오른쪽으로 이동함
흥미로운 점은 다른 박스가 이 주변을 흐른다는 것
절대적인(absolute) 위치와 고정된(fixed) 위치
절대와 고정 배치는 일반적인 흐름과 무관하게 결정되고, 일반적인 흐름에 관여하지 않으며, 면적은 부모에 따라 상대적임
고정인 경우 뷰포트로부터 위치를 결정함
고정된 박스는 문서가 스크롤되어도 따라 움직이지 않음
층 표현
이것은 CSS의 z-index 속성에 의해 명시됨. 층은 박스의 3차원 표현이고 "z 축"을 따라 위치를 정함
박스는 (stacking contexts라고 부르는) 스택으로 구분됨
각 스택에서 뒤쪽 요소가 먼저 그려지고 앞쪽 요소는 사용자에게 가까운 쪽으로 나중에 그려짐
가장 앞쪽에 위치한 요소는 겹치는 이전 요소를 가린다.
스택은 z-index 속성에 따라 순서를 결정
z-index 속성이 있는 박스는 지역 스택(local stack)을 형성
뷰포트는 바깥쪽의 스택(outer stack)