브라우저 점유율 (한국 데이터)
🖥️ desktop
📱 mobile & tablet
브라우저 호환성
1. 웹 브라우저 엔진의 주요 기능
- HTML 및 CSS 문서를 해석하여, 웹 페이지의 렌더링을 담당합니다.
- JavaScript 코드를 해석하여, 웹 페이지의 동작을 제어합니다.
- 웹 페이지에서 사용자 입력 이벤트를 처리하여, 웹 페이지의 동작을 변경합니다.
- 웹 페이지에서 네트워크 요청을 처리하여, 웹 페이지에 포함된 리소스를 가져옵니다.
2. 엔진 설명
👉🏻 대부분 웹킷이기 때문에 호환성이 좋아졌고, 호환성에 대한 큰 의미는 없어졌다.
브라우저 렌더링
가장 많은 점유율을 차지하고 있는 Blink엔진 기준으로 브라우저 렌더링 과정은 아래와 같다.
- HTML Parsing에서 시작하여 구성을 분석하는 Style, Layout,Paint과정을 거쳐 Layer를 구성하고 합성 스레드와 GPU가 협력하여 화면에 그리는 과정으로 진행된다.
1. Parsing
가장 첫 단계로 메인 스레드에서 HTML을 브라우저가 해석할 수 있는 자료구조인 DOM Tree로 변환하는 작업이 진행된다.
- 이 과정은 HTML파서가 <link> 또는 async, defer가 없는 <script>와 같은 블로킹 리소스를 만나기 전까지 진행된다.
- CSS파일의 경우 FOUC(Flash Of Unstyled Content: 외부의 CSS가 불러오기 전에 잠시 스타일이 적용되지 않은 웹 페이지가 나타나는 현상)를 방지하기 위해 파싱과 렌더링이 차단된다.
- <script>태그도 DOM을 변경하는 코드document.write()를 포함할 수 있기 때문에 파싱을 멈춘다.
2. Style
DOM Tree파싱 이후 브라우저는 CSS를 파싱해서 세가지 단계로 각 DOM의 스타일을 계산한다.
2-1. CSS -> 스타일 시트
<link>태그로 로드하는 CSS, <style>, 그리고 inline style의 정보를 바탕으로 브라우저가 해석할 수 있는 스타일 시트를 생성한다.
2-2. 단위변환
CSS는 px, %, em, rem등 다양한 단위로 작성할 수 있는데, rem등 상대적인 값은 픽셀로 치환되어 계산된다.
'픽셀'로 표현하는 이유: 렌더링 마지막 단계에서 비트맵 데이터를 구성하는 데, 비트맵 데이터가 픽셀로 구성되기 때문이다.
2-3. 스타일 계산
마지막으로 CSS 오버라이딩 등을 고려하여 최종 스타일을 계산한다.
3. Layout
Layout단계에서는 레이아웃 트리를 구성한다. DOM트리와 스타일 시트를 기반으로 어떤 요소를 어디에 렌더링 해야하는 지를 결정한다. 레이아웃 트리엔 페이지에 렌더링 되는 정보만 포함되기 때문에 display:none으로 처리된 요소는 포함되지 않는다.
레이아웃 트리를 구성하는 과정에서는 폰트 크기에 따른 단락의 줄바꿈까지 모두 계산된다.
4. PrePaint
PrePaint단계는 레이어를 구성하기 위한 준비 단계로, 크게 두가지 작업이 진행된다.
4-1. Paint Invalidation
이전단계(스타일, 레이아웃)에서 변화가 생길 경우(dirty bit), 캐싱해둔 paint기록을 무효화한다.
4-2. Property Tree
Property Tee는 각 레이어에 할당되는 속성이다.
예를들어 CSS속성 중 transform, opacity등의 속성을 적용할 경우, Property Tree에 반영되고 이후 레이어를 합치는 단계에서 필요한 효과를 빠르게 적용할 수 있다.
기존에는 Property Tree에서 다뤄지는 데이터가 레이어에 함께 저장되어 있었기 때문에 특정 노드의 속성이 변경되면 해당 노드의 하위 노드에도 변경된 값을 반영하면서 노드를 순회해야 했다. 최신 Blink엔진에서는 이런 속성을 별도로 관리하고 각 노드에서는 Property Tree의 노드를 참조하는 방식으로 변경되었다.
5. Paint
paint과정은 실제로 화면을 그리는 과정이 아니라 어떻게 그려야하는지에 대한 정보를 담고 있는 Paint Records를 생성하는 과정이며, 아래 세가지 정보가 포함된다.
- Action
- Position
- Style
6. Layerize
Layerize과정은 paint과정의 결과물을 사용해서 Composited Layer List라는 데이터를 생성하는 단계이다. layout단계에서 Layout Object로 구성된 Layout Tree가 생성되고, Layout Object에서 아래 조건을 만족하면 별도의 Paint Layer가 생성된다.
- 최상위 요소
- position: relative|absolute사용
- 3D(translate3d, preserve-3d,...)나 perspective transform사용
- <video>, <canvas>태그 사용
- CSS filter나 alpha mask 사용
- 이 조건을 만족하지 않아 별도의 Paint Layer로 생성되지 않은 LayoutObject는 가까운 상위 Paint Layer와 대응된다. (두개 이상의 Layout Object가 하나의 Paint Layer로 다뤄질 수 있다.)
Paint Layer중 Compositing Trigger를 가지고 있거나 스크롤 가능한 컨텐츠가 있을 경우 별도의 Graphics Layer가 생성된다.
Compositing Trigger 예
- 3D 변형: translate3d, translateZ …
- <video>, <canvas>, <iframe> 요소
- position: fixed
- CSS 트랜지션과 애니메이션을 사용해 구현한 transform과 opacity 애니메이션
- will-change
- filter
분리된 Graphics Layer들은 독립적인 픽셀화가 가능하며 프레임마다 raster과정을 다시 실행할 필요 없이 GPU연산이 가능하기 때문에 바른 스크롤링과 애니메이션이 가능하다.
7. Commit
Layerize단계의 출력인 Composited Layer List는 PrePaint단계에서 생성한 Property Tree와 함께 합성 스레드(Composited Thread)로 복사된다. 이 과정을 **'Commit'**이라고 하며, 이는 메인 스레드에서의 마지막 작업이다. 커밋 이후에는 자바스크립트를 실행하거나 렌더링 파이프라인을 다시 실행할 수 있다.
메인 스레드에서의 작업은 종료되었지만, 아직 한 프레임을 그리는 렌더링 과정은 종료되지 않았다. 메인 스레드에서의 작업만으로는 화면을 렌더링할 수 없고, 합성 스레드와 GPU에서의 작업이 완료되어야 한다.
이렇게 스레드 기반으로 작업을 분리한 이유는 작업을 병렬적으로 처리하기 위함으로, 합성 스레드에서 이후 렌더링 단계를 진행하는 동안 메인 스레드는 순수한 렌더링 파이프라인 과정을 처리할 수 있다.
7-1. 합성 스레드
합성 스레드에서는 메인 스레드와 별개로 레이어를 합성하고 사용자 입력을 처리한다.
레이어 합성
브라우저가 화면에 그리는 실제 렌더링을 하기 위해서는 앞선 단계를 통해 알고 있는 HTML구조와 각 요소의 스타일, 기하학적 속성, 페인트 속성에 대한 정보를 픽셀로 변환해야 하며, 이 작업을 픽셀화(래스터화, rasterizing)라고 한다.
합성은 웹 페이지의 각 부분을 레이어로 분리해 별도로 픽셀화하고 합성 스레드에서 합성하는 기술이다. 스크롤되었을 때 레이어는 이미 픽셀화되어 있으므로(이전에는 사용자가 스크롤하면 나머지 빈 부분을 추가로 픽셀화했었음) 새 프레임을 합성하기만 하면 된다.
사용자 입력
컴포짓 레이어에서 발생하는 스크롤 이벤트는 메인 스레드를 거치지 않고 컴포짓 스레드에서 처리할 수 있다. 단, 연결된 이벤트 핸들러가 존재하지 않아야 한다.
자바스크립트의 실행은 메인 스레드의 작업이므로 웹 페이지가 합성될 때 컴포짓스레드는 이벤트 핸들러가 연결된 영역을 '고속 스크롤 불가 영역(non-fast scrollable region)'이라고 표시한다. 웹페이지의 이 영역에서 이벤트가 발생했을 때 컴포짓 스레드가 입력 이벤트를 메인 스레드로 보내야 하는지를 이 정보로 확인할 수 있다. 입력 이벤트가 고속 스크롤 불가 영역 밖에서 발생했다면 컴포짓 스레드는 메인 스레드에서 commit된 정보를 바탕으로 메인 스레드를 거치지 않고 새 프레임을 합성한다.
따라서, 이벤트 핸들러의 상위 요소에 연결하는 이벤트 위임 패턴의 코드는 예기치 않은 스크롤 성능 저하를 야기할 수 있다.
브라우저 관점에서 보면 웹 페이지의 모든 영역이 고속 스크롤 불가 영역으로 표시되어 컴포짓 스레드는 입력 이벤트가 발생할 때마다 메인 스레드와 통신해야 하고 메인 스레드의 작업을 기다려야하기 때문에 스크롤을 부드럽게 처리할 수 없다.
이러한 문제를 방지하기 위해 이벤트 리스너에 pasive옵션을 설정할 수 있다.
이벤트가 발생되는 시점에서 defaultPrevented값을 무시하게 되는데, 이는 메인 스레드에서 이벤트 입력을 받긴 하지만 컴포지터가 메인 스레드의 처리를 기다리지 않고 새 프레임을 합성해도 된다는 것을 의미한다.
document.body.addEventListner('tochstart', event => {
if (event.target === area) {
event.preventDefault();
}
}, { passive: true })
8. Tilling
컴포짓 스레드는 메인 스레드에서 넘겨 받은 각 레이어를 픽셀화한다. 이 때, 레이어의 크기가 클 수 있기 때문에 컴포짓 스레드는 레이어를 타일형태로 분할한다. 각 타일에는 draw과정에서 생성한 Paint Record가 포함되고, 각 타일은 viewport포함 여부 등에 따라 다른 우선순위로 픽셀화된다.
9. Raster
Raster는 타일에 저장된 draw명령어를 실행하는 과정이다. Blink엔진에서는 Skia라는 그래픽 라이브러리를 사용하여 비트맵 이미지를 생성하고 이를 GPU메모리에 저장한다.
이전 크로미움 아키텍처에서 래스터 과정은 렌더러 프로세스의 래스터 스레드에서 수행됐지만 최근에는 GPU프로세스에서 수행된다. 이를 **'하드웨어 가속'**이라고 한다.
모든 타일에 래스터화된 후 DrawQuad라는 데이터를 생성한다. 쿼드는 타일을 어디에, 어떻게 그릴지에 대한 정보를 포함하며 이 정보는 앞서 생성한 레이어와 Property Tree정보를 바탕으로 생성된다.
10. Activate
합성 스레드는 pending tree와 active tree를 가지고 swap하는 형태의 멀티 버퍼링 패턴을 가지고 있다.
래스터작업은 비동기로 진행되는데, 컴포짓 스레드에 이전 작업이 처리 중일 때 새로운 커밋이 들어올 경우 새로운 커밋에 대한 래스터 작업을 진행하기 전 이전 커밋에 대한 내용을 보여주어야하기 때문이다.
pending tree는 커밋을 받고 렌더링에 필요한 작업이 완료되면 pending tree를 active tree로 복제한다. 이렇게 분리된 트리구조로 인해 active tree에서 GPU작업을 하는 동안 pending tree에서 커밋된 변경사항을 대기시킬 수 있다.
마지막으로 activate된 쿼드들은 Compositor Frame이라는 데이터로 묶여 GPU프로세스로 전달된다. 합성스레드의 최종 목표는 커밋받은 레이어를 쪼개서(tiling) 픽셀화(raster)하고 Frame으로 만들어 GPU에 전달하는 것이다.
11. Display
마지막 작업으로, GPU프로세스의 viz스레드에서 여러개의 Compositor Frame을 단일 Compositor Frame으로 합치고 화면에 픽셀을 렌더링하면서 한 프레임을 그리는 렌더링 파이프라인이 종료된다.
참조