Populating the page: how browsers work

This translation is in progress.

ユーザーは、読み込みが速く、スムーズにインタラクションできるコンテンツによるウェブのエクスペリエンスを求めています。したがって、開発者はこのふたつの目標を達成するために努力しなければいけません。

どうやって性能そして体感性能を改善するか理解するために、ブラウザーがどのように動作するか理解することが役に立ちます。

Overview

速いサイトはより良いユーザーエクスペリエンスをもたらします。ユーザーは読み込みが速く、スムーズに対話できるコンテンツによるエクスペリエンスを求め、期待しています。

ウェブの性能におけるふたつの大きな課題は、レイテンシーに関する諸問題と、多くの場合ブラウザーはシングルスレッドであるという事実に関する諸問題を理解することです。

レイテンシーは、速い読み込みを実現するために克服しなければならない主要な脅威です。読み込みを速くするための開発者の目標は、リクエストされた情報をできる限り速く送信すること、もしくは、少なくともとても速いように見せることです。ネットワークのレイテンシーとは、バイト情報をコンピューターまで送信するのにかかる時間のことです。ウェブの性能において私たちがやるべきことは、ページのができるだけ素早く読み込まれるようにすることです。

ほとんどの場合、ブラウザーはシングルスレッドだと考えられます。スムーズなインタラクションを実現するための開発者のゴールは、滑らかなスクロール、タッチ操作への反応など、適切なサイトのインタラクションを実現することです。メインスレッドが全ての処理を時間内に完了し、さらに常にユーザーのインタラクションをハンドリングできるよう保証するために、描画時間が鍵となります。ウェブの性能は、ブラウザーのシングルスレッドとしての特性を理解し、メインスレッドの責務を最小限に抑え、可能かつ適切な場合には、描画のスムーズさとインタラクションへの即時の反応を保証することによって改善できます。

ナビゲーションはウェブページを読み込む最初の一歩です。ユーザーがURL をアドレスバーに入力したり、リンクをクリックしたり、またはフォームを送信したりしてページをリクエストする時、常にそれが発生します。

ウェブの性能に関する目標の一つは、ナビゲーションが完了するまでにかかる時間を最小限に抑えることです。理想的な状態では、一般的にこの時間が長すぎることはありませんが、レイテンシーと帯域幅が遅れを発生させる原因となります。

DNS Lookup

ウェブページへのナビゲーションの最初の一歩は、そのページのアセットがどこにあるか見つけることです。たとえばあなたが https://example.com へアクセスする場合、その HTML のページは IP アドレスが93.184.216.34のサーバーに存在します。あなたがこれまでに一度もこのサイトを訪れたことがなかったなら、DNS ルックアップが必要となります。

ブラウザーは DNS ルックアップをリクエストし、そのリクエストは最終的にネームサーバーによって処理され、ネームサーバーが IP アドレスを回答します。この最初のリクエストの後、回答された IP アドレスはしばらくの間キャッシュされる場合が多く、ネームサーバーへ再接続する代わりにキャッシュから IP アドレスを取得することによってその後に続くリクエストの速度を向上します。

DNS ルックアップは、一般的に、一回のページ読み込みの中でホスト名ごとに一回だけ必要となります。しかし、DNS ルックアップは要求されたページが参照する個別のホストネームそれぞれに対して実行される必要があります。あなたが必要とするフォントや画像、スクリプト、広告、メトリクスの全てが異なるホスト名を持つ場合には、それぞれに対する DNS ルックアップが必要です。

Mobile requests go first to the cell tower, then to a central phone company computer before being sent to the internet

これはとくにモバイルネットワークにおいて性能上の問題となる場合があります。ユーザーがモバイルネットワーク上に存在する場合、それぞれの DNS ルックアップは、権威 DNS サーバーへ到達するために、電話機から基地局へ送信されなければなりません。電話機と基地局、そしてネームサーバー間の距離によって大きな遅延が発生する場合があります。

TCP Handshake

いったん IP アドレスがわかれば、ブラウザーは TCP 3ウェイハンドシェイクを通じてサーバーとのコネクションを作成します。この仕組みは、通信を意図する2つの主体が—この場合はブラウザーとウェブサーバーが—、データを送信する前に、主に HTTPS を用いて、ネットワーク TCP ソケットコネクションのパラメーターを交換できるように設計されています。

TCP の3ウェイハンドシェイクは、しばしば、"SYN-SYN-ACK"、またはより正確に、SYN、SYN-ACK、ACK と呼ばれます。それは、2つのコンピューターの間で TCP のセッションを開始するために、TCP によって3つのメッセージが送受信されるためです。すなわち、これは各サーバー間で3回以上のメッセージのやりとりが必要であり、そのためのリクエストが生成されなければいけないことを意味します。にk

TLS Negotiation

HTTPSによって確立される安全なコネクションでは、もう一つのハンドシェイクが必要となります。このハンドシェイク、より正確に言うと TLS ネゴシエーションは、通信の暗号化に使用する暗号の種類を決定し、サーバーを認証し、実データの送信が始まる前に安全な通信の準備ができていることを確認します。この処理は、コンテンツへのリクエストが実際に送信される前に、さらに3回のラウンドトリップを追加で必要とします。

The DNS lookup, the TCP handshake, and 5 steps of the TLS handshake including clienthello, serverhello and certificate, clientkey and finished for both server and client.

ページの読み込みにかかる時間は長くなっていしまいますが、ブラウザーとサーバーの間で送信されるデータが第三者に解読されないという安全な通信はその代償を払うに値するものです。

8回のラウンドトリップを経て、ブラウザーはようやくリクエストを送ることができます。

Response

ウェブサーバーへのコネクションが確立されると、ブラウザーはユーザーに代わって最初の HTTP GET リクエストを送信します。ウェブサイトであれば、多くの場合、その対象はひとつの HTML ファイルです。リクエストを受け取ったサーバーは、適当なレスポンスヘッダーと HTML のコンテンツを返します。

<!doctype HTML>
<html>
 <head>
  <meta charset="UTF-8"/>
  <title>My simple page</title>
  <link rel="stylesheet" src="styles.css"/>
  <script src="myscript.js"></script>
</head>
<body>
  <h1 class="heading">My Page</h1>
  <p>A paragraph with a <a href="https://example.com/about">link</a></p>
  <div>
    <img src="myimage.jpg" alt="image description"/>
  </div>
  <script src="anotherscript.js"></script>
</body>
</html>

この最初のリクエストに対するレスポンスは、受信データの最初のバイト (First Byte) を含んでいます。Time to First Byte (TTFB) は、ユーザーがリクエストを実行した時点から—たとえば、リンクをクリックした時点から—HTMLの最初のパケットを受信するまでの時間を指します。最初に受信するパケットのサイズは一般的に 14KB です。

上記の例ではリクエストされた内容は明らかに 14KB 未満です。しかし、この後説明するように、リンクされたリソースは、ブラウザーが解析処理の中でそのリンクにたどり着くまでは、リクエストされることはありません。

TCP Slow Start / 14kb rule

最初に受信するパケットはおそらく14KB でしょう。これには、(glossary('TCP slow start','TCP スロースタート')}} という、ネットワークコネクションのスピードをコントロールするアルゴリズムが影響しています。スロースタートは、ネットワークの最大の帯域幅が確定するまで徐々に送信するデータ量が増やします。

TCP スロースタートでは、最初のパケットを受信した後、サーバーは次のパケットのサイズを 28KB まで倍増させます。さらに後続のパケットは、事前に決めた上限に達するか、ネットワークの輻輳を検知するまでサイズを増やします。

TCP slow start

もしあなたが最初のページ読み込みに関する 14KB ルールを聞いたことがあるなら、TCP スロースタートがその理由です。そしてそれはウェブの性能の最適化がこの最初の 14KB にフォーカスする理由でもあります。TCP スロースタートは、ネットワークの輻輳を防ぐために、ネットワーク性能に対して適正なデータ転送速度を徐々に探り当てます。

Congestion control

サーバーが TCP パケットとしてデータを送信する一方、ユーザークライアントは確認応答、ACK を返すことでデータが配送されたことを確認します。コネクションはハードウェアやネットワークの状況によって性能上の制限を受けます。もしサーバーが大きすぎるパケットを速すぎる速度で送信したならば、ネットワークはダウンしてしまうでしょう。確認しておきたいことは、確認応答がない場合があるということです。サーバーはこれを missing ACK として処理します。輻輳制御アルゴリズムは、パケットを送信して確認応答を受け取るこのフローを使って送信レート (send rate) を決定します。

Parsing

データの最初の塊を受け取ったら、ブラウザーは受信した情報のパース処理を始めることができます。パース処理は、ネットワークを通じて受け取ったデータを DOMCSSOM に変換するステップです。DOM と CSSOM は、レンダラーがページをスクリーンへ描画するために利用されます。

DOM はブラウザー向けマークアップの内部的な表現です。DOM は外部に公開され、 JavaScript の様々な API を通じて操作することができます。

ブラウザーは、リクエストしたページの HTML 全体が最初の 14KB のパケットより大きかったとしても、手持ちのデータに基づいてパース処理を開始し、サイトを描画しようとします。これが、ウェブの性能を最適化する場合に、ブラウザーがページの描画を始めるために必要なすべてのデータ、あるいは少なくともページのテンプレート (最初の描画に必要となる CSS と HTML) を最初の 14KB に含めることが重要となる理由です。何か一つでもスクリーンに描画を行うには、その前に HTML と CSS、JavaScript がパースされなければいけません。

Building the DOM tree

クリティカルレンダリングパスの5つのステップを説明します。

最初のステップは HTML のマークアップを処理し、DOM ツリーを構築することです。 HTML のパース処理は、トークン化とツリーの構築に分かれます。HTML のトークンは開始タグと終了タグ、属性の名前と値を含みます。ドキュメントが正しく構成されていれば、それをパースする処理は単純で、速度も速くなります。パーサーはトークン化された入力情報をドキュメントツリーを構成するドキュメントに変換します。

DOM ツリーはドキュメントの内容を表します。<html> 要素は最初のタグであり、ドキュメントツリーのルートノードとなります。ツリーには、異なるタグ同士の関係と階層構造が反映されます。他のタグの中にネストされたタグは子要素となります。DOM ノードの数が増えるほど、DOM ツリーの構築にかかる時間は長くなります。

The DOM tree for our sample code, showing all the nodes, including text nodes.

パーサーが画像などのノンブロッキングリソースを発見した場合、ブラウザーはそれらのリソースをリクエストし、パース処理を継続します。CSS ファイルに遭遇した場合もパース処理を続けることができます、しかし <script> タグ、とくに async または defer 属性がない場合、は描画処理がブロックされ、HTMLのパース処理が停止します。ブラウザーのプリロードスキャナーが処理の速度を速めますが、それでも過度のスクリプトの使用は重大なボトルネックになり得ます。

Preload scanner

ブラウザーが DOM ツリーを構築する間は、そのプロセスがメインスレッドを占有します。その一方でプリロードスキャナーが処理可能なコンテンツをパースし、CSS や JavaScript、ウェブフォントのような優先度の高いリソースをリクエストします。プリロードスキャナーのおかげで、リクエストするべき外部リソースへの参照をパーサーが見つけるのを待つ必要がなくなります。バックグラウンドでリソースを取得するため、メインのHTMLパーサーが該当のアセットにたどり着いた時には、すでにそれらのリソースが転送中あるいはダウンロード済みとうことが起こり得ます。プリロードスキャナーによる最適化によってブロッキングを減らすることができます。

<link rel="stylesheet" src="styles.css"/>
<script src="myscript.js" async></script>
<img src="myimage.jpg" alt="image description"/>
<script src="anotherscript.js" async></script>

この例では、メインスレッドが HTML と CSS をパースしている間、プリロードスキャナーがスクリプトと画像を探索し、それらのダウンロードを開始します。もし JavaScript の実行順序が重要でないなら、スクリプトがプロセスをブロックしないよことを保証するために async 属性または defer 属性を追加しましょう。

CSS 取得の待ちは HTML のパースあるいはダウンロードをブロックしません。しかし、それは JavaScript をブロックします。その理由は、JavaScript が しばしば CSS プロパティーの影響を問い合わせることに使用されるためです。

Building the CSSOM

クリティカルレンダリングパスの2つめのステップは CSSを処理して CSSOM ツリーを構築することです。CSS のオブジェクトモデルは DOM によく似ています。DOM と CSSOM はどちらもツリーです。この2つは独立したデータ構造を持っています。ブラウザーは、CSS のルールをブラウザーが理解できるスタイルのマップに変換します。ブラウザーは CSS のルールセットを読み取り、CSS セレクターにもとづいて、親、子、兄弟の関係からなるノードのツリーを生成します。

HTMLの場合と同じように、ブラウザーは受信した CSS のルールを自身が実行可能なものに変換しなければいけません。そのために、ブラウザーは HTML をオブジェクトに変換する場合と同じプロセスを CSS に対しても実行します。

CSSOM ツリーはユーザーエージェントのスタイルシートから取得したスタイルを含みます。ブラウザーは、ノードに対して適用されるもっとも一般的なルールからスタートして、より特定されたルールを再帰的に適用し、最終的なスタイルを計算します。つまり、プロパティーの値をカスケードします。

CSSOM の構築はとても高速なため、現在の開発者ツールはそれ自体をユニークな色では表示しません。代わりに、開発者ツールの「スタイルの再計算」は、CSS を解析し、CSSOM ツリーを構築し、再帰的にスタイルを計算するトータルの時間を表示します。ウェブの性能の最適化の観点から言えば、CSSOM を生成するトータルの時間は一般的に1回の DNS ルックアップにかかる時間よりも少ないという点で、それほどの苦労はないと言えます。

Other Processes

JavaScript Compilation

CSS がパースされ、CSSOM が生成される間、JavaScript ファイルを含む他のアセットが(プリロードスキャナーによって)ダウンロードされます。JavaScriptは、インタープリターに処理され、コンパイル、パースを経て実行されます。スクリプトはパース処理によって抽象構文木に変換されます。いくつかのブラウザーエンジンは、抽象構文木をインタープリターへ引き渡し、メインスレッドで実行されるバイトコードを出力します。これが JavaScript のコンパイル処理に当たります。

Building the Accessibility Tree

ブラウザーは補助機器で使用されるアクセシビリティーツリーも構築します。アクセシビリティーオブジェクトモデル (AOM) は補助機器向けの DOM のようなものです。ブラウザーは、DOM が更新されるとアクセシビリティーツリーも更新します。アクセシビリティーツリーは補助機能それ自体からは変更可能ではありません。

AOM が構築されるまで、そのコンテンツにスクリーンリーダーでアクセスすることはできません。

Render

Rendering steps include style, layout, paint and, in some cases, compositing. The CSSOM and DOM trees created in the parsing step are combined into a render tree which is then used to compute the layout of every visible element, which is then painted to the screen. In some cases, content can be promoted to their own layers and composited, improving performance by painting portions of the screen on the GPU instead of the CPU, freeing up the main thread.

Style

The third step in the critical rendering path is combining the DOM and CSSOM into a render tree.The computed style tree, or render tree, construction starts with the root of the DOM tree, traversing each visible node.

Tags that aren't going to be displayed, like the <head> and its children and any nodes with display: none, such as the script { display: none; } you will find in user agent stylesheets, are not included in the render tree as they will not appear in the rendered output. Nodes with visibility: hidden applied are included in the render tree, as they do take up space. As we have not given any directives to override the user agent default, the script node in our code example above will not be included in the render tree.

Each visible node has its CSSOM rules applied to it. The render tree holds all the visible nodes with content and computed styles -- matching up all the relevant styles to every visible node in the DOM tree, and determining, based on the CSS cascade, what the computed styles are for each node.

Layout

The fourth step in the critical rending path is running layout on the render tree to compute the geometry of each node. Layout is the process by which the width, height, and location of all the nodes in the render tree are determined, plus the determination of the size and position of each object on the page. Reflow is any subsequent size and position determination of any part of the page or the entire document.

Once the render tree is built, layout commences. The render tree identified which nodes are displayed (even if invisible) along with their computed styles, but not the dimensions or location of each node. To determine the exact size and location of each object, the browser starts at the root of the render tree and traverses it.

On the web page, most everything is a box. Different devices and different desktop preferences mean an unlimited number of differing viewport sizes. In this phase, taking the viewport size into consideration, the browser determines what the dimensions of all the different boxes are going to be on the screen. Taking the size of the viewport as its base, layout generally starts with the body, laying out the dimensions of all the body’s descendants, with each element's box model properties, providing placeholder space for replaced elements it doesn’t know the dimensions of, such as our image.

The first time the size and position of nodes are determined is called layout. Subsequent recalculations of node size and locations are called reflows.  In our example, suppose the initial layout occurs before the image is returned. Since we didn't declare the size of our image, there will be a reflow once the image size is known.

Paint

The last step in the critical rendering path is painting the individual nodes to the screen, the first occurence of which is called the first meaningful paint. In the painting or rasterization phase, the browser converts each box calculated in the layout phase to actual pixels on the screen. Painting involves drawing every visual part of an element to the screen, including text, colors, borders, shadows, and replaced elements like buttons and images. The browser needs to do this super quickly.

To ensure smooth scrolling and animation, everything occupying the main thread, including calculating styles, along with reflow and paint, must take the browser less than 16.67ms to accomplish. At 2048 X 1536, the iPad has over 3,145,000 pixels to be painted to the screen. That is a lot of pixels that have to be painted very quickly. To ensure repainting can be done even faster than the initial paint, the drawing to the screen is generally broken down into several layers. If this occurs, then compositing is necessary.

Painting can break the elements in the layout tree into layers. Promoting content into layers on the GPU (instead of the main thread on the CPU) improves paint and repaint performance. There are specific properties and elements that instantiate a layer, including <video> and <canvas>, and any element which has the CSS properties of opacity, a 3D transform, will-change, and a few others. These nodes will be painted onto their own layer, along with their descendants, unless a descendant necessitates its own layer for one (or more) of the above reasons.

Layers do improve performance, but are expensive when it comes to memory management, so should not be overused as part of web performance optimization strategies.

Compositing

When sections of the document are drawn in different layers, overlapping each other, compositing is necessary to ensure they are drawn to the screen in the right order and the content is rendered correctly.

As the page continues to load assets, reflows can happen (recall our example image that arrived late).  A reflow sparks a repaint and a re-composite. Had we defined the size of our image, no reflow would have been necessary, and only the layer that needed to be repainted would be repainted, and composited if necessary. But we didn't include the image size! When the image is obtained from the server, the rendering process goes back to the layout steps and restarts from there.

Interactivity

Once the main thread is done painting the page, you would think we would be "all set." That isn't necessarily the case. If the load includes JavaScript, that was correctly deferred, and only executed after the onload event fires, the main thread might be busy, and not available for scrolling, touch, and other interactions.

Time to Interactive (TTI) is the measurement of how long it took from that first request which led to the DNS lookup and SSL connection to when the page is interactive -- interactive being the point in time after the First Contentful Paint when the page responds to user interactions within 50ms. If the main thread is occupied parsing, compiling, and executing JavaScript, it is not available and therefore not able to responsd to user interactions in a timely (less than 50ms) fashion.

In our example, maybe the image loaded quickly, but perhaps the anotherscript.js file was 2MB and our user's network connection was slow.  In this case the user would see the page super quickly, but wouldn't be able to scroll without jank until the script was downloaded, parsed and executed. That is not a good user experience. Avoid occupying the main thread, as demonstrated in this WebPageTest example:

The main thread is occupied by the downloading, parsing and execution of a  javascript file - over a fast connection

In this example, the DOM content load process took over 1.5 seconds, and the main thread was fully occupied that entire time, unresponsive to click events or screen taps.

See Also