『[作って学ぶ]ブラウザのしくみ』
https://gyazo.com/87893eeec8275432e0c586cf743f79e2
2024/11/9
Webブラウザは、開発者にとってもユーザーにとっても、もはや日常の一部となっているほど身近なソフトウェアです。しかし、近年のブラウザはあまりにも高機能かつ巨大になっており、そのしくみを詳しく理解することは困難です。
そこで、シンプルなブラウザをRustで実装することによって、ブラウザ上でWebサイトを開くまでに何が起きているのかを理解することを本書の目的とします。本書は単体でも読み進められますが、2025年に発売予定の姉妹書『[作って学ぶ]OSのしくみ』で解説・実装している自作OSの上で、本書で作成したブラウザを動かすことができます。それにより、ブラウザとさらにその裏側のしくみも理解できます。 おもしろそうmrsekut.icon
はじめに
本書を読むにあたって
目次
第1章:ブラウザを知る──Webサイトを表示するアプリケーション
ブラウザの役割❶──Webクライアントとしてのブラウザ
クライアント/サーバモデル
Webクライアント
Webサーバ
インターネットとWeb
通信プロトコル
HTTP
URLによるリソースの指定
DNS
ブラウザの役割❷──レンダリングエンジンとしてのブラウザ
Webサイトの構成
HTML
HTMLトークン
CSS
CSSトークン
レイアウトツリー/レンダーツリー
ブラウザの役割❸──JavaScriptエンジンとしてのブラウザ
JavaScript
JavaScriptトークン
抽象構文木(AST)
ブラウザAPI
コアの役割を支えるためのさらなる機能
ストレージとキャッシュ
ストレージ
キャッシュ
拡張機能
PWA
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上で動かす
メイン関数の実装
実行
テストサーバとのやりとり
テストページの作成
ローカルサーバの実行
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>タグのコンテンツを取得する便利関数
テストページの追加
ローカルサーバの構築
おわりに
索引