브라우저 동작 원리 - 주소창에 URL 입력 시 일어나는 과정 (프론트 편1)

Gyu·2022년 7월 13일
0
post-thumbnail

브라우저의 주요 기능

브라우저의 주요 기능은 사용자가 입력한 자원을 서버에 요청하고 사용자에게 보여주는 것입니다. 여기서 자원은 보톤 HTML 문서지만 PDF나 이미지 또는 다른 형태일 수도 있습니다. 자원의 주소는 URI(Uniform Resource Identifier)에 의해 정해집니다.

브라우저는 HTML과 CSS 명세에 따라 HTML 파일을 해석해서 표시하는데 이 명세는 웹 표준화 기구인 W3C(World Wide Web Consortium)에서 정합니다. 과거에는 브라우저들이 일부만 이 명세에 따라 구현하고 나머지는 독자적인 방법으로 확장해 웹 제작자가 심각한 호환성 문제를 겪었지만 최근에는 대부분의 브라우저가 표준 명세를 따릅니다.

일반적으로 브라우저의 사용자 인터페이스는 아래와 같습니다.

  • URI를 입력할 수 있는 주소 표시 줄
  • 이전 버튼과 다음 버튼
  • 북마크
  • 새로 고침 버튼과 현재 문서의 로드를 중단할 수 있는 정지 버튼
  • 홈 버튼

브라우저의 기본 구조

  • 사용자 인터페이스 : 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 부분.
  • 브라우저 엔진 : 모든 웹 브라우저의 핵심 구성 요소로 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.
  • 렌더링 엔진 : 사용자가 요청한 웹 페이지를 화면에 렌더링하는 역할. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함.
  • 통신 : HTTP 요청과 같은 네트워크 호출에 사용됨되며 플랫폼 독립적인 인터페이스. 각 플랫폼 하부에서 실행됨. 인터넷 통신과 관련된 보안 문제를 처리
  • UI 백엔드 : 콤보 박스와 창 같은 기본적인 장치를 그림. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서, OS 사용자 인터페이스 체계를 사용.
  • 자바스크립트 해석기 : 자바스크립트 코드를 해석하고 실행.
  • 자료 저장소 : 자료를 저장하는 계층으로 쿠키 등을 저장하는 웹 데이터베이스

렌더링 엔진

렌더링 엔진의 역할은 서버로 부터 받은 응답 내용 즉 HTML을 브라우저 화면에 표시하는 일입니다. 렌더링 엔진은 HTML 및 XML 문서와 이미지를 표시할 수 있습니다. 렌더링 엔진은 HTTP 통신을 통해 요청한 문서의 내용을 얻는 것으로 시작하는데 문서의 내용은 보통 8KB 단위로 전송됩니다.

모든 브라우저는 고유한 렌더링 엔진이 있습니다. 브라우저마다 렌더링 엔진이 다르기 때문에 사용자 화면에서 웹 페이지를 해석하는 방식이 다 다릅니다.(프론트 엔드 개발자가 고통 받는 이유…) 여기서 브라우저간 호환성 문제가 발생합니다. 때문에 우리 웹 개발자에게(특히 프론트 엔드) 여러 브라우저에서 기능 및 디자인 측면에서 웹 애플리케이션의 일관성을 확인하는 크로스 브라우저 테스트는 필수라고 할 수 있습니다. 대표 브라우저의 렌더링 엔진은 아래와 같습니다.

  • 파이어폭스 : 모질라의 개코(Gecko) 엔진
  • IE : 트라이던트(Trident) 엔진
  • 사파리, 크롬(ios): 웹킷(Webkit) 엔진(최초 리눅스 플랫폼에서 동작하기 위해 제작된 오픈소스 엔진)
  • 크롬(28버전 이후), 오페라 : 블링크(blink) 엔진

렌더링 엔진이 어떻게 동작하는지만 알면 브라우저의 동작 원리를 모두 알 수 있습니다. 렌더링 엔진 기본 동작 과정은 아래와 같습니다.

  1. DOM 트리 구축을 위한 HTML 파싱 (Parsing)
  2. 렌더 트리 구축 (Attachment)
  3. 렌더 트리 배치 (Layout or Reflow)
  4. 렌더 트리 그리기 (Paint)

이제 각 단계에 대해 자세히 알아보겠습니다.

Parsing 및 DOM 트리 CSSOM 트리 구축

Parsing이란?

파싱은 렌더링 엔진에서 매우 중요한 과정이기 때문에 조금 더 자세히 살펴보겠습니다. 그렇다고 너무 자세히 파보면 끝도 없기 때문에 브라우저 렌더링 과정을 이해할 정도까지만 살펴보겠습니다.

언어학에서 파싱은 구문 분석이라고합니다. 문장을 이루고 있는 구성 성분으로 문장을 분해하고, 구성 성분들 사이의 위계 관계를 분석하여 문장의 구조를 결정하는 것을 말합니다. 컴퓨터 과학에서 파싱은 일련의 문자열을 의미있는 토큰(token, 어휘 분석 단위)으로 분해하고 이들로 이루어진 파스 트리(parse tree)를 만드는 과정을 말합니다. 쉽게말해 파싱은 개발자가 작성한 코드를 컴퓨터가 이해할 수 있도록, 코드를 토큰이라는 단위로 쪼개고 구조화하는 과정이라고 생각하시면 됩니다.

브라우저는 서버로부터 HTML을 응답받으면 먼저 문서 파싱을 통해 브라우저가 HTML 코드를 이해하고 사용할 수 있는 구조로 변환하는 작업을 진행합니다. 파싱 결과는 보통 문서 구조를 나타내는 노드 트리인데 파스 트리(parse tree) 또는 문법 트리(syntax tree)라고 부릅니다.

파서 및 파싱 문법

파싱을 행하는 프로그램을 파서라고하는데, 파서는 보통 두 가지 일을 합니다.

  1. 자료를 유효한 토큰으로 분해하는 어휘 분석기(토큰 변환기) 역할. 공백과 줄바꿈 같은 의미없는 문자를 제거
  2. 언어 구문 규칙에 따라 문서 구조를 분석하여 파싱 트리를 생성하는 파서 역할

파싱은 어휘 분석기를 통해 토큰화 된 코드가 생성되고, 이를 파서가 해석하는 순서로 이루어집니다. 토큰화라는 것은 의미가 있는 최소 단위로 코드를 쪼개는 것을 의미합니다. 예를 들어 <div></div> 라는 코드를 토큰화하면 ['<', 'div', '>', '</', 'div', '>'] 처럼 나타낼 수 있습니다.

파싱 구분 및 문법

우리가 외국어를 배울 때 그 언어의 단어와 문법을 배우고, 그에 따라 언어를 말하고 해석할 수 있습니다. 파싱도 이와 같습니다. 파싱은 개발자가 작성한 코드가 해당 프로그램 언어의 문법(grammer)을 모두 따르는지 확인하는 과정입니다. 대부분의 프로그래밍 언어는 문맥 자유 문법이라는 것에 속하며 파서는 이 문맥 자유 문법에 따라 문서를 파싱합니다.

문법은 어휘(vocabulary)와 문법 규칙(syntax rule)으로 구성이 되어 있습니다. 어휘는 알파벳이나 한글처럼 사용할 수 있는 단어와 그 조합들을 의미하며, 문법 규칙은 어휘 사이에 적용될 수 있는 규칙들을 의미합니다. 가령 숫자의 계산에서 곱하기는 앞뒤로 정수가 와야 하고, 더하기보다 더 높은 우선순위를 갖고 있는 것 같은 규칙들처럼요.

문법은 어휘와 문법 규칙으로 구성되어 있기 때문에 파싱도 이 두 가지로 구분할 수 있습니다.

  • 어휘 분석 : 자료를 토큰으로 분해하는 과정. 토큰은 유효하게 구성된 단위의 집합체로 용어집이라고 할 수 있다. 인간이 언어로치면 사전에 등장하는 모든 단어가 이에 해당한다.
  • 구문 분석 : 언어의 구문 규칙(문법 규칙)을 적용하는 과정.

파싱 과정

파싱은 아래의 과정이 반복됩니다.

  1. 파서는 보통 어휘 분석기로부터 새 토큰을 받아 구문 규칙과 일치하는지 확인한다.
  2. 규칙에 맞으면 토큰에 해당하는 노드가 파싱 트리에 추가되고 파서는 또 다른 토큰을 요청한다.
  3. 규칙에 맞지 않으면 파서는 토큰을 내부적으로 저장하고 토큰과 일치하는 규칙이 발견될 때까지 요청한다.
  4. 맞는 규칙이 없는 경우 예외로 처리하는데 이것은 문서가 유효하지 않고 구문 오류를 포함하고 있다는 의미다.

변환

파싱 결과는 파스 트리라고 설명했습니다. 그러나 이 파스 트리는 최종 결과물이 아닙니다. 파싱은 보통 문서를 다른 문서로 변환합니다. 컴파일로 예를 들면 소스 코드를 기계 코드로 변환시키는 컴파일러는 파스 트리 생성 후 이를 기계 코드 문서로 변환합니다.

파서의 종류

파서는 기본적으로 하향식 파서와 상향식 파서가 있습니다.

  • 하향식 파서
    • 구문의 상위 구조(가장 높은 수준의 규칙)로부터 일치하는 부분을 찾기 시작
  • 상향식 파서
    • 낮은 수준에서 높은 수준으로 규칙을 찾는다.
    • 부분적으로 일치하는 표현식을 파서 스택(parser stack)에 쌓는다.
    • 입력 값의 오른쪽으로 이동하기 때문에 이동-감소 파서라고 부른다.

파서 생성기

파서는 파서 생성기라는 도구를 통해 만들어집니다. 이 파서 생성기에 언어의 어휘나 구문 규칙 같은 문법을 부여하면, 파서 생성기에 해당 언어에 맞는 파서를 생성해줍니다. 파서를 생성하는 것은 파싱에 대한 깊은 이해를 필요로하고, 개발자가 수동으로 파서를 최적화하여 생성하는 것은 쉬운일이 아니기 때문에 파서 생성기는 매우 유용합니다.

웹킷은 어휘 생성을 위한 플렉스(Flex)와 파서 생성을 위한 바이슨(Bison), 이 두 가지를 파서 생성기로 사용합니다.

HTML 파싱

드디어 파싱의 기본에 대한 설명이 끝났습니다. 그렇다면 이제 브라우저의 렌더링 엔진이 어떻게 HTML을 파싱하는지 알아보겠습니다.

브라우저는 HTML, CSS, Javascript 이 세 엔어를 해석할 수 있습니다. 그 중 Javascript는 렌더링 엔진이 아니라 Javascript 해석기라는 별도의 레이어에서 해석됩니다.(브라우저 기본구조 참고) 렌더링 엔진에서는 HTML과 CSS만 파싱 되므로, 이 두 부분에 대해서만 살펴보겠습니다.

HTML 파서

HTML 파서는 HTML 마크업을 파싱 트리로 변환합니다. 그런데 이 HTML 파서는 우리가 앞서 살펴본 전통적인 파서와 다릅니다. 모든 전통적인 파서는 HTML에 적용 할 수 없습니다. 엥? 그러면 우리는 파서에 대해 괜히 살펴 본걸까요? 아니요 그렇지 않습니다. 우리가 전통적인 파서는 HTML에는 적용할 수 없지만 CSS와 자바스크립트에는 적용할 수 있습니다. 또한 전통적인 파서는 같은 마크업 언어인 XML과 HTML을 XML형태로 재구성한 XHTML에도 적용할 수 있습니다. 그렇다면 대체 왜 HTML에만 적용할 수 없는 걸까요? 이걸 이해하기 위해서는 먼저 DOM에 대해서 알아야 합니다.

DOM

앞에서 브라우저는 HTML 문서를 토큰화해 파스 트리를 생성한다고 했습니다. 파스트리는 브라우저가 읽어야할 HTML 코드를 트리모양으로 구조화해 나타낸 것입니다. 그러나 이 파스 트리로는 화면을 그릴 수 없습니다. 왜냐하면 브라우저는 파스 트리를 이용해 DOM(Document Object Model) 트리를 새로 만들기 때문입니다. 그렇다면 파스 트리와 DOM 트리의 차이는 무엇일까요?

파스트리는 토큰화한 문자열을 단순하게 구조화한 트리입니다. 그러나 DOM은 HTML 문서의 객체 표현으로 마크업과 1:1의 관계를 갖고있습니다. DOM 트리는 우리가 실제로 상호작용할 수 있는 HTML 엘리먼트로 이루어진 트리이기 때문에 우리가 실제로 자바스크립트로 상호작용할 수 있는 부분입니다.

왜 일반적인 파서를 HTML에 적용할 수 없을까?

첫 번째 이유 : HTML은 문맥 자유 문법이 아니다.

앞에서 파서는 문맥 자유 문법에 따라 파싱을 한다고 말씀드렸습니다. 그런데 HTML이 문맥 자유 문법이 아닙니다. 이 말도 조금 어렵죠? 이것도 조금 더 쉽게 표현해 보겠습니다. HTML은 암묵적으로 태그에 대한 생략이 가능합니다. 개발자가 실수로 시작 혹은 종료 태그 등을 생략해도 정상 작동합니다. 전반적으로 엄격한 XML에 비해 HTML은 너그럽고 유연한 문법을 갖고 있습니다. 이게 바로 HTML이 문맥 자유 문맥이 아니라는 뜻입니다. HTML은 이러한 너그러운 문법때문에 개발하기 편하고 인기가 많지만, 공식적인 문법으로 HTML을 작성하기 어렵다라는 문제가 있습니다. 정리하자면 HTML은 파싱하기 어렵고 전통적인 구문 분석이 불가능하기 때문에 문맥 자유 문법이 아니라는 것이며 이 때문에 XML 파서로도 파싱하기 쉽지 않습니다.

HTML을 작성하면서 유효하지 않은 구문(invalid syntax) 관련 에러를 본적이 없을 겁니다. 이는 브라우저가 요류를 교정하기 때문입니다. 아래의 오류가 가득한 HTML이 있다고 가정해보겠습니다.

<body>
<p class=highlight>Hello
<div><span>World

하지만 이 코드를 실제로 브라우저에서 실행하면 아래와 같은 결과가 나옵니다.

<body>
  <p class="highlight">Hello</p>
  <div><span>World</span></div>
</body>

이러한 규칙들은 HTML Document Type Definition (DTD)에 의해 정의어 있습니다. HTML 파서는 명세된 규칙들을 따르는 예외 처리를 따로 해주어야 하는데, 이는 일반적인 파서의 규칙만으로는 적용하기 어렵습니다.

두 번째 이유 : HTML 파싱은 중단 될 수 있다.

브라우저는 HTML 파싱 도중 <script>, <link> 같은 외부 태그를 만나게 되면 HTML 파싱을 즉시 중단합니다. 그리고 해당 태그의 해석을 실행합니다. 만약 해당 태그가 외부 파일을 참조하고 있다면 다운로드를 한 후 해석을 시작합니다. 이는 네트워크를 통해 먼저 받아온 코드부터 해석을 실행할 수 있는 HTML과는 달리 외부 컨텐츠들은 증분적(Incrementally)으로 해석을 할 수 없기 때문입니다. 또 다른 이유는 <script>에 DOM을 직접 수정할 수 있는 내용이 있을 수도 있기 때문입니다. 가령 document.write() 같은 API를 사용하면 HTML을 파싱하고 있는 도중에도 DOM 엘리먼트를 동적으로 삽입할 수 있습니다. 이로 인해 외부 컨텐츠를 해석하고 실행하기까지 HTML의 파싱은 중단됩니다.

이 문제를 해결하기 위해 웹킷과 파이어폭스는 예측 파싱 기법을 이용해 별도의 쓰레드에서 외부 스크립트, 링크, 스타일 등을 불러오기도 합니다.

세 번째 이유 : HTML 파싱을 재시작 될 수 있다.

앞서 말씀드린 것처럼 HTML 파싱은 어떠한 외부 요인으로 인해 방해받을 수 있습니다. 파싱 중간에 외부 요인으로 인해 DOM이 추가, 변경, 삭제가 될 수 있습니다. 이 경우 HTML은 처음부터 다시 파싱을 거칩니다. 즉, 바이트를 문자로 변환하고, 토큰을 식별한 후 노드로 변환하고 DOM 트리를 빌드합니다. 이 때문에 처리해야 할 HTML이 많을 때에는 파싱 시간이 오래 걸릴 수 있습니다.

CSSOM 트리 구축

CSS는 HTML과 달리 문맥 자유 문법이고 공식적인 명세가 있기 때문에 일반적인 파서로 파싱이 가능합니다. 즉 HTML에 비해 파싱과정이 복잡하지 않습니다.

일반적으로 CSS을 링크하는 코드가 HTML 코드 내에 삽입되어 있기 때문에, HTML을 파싱하는 도중에 CSS 파싱이 시작됩니다. 네트워크를 통해 먼저 받아온 코드부터 해석을 실행할 수 있는 HTML 파서와는 달리, CSS 파서는 전체 파일을 모두 다운로드할 때까지 파싱을 시작할 수 없습니다. 전체 CSS 파일을 다운로드 한 후 CSS 파싱 과정이 끝나게 되면, 코드에서 명세한 내용과 순서를 바탕으로 DOM과 같은 트리를 구성하는데 이를 CSSOM(CSS Object Model) 트리라 부릅니다. 이 트리에는 스타일, 규칙, 선택자 등의 정보가 노드에 들어가게 됩니다.

파싱 정리

앞서 한 설명을 토대로 브라우저 렌더링 엔진이 기본 동작 과정 중 파싱 단계 부분을 간단히 정리해보겠습니다.

  1. 렌더링 엔진은 HTML을 토큰화하여 파스 트리를 생성합니다.
  2. 그리고 파스 트리를 이용해 DOM 트리를 생성합니다.
  3. 이때 스타일 태그를 만나면 HTML 파싱을 중단하고 CSS를 파싱하여 CSSOM 트리를 생성합니다.
  4. CSS 파싱을 마치면 이전에 중지한 HTML 파싱이 중단된 지점부터 다시 파싱을 합니다.
  5. 스크립트 태그를 만나면 다시 해석을 중지하고 자바스크립트 엔진에게 제어 권한을 넘깁니다.
  6. 자바스크립트 파싱이 끝나면 중단되었던 HTML 파싱을 이어서 진행한 후 작업을 완료합니다.

파싱 설명이 너무 길어서 프론트 편은 여러 편으로 나눠 설명하겠습니다.

출처
https://d2.naver.com/helloworld/59361
https://bythem.net/2021/04/23/프론트엔드-개발자라면-알고-있어야-할-브라우저의-2
https://na27.tistory.com/230

profile
애기 프론트 엔드 개발자

0개의 댓글