WEB+DB PRESS plusシリーズ[作って学ぶ]ブラウザのしくみ
──HTTPHTMLCSSJavaScriptの裏側

書籍の概要

この本の概要

Webブラウザは,開発者にとってもユーザーにとっても,もはや日常の一部となっているほど身近なソフトウエアですが,近年のブラウザはあまりにも高機能かつ巨大になってしまったため,その仕組みを詳しく理解することは困難です。そこで,シンプルなブラウザをRustを用いて実装することによって,ブラウザ上でWebサイトを開くまでに何が起きているのかを理解することを目的とします。さらに,作成したブラウザを,別冊で解説・実装している自作OSの上で動かすことによって,ブラウザと更にその裏側を理解していきます。

こんな方におすすめ

  • 普段からブラウザを使用していて,その裏側に少しでも興味を持っている方

本書のサンプル

本書の一部ページを,PDFで確認することができます。

本書の紙面イメージは次のとおりです。画像をクリックすることで拡大して確認することができます。

サンプル画像1

サンプル画像2

サンプル画像3

サンプル画像4

サンプル画像5

目次

  • はじめに
  • 本書を読むにあたって
  • 目次

第1章:ブラウザを知る──Webサイトを表示するアプリケーション

ブラウザの役割❶──Webクライアントとしてのブラウザ

クライアント/サーバモデル
  • Webクライアント
  • Webサーバ
インターネットとWeb
  • 通信プロトコル
  • HTTP
  • URLによるリソースの指定
  • DNS

ブラウザの役割❷──レンダリングエンジンとしてのブラウザ

Webサイトの構成
HTML
  • HTMLトークン
  • DOMツリー
CSS
  • CSSトークン
  • CSSOM
  • レイアウトツリー/レンダーツリー

ブラウザの役割❸──JavaScriptエンジンとしてのブラウザ

JavaScript
  • JavaScriptトークン
  • 抽象構文木(AST)
ブラウザAPI

コアの役割を支えるためのさらなる機能

ストレージとキャッシュ
  • ストレージ
  • キャッシュ
拡張機能
PWA
UIにまつわる機能

マルチプロセスアーキテクチャ

プロセス
  • ブラウザプロセス/レンダラプロセス
スレッド
  • UIスレッド/メインスレッド
  • ワーカースレッド
    • [Column] iOS上でのブラウザアプリ

ブラウザのセキュリティ対策

サイト分離(Site Isolation)
同一生成元ポリシー(Same Origin Policy)
  • オリジン間リソース共有(CORS)
コンテンツセキュリティポリシー(CSP)

本書のゴール・注意点

第2章:URLを分解する──リソースを指定する住所

URLとは

スキーム(scheme)
ホスト(host)
ポート番号(port)
パス(path)
クエリパラメータ(searchpart)

URLの構文解析の実装

ライブラリクレートの作成
実装するファイルの追加
Url構造体の作成
parseメソッドの作成
URLの分割の実装
  • スキームの確認
  • ホストの取得
  • ポート番号の取得
  • パス名の取得
  • クエリパラメータの取得
parseメソッドの完成
ゲッタメソッドの追加
    • [Column] clone()はなぜ必要?

ユニットテストによる動作確認

成功ケース
失敗ケース
テストの実行

第3章:HTTPを実装する──ネットワーク通信を支える約束事

HTTPとは

HTTPのバージョンの違い
  • HTTP/1.1の特徴
  • HTTP/2の特徴
  • HTTP/3の特徴
HTTPの構成
  • リクエストラインとは
  • ステータスラインとは
  • ヘッダとは
  • ボディとは

HTTPクライアントの実装

サブプロジェクトの作成
  • サブプロジェクトのCargo.tomlの変更
  • ルートディレクトリのCargo.tomlの変更
  • Features
  • バイナリターゲットの設定
リクエストの構築
  • HttpClientの作成
  • ホスト名からIPアドレスへの変換
  • ソケットアドレスの定義
  • ストリームの構築
  • リクエストラインの構築
  • ヘッダの構築
リクエストの送信
レスポンスの受信
HTTPレスポンスの構築
  • HttpResponse構造体の作成
  • Header構造体の作成
  • エラー構造体の作成
  • 文字列の前処理
  • ステータスラインの分割
  • ヘッダとボディの分割
  • HttpResponse構造体を返す
  • ゲッタメソッドを追加する

ユニットテストによる動作確認

成功ケース
失敗ケース
テストの実行

WasabiOS上で動かす

http://example.comへのアクセス
  • メイン関数の実装
  • 実行
テストサーバとのやりとり
  • テストページの作成
  • ローカルサーバの実行
  • localhost
  • メイン関数の変更
  • 実行

第4章:HTMLを解析する──HTMLからDOMツリーへの変換

HTMLとは

HTMLの構成要素
  • タグ
  • コンテンツ
  • 要素
  • 属性
DOMとは
  • DOMツリーを構成するノード

HTMLの字句解析──トークン列の生成

字句解析とは
トークン化アルゴリズム
実装するディレクトリとファイルの作成
HtmlTokenizer構造体の作成
HtmlToken列挙型の作成
Attribute構造体の実装
ステートマシンの実装
  • Iteratorの実装
  • Data状態の実装
  • TagOpen状態の実装
  • 文字の再利用
  • EndTagOpen状態の実装
  • TagName状態の実装
  • BeforeAttributeName状態の実装
  • AttributeName状態の実装
  • AfterAttributeName状態の実装
  • BeforeAttributeValue状態の実装
  • AttributeValueDoubleQuoted状態の実装
  • AttributeValueSingleQuoted状態の実装
  • AttributeValueUnquoted状態の実装
  • AfterAttributeValueQuoted状態の実装
  • SelfClosingStartTag状態の実装
  • ScriptData状態の実装
  • ScriptDataLessThanSign状態の実装
  • ScriptDataEndTagOpen状態の実装
  • ScriptDataEndTagName状態の実装
  • 一時的なバッファの管理

ユニットテストによる字句解析の動作確認

空文字のテスト
開始タグと終了タグのテスト
属性のテスト
空要素タグのテスト
スクリプトタグのテスト

HTMLの構文解析──ツリーの構築

実装するディレクトリ,ファイルの作成
ノードの構造
  • 循環参照問題
  • ノードのゲッタ・セッタメソッドの実装
ノードの種類
Window構造体の作成
Element構造体の定義
Parser構造体の作成
ツリー構築アルゴリズム
  • Initial状態の実装
  • BeforeHtml状態の実装
  • BeforeHead状態の実装
  • InHead状態の実装
  • AfterHead状態の実装
  • InBody状態の実装
  • Text状態の実装
  • AfterBody状態の実装
  • AfterAfterBody状態の実装
    • [Column] 間違ったHTMLをできる限り描画するブラウザ
要素ノードの追加
開いている要素のスタックの管理
テキストノードの追加
段落タグ(<p>)の追加
  • ElementKind列挙型に段落の追加
  • InBody状態の変更
見出しタグ(<h1>,<h2>)の追加
  • ElementKind列挙型に段落の追加
  • InBody状態の変更
リンクタグ(<a>)の追加
  • ElementKind列挙型に段落の追加
  • InBody状態の変更
テキストの追加
  • InBody状態の変更

ユニットテストによる構文解析の動作確認

PartialEqとEqトレイト
Node構造体にPartialEqトレイトの実装
空文字のテスト
bodyノードのテスト
テキストノードのテスト
複数ノードのテスト

WasabiOS上で動かす

メイン関数の変更
Browser構造体の作成
Page構造体の作成
HttpResponseからDOMツリーを作成
デバッグ用にDOMツリーを文字列に変換
実行

第5章:CSSで装飾する──CSSOMとレイアウトツリーの構築

CSSとは

CSSの構成要素
  • セレクタ
  • プロパティ
  • 宣言ブロック
  • ルール
CSSOM
レイアウトツリー
  • フロー
  • ボックスモデル
描画

CSSの字句解析──トークン列の生成

実装するディレクトリ・ファイルの作成
    • [Column] HTMLを策定するWHATWGとCSSを策定するW3C
CssToken列挙型の作成
CssTokenizer構造体の作成
次のトークンを返すメソッドの実装
  • 記号トークンを返す
  • 文字列トークンを返す
  • 数字トークンを返す
  • 識別子トークンを返す

ユニットテストによる字句解析の動作確認

空文字のテスト
1つのルールのテスト
IDセレクタを持つルールのテスト
クラスセレクタを持つルールのテスト
複数のルールのテスト

CSSの構文解析──CSSOMの構築

実装するディレクトリ・ファイルの作成
CssParser構造体の作成
CSSOMのノードの作成
  • ルートノード(StyleSheet)の作成
  • ルールノード(QualifiedRule)の作成
  • セレクタノード(Selector)の作成
  • 宣言ノード(Declaration)の作成
  • コンポーネント値ノード(Component value)の作成
CSSOMの構築
  • 複数のルールの解釈
  • 一つのルールの解釈
  • セレクタの解釈
  • 複数の宣言の解釈
  • 1つの宣言の解釈
  • 識別子の解釈
  • コンポーネント値の解釈

ユニットテストによる構文解析の動作確認

空文字のテスト
1つのルールのテスト
IDセレクタのテスト
クラスセレクタのテスト
複数のルールのテスト

レイアウトツリーの構築

実装するディレクトリ・ファイルの作成
LayoutView構造体の作成
DOMツリーの特定の要素を取得する関数の作成
LayoutObject構造体の作成
  • ゲッタ/セッタメソッドの追加
  • ブロック要素とインライン要素
  • LayoutPoint構造体の作成
  • LayoutSize構造体の作成
ComputedStyleの作成
  • ゲッタ/セッタメソッドの追加
  • Color構造体の作成
  • FontSize列挙型の作成
  • DisplayType列挙型の作成
  • TextDecoration列挙型の作成
レイアウトツリーの作成
レイアウトオブジェクトのインスタンス化
  • ノードが選択されているかを判断するメソッド
  • CSSルールの適用(Cascading)
  • 指定値の決定(Defaulting)
  • ブロック/インライン要素の最終決定
ノードの位置/サイズ情報の更新
  • 定数の設定ファイル
  • サイズの計算
  • 位置の計算

ユニットテストによるレイアウトの動作確認

LayoutObject構造体にPartialEqトレイトの実装
テスト用の便利関数の作成
空文字のテスト
<body>タグのみのテスト
テキスト要素のテスト
bodyがdisplay:noneのテスト
複数の要素がhidden:noneのテスト

GUI描画のための準備

DisplayItem列挙型の作成
LayoutObjectノードの描画
  • テキストを折り返す
DisplayItemの管理
  • Page構造体にフィールドを追加する
  • receive_responseメソッドを更新する
  • create_frameメソッドを更新する
  • set_layout_viewメソッドを追加する
  • paint_treeメソッドを追加する
  • DisplayItemのベクタのゲッタメソッドを追加する

第6章:GUIを実装する──ユーザーとのやりとり

GUIとは

GUIアプリケーションのウィンドウの作成

サブプロジェクトの作成
  • サブプロジェクトのCargo.tomlの変更
  • 実装するファイルの作成
背景となる白い四角を描画する
ツールバーを描画する
  • 定数を追加する
  • noliライブラリの描画API
  • ツールバーを描画する
UIを開始するメソッドを追加する
アプリケーションの開始時にウィンドウを描画する
  • Cargo.tomlを変更する
  • main.rsを変更する

ユーザーの入力を取得

マウスの位置を取得する
マウスのクリックを取得する
文字を入力する
ツールバーをクリックして入力を開始する
  • InputMode列挙型を作成する
  • URLの文字を保存する
  • URLの情報をツールバーに反映する
  • ツールバーをクリックしてInputModeを変更する
マウスを描画する
  • Cursor構造体を追加する
  • WasabiUIにマウスカーソルを追加する
  • マウスカーソルを描画する

アドレスバーからナビゲーション

Enterキーによってナビゲーションを開始する
コンテンツエリアをリセットする
ネットワークの実装をUIコンポーネントに渡す
  • 関数ポインタ
  • クロージャ
  • handle_urlの実装
  • handle_url関数ポインタを渡す

ページの内容の描画

テキストを描画する
  • 文字を出力するAPIを使用する
  • 描画するための関数を実装する
  • 文字の大きさの型変換を行う
  • update_uiメソッドを更新する
テキストリンクを描画する
  • 文字を出力するAPIで下線を引く
  • update_uiメソッドを更新する
四角を描画する
WasabiOSの上で動かす

リンククリックでナビゲーション

handle_mouse_inputメソッドを更新する
clicked関数を追加する
  • DOMツリーのノードの指定した属性の値を取得する
  • find_node_by_positionメソッドを追加する
  • find_node_by_position_internal関数を追加する
WasabiOSの上で動かす

第7章:JavaScriptを動かす──ページの動的な変更

JavaScriptとは

インタプリタ,JIT,コンパイラ言語
動的なページと静的なページ
  • サーバサイドレンダリングとクライアントサイドレンダリング
ブラウザAPI
ECMAScript

JavaScriptの加算/減算の実装

実装するディレクトリの作成
トークン列挙型の作成
JsLexer構造体の作成
次のトークンを返す関数の実装
  • 記号トークンを返す
  • 数字トークンを返す
ユニットテストによるレキサーの動作確認
  • 空文字のテスト
  • 1つの数字トークンのみのテスト
  • 足し算のテスト
加算・減算の文法規則
  • ECMAScriptで定義されている文法規則
  • 実装する文法規則
抽象構文木(AST)の構築
  • 式と文
  • ノードの作成
  • JsParser構造体の作成
  • Program構造体の作成
  • ASTを構築するメソッドの作成
  • SourceElementの解釈
  • Statementの解釈
  • AssignmentExpressionの解釈
  • AdditiveExpressionの解釈
  • LeftHandSideExpressionの解釈
  • MemberExpressionの解釈
  • PrimaryExpressionの解釈
ユニットテストによるパーサの動作確認
  • 空文字のテスト
  • 1つの数値だけのテスト
  • 足し算のテスト
ランタイムの実装
  • JsRuntime構造体の作成
  • ASTの実行
  • 各ノードを評価するevalメソッドの実装
  • RuntimeValue列挙型の作成
  • RuntimeValueどうしの加算・減算
ユニットテストによるランタイムの動作確認
  • 数値のみのテスト
  • 足し算のテスト
  • 引き算のテスト

JavaScriptの変数の実装

変数,キーワード,文字列トークンの追加
nextメソッドの変更
  • キーワードトークンを返す
  • 変数トークンを返す
  • 文字列トークンを返す
レキサーのユニットテストの追加
  • 変数の定義のテスト
  • 変数の呼び出しのテスト
実装するBNFの確認
  • ECMAScriptでの定義
  • 実装する文法規則
ASTの変更
  • ノードの追加
  • Statementの解釈の変更
  • VariableDeclarationの解釈
  • Identifierの解釈
  • Initialiserの解釈
  • AssignmentExpressionの解釈の変更
  • PrimaryExpressionの解釈の変更
パーサのユニットテストの追加
  • 変数定義のテスト
  • 変数呼び出しのテスト
ランタイムの変更
  • 変数を扱うEnvironment構造体の追加
  • 変数の取得
  • 変数の追加と更新
  • evalメソッドの変更
  • RuntimeValueに文字列の追加
ランタイムのユニットテストの追加
  • 変数定義のテスト
  • 変数呼び出しのテスト
  • 変数変更のテスト

JavaScriptの関数呼び出しの実装

レキサーの変更
レキサーのテストの変更
実装するBNFの確認
  • ECMAScriptでの定義
  • 実装する文法規則
ノードの追加
パーサの変更
  • SourceElementの解釈の変更
  • FunctionDeclarationの解釈
  • FormalParameterListの解釈
  • FunctionBodyの解釈
  • Statementの解釈の変更
  • LeftHandSideExpressionの解釈の変更
  • Argumentsの解釈
  • MemberExpressionの解釈の変更
ASTのユニットテストの追加
  • 関数定義のテスト
  • 引数付き関数定義のテスト
  • 関数呼び出しのテスト
ランタイムの変更
  • evalメソッドの変更
  • Function構造体の追加
ランタイムのユニットテストの追加
  • 関数定義/呼び出しのテスト
  • 引数付き関数定義/呼び出しのテスト
  • ローカル変数のテスト

ブラウザAPIの追加

getElementByIdメソッドのサポート
  • MemberExpressionの解釈の変更
  • ブラウザAPIを呼び出すメソッドの追加
  • 特定のIDの要素を取得する便利関数
  • RuntimeValueにHtmlElementを追加する
  • ランタイムにDOMツリーを渡す
  • ブラウザAPIを呼び出す
textContentによるDOMノードの操作
  • MemberExpressionの解釈の変更
  • AssignmentExpressionの解釈の変更

WasabiOS上で動かす

HTTPレスポンスを受け取ったときにJavaScriptを実行する
  • <script>タグのコンテンツを取得する便利関数
テストページの追加
ローカルサーバの構築

おわりに

  • 索引

著者プロフィール

土井麻未(どいあさみ)

名古屋市立大学芸術工学部でデザインを学びながら,フロントエンド,バックエンドのウェブ開発を独学で学ぶ。名古屋大学大学院情報学研究科では,コンピュータを使って生命の謎に迫る人工生命の分野で研究。低レイヤーの分野に興味があり,趣味の時間でRISC-Vエミュレータを開発中。現在はGoogleでソフトウェアエンジニアとしてブラウザ開発に従事している。