📖

📚ブラりザの仕組みを孊ぶ

2021/05/29に公開
3件

Photo by Remotar Jobs on Unsplash
Photo by Remotar Jobs on Unsplash

Webフロント゚ンゞニアたるもの、ブラりザの仕組みに興味を持぀のは自然の摂理です。本蚘事では、私がブラりザの仕組みを孊んでいく過皋を備忘録ずしお残したす。

みんな倧奜きChrome

Webフロント゚ンゞニアに愛されおいるブラりザずいえば、IEChromeですよね。
ブラりザでHTML,CSS,JSの動䜜確認するのは、日垞茶飯事です。
ブラりザによっお動䜜が異なるこずは、Webフロント゚ンゞニアなら呚知の事実です。
じゃあ、なんで動䜜が違うのかずいうず、

  • 「レンダリング゚ンゞンが違うから〜」
  • 「Javascript゚ンゞンが違うから〜」

ぐらいは知っおいるんじゃないかなず思いたす。
じゃあ、そのレンダリング゚ンゞンっおどういう仕組みで動いおいるのでしょうか。
気になりたすよね。

Chromiumに぀いお

Chromiumも、たぶんご存じの方倚いのかなず思いたすので、簡単に説明したす。

Chromiumは、オヌプン゜ヌスのプロゞェクト名であり、ブラりザ名でもありたす。
Chromeは、Chromiumを元に開発されおいたす。
詳しい説明はChromium - Wikiを芋おください。

オヌプン゜ヌスっおこずは、゜ヌスコヌドが誰でも読めちゃうっおこずですよね。
だったら、ブラりザの動䜜を知るこずができちゃうじゃないですか
わヌい😎

Chromiumのリバヌス゚ンゞニアリング

ではさっそく、Chromiumの゜ヌスコヌドを芋おいきたしょう。

これです。

chromium/src
chromium/src - source.chromium.org

Chromium - Wikiによれば、Chromiumの゜ヌスコヌドは玄3,500䞇行あるそうです。
しかも、蚀語はC++。私はあたりそれを詳しくないのです😞。

実際に゜ヌスコヌドをロヌカルマシン(Macbook Air)ぞチェックアりトし、ビルドをしおみたした。
マシンが貧匱だずいうのもあるんですが、ビルドに半日ぐらいかかっおしたいたした。ヘトヘトです。
これじゃあさすがに、手軜にブラりザの動䜜確認はできそうにないです。

ブラりザの仕組み資料を読む

ちょっず趣向を倉えお、次のような資料を読むこずにしたした。

では、さっそく芋おいきたす。
最初に目に぀くのが、ブラりザの䞻な構成芁玠です。

ブラりザの䞻な構成芁玠
ブラりザの䞻な構成芁玠 - www.html5rocks.com

構成芁玠の内、ナヌザヌむンタヌフェヌス、ブラりザ゚ンゞン、レンダリング゚ンゞンに着目したす。
それぞれ、次の圹割がありたす。

  • ナヌザヌむンタヌフェヌス
    • アドレスバヌや戻る/進むボタンのようなUIを担圓
  • ブラりザ゚ンゞン
    • UIずレンダリング゚ンゞンの間の凊理を敎理
  • レンダリング゚ンゞン
    • 芁求されたコンテンツ(HTMLなど)の衚瀺を担圓

ちなみに、Chromiumのレンダリング゚ンゞンには、webkitを䜿っおいたしたが、blinkに倉わりたした。

ブラりザの基本的なフロヌは、次の図の通りです。

レンダリングの基本的なフロヌ
レンダリングの基本的なフロヌ - www.html5rocks.com

Webkitのメむンフロヌ
Webkitのメむンフロヌ - www.html5rocks.com

  1. Parsing HTML to construct the DOM tree
  2. Render tree construction
  3. Layout of the render tree
  4. Painting the render tree

それぞれ芋おいきたす。


① Parsing HTML to construct the DOM tree

1は、HTMLをレキサ(字句解析. ex:flex)・パヌサ(構文解析. ex:bison)を䜿っおDOMツリヌを構築したす。

レキサでは、ステヌトマシンによっお読み蟌み状態を管理し぀぀トヌクンを識別したす。空癜ずかコメントなどは削陀されたす。

レキサから識別されたトヌクンをパヌサに枡し、構文解析しおいきたす。
HTMLはDTDDocument Type Definitionで文脈自由文法なため、機械的に解析できたす。
ただ、HTMLは寛倧な仕様で、次のようなパタヌンも蚱容するようになっおいたす。

  • <br>の代わりの</br>
  • 迷子のテヌブル
  • 入れ子のフォヌム芁玠
  • 深すぎるタグ階局
  • 配眮に誀りのある html たたは body 終了タグ

パヌサからDOMDocument Object Modelを構築したす。
DOMは、これたでの単なるテキストから、APIを持たせたオブゞェクトモデルを䜜るこずで、
以降はDOMを䜿っお凊理しやすくなりたす。

サンプル マヌクアップのDOMツリヌ
サンプル マヌクアップのDOMツリヌ - www.html5rocks.com

これたではHTMLの話をしおいたしたが、HTMLず䞊行しおCSSも同様に凊理しおいきレンダヌオブゞェクトずいうオブゞェクトを䜜っおいきたす。これは、スタむル情報を付䞎したオブゞェクトになりたす。
基本的に、CSSずHTMLは互いに独立しおいるので、䞊列凊理が可胜です。䟋えば、CSSを凊理したこずで、HTMLが倉化するこずはないはずです。

CSSの解析
CSSの解析 - www.html5rocks.com

ただ、Javascriptは話が違うので、Javascriptが読み蟌たれた時点でHTMLのパヌスを䞭断しおJavascriptのパヌスが開始されたす。
たた、Javascriptが、ただ読み蟌たれおいないスタむルシヌトの圱響を受けそうな特定のスタむルプロパティにアクセスした堎合、Javascriptはブロックされたす。


② Render tree construction

①のDOMずレンダヌオブゞェクトから、レンダヌツリヌを構築したす。
DOMずレンダヌオブゞェクトは、1察1ずいう蚳ではなく、䟋えばhead芁玠や、display:none;の芁玠もレンダヌツリヌに含たれたせん。
レンダヌツリヌの曎新は、DOMツリヌが曎新される床に行われたす。

レンダヌツリヌず察応するDOMツリヌ
レンダヌツリヌず察応するDOMツリヌ - www.html5rocks.com

レンダヌオブゞェクトからスタむルを蚈算するのですが、ちょっず耇雑です。
詳しくは、スタむルの蚈算を芋おください。


③ Layout of the render tree

レンダヌツリヌから、レむアりト情報を蚈算しおいきたす。
レむアりト情報ずは、䜍眮(x,y)ずサむズ(width,height)です。

レンダヌツリヌのルヌトから再垰的にレむアりト情報を蚈算(layoutメ゜ッド)しおいきたす。

  1. 芪レンダラヌが自身の幅を決定したす。
  2. 芪が子を確認しお、
    1. 子レンダラヌを配眮したすxずyを蚭定したす。
    2. 必芁な堎合は子のlayoutメ゜ッドを呌び出したす。これにより、子の高さを蚈算したす。
  3. 芪は子の高さの环積、マヌゞンの高さ、パディングを䜿甚しお、自身の高さを蚭定したす。この高さは芪レンダラヌのさらに芪によっお䜿甚されたす。

※ レむアりト凊理 参考

CSSボックスモデルの図を参考たでに共有しおおきたす。

CSS 基本ボックスモデル
CSS 基本ボックスモデル - developer.mozilla.org


④ Painting the render tree

ようやく描画したす。
どこに描画するかずいう配眮方法に぀いお考えるこずになりたす。
倧きく分けお、3぀に分かれたす。

  • 通垞
    • オブゞェクトはドキュメント内の堎所に埓っお配眮されたす。぀たり、レンダヌツリヌ内の堎所はDOMツリヌ内の堎所ず同様になり、ボックスの皮類や寞法に埓っおレむアりトされたす。
      • position:static,relative
  • フロヌト
    • オブゞェクトは最初に通垞のフロヌのようにレむアりトされおから、巊右のできるだけ遠くに移動されたす。
      • float:right,left
  • 絶察
    • オブゞェクトはレンダヌツリヌ内でDOMツリヌずは異なる堎所に配眮されたす。
      • position:absolute,fixed

※ 配眮方法

配眮方法が分かれば、今床は描画する圢に぀いお考えたす。ブロックボックスずむンラむンボックスです。

ブロックずむンラむンの配列
ブロックずむンラむンの配列 - www.html5rocks.com

ブロックボックスは、短圢の圢であり垂盎に䞊びたす。
むンラむンボックスは、独自の圢を持たず氎平に䞊びたす。

z-indexのようなプロパティでは、スタッキングコンテキストずいう抂念を知る必芁がありたす。
詳しくは、重ね合わせコンテキスト - developer.mozilla.org をご確認ください。


ブラりザを自䜜しおみる

前章では、資料を通しおブラりザの動䜜が理解できたした。
読むだけじゃなく、動かしお理解しおみたいずは思いたせんか
そうです、自䜜しおみたしょう。

Rust補のServoずいうブラりザ゚ンゞンを開発しおいる人が曞いた、次のブラりザ自䜜に関する蚘事がずおも分かりやすいです。

Toyブラりザ゚ンゞン(mbrubeck)のメむンフロヌが、これたでの話ずずおも䌌おいたす。

Toyブラりザ゚ンゞン(mbrubeck)のメむンフロヌ
Toyブラりザ゚ンゞン(mbrubeck)のメむンフロヌ - limpet.net

Style treeは、これたでの話でいうずRender treeだず思いたす。
Toyブラりザ゚ンゞン(mbrubeck)に、次のHTMLずCSSを読み蟌たせるず、䞋蚘の画像のようなアりトプットになりたす。

<!-- https://github.com/mbrubeck/robinson/blob/master/examples/test.html -->
<html>
  <head>
    <title>Test</title>
  </head>
  <div class="outer">
    <p class="inner">
      Hello, <span id="name">world!</span>
    </p>
    <p class="inner" id="bye">
      Goodbye!
    </p>
  </div>
</html>
/* https://github.com/mbrubeck/robinson/blob/master/examples/test.css */
* {
  display: block;
}

span {
  display: inline;
}

html {
  width: 600px;
  padding: 10px;
  border-width: 1px;
  margin: auto;
  background: #ffffff;
}

head {
  display: none;
}

.outer {
  background: #00ccff;
  border-color: #666666;
  border-width: 2px;
  margin: 50px;
  padding: 50px;
}

.inner {
  border-color: #cc0000;
  border-width: 4px;
  height: 100px;
  margin-bottom: 20px;
  width: 500px;
}

.inner#bye {
  background: #ffff00;
}

span#name {
  background: red;
  color: white;
}

Toyブラりザ゚ンゞン(mbrubeck)のアりトプット
Toyブラりザ゚ンゞン(mbrubeck)のアりトプット - github.com/mbrubeck/robinson

次のリンクにある自䜜ブラりザ゚ンゞンは、mbrubeck/robinsonを参考にしお䜜られたものだそうです。

Toyブラりザ゚ンゞン(askerry)に、次のHTMLずCSSを読み蟌たせるず、䞋蚘の画像のようなアりトプットになりたす。
芋たら分かるず思いたすが、ずおも高機胜です。

<!-- https://github.com/askerry/toy-browser/blob/master/examples/demo.html -->
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Browser Test</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <link rel="stylesheet" href="demo.css"/>
    </head>

    <body>
        <div id="page">
            <header class="header">
                <h1>Toy Browser Engine</h1>
            </header>
            <div id="main">
                <div id="navbar">
                    <a href="#" class="navitem">
                        Home
                    </a>
                    <a href="#" class="navitem">
                        About
                    </a>
                    <a href="#" class="navitem">
                        Some random stuff
                    </a>
                    <a href="#" class="navitem">
                        Conclusion
                    </a>
                    <img class="img" src="images/otters.jpg"/>
                </div>
                <div id="content">
                    <h2>What is this?</h2>
                    This is a <b>toy</b> browser engine, implemented for
                    <span>fun </span> <img class="icon" src="images/fun.png"/>
                    and <span>glory <img class="icon" src="images/glory.png"/></span>.
                    <h2>Why would anyone do this?</h2>
                    This seems pretty pointless! But I had a few goals:
                    <ul>
                        <li>Something to build to learn C++</li>
                        <li>Learn more about how browsers work</li>
                        <li>Make something I've never made before</li>
                    </ul>
                    <h2>What can it do?</h2>
                    <p>
                        Currently, the engine can parse a subset of HTML
                        and build a DOM tree. It can also parse a small subset of
                        CSS (sometimes incorrectly) and use simple selector matching
                        to apply styles to elements.
                    </p>
                    <p>
                        It supports <em>very basic</em> rendering of boxes, images, and
                        text with simple block and inline layouts.
                    </p>
                </div>
            </div>
        </div>
    </body>
</html>
/* https://github.com/askerry/toy-browser/blob/master/examples/demo.css */
body {
    font-family: Arial, sans-serif;
    background-color: #BFC0C0;
    color: #253237;
    font-size: 16px;
}
#page {
    padding: 20;
    /* width: 800px; */
    margin: auto;
}

header {
    padding: 10px;
    padding-left: 20px;
    background-color: #434371;
    color: #4acebd;
}

span {
    color: #4acebd;
}

#main {
    background-color: white;
    display: flex;
}

#navbar {
    width: 180px;
    padding: 30px;
    background-color: #4acebd;
    height: 500px;
}

.navitem {
    display: block;
    text-align: center;
    background-color: #434371;
    color: #4acebd;
    margin-top: 5px;
    margin-bottom: 5px;
    padding: 10px;
    border-radius: 4px;
    border-style: solid;
    border-width: 2px;
    border-color: #253237;
}

#content {
    padding: 20px;
    width: 500;
}

.img {
    width: 180px;
}

.icon {
    width: 2em;
}

h2 {
    color: #434371;
}

li {
    margin-bottom: 5px;
}

Toyブラりザ゚ンゞン(askerry)のアりトプット
Toyブラりザ゚ンゞン(askerry)のアりトプット - github.com/askerry/toy-browser

私ずしおは、こちらの方が興味があるので、たずこちらを知り、それをRust版で䜜り盎したいなず思いたす。

C++ を孊ぶ

さお、C++を孊ぶために、次のサむトをざっず眺めおみたす。

自䜜ブラりザの゜ヌスコヌド

askerry/toy-browserのメむンコヌド(main.cc)を茉せたす。

/* https://github.com/askerry/toy-browser/blob/master/src/main.cc */
namespace {

void renderWindow(int width, int height, const style::StyledNode &sn,
                  sf::RenderWindow *window) {
  layout::Dimensions viewport;
  viewport.content.width = width;
  viewport.content.height = height;
  // Create layout tree for the specified viewport dimensions.
  std::unique_ptr<layout::LayoutElement> layout_root =
      layout::layout_tree(sn, viewport);
  // Paint to window.
  paint(*layout_root, viewport.content, window);
}

int windowLoop(const style::StyledNode &sn) {
  // Create browser window.
  std::unique_ptr<sf::RenderWindow> window(new sf::RenderWindow());
  window->create(sf::VideoMode(FLAGS_window_width, FLAGS_window_height),
                 "Toy Browser", sf::Style::Close | sf::Style::Resize);
  window->setPosition(sf::Vector2i(0, 0));
  window->clear(sf::Color::Black);
  // Render initial window contents.
  renderWindow(FLAGS_window_width, FLAGS_window_height, sn, window.get());
  // Run the main event loop as long as the window is open.
  while (window->isOpen()) {
    sf::Event event;
    while (window->pollEvent(event)) {
      switch (event.type) {
        case sf::Event::Closed:
          window->close();
          break;

        case sf::Event::KeyPressed:
          logger::debug("keypress: " + std::to_string(event.key.code));
          break;

        case sf::Event::Resized:
          logger::debug("new width: " + std::to_string(event.size.width));
          logger::debug("new height: " + std::to_string(event.size.height));
          window->clear(sf::Color::Black);
          renderWindow(event.size.width, event.size.height, sn, window.get());
          break;

        case sf::Event::TextEntered:
          if (event.text.unicode < 128) {
            logger::debug(
                "ASCII character typed: " +
                std::to_string(static_cast<char>(event.text.unicode)));
          }
          break;

        default:
          break;
      }
    }
  }
  return 0;
}
}  // namespace
int main(int argc, char **argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, true);

  // Parse HTML and CSS files.
  const std::string source = io::readFile(FLAGS_html_file);
  std::unique_ptr<dom::Node> root = html_parser::parseHtml(source);
  const std::string css = io::readFile(FLAGS_css_file);
  const std::unique_ptr<css::StyleSheet const> stylesheet = css::parseCss(css);

  // Initialize font registry singleton.
  text_render::FontRegistry *registry =
      text_render::FontRegistry::getInstance();

  // Align styles with DOM nodes.
  std::unique_ptr<style::StyledNode> styled_node =
      style::styleTree(*root, stylesheet, style::PropertyMap());

  // Run main browser window loop.
  windowLoop(*styled_node);

  // Delete styled node and clear font registry.
  styled_node.reset();
  registry->clear();
  return 0;
}

次のずおり、これたで孊んできたメむンフロヌず、C++がずおも䌌おいるこずが分かりたす。

  1. HTMLずCSSをパヌス
// Parse HTML and CSS files.
const std::string source = io::readFile(FLAGS_html_file);
std::unique_ptr<dom::Node> root = html_parser::parseHtml(source);
const std::string css = io::readFile(FLAGS_css_file);
const std::unique_ptr<css::StyleSheet const> stylesheet = css::parseCss(css);
  1. 1の結果からStyle tree(Render tree)を構築
// Align styles with DOM nodes.
std::unique_ptr<style::StyledNode> styled_node =
    style::styleTree(*root, stylesheet, style::PropertyMap());
  1. 2の結果からLayout treeを構築
// Create layout tree for the specified viewport dimensions.
std::unique_ptr<layout::LayoutElement> layout_root =
    layout::layout_tree(sn, viewport);
  1. 3をpaintずいう描画
// Paint to window.
paint(*layout_root, viewport.content, window);

Re: ブラりザの仕組み資料を読む

もう䞀床、ブラりザの仕組み: 最新りェブブラりザの内郚構造 を読むず、初めお読んだずきに比べお、深く理解できるんじゃないかなず思いたす。

最埌に

ブラりザの動䜜に぀いお資料や自䜜を通しお理解を深めたした。

ブラりザの動䜜が分かれば、ブラりザに優しいWebフロント゚ンド開発ができるず思いたす。

(今床こそChromiumのリバヌス゚ンゞニアリングができるかもしれたせん。)

その他

Chromiumのアドベントカレンダヌがありたした。参考たでにざっず芋おみるず良いでしょう。
Chromium Browser Advent Calendar 2017

Discussion

tellesbomtellesbom

「ブラりザの仕組みを孊ぶ」の蚘事を芋お、askerry/toy-browserをダりンロヌドし、
ビルドしお芋ようず思いたした。
゚ンゞニアでもなく、興味のあるこずを独孊で勉匷しおいるものです。
askerry/toy-browserペヌゞのInstructionsにbazelずsfmlをむンストヌルし、WORKSPACEファむルのnew_local_repository
を曎新しおsfmlがむンストヌルされおいる堎所を指すように倉曎する
ず曞かれおいたため、たずbazelをむンストヌルしhelloworldのような簡単なものをビルドするこずができたした。
それからいろいろサむト等芋おたしたが、toy-browserをビルドするこずができたせん。
初心者のため、sfmlをむンストヌルするずいうこず自䜓もよくわかっおいないのかもしれたせん・・・
具䜓的に䜕をすればビルドするこずができるのでしょうか
ちなみにこの蚘事は参考になりたすか
環境はmacOS Monterey ver12.4です

tellesbomtellesbom

そうですか。Github自䜓やったこずがないからそっからですね。
ちょっずやっおみたす。
たた行き詰たったら質問するかもです。