이 글은 Tali Garsiel의 "How Browsers Work: Behind the scenes of modern web browsers"를 번역한 글입니다.
HTML과는 다르게, CSS는 context free grammer(문맥 자유 문법)
이기 때문에 General parser 에서 설명한 것과 같이 파싱할 수 있습니다.
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
는 className과 같은 identifier의 줄임말입니다. name
은 요소의 id를 나타냅니다.
CSS의 구문 규칙은 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*] ')' ]
;
아래와 같은 ruleset
이 있다고 가정합시다.
div.error, a.error {
color:red;
font-weight:bold;
}
div.error와 a.error는 선택자입니다. 중괄호 안의 내용은 ruleset
에 적용할 규칙을 의미합니다. 이러한 구조는 아래와 같이 정의됩니다.
ruleset
: selector [ ',' S* selector ]*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
위의 정의를 해석하면, ruleset
에는 쉼표와 공백으로 구분되는 1개 이상의 선택자, 그리고 세미콜론으로 구분되는 1개 이상의 선언을 포함한 중괄호로 이루어져 있습니다.
Webkit은 CSS 문법으로부터 파서를 자동생성하기 위해 Flex와 Bison 이라는 파서 생성기를 사용합니다. Bison은 Bottom up shift-reduce parser
를 생성합니다.
Firefox는 직접 만든 Top down parser
를 사용합니다.
Webkit과 Firefox 모두 CSS 파일을 StyleSheet 객체로 파싱합니다. 객체는 CSS 규칙을 포함하고, 이 규칙에는 선택자, 선언 객체 그리고 CSS 문법에 맞는 다른 객체이 포함됩니다.
웹은 파싱과 실행이 동시에 진행되는 동기화 모델입니다. 개발자는 파서가 브라우저가 script
태그를 만나는 즉시, 파싱하고 실행하기를 바랍니다. 스크립트가 실행되는 동안, 문서 파싱은 중단됩니다. 스크립트가 외부에 있는 경우, 네트워크로부터 리소스를 가져오는 작업도 실시간으로 실행되며, 파싱은 중단됩니다. 이는 HTML4와 HTML5의 명세에도 설명되어 있습니다. 스크립트가 파싱이 되고 실행되어야 하는 경우, 개발자는 script
태그에 defer
속성을 추가하여 파싱을 중단시키지 않고 파싱이 완료된 후에 실행되게 할 수 있습니다. HTML5에는 스크립트가 다른 쓰레드에서 비동기적으로 실행되도록 하는 속성을 추가했습니다.
Webkit과 Firefox 모두 Speculative parsing
과 같은 최적화 프로세스가 있습니다. 스크립트를 실행하는 동안, 다른 쓰레드에서는 문서의 남은 부분을 파싱하고, 네트워크가 필요한 리소스를 찾아 이를 내려받습니다. 이 방법을 사용하면, 리소스를 동시에 받을 수 있고, 따라서 전체적인 속도가 향상됩니다.
주의:
Speculative parser 는 외부 스크립트, css, 사진과 같은 외부 리소스만 파싱합니다.
DOM 트리를 수정하는 과정은 메인 파서가 진행합니다.
스타일 시트는 다른 모델을 사용합니다. 이론적으로는 스타일 시트는 DOM 트리를 수정하지 않기 때문에 문서 파싱을 멈추고 기다릴 이유가 없습니다. 그러나, 문서를 파싱하는 동안 스크립트가 스타일 정보를 요청하는 경우에는 얘기가 달라집니다. 스타일의 로드와 파싱이 완료되지 않았다면, 스크립트는 잘못된 정보를 사용하여 문제가 발생합니다. 이는 코너 케이스같지만, 생각보다는 흔한 일입니다.
Firefox에서는 스타일시트가 로드나 파싱되는 동안, 모든 스크립트를 중단시킵니다.
Webkit은 아직 로드되지 않은 스타일 시트가 영향을 끼칠 수 있는 스타일을 스크립트에서 사용하려 할 때 스크립트를 중단시킵니다.
참고 자료
How Browsers Work: Behind the scenes of modern web browsers
브라우저는 어떻게 동작하는가?