実際のところ「ブラウザを立ち上げてページが表示されるまで」には何が起きるのか

9月15日(土) DNSについて追記しました。バックエンド？今はクラウドがきっと上手くやってくれるので深く考える必要は無いんですよ（知らないので書けません😔）

# 問題のツイート
<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">面接の質問で「ブラウザを立ち上げてページが表示されるまでの仕組みを全て知ってる限り説明してください」ってのをやると結構Web系の知識どれだけあるか分かると思ってる</p>&mdash; 🍛🍺 (@tan_go238) <a href="https://twitter.com/tan_go238/status/1039100159701860353?ref_src=twsrc%5Etfw">September 10, 2018</a></blockquote>

# 解釈
今回は「ChromeのURL欄に入力してからページが表示されるまで」をやります。ブラウザの起動云々はWeb系の話じゃないと信じてます。

# 1. HTTPリクエストが飛ぶ
**[HTTP2のヘッダ圧縮技術](https://qiita.com/0xfffffff7/items/39b944e3845ab3776b63)**に全て書いてありました。
(追記)下のリクエストヘッダはテキストで書かれていますが、実際にはこれをバイナリにしたものが飛んでいるとのことです。[segfo](https://qiita.com/segfo)さんありがとうございます！
> 更にいうと、TLSセッションの確立についても抜けてますし、ALPNの話も、http/2上でのネゴシエーション（サーバプッシュどうするかとか）の話がないのでhttp/2前提であれば少々どころではなく相当抜けがあるかと思います。

らしいです。この辺りは僕の知識がなくて色々抜けているのでこの記事だけを読んで分かったつもりにならないよう気をつけましょう。

```:実際のリクエストヘッダ
:authority: www.google.co.jp
:method: GET
:path: /
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
accept-encoding: gzip, deflate, br
accept-language: ja,en-US;q=0.9,en;q=0.8
cache-control: max-age=0
cookie: 🤫
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36
x-client-data: CJW2yQEIpbbJAQjBtskBCKmdygEI2J3KAQjancoBCKijygEIh6fKAQ==
```
今回はリクエストメソッドがGETなのでヘッダのみですが、POSTやPUTの場合はデータがリクエストボディで送られます。詳しくは**[HTTPメソッド(CRUD)についてまとめた](https://qiita.com/Ryutaro/items/a9e8d18467fe3e04068e)**などを参照してください。

余談ですが、何年か前に**[【パズル１】ほとんどのエンジニアには解けるが、下位10％のダメなエンジニアにだけ解けないパズル?](https://qiita.com/jabba/items/78ba766a4f6849df352c)**で遊んだのを思い出しました。楽しくHTTPメソッドについて学べるパズルなので解いていない人はぜひ解いてみてください。

```:実際のレスポンスヘッダ
HTTP/1.1 200
status: 200
date: Tue, 11 Sep 2018 09:36:30 GMT
expires: -1
cache-control: private, max-age=0
content-type: text/html; charset=UTF-8
strict-transport-security: max-age=3600
content-encoding: br
server: gws
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
set-cookie: 1P_JAR=2018-09-11-09; expires=Thu, 11-Oct-2018 09:36:30 GMT; path=/; domain=.google.co.jp
alt-svc: quic=":443"; ma=2592000; v="44,43,39,35"
```

レスポンスヘッダは上みたいな感じで、このヘッダと一緒に届くレスポンスボディが`<!doctype html>〜〜`というhtmlドキュメントです。

もっと低レイヤーについては**[OSI参照モデルまとめ](https://qiita.com/tatsuya4150/items/474b60beed0c04d5d999)**が分かりやすいです。

(追記)HTTPリクエストからレスポンスまでを飛ばし過ぎという声が多かったのでもうちょっと書きます。

## DNS
DNS？何ですかそれは。

- [ドメイン名のしくみ](https://www.nic.ad.jp/ja/dom/system.html) | 何も知らない人用
- [DNSパケットフォーマットと、DNSパケットの作り方 (1/2)](http://www.atmarkit.co.jp/ait/articles/1601/29/news014.html) | じゃあ実際DNSプロトコルってどんなデータを送ってるの？という人用
- [Cloudflareが1.1.1.1で超高性能DNS始めたし、いっちょ俺のパソコンもDNS over HTTPSしてみる](https://qiita.com/onokatio/items/42fb4a2811600680591b) | 俺は検閲には屈しねぇ！という人用

DNSプロトコルってよく分かりませんしHTTPSで名前解決できたら楽でいいですよね。

**(追記)**なんかはてブとかtwitterとか見てるとDNSに興味津々の方が多いようだったので詳しく調べてみました。

###DNSプロトコル

```:実際のDNSクエリ例
0020   xx xx xx xx xx xx xx xx xx xx c4 db 01 00 00 01
0030   00 00 00 00 00 00 03 77 77 77 06 67 6f 6f 67 6c
0040   65 02 63 6f 02 6a 70 00 00 01 00 01
```
`c4 db`がトランザクションIDです。どのクエリとどの応答が一致しているかを確かめるために使用します。
次の`01 00`は2bitで表すと`0000 0001 0000 0000`となります。まず前半の8bitを見ていきます。

- `0... ....` 先頭の１bitはクエリなら0、レスポンスなら1となるフラグです。
- `.000 0...` 次の4bitは[オペレーションコード](https://tools.ietf.org/html/rfc5395#section-2.2)です。0は通常のクエリです。
- `.... .0..` 管理権限フラグだそうです。DNSが権威サーバーかどうかについてを表しており、リクエストでは意味のないbitです。
- `.... ..0.` <ruby><rb>古</rb><rt>いにしえ</rt></ruby>のDNSは1パケット512byteまでしか扱えなかったらしく、メッセージが512byteを超えた場合には途中で切り捨てられていることを示す必要があったようです。切り捨てられた場合1になります。
- `.... ...1` フルリゾルブを要求するbitです。

後半は全て0ですが、ほとんどがレスポンスでしか意味のないbitのようです。クエリで意味のあるのは`.Z.C ....`の2箇所で、Zは予備bit、Cは[DNSSEC](https://www.nic.ad.jp/ja/newsletter/No43/0800.html)を禁止する要求らしいです。

その後`00 01 00 00 00 00 00 00`と0が続きますが、前から2byteずつ「Questionセクション」、「Answerセクション」、「Authorityセクション」、「Additionalセクション」の数を表しています。今回はQuestionセクションが1つ（www.google.co.jp）なので最初だけ1であとは全部0です。

残りの部分がQuestionセクションです。
`03 77 77 77 06 67 6f 6f 67 6c 65 02 63 6f 02 6a 70 00 00 01 00 01`

- `03` 文字数
- `77 77 77` www
- `06` 文字数
- `67 6f 6f 67 6c 65` google
- `02` 文字数
- `63 6f` co
- `02` 文字数
- `6a 70` jp
- `00` 終端のnull
- `00 01` どのレコードを問い合わせるか？について。1はAレコード
- `00 01` どのクラスを問い合わせるのか？について。クラスって何やねん。1はインターネットを表すらしい...。


```:実際のDNSレスポンス例
0020   xx xx xx xx xx xx xx xx xx xx c4 db 81 80 00 01
0030   00 01 00 00 00 00 03 77 77 77 06 67 6f 6f 67 6c
0040   65 02 63 6f 02 6a 70 00 00 01 00 01 c0 0c 00 01
0050   00 01 00 00 00 0d 00 04 ac d9 a1 43
```

- `c4 db` トランザクションIDです。リクエストと同じ。
- `81 80` `1000 0001 1000 0000`です。前半8bitは上を見てください。
    - `1... ....` サーバーがフルリゾルブ出来たかどうか。
    - `..0. ....` DNSSECに成功していたら1となるフラグ。
    - `.... 0000` 応答コード。0はNo Error

次の8byteは4つのセクション（Question、Answer、Authority、Additional）の数を返すのですが、リクエストと比べてみるとAnswerの数が1増えていることが分かりますね。

- `03 77 ~ 00 01` Questionセクションの中身。リクエストと同じものが返って来ています。
- `c0 0c` `1100 0000 0000 1100`です。
    - 最初の`11..`はポインタであることを示しているらしいです。
    - 残りの`..00 0000 0000 1100`は10進数だと12に相当するのですが、これはDNSレスポンスの先頭から12byte後を示していて、つまり`03 77~`でありwww.google.co.jpです。
- `00 01` タイプがAレコードである事を示しています。
- `00 01` インターネット・クラス（謎）
- `00 00 00 0d` TTL=13
- `00 04` データの長さ
- `ac d9 a1 43` 172.217.161.67

### DNS over HTTPS (DoH)
DNSプロトコルは暗号化されてないので中身が丸見えで危険だし、そもそもDNSプロトコルって覚えるの面倒なんだけど。。。httpsでクエリ飛ばせないの？という状況を解決できるのがDoHです。

```bash:DoHリクエストの例
curl -H 'accept: application/dns-json' 'https://1.1.1.1/dns-query?name=www.google.co.jp&type=A'
```
```json:DoHレスポンスの例
{"Status": 0,"TC": false,"RD": true, "RA": true, "AD": false,"CD": false,"Question":[{"name": "www.google.co.jp.", "type": 1}],"Answer":[{"name": "www.google.co.jp.", "type": 1, "TTL": 18, "data": "216.58.197.131"}]}
```

使いやすすぎワロタ

（追記）1.1.1.1はCloudflareの提供するPublic DNSサービスです。

- https://1.1.1.1/ja-jp/

Wikipediaの[Public DNSサーバーの一覧ページ](https://en.wikipedia.org/wiki/Public_recursive_name_server)などでどのPublic DNSサーバーがDoHに対応しているかが分かります。

##コネクションの確立
イメージ画像
<img width="495" alt="スクリーンショット 2018-09-15 6.12.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/278434/66cd637d-2c0a-0e92-c772-3cb3fcc7adb1.png">

- [【TCP】コネクションの確立までの道のり](https://qiita.com/tatsuya4150/items/ae4d5e95e43ce0670bc0)

# 2. レンダリングエンジンが描画を行う
レスポンスボディで返ってきたhtmlドキュメントを元にブラウザに搭載されているレンダリングエンジンが画面を描画します。

Chromeの場合は**Blink**というエンジンが搭載されています。
![レイヤー図](https://lh5.googleusercontent.com/_q72J6lXdlpPLynuq4KQ664C8aonSlSAPvq-4m69ksN5myflXc0mJJD2ErONQsCnMTWIqfGE7Mdl1UEs3wAy5XdhAok0W7KWZTx7qn5jriV5EjAVH9DSTFsW342ud8Xu7t21B2zR=s800)
(image from [How Blink works](https://docs.google.com/document/d/1aitSOucL0VHZa9Z2vbRJSyAIsAz24kX8LFByQ5xQnUg/edit) by [Kentaro Hara](https://twitter.com/xharaken))

で、このBlinkで行われているレンダリングプロセスが下図です。
![レンダーパイプライン](https://lh6.googleusercontent.com/UcVo6oGrNzw0xjsQ50JxkyACThFwWMe-0hcXpx0kULXyFijIThbvcqL5fqlDpwxuXComhzEjwGRri307iNNcmpQme5sBg1nWIw55RCXZfz4dPvXfuXlSsIbkBkjrYYrYzEkEaBr8=s800)
(image from [How Blink works](https://docs.google.com/document/d/1aitSOucL0VHZa9Z2vbRJSyAIsAz24kX8LFByQ5xQnUg/edit) by [Kentaro Hara](https://twitter.com/xharaken))

htmlドキュメントを解釈してDOMを構築し、styleを当てはめてレイアウトしてから描画です。あとは**[Life of a Pixel 2018](https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit)**を見てください。マジで分かりやすいのでオススメです。これを見た後じゃあ自分で書き直そうという気にはなれませんね。

でも「どうしてもお前が書いた解説が読みたいんだ！！！」という人の為に書きます。正直これ以降の部分は質が微妙です。プロのあなたの助けを求めています。コメント・編集リクエストお待ちしています。

## DOMとは
Document Object Modelの略です。[chromiumのDOMのページ](https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/dom/README.md)に実装などの情報がまとまっています。

「いやDocument Object Modelって何やねん」となると思うのですが、端的にいうと木です。tree。

- [データ構造の選択次第で天国と地獄の差 (3/3)](http://www.atmarkit.co.jp/ait/articles/0809/01/news163_3.html) | tree構造とは

DOMはChromeで「⌘+⌥+i」を押すと出現する検証モードの「element」の項目に並んでいるアイツです。キーボードに⌘キーや⌥キーが無い場合は[こちら](https://www.apple.com/jp/mac/)へどうぞ。

```:(例)google.co.jpのDOM
DOCUMENT (always the root)
 └─HTML
   ├─HEAD
   │  └─...
   └─BODY
      ├─DIV
      │  └─...
      ├─SCRIPT
      └─SCRIPT
```
それぞれのnodeの実装
![container node graph](https://hayato.io/2017/dom/next-sibling.svg)
(image from [core/dom/README.md](https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/core/dom/README.md) by [きらきら☆はやとたん](https://twitter.com/hayatoito))
親はfirstChildとlastChildの情報しか持ってないそうです。連結リストってヤツですね。

レンダリングエンジンはHTML文章を解析し、上記のようなDOMツリーを構築します。

## Style
レンダリングエンジンはCSSを解析してCSS Object Modelツリーを構築します。

```CSS:元のCSS
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
```
⬇️CSSOM
![CSSOM](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/images/cssom-tree.png)
(image from [オブジェクト モデルの構築](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/constructing-the-object-model) by [Ilya Grigorik](https://twitter.com/igrigorik))

DOMとCSSOMを組み合わせるとRender Treeが完成します。
![render tree](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/images/render-tree-construction.png)
(image from [レンダリング ツリーの構築、レイアウト、ペイント](https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction) by [Ilya Grigorik](https://twitter.com/igrigorik))

ちなみにこのCSSOMはJavascriptにAPIが公開されているので**[window.getComputedStyle() で要素のスタイルを取得する](https://qiita.com/amamamaou/items/bb79bec002a6ff033810)**事ができます。

## Layout
レンダリングツリーを元に、個々の要素がウィンドウ内のどこに配置されるかを決めていきます。

![css box model](https://www.w3.org/TR/css-box-3/images/box.png)
(image from [CSS Box Model Module Level 3](https://www.w3.org/TR/css-box-3/#the-css-box-model))

上の画像みたいなCSS Box Modelを使って、親から子へと再帰的に、つまり子は親のcontent内にレイアウトされていきます。CSSでは親要素との相対的な位置関係で記述されていますが、レイアウト処理の出力段階では画面上の絶対的な位置になります。

![Life of a Pixel 2018 (1).png](https://qiita-image-store.s3.amazonaws.com/0/278434/8a1915cf-763b-06c2-edce-ee1583ca041a.png)
(image from [Life of a Pixel 2018](https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit))

![Life of a Pixel 2018.png](https://qiita-image-store.s3.amazonaws.com/0/278434/4fcee3c4-a124-cf4d-aeef-5dd40f987da3.png)
(image from [Life of a Pixel 2018](https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit))

中のコンテンツがスペースをオーバーしてもOKで、その時の選択肢は全て表示する「visible」、オーバーした部分を表示しない「hidden」、そしてスクロールできるようにする「scroll」の3つです。

- [SCROLLING IN BLINK](https://docs.google.com/presentation/d/1pwx0qBW4wSmYAOJxq2gb3SMvSTCHz2L2TFx_bjsvm8E/preview?slide=id.p) | スクロールの仕組みについて。position: stickyって初めて知ったけどめっちゃ便利じゃないですか？もっと早く知りたかった……

後LayoutNGなる新しいレイアウトシステムが開発中らしい。

- [LayoutNG](https://docs.google.com/document/d/1uxbDh4uONFQOiGuiumlJBLGgO4KDWB8ZEkp7Rd47fw4/preview)
- [Next gen rendering engine](https://docs.google.com/presentation/d/11rr_vo7UNS6icihnWZMx45O4y5JiSegZKYLQKA1LAdo/edit?ts=599d038f&pli=1#slide=id.p)

## Paint
表示されるべき要素とそのスタイルが計算できたので、最後にそれぞれの要素を実際のピクセルに落とし込んでいきます。
![Life of a Pixel 2018 (2).png](https://qiita-image-store.s3.amazonaws.com/0/278434/1cb34593-37e3-1855-4787-30096c27538c.png)
(image from [Life of a Pixel 2018](https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit))

DOMツリーとは関係なく、描画リストの順番で描画されていきます。CSSのz-indexを設定する事で順番を明示的に指定できます。

![Life of a Pixel 2018 (3).png](https://qiita-image-store.s3.amazonaws.com/0/278434/81f7068f-6e68-90ba-ed6c-10603e6408e2.png)
(image from [Life of a Pixel 2018](https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit))

また、backgroundやfloatなどの要素によってどれが上に来るのかが変わるので、背後の要素が部分的に前に出て来ることもあるらしいです。

この描画工程が終わると画面にページが表示されます。

## Compositor thread
ブラウザのレンダリング処理は全てmainスレッドという一つのスレッドで行われます。スレッドが一つしか無いというのは、javascriptなどで何か重たい処理を実行してしまいスレッドを埋めてしまった場合にスクロールなど基本的な操作すらおぼつかなくなってしまう危険があるということです。

そこで、スクロールやアニメーション、ズームなど「レイヤーの移動・スケールの変更」で対処できるものに関しては、レンダリングが終わった後に別スレッドへと移動させ、スムーズな操作を可能にしようというのが「[Compositor Thread Architecture](https://dev.chromium.org/developers/design-documents/compositor-thread-architecture)」です。
![Life of a Pixel 2018 (4).png](https://qiita-image-store.s3.amazonaws.com/0/278434/e99fd842-7bea-01df-b165-2e160eaf4e07.png)
(image from [Life of a Pixel 2018](https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit))

# 終
プロの方々のコメントや[編集リクエスト](https://help.qiita.com/ja/articles/qiita-edit-request)をお待ちしています。よろしくお願いします。

この記事よりも**[Life of a Pixel 2018](https://docs.google.com/presentation/d/1boPxbgNrTU0ddsc144rcXayGA_WF53k96imRH8Mp34Y/edit)**を読みましょう。ブラウザのレンダリングについて学ぶなら最高にオススメのスライドです。