28. 全部つなぐ — main.rs、ローカルサーバー、途中の木を覗くオプション
完成版の は、 かファイルを受け取り、 に HTML を渡し、文字のを表示します。Page::load の中は 5 段で、HTML → (E 章)、DOM の <style> → (F 章)、DOM の <script> → 実行(H 章)、DOM + CSSOM → と座標(G 章)、箱の木 → (G 章)です。URL のときはその前に B 章と C 章が走ります。
手元で動かす手順です。zip を展開するか GitHub の rust-tiny-browser を git して、(78 本)。 -- sample/index.html で同梱のページが描けます。別の端末で sample フォルダに入り 8000 を動かし、cargo run -- http://localhost:8000/index.html とすると、C 章の経路で同じページが取れます(Python の簡易サーバーは HTTP/1.0 と で返します)。cargo run -- http://example.com/ も通ります(こちらは )。
途中の木を覗くオプションを用意してあります。--tokens は D 章の列、--dom は E 章の DOM、--layout は G 章の箱の木(座標つき)、--display-list は表示リスト、--headers は C 章のレスポンス。自分で書いた HTML を --file で渡して、どの段で何が起きるかを追ってください。たとえば <p> の閉じ忘れを書いて --dom、 を書いて --layout、長い文を書いて --display-list、という具合です。
ここから先に足すなら、仕様の節とレッスンの対応表が道しるべになります。文字参照なら HTML Standard 13.2.5.72 以降のを D 章に、結合子つきなら parse_selector_list とマッチングを F 章に、margin / padding なら と layout_block を G 章に、if / for / 配列なら ・・runtime の 3 か所を H 章に。どれも「仕様の該当節を読み、同じ名前で の腕か関数を足す」形です。
本物のブラウザでは: は「JavaScript の実行、DOM、CSS、レイアウト計算を含む重要なことの大半がで起きる」単一スレッドの型で(platform/scheduler/TaskSchedulingInBlink.md)、仕事は 付きのタスクとしてフレームのタスクランナーに積まれ、入力タスクが最優先、背景タブは凍結や間引きがかかります。文書の状態は DocumentLifecycle(core/dom/document_lifecycle.h)で管理され、スタイル → レイアウト → の更新は HTML Standard のの「」で 1 フレームごとに回ります。この回の Page::load() が 1 回だけ通す流れを、本物は仕事が来るたびに必要な段からやり直します。
開発のどこに活かすか: 全体を一度つないだあとに「途中の木を出すオプション」を持っておくのは、業務のパイプライン処理でも効きます。段ごとの出力を見られると、不具合がどの段のものかを切り分ける時間が桁で変わります。また、仕様の節番号をコードのコメントに書いておく習慣は、将来の自分や同僚が「なぜこの分岐があるか」を仕様に戻って確かめられるようにします。
点線の付いた語はタップすると説明が出ます。用語集を開く →
サンプルコード(フレームワーク環境が必要なため表示のみ)
// src/main.rs
//! コマンドラインの入口。
//!
//! ```text
//! tinybrowser http://localhost:8000/index.html # HTTP で取ってきて描く
//! tinybrowser --file page.html # ローカルのファイルを描く
//! 追加オプション: --dom(DOM を表示) --tokens(HTML のトークンを表示) --layout(箱の木を表示)
//! --display-list(表示リストを表示) --headers(HTTP のヘッダーを表示)
//! ```
use std::process::ExitCode;
use tinybrowser::browser::Page;
use tinybrowser::html::dom::Node;
use tinybrowser::html::tokenizer::HtmlTokenizer;
use tinybrowser::renderer::layout::dump_layout;
use tinybrowser::renderer::paint::dump_display_list;
use tinybrowser::{http, url};
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
let flags: Vec<&str> = args.iter().filter(|a| a.starts_with("--")).map(|s| s.as_str()).collect();
let positional: Vec<&str> = args.iter().filter(|a| !a.starts_with("--")).map(|s| s.as_str()).collect();
let html = if flags.contains(&"--file") {
let Some(path) = positional.first() else {
eprintln!("使い方: tinybrowser --file <path.html>");
return ExitCode::from(2);
};
match std::fs::read_to_string(path) {
Ok(s) => s,
Err(e) => {
eprintln!("読めません {path}: {e}");
return ExitCode::from(1);
}
}
} else {
let Some(target) = positional.first() else {
eprintln!("使い方: tinybrowser <http://host[:port]/path> | --file <path.html>");
return ExitCode::from(2);
};
let parsed = match url::parse(target) {
Ok(u) => u,
Err(e) => {
eprintln!("URL が不正です: {e}");
return ExitCode::from(1);
}
};
println!("GET {} (host={} port={} path={})", parsed, parsed.host, parsed.port, parsed.request_target());
let response = match http::fetch(&parsed) {
Ok(r) => r,
Err(e) => {
eprintln!("{e}");
return ExitCode::from(1);
}
};
println!("{} {} {}", response.version, response.status_code, response.reason_phrase);
if flags.contains(&"--headers") {
for h in &response.headers {
println!(" {}: {}", h.name, h.value);
}
}
response.body
};
if flags.contains(&"--tokens") {
println!("── HTML トークン ──");
for t in HtmlTokenizer::new(&html) {
println!("{t:?}");
}
}
let mut page = Page::new();
page.load(&html);
if flags.contains(&"--dom") {
println!("── DOM ──");
if let Some(doc) = &page.document {
print!("{}", Node::dump(doc));
}
}
if flags.contains(&"--layout") {
println!("── 箱の木 ──");
if let Some(root) = &page.layout_root {
print!("{}", dump_layout(root));
}
}
if flags.contains(&"--display-list") {
println!("── 表示リスト ──");
println!("{}", dump_display_list(&page.display_list));
}
for line in &page.console {
println!("[console] {line}");
}
for e in &page.script_errors {
eprintln!("[script error] {e}");
}
println!("{}", page.ascii());
ExitCode::SUCCESS
}
手元で動かす(配布物)
- 完成版のプロジェクト(tinybrowser.zip)展開して cargo test / cargo run
- GitHub: kazumasamatsumoto/rust-tiny-browser ↗git clone でも同じもの。手を入れて差分を見るならこちら
- 試し用のページ(sample.html)
図やコードに出てくる用語(タップで説明)
学んだ概念を、素朴な例から実務寄りの例まで3つの具体例で確認します。
公式ドキュメントで詳しく ↗