[ Network ] 웹 브라우저의 동작 과정 (with. DOM, CSSOM)

duck-ach·2023년 2월 27일
0

Network

목록 보기
7/8
post-thumbnail

🐰 개요

웹 개발자로써 웹 페이지가 어떻게 구현되는지에 대해 꼭 알아야 할 것 같아서 책도 읽어보고 검색하여 여러 글을 읽어보다가 정리가 필요할 것 같아서 정리해본다.

웹 브라우저에도 종류가 여러종류가 있는데 statcounter.com 사이트에서 2023-02년 기준 Chrome이 65.4%로 이용률이 어마어마 하다는 것을 알 수 있다.

웹 브라우저의 동작과정이라하여 들어왔을텐데 처음부터 Chrome 이용률 이야기가 대뜸나왔지만, 다 이유가 있다.
브라우저마다 Rendering Engine이 다 다르기 때문이다.

Rendering Engine은 요청받은 나용을 브라우저 화면에 표시해주는 역할을 하는데, 브라우저마다 Rendering Engine이 다르기 때문에 모든 브라우저가 동일한 소스를 화면에 동일하게 그려주지 않고 엔진마다 읽을 수 있는 코드의 버전도 달라서 Cross Browsing Issue가 발생하기도 한다.

위와 같은 이유로 이용자가 가장 많은 Chrome에서 front를 구축하고 테스트를 많이 해보았다.
나도 이번 포스팅에서는 Chrome기준으로 포스팅을 할 것이다.

Rendering Engine

IE : Trident
Edge : EdgeHTML, Blink
Chrome : Webkit, Blink(버전 28 이후)
Safari : Webkit
FireFox : Gecko

크롬의 경우 Webkit을 사용하다가 Webkit을 Fork하여 Blink엔진을 자체적으로 구현하여 사용한다.

🐰 웹 브라우저와 웹 서버의 통신 과정

웹 브라우저는 사용자가 검색 또는 웹 브라우저 이동, 로그인 등 우리가 웹브라우저로 할 수 있는 일들로 자원을 요청하면, 요청한 자원을 브라우저에 표시해주는 것이다.

예시로 우리가 naver 페이지로 이동하기위해 URL창에 www.naver.com 을 입력하면 이동한다. 이것이 바로 웹 브라우저의 가장 중요한 주요 기능이라고 할 수 있다.

Request : '요청' 이라고도하며, 웹 브라우저가 웹 서버에게 웹 페이지를 달라고 하는 것을 말한다.
Response : '응답' 이라고도하며, 웹 브라우저가 요청했던 웹 페이지를 웹 브라우저에게 제공하는 것을 말한다.

보통 웹 브라우저와 웹 서버는 다른 컴퓨터에 위치한다.
웹 브라우저와 웹 서버는 서로 다른 컴퓨터에 위치하고 있기 때문에 웹 브라우저가 웹 서버에 연결하려면 웹 서버가 실행중인 컴퓨터의 주소를 알아야하는데 이를 IP주소라고 한다.
(IP주소는 192.168.0.1 처럼 숫자로 이루어져 있어서 외우기 쉽지 않기때문에, www.naver.com 과 같이 사람이 기억하기 좋은 도메인 이름을 사용한다.)

1. www.naver.com 을 입력하면 입력한 URL 주소 중, 도메인 이름에 해당하는 naver.com을 DNS(Domain Name Server)에서 검색한다.

웹 브라우저와 웹 서버는 IP주소를 이용하여 연결하기 때문에 도메인 이름을 IP 주소로 변환할 필요가 있는데 이때 DNS(Domain Name Server)를 사용한다.

웹 브라우저는 DNS 서버에 검색하기 전에 캐싱된 IP주소를 먼저 반환한다.

웹 브라우저에서 URL을 입력하면 웹 브라우저는 해당 도메인이름에 해당하는 IP주소를 가장 가까운 DNS에 요청하고, DNS는 사용자가 입력한 URL 정보와 함께 IP주소를 응답으로 제공한다.

2. 가장 가까운 DNS 에서 해당 도메인 이름에 해당하는 IP 주소를 찾아 사용자가 입력한 URL 정보와 함께 전달을 한다.

DNS는 호스팅하고 있는 서버의 IP주소를 찾기 위해 DNS query를 전달한다.
DNS query는 현재 DNS에 원하는 IP주소가 존재하지 않으면 다른 DNS서버를 찾을 때까지 방문하는 것을 반복한다.

해당 도메인 이름에 맞는 IP주소로 변환하는 과정은 점.을 기준으로 계층적오르 구분하여 구성이 된다.

탐색순서는 뒤에서부터 해당 도메인 이름에 맞는 지역 DNS를 탐색하며 Root DNS 서버가 나올때까지 거꾸로 탐색한다.

이렇게 Local DNS 서버가 여러 DNS 서버에게 차례대로 물어봐서 답을 찾는 과정을 Recursive Query라고 부른다.

3. 웹 브라우저는 DNS로부터 전달받은 IP주소로 웹서버에게 해당 URL에 맞는 웹 페이즈를 응답받는다.

도메인이름을 DNS에서 변환한 후 IP주소를 응답받아 해당 웹 서버에게 연결한 뒤, URL에 해당하는 웹 페이지를 요청하고, 웹 페이즈를 응답받는다.

해당 HTTP 요청 메시지는 TCP/IP(TCP는 전송제어 프로토콜) 프로토콜을 이용하여 데이터를 어떻게 보낼지, 어떻게 맞출 지 정한다.

IP의 특징인 비신뢰성비연결성으로 인해 IP주소 만으로는 통신을 할 수 없다.
그러므로 신뢰성과 연결성을 책임지는 TCP를 활용하여 통신을 한다.

4. WAS와 웹 서버에서 웹 페이지 작업을 처리한다.

웹 서버 혼자서 모든 로직처리와 데이터 관리를 하게되면 서버에 과부하가 일어날 가능성이 높다. 그래서 서버의 조력자인 WAS가 처리를 도와주게 된다.

도와준다고 해서 같은일을 같이 처리하는 것은 아니고 역할이 나뉘는데,

Web Server : 정적인 파일(HTML, CSS, image 파일 등)을 처리한다.
WAS(Web Application Server) : 동적인 파일(Javascript 등)을 처리한다.

WAS는 사용자의 컴퓨터나 장치에 웹 애플리케이션을 수행해주는 미들웨어이다.
주로 동적인 자원을 처리하는데에 포커스가 맞춰져 있으며, DB에서 필요한 데이터 정보를 받아 그에 맞는 파일을 생성하기도 한다.

5. WAS에서의 작업 처리 결과들을 웹 서버로 전송하고, 웹 서버는 웹 브라우저에게 HTML 문서 결과를 전달한다.

전달 과정에서 Status code를 통하여 서버 요청에 따른 결과 및 상태를 전달한다.

1XX : 정보가 담긴 메세지
2XX : OK. 정상
3XX : Client를 다른 URL로 Redirect
4XX : Client 측에서 Error 발생(잘못된요청이나 주소 등등 너무 다양함)
5XX : Server 측에서 Error 발생(데이터 누락, mapping값오류 등등 너무 다양함)

🐰 브라우저 렌더링 과정

웹 브라우저와 웹 서버의 통신이 끝나고, 브라우저에 출력되는 단계를 Critical Rendering Path라고 하며, 크게 5단계로 구분된다.

Critical Rendering Path 5단계

    1. HTML Markup을 처리하고 DOM Tree를 Build한다.
    1. CSS Markup을 처리하고 CSSOM Tree를 Build한다.
    1. DOM 및 CSSOM을 결합하여 Render Tree를 Build한다.
    1. Render Tree에서 Layout을 실행하여 각 Node의 기하학적 형태를 계산한다.
    1. 개별 Node를 화면에 Paint 한다.

1. HTML Markup을 처리하고 DOM Tree를 Build한다.

HTTP 혹은 HTTPS 통신을 통해 byte 형태의 HTML 파일을 가져오게 된다.
이후 이 byte 형태의 데이터를 DOM으로 전환하는 작업을 수행한다.

HTML Markup

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="utf-8">
    <title>페이지 제목</title>
  </head>
  <body>
    <h1>안녕하세요</h1>
  </body>
</html>

Byte → Character
브라우저가 HTML의 원시 byte를 디스크나 네트워크에서 읽어와서 해당 파일에 대해 지정된 인코딩(ex. UTF-8)에 따라 개별 문자로 변환한다.

Character → Token

  1. 초기 상태는 "자료"이다.
  2. < 문자를 만나면 "태그열림" 상태로 변한다. 이후 a-z문자를 만나면 "태그이름" 상태로 변하고, > 태그가 나오면 문자 토큰을 발행하고 다시 "자료"상태로 변한다.
  3. < 문자에 도달하면 다시 "태그열림" 상태가 된다. / 문자는 종료태그 Token을 생성하고 "태그이름" 상태로 변경한다. > 태그가 나오면 다시 새로운 토큰이 발행되고 "자료" 상태가 된다.

Token → Node
토큰은 속성 및 규칙을 정의하는 '객체'로 변환된다.

Node → DOM

  • HTML Markup이 여러 태그 간의 관계를 정의하기 때문에 생성된 객체는 트리 데이터 구조 내에 연결된다.
  • 트리 데이터 구조에는 원래 Markup에 명시된 상위-하위 관계도 포함된다. (html태그는 head객체의 상위 등)

2. CSS Markup을 처리하고 CSSOM Tree를 Build한다.

Rendering Engine은 HTML문서를 한줄 한줄 위에서부터 순차적으로 parsing 하며 DOM을 생성한다. 그러다 CSS를 로드해주는 link 태그 혹은 style 태그를 만나면 DOM 생성을 중지한 후 CSS parsing의 결과물인 CSSOM을 생성하는 과정을 진행한다.

<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="style.css"> <!-- DOM 생성을 중지, CSSOM 생성을 시작 -->
  ...

CSS parsing 과정도 byte > character > token > node > CSSOM 생성 순으로 HTML의 parsing 과정과 동일하다.

CSSOM이 트리구조를 가지는 이유는 페이지에 있는 객체의 최종 스타일을 계산할 때 브라우저가 해당 노드에 적용 가능한 가장 일반적인 규칙으로 시작한 후 더욱 구체적인 규칙을 적용하는 방식(하향식)으로 계산된 스타일을 재귀적으로 세분화한다.

번외: Javascript를 파싱하는 과정

  1. Rendering Engine은 HTML문서를 한 줄씩 순차적으로 parsing 하다가 Javascript 파일을 로드하는 <script> 태그를 만나면 DOM 생성을 일시 중지한다.
  2. <script> 태그의 src에 정의된 javascript 파일을 서버에 요청하여 응답받으면 javascript 코드를 parsing 하기위해 Javascript Engine에게 제어권을 넘긴다.
  3. javascript parsing 이 끝나면 다시 Rendering Engine으로 제어권이 넘어오고, DOM 생성을 이어나간다.

만약 생성되지 않은 DOM을 javascript로 조작하게 된다면 에러가 발생하거나 제대로 동작하지 않을 수 있다.
따라서, 우리가 주로 코드를 짤 때 javascript는 HTML요소의 아랫쪽에 두거나, ready()와 같은 함수를 사용하여 DOM 생성이 완료된 시점에 javascript가 실행되도록 한다.

3. DOM 및 CSSOM을 결합하여 Render Tree를 Build한다.

DOM과 CSSOM은 각기 다른 측면을 캡처하는 서로 독립적인 객체이다. 하나는 문서의 콘텐츠를 설명하고, 다른 하나는 문서에 적용되어야 하는 스타일 규칙을 설명한다. 브라우저는 이 둘을 결합하여 Rendering Tree를 Build 한다.

가장 중요한 것은 Rendering Tree에는 페이지를 Rendering 하는 데 필요한 Node만이 포함된다는 것이다.

  • 규칙 1 : 순회하는 모든 노드들이 Rendering Tree에 포함되는 것이 아니다.
    • Rendering 하는데 필요하지 않은 meta, script와 같은 태그들은 그냥 지나간다.
    • display: none;과 같은 CSS를 통해 숨겨진 노드는 Rendering Tree에서 누락된다.
  • 규칙 2 : 표시된 각 노드에 대해 적절하게 일치하는 SCCOM 규칙을 찾아 적용한다.
  • 규칙 3 : 표시된 노드를 콘텐츠 및 계산된 스타일과 함께 내보낸다.

최종 출력은 화면에 표시되는 모든 노드의 콘텐츠 및 스타일 정보를 모두 포함하는 Rendering Tree이다.

Rendering Tree가 생성되고 난 후에는 'Layout' 단계로 진행한다.

4. Render Tree에서 Layout을 실행하여 각 Node의 기하학적 형태를 계산한다.

이 단계에서 Rendering Tree의 각 Node들의 위치와 크기가 계산된다.
페이지에서 각 객체의 정확한 위치와 크기를 계산하기 위해 브라우저는 렌더링 트리를 루트에서부터 순회한다,

viewport 내에서 각 요소의 정확한 위치와 크기를 정확하게 캡처하는 "상자모델"이 출력되고, 모든 상대적인 측정값은 화면에서 절대적인 픽셀로 변환된다.

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Hi</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

5. 개별 Node를 화면에 Paint 한다.

지금까지 계산한 스타일 및 기하학적 형태를 바탕으로, Rendering Tree의 각 노드를 화면의 실제 픽셀로 변환한다.
이 단계를 '페인팅' 또는 '레스터화' 라고 한다.

Rendering 5단계가 완료되고 난 후

결국 Critical Rendering Path는 크게 5개의 흐름으로 구성된다. 하지만, 이 5개의 과정을 순서대로 거치고나서 종료되는 것이 아니다. 브라우저의 렌더링 과정은 반복해서 실행될 수 있다. 예를 들어, 다음과 같은 경우 반복해서 레이아웃 계산과 페인팅이 재차 실행된다.

  • 자바스크립트에 의한 노드 추가 또는 삭제
  • 브라우저 창의 리사이징에 의한 뷰포트 크기 변경
  • HTML 요소의 레이아웃에 변경을 발생시키는 width/height, margin, padding, border, display, position, top/right/bottom/left 등의 스타일 변경

레이아웃 계산과 페인팅을 다시 실행하는 리렌더링은 비용이 많이 드는, 즉 성능에 악영향을 주는 작업이다. 따라서 가급적 리렌더링이 빈번하게 발생하지 않도록 주의할 필요가 있다.

🐰 참고

profile
자몽 허니 블랙티와 아메리카노 사이 그 어딘가

0개의 댓글