ブラウザレンダリングの仕組みについて勉強してみた
WEBフロントエンドハイパフォーマンスチューニングの2章
なぜ仕組みを学ぶのか
ブラウザのレンダリングエンジンの内部処理は複雑で単にコードを書いているだけではわからないようになっています。
仕組みを知っておく利点
パフォーマンス上の問題となる書き方を未然に防ぐことができる。
パフォーマンス上の問題となる書き方をした時にどのあたりがボトルネックになっているか推測しやすい。
最適化を施すときには、どのテクニックをどのように適用すればボトルネックを解消できるか判断しやすくなります。
レンダリングエンジンの流れを理解することは重要であると同時にコードを書いている時はあまり意識できない上に流れが複雑。
ブラウザの仕組み
ブラウザは、いくつかのソフトウェアコンポーネントによって構築されている。
ブラウザ内のコンポーネント
レンダリングエンジン
JavaScriptエンジン
レンダリングエンジンとは
ブラウザの内部で利用されるHTMLの描画エンジンのこと。HTMLや画像ファイルやCSSやJavaScriptなどの各種リソースを読み取って画面上のピクセルとして描画するソフトウェアコンポーネント。
table: レンダリングエンジンの種類
Google Chrome Blink
Mozilla Firefox Gecko
Safari Webkit
Internet Explorer Trident
Microsoft Edge EdgeHTML
Opera Blink
Vivaldi Blink
※ レンダリングエンジンはブラウザそのものではない。
ランタイム内にレンダリングエンジンを組み込むことで、アプリの描画にHTMLなどの技術を利用できるようになったのがElectronなどのハイブリッドアプリ。
JavaScriptエンジンとは
その名の通り、JavaScriptの実行環境を提供するソフトウェアコンポーネント。
table: JavaScriptエンジンの種類
Google Chrome Blink V8
Mozilla Firefox Gecko SpiderMonkey
Safari Webkit Nitro
Internet Explorer Trident Chakra
Microsoft Edge EdgeHTML Chakra
DOMツリーやCSSOMツリーなどの内部のオブジェクトやAPIに対して、JavaScriptからアクセスできるようにするバインディングが提供される。また、Google ChromeやMozilla Firefoxで利用されている拡張機能を実行するのにも利用されている。
※CSSOMツリーは、CSS Object Modelを意味する略語であり、ブラウザでロードされたCSSのツリー構造を保持する仕組みです。
DOMツリー
https://gyazo.com/ac18e5405ba6ca3397c740950fcf453a
CSSOMツリー
https://gyazo.com/d06742c0265b5eb345db73cae821b715
ブラウザのレンダリングの流れ
大まかな流れ
Loading => Scripting => Rendering => Painting
の四つの工程があり、四つの工程のことをフレーム(Frame)と呼びます。
さらに詳細に書くと
Loading
Download
Parse
Scripting
Rendering
Caluculate Style (スタイルの計算)
Layout
Painting
Paint
Rasterise (ビットマップ化)
Composite Layers (レイヤーの合成)
Loading
Loading <- ここ
Download
Parse
Scripting
Rendering
Caluculate Style (スタイルの計算)
Layout
Painting
Paint
Rasterise (ビットマップ化)
Composite Layers (レイヤーの合成)
まず行われるのがリソース読み込み(Loading)と呼ばれる処理です。主に次の二つの処理があります。
Download
HTMLファイルなどのリソース(HTML, CSS, JavaScriptファイル、画像など)をサーバーからダウンロードする
Parse
リソースをパース(構文解析)して、レンダリングエンジンの内部表現に変換される。
例えば、HTMLはDOMツリーにCSSはCSSOMツリーに変換される。
HTMLの読み込み
ブラウザは与えられたwebページのURLを元にHTTPリクエストを送信し、HTTPレスポンスとしてHTMLを取得します。
読み込んだHTMLを解釈してDOMツリーを構築する。
1. 字句解析によるトークンのリスト化
レンダリングエンジンはHTMLを受け取りつつ、字句解析によるトークン化を行う。
2. 構文解析による構文木の構築
トークンの列を解析し、木構造のデータにする。
3. 構文木内にあるJavaScriptを実行しつつDOMツリーの構築
HTMLパーサーは構文木内に含まれるJavaScriptを同期的に実行していく。
code: html
<body>
<p><script>
document.write('Hello World!')
</script></p>
</body>
文字列を書き出すメゾッド。DOMツリーは、JavaScriptの実行結果に依存する。
つまり、HTMLをDOMツリーに変換する過程の中でJavaScriptの実行して、その結果、 document.write()で書き出された文字列があれば同期的にトークンの列に追加していく。
CSSの読み込み
レンダリングエンジンは、link要素を使った外部CSSファイルの宣言を見つけるとそのCSSファイルの取得・読み込みを行います。また、Style要素を使ってHTMLファイル内部に埋め込まれたCSSの読み込みを行います。
読み込まれたCSSはパースされてCSSOMツリーに変換される。
Scripting
Loading
Download
Parse
Scripting <- ここ
Rendering
Caluculate Style (スタイルの計算)
Layout
Painting
Paint
Rasterise (ビットマップ化)
Composite Layers (レイヤーの合成)
レンダリングエンジンは、JavaScriptのコードをJavaScriptエンジンに引き渡して実行させます。
JavaScriptの実行の流れ
JavaScriptコード -> 字句解析 -> トークン列 -> 構文解析 -> 抽象構文木 -> コンパイル ->実行可能コード -> 実行
JavaScriptのソースコードを字句解析を通じてトークンの列に変換します。次にトークンの列を、構文解析にかけて抽象構文木に変換します。そして抽象構文木を実行可能な形式にコンパイルする。
どのようなコンパイル処理が行われるかはJavaScriptエンジンによってまちまち、、、
多くはJITコンパイル型の実装
コンパイラがコードをCPUが直接解釈できる機械語に変換します。
他には、処理系内部の仮想マシン用コードへの変換する処理や両方を用いた処理(トレージング JIT)
実行可能な形式にコンパイルされたJavaScriptのコードは、処理系内部の仮想マシン、もしくはCPUで実行されます。
※JavaScript内では、DOM APIを通じてDOMツリーを操作できます。DOMツリーを操作すると、後続するRenderingフェーズや Paintingフェーズを引き起こすことがあります。また、Ajaxを利用して外部のリソースを取得するとLoadingフェーズを引き起こします。
Rendering
Loading
Download
Parse
Scripting
Rendering <- ここ
Caluculate Style (スタイルの計算)
Layout
Painting
Paint
Rasterise (ビットマップ化)
Composite Layers (レイヤーの合成)
Caluculate Style(スタイルの計算)
CSSOMツリー内をすべて参照して、CSSルールのCSSセレクタのマッチング処理がこのときに行われます。
すべてのDOM要素に対してCSSルールのCSSセレクタがマッチするかを総当たりで試行します。個別のDOM要素に対して、どのようなCSSプロパティが適用されるか判断。
code: css
div.my-button, button {
color: white;
}
div.my-buttonとbuttonがそれぞれCSSセレクタ、波括弧に囲まれた部分がCSSルールセットの宣言ブロックです。
DOMツリーの中にDOM要素が100個あり、CSSルールセットが50個あった場合
マッチング処理は100 * 50 = 5000回のマッチング処理が行われる。
Layout
DOM要素に当たるCSSプロパティを算出した後、レンダリングエンジンはDOMツリー内の全てのノードの視覚的なレイアウト情報の計算、レイアウトを行います。
レイアウト情報
要素の大きさ
要素のマージン
要素のパディング
要素の位置
要素のz軸の位置
ここまでの処理をまとめるとRenderingはDOMツリーとCSSOMツリーをくっつけて描画できる状態になる(個人解釈)。Render Treeという。
https://gyazo.com/87c427b8669d152fc3775211cdc2dba8
Painting
Loading
Download
Parse
Scripting
Rendering
Caluculate Style (スタイルの計算)
Layout
Painting <- ここ
Paint
Rasterise
Composite Layers (レイヤーの合成)
このフェーズでようやくレンダリングエンジンは実際のピクセル描画してユーザーの目に触れます。
Paint
PaintではRender Treeを元にDisplay Listと呼ばれる2Dグラフィックエンジン向けの命令の列を生成します。
※グラフィックエンジンはブラウザの実装によって様々。例えばBlinkだったらskiaというGoogleによって管理されているオープンソース。skiaはFirefoxにも使われてる。
Rasterise
ラスタライズでは、命令の列を用いて実際にピクセル(ビットマップ)へと描画します。
この時、レイヤーと呼ばれる単位で一枚一枚描画される。
レイヤーが生成されるのは次のような条件がある時
要素がposition: absoluteなスタイルプロパティが適用されている
要素がposition: fixedなスタイルプロパティが適用されている
要素がtransform: translate3D(0px, 0px, 0px)などのGPUで描画・合成されるCSSプロパティを持っている
要素にopacityCSSプロパティが適用されており、透過して背後のコンテンツが表示される必要がある
このようなレイヤーという単位で一度実際のピクセル描画をするのは、再レンダリングするときにレイヤーを再利用することが可能となり素早く再レンダリングすることができるようになるから
Composite Layers (レイヤーの合成)
Composite Layersでは、ピクセルにしたレイヤーを合成して最終的なレンダリング結果を生成。
CPUによる合成とGPUによる合成があり基本的にCPUによる処理だが、3D系の奥行きのz軸を伴う変形処理はGPUの方が処理が早く、3D変形関数が利用されるときにレイヤーを生成し、GPUにテクスチャとして転送した後GPU上で変形処理の計算を行って合成します。
これでURLを与えられたレンダリングエンジンがコンテンツを表示できました!
補足:
一度描画された後もユーザーやブラウザのなんらかのアクションやJavaScript内のイベントなどによって再レンダリングされます。再レンダリングと言ってもこれまでの工程を全て繰り返すわけではなく、構築した内部表現のオブジェクトをなるべく使い
ます。
onuma.icon
昔僕も勉強会でやりましたね
andsaki.icon えっ、まんま同じじゃないですか笑
umamichi.icon html parse, script 実行などの順番