본문 바로가기

Develop/Frontend 가이드

[FE] 브라우저 렌더링 Browser rendering - 2단계 스타일링 'style'

반응형

브라우저 렌더링
https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y

브라우저 렌더링 Browser rendering

브라우저는 'content (콘텐츠)' 를 'rendering (렌더링)' 해서 'pixel image(픽셀 이미지)' 즉 화면을 만듭니다.

  1. 브라우저 렌더링 Browser rendering - 0단계 소개
  2. 브라우저 렌더링 Browser rendering - 1단계 파싱 'parse'
  3. 브라우저 렌더링 Browser rendering - 2단계 스타일링 'style'
  4. 브라우저 렌더링 Browser rendering - 3단계 레이아웃 'layout'
  5. 브라우저 렌더링 Browser rendering - 4단계 페인팅 'paint'
  6. 브라우저 렌더링 Browser rendering - 5단계 변경 'change'

렌더링 파이프라인 Rendering pipeline

Blink 렌더링 파이프라인
Blink 렌더링 파이프라인

2단계 스타일링 'style'

이전 단계에서 HTML 텍스트 파일을 해석해서 DOM 을 만들었고 스타일링 단계에서는 CSS 텍스트 파일에 정의된 스타일을 DOM 에 적용할 수 있도록 해석하는 작업을 진행합니다. HTML 파일과 달리 CSS 파일은 해석하기 까다로운 구조로 정의되어 있어서 어떻게 해석되어 관리되는지를 자세하게 설명드리도록 하겠습니다.

스타일을 적용 적용하는 과정
스타일을 적용 적용하는 과정

Chrome 의 렌더링 엔진 Blink 는 Document 로 모든 콘텐츠를 관리합니다. Document 의 함수 중 UpdateStyleAndLayoutTree 함수는 렌더링 단계 중 스타일링과 레이아웃을 수행하는 함수이고, 위 그림처럼 UpdateStyle 함수를 호출하게 됩니다. UpdateStyle 은 스타일링만 수행하는 함수입니다.

// third_party/blink/renderer/core/dom/document.cc
void Document::UpdateStyleAndLayoutTree() {
  // ...
  UpdateStyleAndLayoutTreeForThisDocument();
  // ...
}

void Document::UpdateStyleAndLayoutTreeForThisDocument() {
  // ...
  UpdateStyle();
  // ...
}

void Document::UpdateStyle() {
  // ...

  // StyleEngine 에게 위임해서 콘텐츠의 스타일과 레이아웃 트리를 업데이트합니다.
  GetStyleEngine().UpdateStyleAndLayoutTree();

  // ...

위 코드를 보면 결국 스타일링 작업은 DocumentStyleEngine 에게 위임하여 처리한다는 사실을 확인할 수 있습니다. 즉 StyleEngineDocument 의 스타일을 계산하고 관리하는 역할을 맡고 있습니다.

// third_party/blink/renderer/core/css/style_engine.cc
// StyleEngine 생성자 함수
StyleEngine::StyleEngine(Document& document)
    : document_(&document),
      // Style Engine 은 생성될 때 StyleResolver 를 초기화합니다.
      resolver_(MakeGarbageCollected<StyleResolver>(document)),
}

void StyleEngine::UpdateStyleAndLayoutTree() {
   // ...
     // 그림에서 설명하는 Style recalc 과정을 진행합니다.
      RecalcStyle();
      RebuildLayoutTree();
  // ...
}

StyleEngineStyleResolver 를 가지고 있는데, StyleResolver 는 모든 CSS 규칙 중 DOM 트리의 각 Element 에 어떤 스타일 규칙을 적용할지 계산하는 역할을 맡고 있습니다. Chrome DevTools 에서 볼 수 있는 'Computed Style' 은 StyleResolver 가 만들어 주는 정보입니다. StyleResolver 에게 제공되는 StyleSheetContents 들은 CSSParser 가 만들어서 Document 가 보관하게 됩니다.

CSS 파일을 해석하는 과정
CSS 파일을 해석하는 과정

StyleSheetContents 들은 위 그림 순서로 만들어 집니다. 먼저 CSS 텍스트 파일을 CSSParser 에서 토큰화한 뒤, 토큰을 하나씩 처리하면서 스타일 규칙 StyleRule 을 만들고 StyleSheetContents 안에 담습니다.

// third_party/blink/renderer/core/css/parser/css_parser_impl.cc
ParseSheetResult CSSParserImpl::ParseStyleSheet(
    const String& string,
    const CSSParserContext* context,
    StyleSheetContents* style_sheet,
    // ...) {
  // ...

  // CSS 파일을 해석할 준비를 합니다.
  CSSTokenizer tokenizer(string);
  CSSParserTokenStream stream(tokenizer);
  CSSParserImpl parser(context, style_sheet);

  ParseSheetResult result = ParseSheetResult::kSucceeded;
  // ConsumeRuleList 함수로 토큰을 하나하나 해석합니다.
  bool first_rule_valid = parser.ConsumeRuleList(
      stream,
      // ...
        // 해석을 완료한 StyleRule 을 StyleSheetContents 안에 담습니다.
        style_sheet->ParserAppendRule(rule);
      });
  return result;
}

template <typename T>
bool CSSParserImpl::ConsumeRuleList(CSSParserTokenStream& stream,
                                    RuleListType rule_list_type,
                                    const T callback) {
  // ...
  // Token 을 하나하나 처리하여 StyleRule 을 만듭니다.
  while (!stream.AtEnd()) {
    StyleRuleBase* rule;
    switch (stream.UncheckedPeek().GetType()) {
      // ...
      case kAtKeywordToken:
        rule = ConsumeAtRule(stream, allowed_rules);
        break;
    // ...
    if (rule) {
      allowed_rules = ComputeNewAllowedRules(allowed_rules, rule);
      callback(rule);
    }
  }
  // ...
}

이 때 StyleRuleStyleResolverElement 에 스타일을 적용할지 여부를 결정할 수 있도록 돕는 CSSSelectorList 와 어떤 속성에 어떤 값을 적용할지 기록한 CSSPropertyValueSet 가 기록되어 있습니다.

// third_party/blink/renderer/core/css/parser/css_parser_impl.cc
// 예시로 ViewPort 와 관련한 스타일을 해석하는 함수입니다.
StyleRuleViewport* CSSParserImpl::ConsumeViewportRule(
    CSSParserTokenStream& stream) {
  // ...
  return MakeGarbageCollected<StyleRuleViewport>(
      // CSSPropertyValueSet 을 StyleRuleViewport 를 생성할 때 전달합니다.
      CreateCSSPropertyValueSet(parsed_properties_, kCSSViewportRuleMode));
}

// CSSPropertyValueSet 을 생성하는 함수입니다.
static ImmutableCSSPropertyValueSet* CreateCSSPropertyValueSet(
    HeapVector<CSSPropertyValue, 256>& parsed_properties,
    CSSParserMode mode) {
  // ...
}
// third_party/blink/renderer/core/css/style_rule.h

// A single rule from a stylesheet. Contains a selector list (one or more
// complex selectors) and a collection of style properties to be applied where
// those selectors match. These are output by CSSParserImpl.

// StyleRule 정의 일부입니다.
class CORE_EXPORT StyleRule : public StyleRuleBase {
 public:
  // ...
  // CSSSelectorList 를 가지고 있습니다.
  const CSSSelectorList& SelectorList() const { return selector_list_; }
  // CSSPropertyValueSet 를 가지고 있습니다.
  const CSSPropertyValueSet& Properties() const;

순서대로 정리하자면 다음과 같은 순서로 동작한다고 볼 수 있습니다.

  1. Document 가 만들어지고 HTML 파일이 해석되어 DOM 트리가 생성됩니다.
  2. CSSParser 로 CSS 파일을 해석하여 StyleRule 을 담은 StyleSheetContents 를 준비합니다.
  3. Document 가 스타일을 업데이트할 때 StyleResolver 에게 준비된 StyleSheetContents 들을 전달합니다.
  4. StyleResolver 는 전달받은 StyleSheetContents에 있는 StyleRule 들로 ComputeStyle 을 생성합니다.

참고

Life of a pixel (Chrome University 2019)
Life of a pixel
How Blink works

반응형