ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [2020.08.04] 브라우저 동작원리
    개발 블로깅/기타 개념 2020. 8. 4. 18:20

     

    현재 정보화 사회에 이제는 없어서는 안 될 프로그램.

    프론트엔드 개발자라면 무조건 다룰 수밖에 없는 도구이자, 끝없는 정보 바다에 접속하기 위한 필수 도구.

    웹 브라우저.

     

    우리가 평소에 접하는 이 화면은 도대체 어떻게 나타나게 되는 것일까?

    우리는 평소처럼 당연하게 인터넷 주소창에 주소를 입력하고 거기에 해당되는 홈페이지 화면을 접하며 사이트를 돌아다니기만 했지, 이렇게 화면이 보여지는 과정이 어떻게 동작하는지는 생각해보지 못했을 것이다. (사실 그렇게까지 생각해 볼 필요도 없겠지만...)

    프론트엔드 개발자 역시, HTML과 CSS 문서를 작성하고 Javascript 코드를 짜면서 기대하던 동작을 확인하며 웹 개발을 했을 것이고, 실제로 이 HTML문서가 어떻게 자리를 잡아서 웹 페이지에 각자 자리에 DOM 요소들이 세팅이 되고 CSS 스타일이 적용돼서 그려지는지 생각해볼 기회가 없었을 것이다. 나 역시 그랬으니까.

    그러다 우연히 Naver D2 블로그 중 '브라우저는 어떻게 동작하는가?' 라는 글을 읽으면서, 웹 개발 관련이 아닌 브라우저 자체에 흥미를 느낄 수 있는 기회가 생겼고 프론트엔드 개발자로서 매우 유익한 지식이 아닐 수 없을 것이란 생각이 들었다.

    그래서 이번 기회로 Naver D2의 글 기반과 내가 직접 이것저것 찾아보며 알게 된 내용을 이번 블로깅으로 정리해보려고 한다.

     

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

     

    우선 이 글을 쓰기 전에, 곰곰이 한 번 생각해보았다.

    "굳이 브라우저 내부가 동작하는 원리에 대해 알아야 할 필요가 있을까? 이것을 알면 무슨 도움이 되지?"

    사실 브라우저 자체에 대한 관심과 컨트리뷰트 생각이 없다면, 브라우저 내부 동작 원리에 대해 전혀 모른다고 해도 문제 될 것은 없을 것이다.

    그러나 이것은 마치, 우리가 현대 시대 안에서 살아가는데 지나간 '역사'를 공부하는 것과 같다는 생각이 들었다. 과거로부터 친숙하게 다뤄오던 브라우저의 내부를 어느 정도 알고 있다면 우리가 프론트엔드로써 조금 더 폭넓은 웹 개발 사상을 가지게 되지 않을까 라는 생각도 들었고, 어떤 것이든 의미없는 공부는 없다..!

    그럼 바로 시작해 봅시다.


     

    브라우저의 주요 기능

    브라우저의 주요 기능은, '선택한 자원을 서버에게 요청하고, 전송받은 자원을 브라우저 화면에 표시'하는 것이다.

    자원의 종류는 아래와 같은 요소들이 될 수 있다.

    자원의 종류

    • HTML
    • CSS
    • JavaScript
    • PDF
    • Image
    • 기타 등등

     

    그러면 이러한 자원은 브라우저가 어떻게 서버에게 요청을 할 수 있는 것일까? 여기서 우리는 URI(Uniform Resource Identifier) 라는 개념을 알아야 한다.

    URI는 각 자원의 서버 주소를 말한다. 이 주소는 서버 어딘가에 명시되어 있고, 명시된 주소를 통해 서버에게 해당 주소의 자원을 요청하여 받아오게 된다.

    URI를 통한 자원 요청 방법은 브라우저에 있는 주소입력 창이 될 수도 있고, 데이터 요청을 위한 API 주소가 될 수도 있다. 클라우드 서비스로 따지면 S3 버킷 안에 들어있는 이미지의 엔드포인트가 될 수도 있겠다.

    💡한 가지 예를 들어보자.
    우리가 브라우저 주소입력 창에 www.naver.com을 입력했다.
    그러면 해당 주소에 맞는 페이지를 브라우저 화면에 보여주기 위해 HTML, CSS, Javascript 파일을 포함하여 해당 페이지에 관련된 모든 리소스 파일(이미지, 동영상 파일 등)들을 네이버 서버에게 요청을 하고 받아올 것이다.

    그러면 여기서 'www.naver.com'은 URI가 되고, 페이지를 보여주기 위해 요청해서 받아온 모든 파일들이 자원이 되는 것이다.

     

     

    브라우저의 호환성

    브라우저는 HTML, CSS과 같은 프로그래밍 언어뿐 아니라, 이미지 파일이나 동영상 파일도 브라우저에서 바로 표시를 할 수 있다.
    이는 이러한 파일들에 대한 호환이 되기 때문에 가능한 것인데, 어떻게 이렇게 하나하나 확장자나 형태가 다른 데이터들과 상호작용이 가능한 것일까?

    그것은 웹 표준 덕분이다.

    웹 표준이란, '웹에서 표준적으로 사용되는 기술이나 규칙'을 말한다. 웹 상에서 사용되는 모든 파일은 이러한 '웹 표준'을 가지게 되어 브라우저 내에서 어떠한 데이터 및 파일과도 상호작용이 가능하다.

    이러한 웹 표준은 W3C(World Wide Web Consortium)라는 웹 표준화 기구에 의해 명세가 되었고, 이들은 1994년 10월에 설립되어 아직까지도 웹 상호운용성('어떠한 소프트웨어나 하드웨어에서도 웹에 접근할 수 있어야 한다')이라는 그들의 목표를 위해 노력 중이다. 

     

    브라우저의 인터페이스

    브라우저의 사용자 인터페이스는 브라우저마다 서로 닮아 있는데, 다음과 같은 요소들이 일반적이다.

    • 주소 입력창
    • 이전/다음 페이지 이동 버튼
    • 페이지 새로고침 버튼
    • 페이지 요청 중 중단 버튼
    • 북마크

     

    재미있는 것은, 이러한 인터페이스 요소들은 표준 명세된 UI가 아니라는 것이다.

    HTML5 명세에서는 주소표시줄, 상태 표시줄 등 일반적인 요소를 제외하고 브라우저의 필수 UI를 정의하지 않았지만 그럼에도 불구하고 수년간 서로의 장점을 모방하면서 현재의 브라우저 UI가 생기게 된 것이다.

     

     

    브라우저의 기본 구조

    브라우저 내 기본적인 구조는 아래와 같다.

    출처: Naver D2 '브라우저는 어떻게 동작하는가?'

    # 사용자 인터페이스

    앞서 설명했던 부분과 같이, 브라우저에서 요청한 자원 및 페이지를 보여주는 화면을 제외한 모든 부분을 말한다. 우리는 이 사용자 인터페이스를 통해서 브라우저를 동작시킨다.

    ex) 이전/다음 버튼, 북마크, 주소 입력창 등

     

    # 브라우저 엔진

    Naver D2 '브라우저는 어떻게 동작하는가?' 글에서는, '사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어'라고만 나와있어서 이해가 잘 되지 않았다.

    더 자세히 찾아보니, 브라우저 엔진은 브라우저 자체에 동작하는 소프트웨어 구성에 대한 엔진을 말하는 것이었고, 브라우저 UI를 통해서 렌더링 요청을 하고 동작을 시킬 수 있기 때문에 D2에서는 '사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어한다' 라고 쓴 것 같다.

     

    #렌더링 엔진

    이번 장에서 메인 핵심이 되는 부분이다. 서버에 요청해서 가져온 자원, 페이지, 콘텐츠를 화면에 표시하는 작업을 한다. 
    이 부분은 나중에 밑에서 조금 더 자세히 알아보도록 하자.

     

    # 통신

    HTTP 요청과 같은 네트워크 호출을 하는 브라우저 내부 계층이다.

     

    # 자바스크립트 해석기

    이름 그대로 자바스크립트 코드를 해석하고 실행한다.

     

    # UI 백엔드

    기본적인 UI 장치를 말한다.
    예를 들어, <Button>이나 <Input> 태그를 쓸 때, 이 태그에 관한 스타일을 따로 적용하지 않아도 브라우저는 이에 맞는 UI화면을 그리게 된다. 이러한 요소들은 해당 서버나 플랫폼에 명시하지 않은 일반적인 인터페이스이며, OS 사용자 인터페이스 체계를 사용한다.

     

    # 자료 저장소

    웹 브라우저 내에서 사용되는 자료 저장 계층이다. 우리가 흔히 알고 있는 쿠키로컬 스토리지 등이 이에 해당된다.
    HTML5 명세에는 브라우저가 지원하는 '클라이언트 스토리지(웹 브라우저 자체의 데이터 베이스)'가 사용되도록 정의가 되어있으며, 이를 사용해야 하는 이유를 아래처럼 작성이 되어있다.

    출처: https://www.html5rocks.com/en/features/storage

     

    1. 사용자가 오프라인 상태여도 웹이 작동하도록 한 뒤, 네트워크가 다시 연결되면 데이터를 다시 동기화 할 수 있다.
    2. 성능 향상을 위해, 동일한 자원에 대한 중복 요청을 줄일 수 있다.
    3. 서버 인프라가 필요하지 않은 보다 쉬운 모델로 구성되어 있어 사용 효율성이 좋다. 대신 데이터가 취약하고 여러 클라이언트가 액세스 할 수 없으므로, 중요하지 않은 데이터 위주로 사용을 권장한다.

     

     

    렌더링 엔진

    렌더링 엔진은 요청받은 자원을 브라우저 화면에 표시하는 작업을 한다. 위에서 설명한 것처럼 자원의 종류는 페이지 렌더에 필요한 HTML, CSS, JavaScript 뿐 아니라 PDF, JPG 등 서버에 요청하여 가져올 수 있는 모든 것이 될 수 있다.

    그러나 여기서는 웹 페이지를 직접 그려내는 HTML, CSS에 대해 초점을 맞추어 설명하도록 한다.

     

    렌더링 엔진의 종류는 여러 가지가 있지만, 그중 대표적인 엔진 두 가지를 소개한다면 웹킷(Webkit)게코(Gecko)가 있다. 

    웹킷은 크롬과 사파리, 게코는 파이어폭스에서 사용되는 엔진이다.

     

    렌더링 엔진 동작 과정

    렌더링 엔진은 통신으로부터 요청한 문서의 내용을 얻는 것으로 시작하는데, 문서의 내용은 보통 8KB 단위로 전송된다.

    웹킷 렌더링 동작 과정

     

    게코 렌더링 동작 과정

     

    웹킷과 게코를 비교해보면, 기본적인 용어가 약간 다르지만 기본적인 동작은 큰 차이 없는 것을 알 수 있다.

    그나마 차이를 보자면, 게코에는 동작 과정 중 '콘텐츠 싱크'라는 과정이 있다.

    웹킷은 HTML과 Style Sheet를 각자 따로 요청 및 파싱 후 합쳐지는 과정이 있지만, 게코는 HTML을 최초로 호출 및 파싱 후, 콘텐츠 싱크 과정을 통해 Style Sheets를 분리해서 작업을 하는 과정이 된다.
    하지만 이는 DOM 요소를 생성하는 공정으로 웹킷과 비교하여 의미 있는 차이점이라고 보지는 않는다.

    웹킷에서 '어태치먼트를 한다'는 것은 DOM 트리에 CSS 규칙을 적용하여 새로운 렌더 트리를 생성하는 작업을 말하는데, 게코에서는 이를 '형상구축을 한다'라고 부른다.

    그럼 지금부터 각 렌더링 엔진의 과정에 대해 조금 더 자세히 살펴보도록 하자.

     

    HTML 파싱

    HTML 파싱 과정에 대해 설명하기 전에, 기본적인 컴파일 동작 과정을 먼저 설명해보려고 한다.

     

    컴파일을 기본 동작

    이 글을 읽는 사람 중 개발 관련된 직업을 가진 사람이라면, 우리가 짜는 소스코드의 프로그래밍 언어인 고급언어(C, Java, Javascript 등)가 컴퓨터에서 동작하기 위해 2진수로 이루어진 기계 언어로 컴파일이 된다는 것을 알고 있을 것이다.

    그러나 이 컴파일 과정을 조금 더 자세히 들여다보면, 소스코드가 컴파일 과정을 가지기 전에 소스코드 내용들을 파싱하여 컴파일이 가능한 단위로 만드는 과정이 있다.

    예를 들면 아래의 그림과 같을 수 있다. 

     

    '파싱'에 대해 조금 더 자세히 알아보자. 
    일반적인 파싱 과정은 '어휘 분석'과, '구문 분석'으로 나뉜다.

    # 어휘 분석
    의미 없는 공백과 줄 바꿈을 제거하고 토큰(의미있는 문자) 단위로 분해하는 과정이다.

     

    # 구분 분석
    언어의 구문 규칙을 적용하는 과정이다. 어휘 분석기로부터 새 토큰을 받아서 구문 규칙과 일치하는지 확인한다.

     

    그런데 이러한 파싱 작업은 렌더링 엔진이 스스로 하는 것이 아니고, '파서'라는 녀석을 통해서 한다. 그리고 이 파서는 파서 생성기에 의해서 만들어진다.

     

    여기서 파서는 뭐고, 파서 생성기가 뭐냐며 조금 혼란스러울 것이다..

     

    우선 파서 생성기란, 각 프로그래밍 언어를 파싱하기 위해서 이에 알맞은 파서를 만들어내기 위한 도구이다.
    대표적으로는 유닉스 표준 유틸리티인 LexYacc이 있다.

    예를 들어 설명하면, 어휘 분석할 파서를 생성하기 위한 Lex와, 구분 분석을 위한 Yacc에 각각 어떤 C코드에 대한 BNF(배커스 나우르 표기법)이라는 문법을 주면, 그것에 맞는 파서를 만들어준다. 그러면 Lex에서 나온 파서(어휘 분석기)는 해당 C 코드를 토큰으로 분해하고, Yacc에서 생성된 파서(구문 분석기)가 이 토큰을 하나씩 호출하며 구문 분석을 하게 되는 것이다.

    💡 BNF(배커스 나우르 표기법)이란?

    컴퓨터 언어에서 언어의 문법을 수학적인 수식으로 나타낼 때 사용하는 언어 도구이며, '문맥자유문법'으로 나타내기 위해 만들어진 표기법이다.

    (아쉽게도... BNF와 문맥자유문법에 대해 조금 더 자세히 알아보려고 노력했으나, 여기부터는 수학적인 개념으로 들어가서 이해하기가 상당히 어려웠다...)

     

     

    크롬, 사파리 엔진인 웹킷에서는 어휘 파서 생성기인 플렉스(Flex)와 문법 파서 생성기인 바이슨(Bison) 이용한다. 참고로 플렉스와 바이슨은 앞에서 소개한 Lex와 Yacc을 기반으로 구현된 또 다른 파서 생성기이다.

    웹킷에서는 이 두 개의 생성기에서 나온 파서를 이용하여 CSS와 JavaScript를 파싱 할 수 있게 된다.
    단지 CSS와 JavaScript만...

     

    "그러면 HTML은?"

     

    못한다.
    HTML은 플렉스와 바이슨은 물론, 모든 전통적인 파서는 HTML에 적용할 수 없다. 

    왜? 그 이유는 HTML은 '문맥 자유 문법'이 아니기 때문이다.

    이 말은 즉슨, 문서가 정확한 규칙에 맞는 상태가 아니어도 해석이 가능하다는 것인데, 그냥 더 쉽게 말하면 우리가 코드를 짤 때 어느 정도 코드 문법 오류가 있어도 이를 수용하고 알아서 처리를 한다는 뜻이다.

    하나의 예를 들어보자.

    <html>  
       <mytag></mytag> // <- 표준 태그가 아님
       <div>
         <p>   // <- 중첩 오류
       </div>
       Really lousy HTML
       </p>
    </html>

     

    위 코드와 같이 문법 오류가 있는 HTML 코드여도, 이를 오류처리로 실행을 중지하는 것이 아니라 제작자의 실수를 알아서 수정하여 올바르게 표시를 해준다.

    이처럼 HTML은 매우 너그럽고 유연하지만, 파싱하기가 매우 어렵고 전통적인 구문 분석으로는 파싱이 불가능하다.

     

    "그러면 HTML 파싱은 도대체 어떻게 하는거지?"

     

    HTML 파서는 다른 프로그래밍 언어와 달리 별도의 HTML 전용 파서가 필요로 한다.

    전용 파서가 필요하다는 뜻은, 플렉스나 바이슨 처럼 유닉스 체계에 들어가 있는 파서가 아니라 HTML전용으로 따로 설계되어 제작된 파서인 것이다.

    HTML 파서에 대해 조금 더 찾아보니, Github에 웹킷과 게코가 오픈소스 라이브러리로 올라와 있고, 해당 라이브러리 안에 HTML 파서가 따로 있는 것을 발견할 수 있었다.

    https://github.com/WebKit/webkit/tree/master/Source/WebCore/html/parser
    https://github.com/mozilla/gecko-dev/tree/master/parser/htmlparser

     

    이것을 보고, 각자 렌더링 엔진을 만들 때 HTML 파서도 함께 만든다는 것을 알 수 있었다.

    추가로, 각 프레임워크마다 사용되는 HTML 파서의 종류가 여러 가지 있다는 것을 알 수 있었다.
    자바로 만들어진 jSoup, 파이썬의 BeautifulSoup 등등, 이들 또한 오픈소스 라이브러리이다.

     

    DTD(Document Type Definition)

    Naver D2 '브라우저는 어떻게 동작하는가?'에서는 DTD를 설명할 때, 'HTML의 정의는 DTD 형식 안에 있는데 SGML 계열 언어의 정의를 이용한 것이다. 이 형식은 허용되는 모든 요소와 그들의 속성 그리고 중첩 구조에 대한 정의를 포함한다.'라고 되어있다.

    난 이 말이 도저히 이해가 되지 않았다. DTD가 무엇이고 SGML 계열은 무엇이며, 뭐가 허용되고 뭐가 중첩된다고 하는데... 도저히 모르겠다.

    그래서 직접 찾아보며 정리를 해봤다.

     

    우선 HTML은 SGML(Standard Generalized Markup Language) 계열의 언어의 정의를 이용한 것이라고 하는데, SGML 계열의 언어는 아래와 같은 방식을 말한다.

    마치 HTML형식과 똑같다. 그럼 이 HTML과 SGML은 차이는 무엇일까?

    HTML은 하이퍼텍스트(링크 기능이 삽입된 문서)를 작성하기 위해서 개발된 프로그래밍 언어이고 마크업(태그 등을 이용해서 데이터의 구조를 명시하는 방식) 언어의 일종이다.

    SGML은 국제 표준으로 지정된 표준 마크업 언어의 규약을 말한다. 

    DTD(Document Type Definition)란, SGML 형태인 언어가 어떤 문서 타입인지 알려주는 것이다.
    예를 들어 아래와 같다.

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    // XHTML 1.0, doctype은 strict, transition, frameset 세가지 중 선택가능
    
    
    <!DOCTYPE html>
    // HTML 5 doctype

     

    문서에 DTD를 정의함으로써 해당 문서에 대한 타입을 정의하게 되고 이것은 파싱할 때 데이터 교환에 대한 표준으로 활용되며, 파싱할 때 문서의 구문 및 구조에 대한 유효성을 검사한다.

     

    이렇게 여차저차... HTML을 드디어 파싱이 되면 파싱트리가 구성되는데, 이 파싱트리를 이용하여 DOM Tree를 만든다.

    이어서 다음 단계인 DOM과 DOM Tree에 대해 알아보자.

     

    DOM

    웹 개발자라면 DOM이 무엇인지 알 것이다.

     

    맞다. 요거다.

    위 이미지의 DOM은 HTML에 작성한 파일 형식 그대로 똑같이 보이고 있다.

     

    VSCode 에디터 화면

    그러면 위의 HTML 문서의 코드도 DOM일까?

     

    네이버 D2 '브라우저는 어떻게 동작하는가?' 글에서는 DOM을 이렇게 설명한다.

    'DOM(Document Object Model)은 HTML 문서의 객체들을 표현이고 외부를 향하는 자바스크립트와 같은 HTML 요소의 연결 지점이다.'

    이 문장을 더 제대로 이해해보자면, HTML문서에 작성한 문자열 내용들을 브라우저에서 사용할 수 있는 Object 객체 데이터로 형상화된 이 DOM인 것이다.

    HTML과 DOM의 차이점 한 줄 요약

    • HTML: HTML 파일에 안에 형식에 맞게 작성된 문자열 텍스트.
    • DOM: 해당 HTML의 텍스트에 맞춰서 생성된 객체 데이터.

     

    그리고 '외부를 향하는 자바스크립트와 같은 HTML 요소의 연결 지점이다'라는 뜻은, 아래 자바스크립트 코드를 보는 것이 이해가 더 빠를 것이다.

    const element = document.getElementById('name'); // <- DOM 접근
    element.innerHTML = 'hello~Inyong'; // 접근한 DOM 내부를 HTML Document 형태로 렌더링
    // 이 뿐 아니라 appendChild, removeChild 등 DOM API로 조작이 가능.

     

     

    DOM은 HTML 마크업과 1:1의 관계를 맺으므로, HTML문서 작성되어 있는 모든 요소가 DOM으로 생성된다.

    HTML과 마찬가지로 DOM은 W3C에 의해 명세가 정해져 있다.

     

    이렇게 DOM Tree까지 완성이 되었다. 
    이제는 이 DOM Tree를 이용해서 실제로 화면에 그려질 정보인 Render Tree를 생성해야 하는데, 이를 위해서는 CSS 파싱된 Style Sheet 값이 필요로 한다.

    그러면 지금부터 CSS 파싱에 대해 알아보자.

     

    CSS 파싱

    CSS파싱 이전에 CSS 선언방식에 대해 먼저 얘기를 해보려고 한다.

    CSS를 정의하는 방법에는 Internal, Inline, External 세가지 방법이 있다.

    Internal과  Inline 방법은 우리가 알고 있는 <style>태그나 HTML 태그 안에 style을 정의함으로써 HTML 문서에 직접 포함하는 형식이다. External 방법은 HTML문서와 별개로 외부의 CSS문서에 정의된 스타일을 참조하는 형식이다. 이는 주로 <link>태그를 이용하여 연결된다.

     

     

    Internal과 Inline 방식은 HTMLstyleElement라는 DOM 객체로 변환하여 DOM Tree에 추가되고, External 방식은 HTMLlinkElement라는 DOM 객체로 변환하여 추가된다.

    HTMLstyleElement는 추가될 때 바로 컴파일 되지만, HTMLlinkElement은 Link로 연관된 리소스 파일을 전부 받아온 후 컴파일이 실행된다.

    위 글만 보면 '그러면 Internal과 Inline 방식이 훨씬 빠르겠네?' 라는 의문이 들 것이다.
    외부에서 파일을 받아오는 것은 상당한 시간이 소모되는 과정이기 때문이다.
    그러나 결론은 이렇다.

     

    "자바스크립트가 없다면 맞고, 있다면 틀린 말이다."

     

    이유를 설명한다면 이와 같다.

    HTML문서 파싱 중 자바스크립트를 만나게 되면, HTML파싱을 멈추고 자바스크립트를 먼저 실행한다. 이는 수 년간 지속됐고 HTML4와 HTML5의 명세에도 정의되어 있는 방식이다.
    (예외: 자바스크립트 속성이 defer 속성을 가질 경우 HTML 파싱 후 실행한다.)

    그리고 자바스크립트가 로딩되지 않은 CSS가 있으면 일시 보류하게 된다.
    왜냐하면, 자바스크립트는 DOM Tree에 포함되는 CSS를 읽고 수정할 수 있기 때문에, CSS보다 먼저 실행이 되면 기대한 바와 다른 결과를 가질수도 있기 때문이다.

    따라서, Internal과 Inline 방식보다 External 방식이 훨씬 효과적이다.

    [2020.10.28 내용 수정]

    External 방식으로 선언하면 위 설명처럼 CSS 파싱이 가장 먼저 일어나기 때문에 가장 성능이 좋을 것이라고 생각했다. 

    그러나 이는 CSS 파싱이 제일 먼저 일어날 뿐이며, 성능과는 전혀 관련이 없는 것이다. 오히려 External 방식을 이용하게 되면 추가로 CSS파일을 다운로드 해야하는 요소가 생기기 때문에, 오히려 웹 통신 성능 면으로는 더 비효율적으로 동작하게 된다.
    (지금 생각해보니 CSS 파싱이 빨리 일어나는 것과 웹 성능은 전혀 연관이 없다... 왜 이렇게 생각했었지...)

    그렇다고 무작정 inline 방식으로 사용하는 것도 옳지 않다. 모두 inline 방식으로 HTML 태그 내에 선언하면 코드의 가독성이 매우 떨어질 수도 있다. 그렇다고 JS 파일 내 Style 태그로 모두 선언하게 되면, 여러 JS파일 내 중복된 스타일의 코드가 많아 성능 저하가 발생할 수도 있다. External 방식을 이용한다면 동일한 스타일을 사용하는 여러 JS파일이 있을 경우 바로 CSS 파일을 선언하여 가져올 수도 있으므로 중복 코드를 줄일 수도 있다. 

    이 처럼 CSS 스타일 선언 방식에 따른 각각의 장,단점이 있으므로 여러 상황을 판단하여 사용하는 것이 가장 BEST이다.

     


    이제 파싱에 대해서 간단히 알아보자.

    위에서 '파서 생성기'에 대해 설명할 때, 웹킷에서는 플렉스(Flex)바이슨(Bison)을 이용한다고 언급을 했었다.

    JavaScript와 CSS는 문맥 자유문법으로, 이 두 개의 파서 생성기에서 나온 파서를 이용해 파싱이 가능하다.

     

    CSS 파싱을 진행하게 되면, DOM과 결합하여 별도의 CSSOM(CSS Object Model)이라는 스타일 관련 객체모델이 생성된다. CSSOM은 HTML의 DOM과 같이 스타일 객체에 접근할 수 있는 인터페이스 객체가 된다.

    이해가 잘 안되어도, 아래의 코드를 보면 '아하~!' 할 것이다.

    const element = document.getElementById('inyong');
    element.style.color = 'green'; // <= 해당 DOM의 CSSOM에 접근하여 스타일 변경.

     

    이제야 CSS 파싱까지 끝냈다.
    다음 단계인 Render Tree로 넘어가보자.

     

    Render Tree

    위의 웹킷의 렌더링 엔진 과정 중에 attechment라는 과정이 있었던 것을 기억할지 모르겠다. (게코는 형상구축이라 부른다.)
    HTML과 CSS파싱이 끝나면 이 attechment과정으로 넘어가게 되고, 실제 화면에 그려질 정보를 담은 Render Tree를 생성하게 된다.

     

    여기서 약간 헷갈릴 수도 있는 부분이, DOM Tree와 Render Tree의 차이일 것이다.

    DOM Tree는 HTML에서 파싱되어 생성된 Document의 객체 모델이 부모 자식 관계로 구성된 트리 형태의 객체 데이터일 뿐이다.
    이와 다르게 Render Tree는 DOM Tree와 각 DOM 요소들에 상응하는 Style을 참조하여, 단순히 화면에 그려질 정보들이 담긴 데이터이다.

     

    Render Tree는 DOM Tree와 1:1로 대응이 일어나지 않는데, 그 이유는 아래와 같다.

     

    1. 비시각적인 요소는 제외

    Render Tree에는 앞서 말한 것처럼 화면에 그려질 정보들을 담은 데이터이기에, 화면에 그려지지 않는 비시각적인 요소들은 Render Tree에 포함이 되지 않기 때문이다.

    비시각적인 요소란 <head>태그display:none 속성을 가진 Element 요소가 될 수 있겠다.

     

    2. 특정 요소에는 여러 렌더러가 필요로 함

    예를 들어 <input type='select'> 태그를 그리려면, '표시 영역, 드롭다운 목록, 버튼' 표시를 위한 3개의 렌더러가 필요로 한다.
    또한 한 줄로만 작성했던 문장이 공간 부족으로 인해 줄 바꿈이 생겼을 때, 새로운 줄은 별도의 렌더러로 추가된다. 이외에도 position이나 float 속성으로 인해 변동되는 자리 배치도에 의해 별도의 렌더러가 생성되기도 한다.

     

    배치

    Render Tree가 생성되면, 이 Render Tree에 대한 페이지 배치가 일어난다.
    배치란, Render Tree의 각 렌더러의 크기와 위치 정보에 대한 계산을 하는 과정이다.

    여기서도 또 어느정도 의아함을 가질수도 있겠다.

     

    "Render Tree 생성할 때 어떻게 그릴지 전부 정해진 것 아니었나? 왜 또 렌더러의 크기와 위치 정보를 계산하는 거지?"

     

    맞다. 전부 정해졌었다.

    그러나 더 정확하게 따지자면, Render Tree에는 각 렌더러가 각자 자신들이 어떻게 그려질지 전부 정해진 것이지, 각자 렌더러들(자신과 형제, 자식 렌더러)끼리 위치 및 크기를 확인해서 페이지라는 흰 도화지 위에 자신들이 어느 위치에서 얼마나 공간을 차지해서 그려질지에 대해서는 정해지지 않은 것이다.

    그래서 이러한 배치 과정을 통해, 렌더러들은 각 영역에 해당하는 위치를 선점 후 viewPort만큼의 면적을 가지며 배치가 정해진다.

    이러한 배치 계산 과정은 왼쪽에서 오른쪽, 위에서 아래 순으로 진행되기 때문에 제일 최상단의 시작점인 0,0 픽셀 위치는 브라우저 창의 제일 왼쪽 상단부터 배치를 시작한다. 

     

    그럼 이 배치과정에 대해 조금만 더 자세히 들어가 보도록 하자.
    (거의 다 왔다. 조금만 더 화이팅 하자..)

     

    배치 방식에는 전역 배치점증 배치 두 가지가 있다.

     

    #전역 배치

    전역 배치는 모든 렌더링에 영향을 주는 전역 스타일 변경이 발생할 때 일어나는 배치 과정이다.
    예를 들어, 해당 페이지의 글꼴 크기나 글꼴 스타일이 변경되는 것과 같이, 모든 렌더러에 영향을 주는 전역 스타일을 변경하면 전역 배치가 발생한다. 전역 배치는 동기적으로 한 번에 처리된다.

     

    #점증 배치

    점증 배치는 '더티 렌더러'가 배치되는 경우 일어나는 배치 과정이다.
    더티 렌더러란, 변경 요소를 브라우저가 지정하여 변경할 특정 렌더러를 뜻한다.

    브라우저에서는 소소한 변경 때문에 페이지 전체를 다시 배치하지 않기 위해서 '더티 비트' 체제를 사용한다.

    예를 들어, 네트워크 통신으로부터 추가 데이터를 전송받아 DOM 트리에 더해지면 새로운 렌더러가 Render Tree에 추가가 될 것이다. 그러면 추가된 특정 부분과 이에 관련된 렌더러들만 다시 배치작업을 하게 된다.
    따라서 점증 배치는 비동기적으로 일어난다.

     

    그리기

    드디어 Render Tree를 이용해서 실제로 페이지에 그리는 과정이다. (마지막이다..!)

    그리기 단계에서는 화면에 내용을 표시하기 위한 Render Tree가 전체적으로 탐색되고, 각 렌더러의 'paint' 메서드가 호출된다. 그리기는 UI 백엔드(<Input>태그 등 기본적으로 정해져 있는 UI) 요소들을 기반으로 그리기 시작한다.

    각각의 요소들은 블록 렌더러 순서에 따라 그리기 시작한다. 이는 CSS에 명세가 되어있는 순서이다.

    1. 배경 색
    2. 배경 이미지
    3. 테두리
    4. 자식
    5. 아웃라인

    모든 요소가 그려진 후 브라우저에서 일부 변경이 생길 경우엔, 해당 렌더러와 그 자식의 배치과정과 repaint 메서드가 발생한다.

    웹킷에서는 리페인팅 전에 기존의 역영을 비트맵으로 저장하여 새로운 영역과 비교하고 차이가 있는 부분만 다시 그리는 과정을 통해, 불필요한 그리기 요소를 최소화시킨다.

     

     

    이러한 렌더링 엔진의 과정을 통하여 HTML과 CSS문서가 아래와 같은 웹 페이지로 그려지게 된다.


    정리하며

    마지막 단계에서 정리를 해 나갈 쯤에 뭔지 모를 소름과 희열감이 느껴졌다...

    브라우저 자체에 대해 이렇게나 깊게 파고들 수 있다니.. 비록 Naver D2의 글에 많이 의존하긴 했지만, 그것 외에도 이해가 어려웠던 부분들을 하나하나 찾아보며 내 나름 최대한 이해하기 쉽도록 정리를 하고나니, 브라우저에 대한 깊은 이해도 뿐 아니라 내가 지금까지 몰랐던 웹 지식과 어정쩡하게 알고 있던 개념들에 대해 확실히 잡을 수 있었다.

    내가 지금까지 블로깅한 내용 중에 가장 열심히 찾아보고 공부하고 정리한 글이 된 것 같다.

     

    (지금 생각해보니, 백수라 시간이 많아서 가능했던 것 같기도..)

     

    '브라우저는 어떻게 동작하는가' 발표 영상 보러가기

     

    적극 참고한 자료

    Naver D2 - 브라우저는 어떻게 동작하는가?

    반응형

    댓글

Designed by Tistory.