본문 바로가기
내가 공부하려고 올리는/web

브라우저 동작 원리(쉽게 알아보기) - CSS 파싱/렌더 트리/스타일 계산(7)

by 결딴력 2021. 11. 7.
반응형

이 글은 이스라엘 개발자 탈리 가르시엘(Tali Garsiel)이 html5rocks.com에 게시한

"How Browsers Work: Behind the scenes of modern web browsers"를 인용하고 있습니다.

아무것도 모르는 초보자의 시선에서

하나하나 모르는 용어는 용어대로 찾아가면서

정리해보는 글입니다.

 

저와같이 아무것도 모르는 초보자를 기준으로 글을 작성했습니다.

 

부디 이 글에 마지막에는

글을 쓰고 있는 저도, 독자도 어렴풋이라도

브라우저가 어떻게 동작하는지 이해할 수 있었으면 좋겠습니다. 🙏

 

이 글은 브라우저 동작 원리 7편입니다.

다른 편은 아래 링크나 블로그에서 참조해주세요!

 

 

CSS parsing(CSS 파싱)


전문

 

CSS는 HTML과 달리 '문맥 자유 문법'입니다.

따라서 서론에서 설명한 파서 유형을 사용하여

구문 분석을 할 수 있습니다.

 

어휘 문법은 각 토큰에 대한 정규식으로 정의됩니다.

구문 문법은 BNF로 설명됩니다.

 

 

 

WebKit CSS parser(웹킷 CSS 파서)


전문

더보기

WebKit uses Flex and Bison parser generators to create parsers automatically from the CSS grammar files. As you recall from the parser introduction, Bison creates a bottom up shift-reduce parser. Firefox uses a top down parser written manually. In both cases each CSS file is parsed into a StyleSheet object. Each object contains CSS rules. The CSS rule objects contain selector and declaration objects and other objects corresponding to CSS grammar.

CSS 파싱

 

웹킷'Flex''Bison'을 파서로 사용합니다.

CSS 파일의 경우 일반적인 파서로 파싱이 가능하기 때문에

이러한 파서를 이용해 파싱을 진행합니다.

 

Bison은 상향식 Shift-reduce 파서를 생성합니다.

Firefox는 하향식 파서를 사용합니다.

두 경우 모두 CSS파일은 Style Sheet 객체로 파싱됩니다.

각 객체는 모두 CSS 규칙을 포함합니다.

 

CSS 규칙 객체는 선택자와 선언 객체

그리고 CSS 문법과 부합하는 다른 객체를 포함합니다.

 

 

 

The order of processing scripts and style sheets

(스크립트와 스타일 시트의 진행 순서)


전문

더보기

Scripts

The model of the web is synchronous. Authors expect scripts to be parsed and executed immediately when the parser reaches a <script> tag. The parsing of the document halts until the script has been executed. If the script is external then the resource must first be fetched from the network–this is also done synchronously, and parsing halts until the resource is fetched. This was the model for many years and is also specified in HTML4 and 5 specifications. Authors can add the "defer" attribute to a script, in which case it will not halt document parsing and will execute after the document is parsed. HTML5 adds an option to mark the script as asynchronous so it will be parsed and executed by a different thread.

Speculative parsing

Both WebKit and Firefox do this optimization. While executing scripts, another thread parses the rest of the document and finds out what other resources need to be loaded from the network and loads them. In this way, resources can be loaded on parallel connections and overall speed is improved. Note: the speculative parser only parses references to external resources like external scripts, style sheets and images: it doesn't modify the DOM tree–that is left to the main parser.

Style sheets

Style sheets on the other hand have a different model. Conceptually it seems that since style sheets don't change the DOM tree, there is no reason to wait for them and stop the document parsing. There is an issue, though, of scripts asking for style information during the document parsing stage. If the style is not loaded and parsed yet, the script will get wrong answers and apparently this caused lots of problems. It seems to be an edge case but is quite common. Firefox blocks all scripts when there is a style sheet that is still being loaded and parsed. WebKit blocks scripts only when they try to access certain style properties that may be affected by unloaded style sheets.

Script

웹 모델은 동기식입니다.

작성자는 <script> 태그를 만나면 즉시

스크립트가 구문 분석되고 실행되기를 기대합니다.

 

스크립트가 실행될 때까지

문서의 파싱은 중단됩니다.

 

만약 스크립트가 외부에 있다면

자원은 반드시 우선적으로 네트워크에서

리소스를 가져와야 합니다.

이 과정 역시 동기식이므로,

리소스를 가져오는 동안

문서의 파싱은 중단됩니다.

 

이러한 모델은 HTML4나 HTML5에 명세되어 있는

수년간 지속되어 온 모델입니다.

 

쉽게 말해보겠습니다.

Script를 정의하는 방식에는 다음과 같은 3가지 방식이 존재합니다.

  1. Internal
  2. Inline
  3. External

Internal과 lnline 방식은<style> 태그를 사용하거나(Internal 방식)

태그 안에 style 요소를 넣어 적용하는 방식(Inline 방식)으로

HTML 문서 안에 직접 스크립트를 정의하는 방식을 말합니다.

 

이에 반해 External 방식은 별도의 스크립트 문서를 외부에 만들어서

그 파일에 정의된 style을 HTML 문서에 적용하는 방식입니다.

 

HTML 문서를 파싱 하면서 이러한 방식의 스타일 요소를 만나게 되면

파싱이 중단됩니다.

이러한 문제점을 해결하기 위해

작성자는 'defer' 속성을 스크립트에 추가하여

파싱이 중단되지 않고 진행이 완료된 후에

스크립트가 실행되게 할 수 있습니다.

 

HTML5는 스크립트를 비동기로 표현하는 옵션을 추가하여

다른 스레드에서 파싱 되고 실행되도록 하였습니다.

 

Speculative parsing

웹킷과 Firefox는 예측 파싱과 같은 최적화를 수행합니다.

스크립트를 실행하는 동안, 다른 스레드에서는

문서의 나머지 부분을 파싱 하고,

가져와야 하는 리소스를 네트워크에서 찾아서 가져옵니다.

 

이러한 방식에서는 리소스는 병렬 연결로 로드되고

전반적인 속도가 향상됩니다.

 

참조

Speculative파서(예측 파서)는

외부 스크립트, 스타일 시트, 이미지와 같은

외부 리소스에 대한 참조만 파싱 합니다.

예측 파서는 DOM 트리를 수정하지 않습니다.

 

Style sheets

반면에 스타일 시트는 다른 모델을 갖습니다.

개념적으로, 스타일 시트는 DOM 트리를 수정하지 않는 것처럼 보이기에

스타일 시트를 기다리고 문서 파싱을 중단할 이유가 없어 보입니다.

 

그러나 문서가 파싱 되는 단계에서,

스타일 시트는 스타일 정보를 요청합니다.

 

만약 스타일이 아직 로드되지 않고 파싱 되지 않았다면,

스크립트는 잘못된 결과를 만들고, 이는 많은 문제를 만듭니다.

 

Firefox는 로드되고 파싱 되고 있는 스타일 시트가 있는 경우

모든 스크립트를 차단합니다.

 

웹킷은 로드되지 않은 스타일 시트에 영향을 끼칠 수 있는

특정 스타일 속성에 접근하려고 할 때

스크립트를 차단합니다.

 

 

------------------------------

2021-12-12 추가 내용

 

정리하자면

JavaScript와 같은 스크립트가 존재한다면,

스크립트에 있는 리소스를 가져오는 동안 파싱이 중단되고,

CSS와 같은 스타일 시트를 만난다면,

스크립트를 차단하게 됩니다.

 

------------------------------

 

 

Render tree construction(렌더 트리 구조)


전문

더보기

While the DOM tree is being constructed, the browser constructs another tree, the render tree. This tree is of visual elements in the order in which they will be displayed. It is the visual representation of the document. The purpose of this tree is to enable painting the contents in their correct order.

Firefox calls the elements in the render tree "frames". WebKit uses the term renderer or render object.
A renderer knows how to lay out and paint itself and its children.
WebKit's RenderObject class, the base class of the renderers, has the following definition:

Each renderer represents a rectangular area usually corresponding to a node's CSS box, as described by the CSS2 spec. It includes geometric information like width, height and position.
The box type is affected by the "display" value of the style attribute that is relevant to the node (see the style computation section). Here is WebKit code for deciding what type of renderer should be created for a DOM node, according to the display attribute:

The element type is also considered: for example, form controls and tables have special frames.
In WebKit if an element wants to create a special renderer, it will override the createRenderer() method. The renderers point to style objects that contains non geometric information.

DOM에 CSS 정보를 더한 것이 CSSOM이고,

DOM과 CSSOM을 합한 것을 '렌더 트리'라 합니다.

 

DOM 트리가 생성되는 동안

브라우저는 렌더 트리를 생성합니다.

 

렌더 트리를 만드는 목적은

콘텐츠가 올바른 순서로 화면에 페인팅하는 것입니다.

 

Firefox는 렌더 트리의 요소를 'frames'라고 부릅니다.

웹킷은 'renderer''렌더 객체'라고 표현합니다.

 

렌더러는 자신과 자신의 자식 객체가

어떻게 배치되고 페인트 되어야 하는 지를 알고 있습니다.

 

렌더러는 일반적으로 CSS2 명세에 설명되어 있는 것처럼

노드의 CSS 박스에 해당하는 직사각형 영역을 나타냅니다.

이는 너비, 높이, 위치와 같은 기하학적 정보를 포함합니다.

 

상자 타입은 노드와 관련된 스타일 속성의

'디스플레이' 값에 영향을 받습니다.

 

요소 타입 역시 고려됩니다.

form 컨트롤과 테이블에는 특별한 프레임이 있습니다.

 

웹킷에서 요소가 특별한 렌더러를 만드는 경우

createRenderer() 메소드를 재정의합니다.

렌더러는 기하학적 정보를 포함하지 않는 스타일 객체를 가리킵니다.

 

 

 

The render tree relation to the DOM tree

(DOM 트리와 렌더러 트리의 관계)


전문

더보기

The renderers correspond to DOM elements, but the relation is not one to one. Non-visual DOM elements will not be inserted in the render tree. An example is the "head" element. Also elements whose display value was assigned to "none" will not appear in the tree (whereas elements with "hidden" visibility will appear in the tree).

There are DOM elements which correspond to several visual objects. These are usually elements with complex structure that cannot be described by a single rectangle. For example, the "select" element has three renderers: one for the display area, one for the drop down list box and one for the button. Also when text is broken into multiple lines because the width is not sufficient for one line, the new lines will be added as extra renderers.
Another example of multiple renderers is broken HTML. According to the CSS spec an inline element must contain either only block elements or only inline elements. In the case of mixed content, anonymous block renderers will be created to wrap the inline elements.

Some render objects correspond to a DOM node but not in the same place in the tree. Floats and absolutely positioned elements are out of flow, placed in a different part of the tree, and mapped to the real frame. A placeholder frame is where they should have been.

렌더트리와 DOM 트리의 대응

The flow of constructing the tree

In Firefox, the presentation is registered as a listener for DOM updates. The presentation delegates frame creation to the FrameConstructor and the constructor resolves style (see style computation) and creates a frame.

In WebKit the process of resolving the style and creating a renderer is called "attachment". Every DOM node has an "attach" method. Attachment is synchronous, node insertion to the DOM tree calls the new node "attach" method.

Processing the html and body tags results in the construction of the render tree root. The root render object corresponds to what the CSS spec calls the containing block: the top most block that contains all other blocks. Its dimensions are the viewport: the browser window display area dimensions. Firefox calls it ViewPortFrame and WebKit calls it RenderView. This is the render object that the document points to. The rest of the tree is constructed as a DOM nodes insertion.

See the CSS2 spec on the processing model.

렌더러는 DOM 요소와 일치하지만, 1대 1 관계는 아닙니다.

비시각적인 DOM 요소는 렌더 트리에 삽입되지 않습니다.

예를 들어 'head'요소를 말합니다.

또한 표시 값이 'none'인 요소들도 트리에 삽입되지 않습니다.

('hidden' 요소는 트리에 나타납니다.)

 

여러 시각적 객체와 일치하는 DOM 요소도 존재합니다.

이들은 단일 직사각형으로 설명될 수 없는 복잡한 구조를 가진 요소입니다.

예를 들어, 'select' 요소는 3개의 렌더러를 갖습니다.

3개의 렌더러는 다음과 같습니다.

  1. 표시 영역
  2. 드롭다운 목록 박스
  3. 버튼

또한 너비가 충분하지 않아 텍스트가 여러 줄로 분할될 때는

추가적인 렌더러가 새로운 줄에 추가됩니다.

 

다중 렌더러의 또 다른 예시는 깨진 HTML입니다.

CSS 명세에 따르면 Inline 요소는 블록 요소 또는 인라인 요소만을 포함해야 합니다.

혼합 콘텐츠의 경우 Inline 요소를 래핑하기 위해 익명의 block renderes가 생성됩니다.

 

일부 렌더러 객체는 DOM 노드와 일치합니다.

하지만 트리의 같은 위치에 위치하지는 않습니다.

Floats이나 position 속성 값이 'absolute'로 처리된 요소는

flow를 벗어나 트리의 다른 부분에 배치되고 실제 프레임에 매핑됩니다.

대신 placeholder 프레임이 있어야 할 위치에 존재합니다.

 

다시 한번 쉽게 설명해 보겠습니다.

Render Tree가 Dom Tree와 1:1 대응이 일어나지 않는 이유는

크게 두 가지입니다.

  1. 비시각적 요소를 제외하기 때문에
  2. 특정 요소에는 다중 렌더러가 삽입되기 때문에

 

'head'태그나 diplay: none 속성을 가진 요소는

비시각적 요소로 렌더 트리에 포함되지 않습니다 -> 1번 이유

 

한 줄로 작성된 문장이 너비가 부족하여 두 줄로 변하는 경우,

특정 태그에 여러 요소가 포함되어 렌더러가 여러 개 생기는 경우,

position 속성 값이 'absolute'이거나 float 속성으로 처리된 요소인 경우

다중 렌더러가 삽입됩니다. -> 2번 이유

렌더트리와 DOM 트리의 대응

 

트리의 생성 흐름

웹킷의 메인흐름

이 흐름도를 기억하시나요?

DOM tree에 스타일 규칙을 적용시키는 것을

웹킷에서는 'Attachment'라고 부릅니다.

 

모든 DOM 노드에는 'attach' 메소드가 있습니다.

Attachment는 동기식이며,

DOM 트리에 노드를 삽입하는 것은

새로운 'attach' 메소드를 호출합니다.

 

Html과 <body> 태그를 처리한 결과

렌더 트리 루트가 생성됩니다.

루트 렌더 객체는 CSS 명세에서 'containing block'이라 부릅니다.

이는 다른 모든 블록을 포함하는 최상위 블록입니다.

이 블록의 치수는 '뷰포트'(브라우저 창의 디스플레이 영역 치수)입니다.

 

Firefox에서는 이를 'ViewPortFrame'이라 부르고

웹킷에서는 이를 'RenderView'라 부릅니다.

 

이 블록은 문서가 가리키는 렌더 객체입니다.

나머지 트리는 DOM 노드 삽입으로 이루어집니다.

 

 

 

Style Computation(스타일 계산)


전문

더보기

Building the render tree requires calculating the visual properties of each render object. This is done by calculating the style properties of each element.

The style includes style sheets of various origins, inline style elements and visual properties in the HTML (like the "bgcolor" property). The later is translated to matching CSS style properties.

The origins of style sheets are the browser's default style sheets, the style sheets provided by the page author and user style sheets–these are style sheets provided by the browser user (browsers let you define your favorite styles. In Firefox, for instance, this is done by placing a style sheet in the "Firefox Profile" folder).

Style computation brings up a few difficulties:

  1. Style data is a very large construct, holding the numerous style properties, this can cause memory problems.
  2. Finding the matching rules for each element can cause performance issues if it's not optimized. Traversing the entire rule list for each element to find matches is a heavy task. Selectors can have complex structure that can cause the matching process to start on a seemingly promising path that is proven to be futile and another path has to be tried.
    • For example–this compound selector:
  1. Means the rules apply to a <div> who is the descendant of 3 divs. Suppose you want to check if the rule applies for a given <div> element. You choose a certain path up the tree for checking. You may need to traverse the node tree up just to find out there are only two divs and the rule does not apply. You then need to try other paths in the tree.
  2. Applying the rules involves quite complex cascade rules that define the hierarchy of the rules.

Let's see how the browsers face these issues:

렌더 트리를 구축하기 위해서

각 렌더 트리 객체의 시각적 속성을 계산해야 합니다.

이는 각 요소의 스타일 속성을 계산하여 수행됩니다.

 

스타일은 다양한 출처의 스타일 시트, Inline 스타일 요소

그리고 HTML(예를 들어, 'bgcolor' 속성)의 시각적 속성이 포함됩니다.

HTML의 시각적 속성들은 CSS 스타일 속성으로 변환됩니다.

 

스타일 시트의 기원은 다음과 같습니다.

  • 브라우저의 기본 스타일 시트
  • 페이지 작성자가 제공하는 스타일 시트
  • 브라우저 유저에 의해 제공된 유저 스타일 시트

 

스타일을 계산하는 것은 다음과 같이 몇 가지 어려움을 야기합니다.

  1. 스타일 데이터는 수많은 스타일 소성을 포함하는 매우 큰 구조라
    메모리 문제를 야기할 수 있습니다.
  2. 최적화되어 있지 않은 경우,
    각 요소에 대한 일치 규칙을 찾는 것은 성능 문제를 일으킬 수 있습니다.
    각 요소에 일치하는 규칙을 찾기 위해 전체 규칙을 탐색하는 것은 힘든 작업입니다.
  3. 규칙을 적용하려면 규칙의 계층 구조를 정의하는 복잡한 cascade 규칙이 필요합니다.

 

그렇다면 브라우저는 이러한 이슈를 어떻게 대처할까요?

 

 

Sharing style data(스타일 데이터 공유)


전문

더보기

WebKit nodes references style objects (RenderStyle). These objects can be shared by nodes in some conditions. The nodes are siblings or cousins and:

  1. The elements must be in the same mouse state (e.g., one can't be in :hover while the other isn't)
  2. Neither element should have an id
  3. The tag names should match
  4. The class attributes should match
  5. The set of mapped attributes must be identical
  6. The link states must match
  7. The focus states must match
  8. Neither element should be affected by attribute selectors, where affected is defined as having any selector match that uses an attribute selector in any position within the selector at all
  9. There must be no inline style attribute on the elements
  10. There must be no sibling selectors in use at all. WebCore simply throws a global switch when any sibling selector is encountered and disables style sharing for the entire document when they are present. This includes the + selector and selectors like :first-child and :last-child.

웹킷 노드는 스타일 객체(RenderStyle)를 참조합니다.

이 객체는 노드에 의해 공유될 수 있는데,

노드가 형제 또는 사촌일 때 공유할 수 있습니다.

다른 조건들은 다음과 같습니다.

  1. 요소는 동일한 마우스 상태에 있어야 합니다.
    (예를 들어, 하나는 :hover 상태에 있을 수 없고, 다른 하나는 있을 수 있는 경우)
  2. 두 요소 모두 ID가 없어야 합니다.
  3. 태그 이름이 일치해야 합니다.
  4. 클래스 속성이 일치해야 합니다.
  5. 매핑된 속성 집합이 동일해야 합니다.
  6. 링크 상태가 일치해야 합니다.
  7. 초점(The focus) 상태가 일치해야 합니다.
  8. 어느 요소도 속성 선택자에 영향을 받아서는 안됩니다.
  9. 요소에 Inline 스타일 속성이 없어야 합니다.
  10. 문서 전체에서 형제 선택자를 사용하지 않아야 합니다.
    형제 선택자는 +선택자와 :first-child 그리고 :last-child를 포함합니다.

 


오늘은 CSS 파싱과 렌더 트리,

그리고 렌더 트리와 DOM 트리의 관계,

마지막으로 스타일 계산하는 방법에 대해 일부 내용을 다뤘습니다.

 

다음 글에서 브라우저가 스타일을 어떻게 계산하는지

남은 내용을 알아보도록 하겠습니다.

 

내용에 대한 오류 발견 시 댓글이나 메일 부탁드립니다.🙇‍♂️🙇‍♂️

반응형

댓글