1. 서론
웹서버에 요청을 하면 브라우저는 우리가 접근하려는 웹페이지의 HTML 파일을 포함한 응답을 받는다. 유저에게 웹페이지를 보여주기 위해서 브라우저는 파일을 파싱, 렌더링, 페인트 해야 한다.
파싱이란 프로그램을 분석하고 실행 환경에서 실제로 실행할 수 있는 내부 형식으로 변환하는 것이다. 즉, 파싱은 우리가 작성한 코드(HTML, CSS)를 브라우저가 작업할 수 있는 형태로 변환하는 것을 의미한다. 파싱은 브라우저 엔진에 의해 수행된다. 브라우저 엔진에는 Gecko, Webkit, Blink 등이 있다.
2. HTML Parsing
2.1 Byte Stream Decoder
- 입력: 3C 62 6F 64 79 3E 48 65 6C 6C 6F 2C 20 3C 73 70 61 6E 3E 77 6F 72 6C 64 21 3C 2F 73 70 61 6E 3E 3C 2F 62 6F 64 79 3E
- 출력: <body>Hello, <span>world!</span></body>
브라우저가 서버로부터 받은 Unicode character stream은 네트워크나 로컬 파일 시스템에서 오는 byte stream이다. bytes는 특정 encoding에 따라 html 파일을 인코딩한 자료이므로 user agent는 bytes를 다시 디코딩해야 한다. user agents는 디코딩할 때 사용할 encoding을 결정하기 위해 encoding sniffing algorithm을 사용한다. 왜냐하면 encoding을 찾기 위해 pre-parsing하는 것은 parsing에 사용된 data structures를 버려야 할 필요성을 줄여 성능이 향상되기 때문이다.
encoding sniffing algorithm 은 user agent가 사용할 수 있는 out-of-band metadata(ex: Content-Type metadata)와 지금까지 사용 가능한 모든 bytes를 입력으로 받아들여 encoding과 confidence를 반환한다. confidence는 tentative, certain, irrelevant 중 하나이다. 인코딩과 그 인코딩에 대한 confidence가 tentative인지 certain인지를 parsing 중에 사용하여 인코딩을 변경할지 여부를 결정한다. 인코딩이 필요하지 않은 경우, 예를 들어 parser가 Unicode character stream에서 작동하고 인코딩을 전혀 사용할 필요가 없는 경우에 confidence는 irrelevant하다. 다음 5가지는 user agents가 encoding sniffing algorithm을 사용하는 유형이다.
- 사용자가 특정 인코딩을 user agent에 명시한 경우, 그 인코딩을 certain confidence로 반환한다.
- transport layer가 encoding을 지정하고 지원하는 경우, 그 인코딩을 certain confidence로 반환한다.
- 더 많은 bytes가 도착할 때까지 기다린다. 예를 들어, user agent는 500ms 또는 1024 bytes 중 더 빠른 시간동안 기다린다.
- 파일의 첫 번째 bytes가 특정 인코딩의 bytes 수보다 더 많고 특정 인코딩의 bytes와 일치하면, 특정 encoding을 certain confidence로 반환한다. 예를 들어, 파일의 첫 번째 bytes가 "FE FF"인 경우 Big-endian UTF-16 인코딩을 반환한다. 그리고 파일의 첫 번째 bytes가 "EF BB BF"인 경우 UTF-8 인코딩을 반환한다.
- 파일에서 explicit character encoding information을 검색한다.
2.2 Input Stream Preprocesser
- 입력: <body>Hello, <span>world!</span></body>
- 출력: <body>Hello, <span>world!</span></body>
Input Stream Preprocessor는 CR, LF, CRLF 등다양한 줄바꿈 문자를 일관된 문자(LF)로 변환, NULL 문자를 제거, 공백 문자를 처리 등 Token화하기 위한 전처리를 수행한다.
2.3 Tokenizer
- 입력: <body>Hello, <span>world!</span></body>
- 출력: ["StartTag: body", "Text: Hello", "StartTag: span", "Text: world!", "EndTag: span", "EndTag: body"]
Tokenizer는 HTML 문서를 개별적인 토큰으로 분해하는 작업을 수행한다. 입력된 HTML 문자열을 한 문자씩 읽어들여 < 문자를 만나면 태그의 시작으로 간주하고, 다음에 나오는 문자를 읽어 태그 이름을 확인한다. 태그 이름 뒤의 >를 만나기 전까지 모든 문자를 태그의 일부로 처리하고, 태그 이름이 끝난 후 공백이 나오면 속성의 시작으로 간주하여 속성 이름과 값을 읽어들인다. <와 > 사이의 내용을 기반으로 시작 태그(StartTag) 또는 종료 태그(EndTag) 토큰을 생성하며, 태그가 아닌 문자열은 텍스트(Text) 토큰으로 처리한다.
2.4 Tree Construction
- 입력: ["StartTag: body", "Text: Hello", "StartTag: span", "Text: world!", "EndTag: span", "EndTag: body"]
- 출력: ["<body>", {}, ["Hello", ["<span>", {}, ["world!"]]]]
Tree Construction 단계에서는 생성된 토큰을 기반으로 HTML 문서의 DOM 트리를 구성한다. StartTag 토큰을 만나면 새로운 요소 노드를 생성하고, Text 토큰을 만나면 텍스트 노드를 생성하여 현재 요소 노드의 자식으로 추가한다. 새로운 요소 노드는 현재 노드의 자식으로 추가되며, 현재 노드는 스택에 저장되어 자식 노드의 처리가 끝나면 스택에서 팝된다. EndTag 토큰을 만나면 현재 노드의 처리를 종료하고 상위 노드로 돌아가며, 이를 통해 최종적으로 계층적인 DOM 트리가 형성된다.
3. CSS Parsing
css를 parsing하는 과정도 위의 html parsing과정과 동일하다. 바이트 스트림을 디코딩한 후, 토큰화하여 트리를 만든다.
3. 렌더링
HTML을 통해 브라우저는 DOM(Document Object Model)을 만들고, CSS로는 CSSOM(CSS Object Model)을 만들게 된다. 이 두 가지가 결합되면 Render Tree가 완성된다. 이 Render Tree는 실제로 화면에 보여질 요소들만 포함하며, 이를 바탕으로 브라우저는 페이지의 Layout을 계산하게 된다. Layout 단계에서는 각 요소의 위치와 크기를 계산하고, 그 다음 Paint 단계에서는 요소들의 색상, 배경, 테두리 등을 적용하게 된다.
만약 JavaScript 등을 이용해 DOM이나 CSS 속성을 변경할 경우, 특히 요소의 크기나 위치를 변경하는 속성들(예: width, height, margin, padding, position, top, left)을 수정하게 되면 Reflow가 발생하게 된다. Reflow는 Layout을 다시 계산하는 과정으로, 부모 요소뿐만 아니라 자식 요소들의 레이아웃까지 다시 계산해야 하기 때문에 성능에 부담을 줄 수 있다. 반면, 색상이나 그림자와 같은 속성은 레이아웃을 변경하지 않으므로 Paint 단계만 다시 진행된다.
Composite 단계에서는 브라우저가 레이어를 합성하여 최종적으로 화면에 그리게 되는데, 이 과정에서 GPU 가속을 사용할 수 있다. 브라우저는 애니메이션이나 트랜지션과 같은 작업을 할 때 GPU를 활용해 성능을 최적화한다. 특히 transform, opacity, filter 같은 CSS 속성들은 GPU 가속을 통해 별도의 레이어로 처리되어, Reflow나 Repaint 없이 부드럽게 렌더링된다.
개발자는 will-change 속성을 사용해 특정 요소가 변화될 속성(예: transform, opacity)을 미리 선언함으로써 브라우저가 해당 요소를 GPU 레이어로 처리하도록 유도할 수 있다. 예를 들어, 애니메이션이 적용될 요소에 will-change: transform;을 선언하면, 브라우저가 미리 레이어를 준비하여 더 부드러운 애니메이션을 구현할 수 있다.
.my-element {
will-change: transform;
}
하지만 will-change 속성은 남용할 경우, 너무 많은 레이어를 만들어 성능에 부정적인 영향을 줄 수 있으므로, 꼭 필요한 요소에만 사용하는 것이 중요하다.
마지막으로, 모든 요소들의 렌더링이 끝나면 브라우저는 Composite 과정을 거쳐 페이지를 실제로 화면에 그리게 된다. Composite는 각 요소를 레이어 단위로 나누어 합성하는 과정으로, 이때 여러 레이어가 결합되어 최종적으로 우리가 보는 웹페이지가 완성된다.
Reference
- W3C. (n.d.). HTML5: A vocabulary and associated APIs for HTML and XHTML. Retrieved from https://dev.w3.org/html5/spec-LC/parsing.html
- MDN Web Docs. (n.d.). Parse. Retrieved from https://developer.mozilla.org/en-US/docs/Glossary/Parse
- MDN Web Docs. (n.d.). CSSOM. Retrieved from https://developer.mozilla.org/en-US/docs/Glossary/CSSOM
- Programming Soup. (n.d.). Browser Rendering. Retrieved from https://programmingsoup.com/browser-rendering
'Computer Science' 카테고리의 다른 글
CPU 스케줄링 (1) | 2024.07.19 |
---|---|
왜 리눅스는 서버OS로 많이 사용되는가: 리눅스 커널 (2) | 2024.07.16 |