브라우저는 어떻게 작동하나?

Sal Jeong·2023년 3월 12일
0

브라우저의 동작 단계

navigation

response

까지의 정리

https://velog.io/@jihyeonjeong11/%EB%B9%BC%EB%A8%B9%EA%B1%B0%EB%82%98-%EB%86%93%EC%B9%9C-%EA%B2%83%EB%93%A4.-URL%EC%9D%84-%EC%9E%85%EB%A0%A5%ED%95%A0-%EB%95%8C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC

이후

1. parsing

render까지의 작동 방식을 다룬다.

https://developer.mozilla.org/en-US/docs/Web/Performance/How_browsers_work#parsing

첫 response 시 TCP slow start로 인해서 받아올 수 있는 데이터의 총량은 14kb로 제한된다.
이후 28kb, 56kb... 식으로 계속해서 늘어난다. -> performance가 중요한 이유
해당 페이지의 html 파일이 14kb에 담기지 않을 만큼 크더라도 브라우저에서는 첫 요청의 패킷에 따라 랜더를 시도한다. 그렇기 때문에, 첫 렌더 시 페이지 전체, 혹은 최소한 템플릿이더라도, 14kb 용량을 맞추는 것이 중요하다. 또한 html, css js가 모두 파스되기 전까지는 렌더가 일어나지 않는다.

Response를 통해 데이터의 chunk를 받아오는 순간부터, 브라우저에서는 받은 데이터를 파싱하기 시작한다. 해당 데이터는 DOM과 CSSOM의 형태로 변환되어 paint 단계에서 사용된다.

2진수 데이터 -> parsing -> 문자열 변환 -> lexing -> 도중 script나 style 태그를 만나면 lexing을 중단하고 cssom이나 script 파싱을 먼저 진행한다.

DOM Document Object Model. XML이나 HTML 을 브라우저가 읽을 수 있는 형태이며 js로 핸들링이 가능함.
CSSOM CSS Object Model

Building the DOM tree

여기서는 CRP(Critial Rendering Path). 브라우저의 렌더 다섯 단계를 설명한다.

2-1. Building the DOM tree

브라우저는 parsing 한 마크업으로 DOM Tree를 만든다. parsing 단계에서 요소가 token화 되고 첫 트리가 만들어진다. HTML token은 start와 end 태그를 포함하며, attributes의 이름과 밸류까지를 포함한다.
그렇기 때문에 sementic 규칙을 지켜 document를 작성하는 것이 중요하다.(parsing 단계에서의 속도 최적화)

parsers에서 토큰화된 도큐먼트를 document tree로 생성한다.

DOM tree란 해당 html문서의 컨텐츠를 가리킨다.

<html> 로 시작해서 </html>로 끝나는 것이며, 그 내부 다른 태그들을 포함한다.

노드의 갯수가 많을 수록, DOM tree를 만드는 시간 역시 늘어난다.

DOM tree를 처리하면서 parser가 non-blocking 데이터를 발견한다면(대표적으로 image),
브라우저에서는 해당 데이터를 위한 요청을 보내면서 parsing을 계속 진행한다.
CSS 파일이 참조된 코드를 만나더라도 계속 진행되지만, async나 defer가 없는 script를 만난다면
parsing을 멈춘다. 브라우저의 preload scanner가 이 부분에서 활용되지만,

script를 중간에 넣는 것이 권장되지는 않으며 속도 저하를 일으킬 수 있다.

Preload scanner

브라우저가 DOM tree를 생성하는 일은 main thread에서 일어난다. 이 때, 비동기 작업이 필요한 부분(css, script, web fonts의 요청) 은 parser에서 Preload scanner로 전달되어 요청이 진행되고 parsing이 계속 진행된다. 메인 쓰레드는 방해받지 않기 때문에 parser가 위 코드에 도달할 경우에는 많은 경우 해당 리소스가 이미 요청되었거나, 요청이 완료된 상황이 일어난다. 로딩이 길어지는 이슈를 해결하는데 유용하다.

<link rel="stylesheet" href="styles.css" />
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="image description" />
<script src="anotherscript.js" async></script>

위 예시에서 메인 쓰레드가 parsing을 진행하면서, preload scanner는 스크립트와 이미지를 요청하고 다운로드하게 된다. script가 메인 쓰레드를 멈추지 않게 하기 위해선 async나 defer를 사용해서 order를 뒤로 빼줄 수 있다.

CSS 요청은 parsing을 멈추지 않지만, script는 parsing을 멈추는데, 그 이유는 JS가 CSS를 변경하는데 사용되기 떄문이다.

2-2. Building the CSSOM

두번째는 CSSOM tree 생성이다. CSS object model은 DOM과 비슷하게 Tree 구조지만 서로 독립된 개체이다. 작성된 CSS 규칙을 매핑해 브라우저에서 읽을 수 있도록 CSS 선택자에 따라 DOM과 유사한 구조의 node의 tree 구조를 만든다.

CSSOM tree는 유저가 작성한 스타일시트 역시포함한다. 브라우저에서 먼저 기본적인 css 규칙들을 정의하며, 재귀를 통해 유저가 작성한 css 규칙을 다시 정의하는 것을 반복한다. (CSS는 cascading 코딩 스타일이기 때문에)

CSSOM 은 아주 빠르며 현재 브라우저의 개발자 도구에서 이 부분만을 확인할 수는 없고 브라우저가 css parsing -> construct CSSOM tree -> calculate user-agent computed styles 의 전체 시간은 확인할 수 있다.

따라서 최적화에 대해서 크게 신경쓸 부분은 아니다. 일반적으로 위 모든 프로세스가 하나의 DNS 검색작업보다 시간이 덜 걸리기 때문이다.

2-2-1. 다른 작업들

JavaScript Compilation

CSS가 파싱되고 CSSOM이 만들어지는 동안 다운로드가 완료된 자바스크립트는
interpreted, compiled, parsed and executed 이 과정을 거친다.

syntax tree 형태로 만들어진 코드를
interpreter에서 실행할 수 있게 만들고,
이는 bytecode화 되어 main thread에서 실행한다.

Building the Accessibility Tree

또한 accessibility tree(accessibility object model AOM)를 생성한다.
이것은 semantic한 버전의 DOM이라고 불리며 DOM이 업데이트시 참조하기위해 AOM을 사용한다.

이것이 완료되기 전까지는 렌더가 진행되지 않는다.

Render

렌더링에서는 style, layout, paint와 compositing 단계를 포함한다.

DOM과CSSOM 트리는 Render tree 로 조합되어 layout 단계에서 실제로 그려질 요소의 visible element를 결정하며, paint 단계에서 그리게 된다. 특별한 경우에는 이러한 과정이 compositing 단계를 거쳐 paint를 cpu가 아닌 gpu에서 실행하게 되어 메인 쓰레드의 부담을 줄여줄 수가 있다.

3. Style

CRP의 세번째 단계는 DOM과 CSSOM을 조합해 render tree를 만드는 과정이다.
render tree는 computed style tree라고 불리기도 하며, DOM tree의 root 부터 모든 노드를 iterate하면서 작성된다.

<head> 나 {display: none;}과 같은 요소들은 render tree에 포함되지 않는다.
visibility: hidden 는 포함됨

이외 모든 노드들은 CSSOM에 의해 자신의 rule을 가지게 된다. CSS cascade 방식에 따라 스타일을 계산해서 노드에 넣어주게 된다.

4. Layout

CRP의 4번째 단계는 layout이다. 여기서는 3.에서 도출된 스타일 값을 통해 렌더 트리에 등록된 실제 노드의 위치, 크기값을 계산한다. 더해서 각 페이지의 객체들의 사이즈와 포지션값역시 계산한다.
이 단계에서 노드의 위치, 크기값이 변경되는 것을 Reflow라고 한다.

Render tree가 만들어지면 layout 단계가 시작된다. Render 트리에서는 어떤 노드가 그려지는지(invisible 포함, none 미포함), 어떤 스타일을 가지고 있는지 계산하지만 dimensions과 위치는 계산하지 않기때문에, 정확한 크기와 위치를 이 단계에서 다시 traverse한다.

Web page에서는 대부분의 것들이 box 형태이다. 특히 이 단계에서는 각각 다른 디바이스와 해상도에서 제공하는 뷰포트의 사이즈가 중요하다. 뷰포트의 크기에 따라 모든 박스들(노드들)의 dimensions값에 따라 크기와 위치를 계산한다.

Layout 단계에서는 body 태그에서부터 시작하는데, 3.과 같이 모든 노드를 재귀적으로 돌면서 계산을 수행하고 각 노드의 box-model 값이 따라 placeholder space 역시 계산해준다.(일반적으로 이미지 로딩이 되지 않을 경우 표현되는 영역)

이렇게 모든 노드의 사이즈와 위치가 결정되는 것을 layout이라고 한다.
노드의 위치, 크기값이 변경되어 재계산되는것을 reflow라고 한다. 가장 일반적인 예시를 들면,
이미지의 크기를 결정하지 않았을 경우: 이미지 없이 레이아웃, 페인트, 렌더 이후 이미지 로딩 후 다시 레이아웃, 페인트, 렌더가 진행됨.

https://gist.github.com/paulirish/5d52fb081b3570c81e3a // Reflow의 이해 및 막는 법

4. Paint

CRP의 마지막 단계는 Paint이다. 실제 노드들을 스크린에 그리는 단계이며 맨 처음 일어나는 paint를 first meaningful paint라고 부른다. 또한 Paint를 Rasterization이라고 부르기도 한다.
이 단계에서는 3.에서 계산된 박스들을 실제 픽셀로 표현한다. 모든 비쥬얼적 요소를을 포함하며: 텍스트, 보더, 쉐도우, 버튼과 이미지, 이를 최대한 빠르게 그려내는 것이 가장 중요한 요소이다.

자연스러운 스크롤과 애니메이션을 만들기 위해서는 메인 쓰레드의 부담을 줄여줄 필요가 있는데, 메인 쓰레드에서 2. 스타일링, 3. 레이아웃, 4. 페인트 모든 작업을 16.67ms 안에 수행하여야 한다.
예를 들어 아이패드는 2048*1536의 해상도와 3,145,000 픽셀값을 위의 시간 안에 끝마쳐야 한다는 것이다.
이 repaint를 빠르게 수행하기 위해서 브라우저는 paint를 layer를 나누어 점진적으로 수행한다. 이때 compositing 단계가 실행된다.

좀더 자세하게, paint 단계에서는 layout 단계의 노드들을 layers로 묶는다. 어떠한 것들은 paint 시 GPU에서 수행되어야 하기 때문에, paint/repaint 시 퍼포먼스를 높이기 위한 필수적인 과정이다.

<video> <canvas> 에서 이 과정이 진행된다.
또한 css에서 3d transform, opacity, will-change 이외 몇몇 값들을 사용할 경우 일어남.

이러한 노드들은 layer에 담겨 (자식들을 포함하지만 자식들 역시 layer를 형성할 수 있으며 이경우는 제외된다)

Layer를 사용하여 퍼포먼스는 향상되지만 메모리 사용량이 증가하게 된다. 이것을 줄이는 것이 web performance를 잡는 전략이 된다.

5. Compositing

paint 단계에서 다른 layer에서 렌더되어 overlapping되는 요소가 있을때, compositing이 실행되어 요소를 그리고, 바른 순서로 그려질 수 있도록 수행한다.

페이지가 assets를 로드 하면서 계속해서 reflow가 일어나는데, 이 경우 repaint와 re-composite가 수행된다. 만약 우리가 image의 사이즈를 지정해 놓았다면 reflow는 일어나지 않을 것이다. 또한 한 layer만 repaint된다.(re-composite도 필요하다면 실행됨) 하지만 많은 경우 이미지는 서버에서 받아와 사이즈를 정적으로 지정하지 않기 떄문에 렌더링 프로세스는 다시 reflow layout -> paint의 과정을 수행한다.

6. Interactivity

메인 쓰레드가 5.의 과정을 마친다면. 위 렌더링 프로세스가 모두 끝났다고 할수 있고 6.은 크게 신경쓸 필요가 없지만, 예를들어서 html 코드에 defer 태그가 붙은 script가 있고 해당 script에서 onload 이벤트를 호출할 경우, 메인 쓰레드는 여전히 바쁜 상태이고 유저 인터랙션을 핸들링하지 못할 수 있다.

Time to Interactive (TTI) 란 DNS 조회, SSL 연결부터 페이지가 상호작용 가능해지는 순간까지의 시간을 계산하는 단위이다. First Contentful Paint 이후, 유저의 요청(상호작용) 에 50ms 이내로 페이지가 응답 가능한 상태를 얘기하는데, 이것은 메인 쓰레드가 위와 같은 사유로 계속 작업을 수행할 경우 50ms 이내의 응답이 불가능 할 것이기 때문이다.

다시 위의 예시를 들어서, first paint는 빠르게 끝났지만, 어떠한 defer script가 2mb가 넘고 인터넷 속도가 느리다고 생각해 보자. 그렇다면 자바스크립트가 로드되고 실행되기 전까지 페이지가 paint되지 않을 것이므로 이 경우 스크롤 및 상호작용이 심각하게 느려질 것이다. 이러한 방법을 최대한 막는 것이 중요하다.

위의 잘못된 예시에서는 DOM content 가 그려지기까지 1.5초 이상이 소요되었고, 메인 쓰레드역시 계속해서 바쁜 상태였기 때문에 유저의 상호작용이 불가능했다.

profile
행복하기 위해 코딩합니다. JS 합니다.

0개의 댓글