Read OSS

WebKit を読み解く:アーキテクチャ概要とコードベースマップ

初級

前提知識

  • ソフトウェアエンジニアリングの基礎知識
  • ブラウザの基本的な仕組み(パース・レンダリング・実行)への理解
  • C++ のファイルおよびディレクトリ構造を読み解く力

WebKit を読み解く:アーキテクチャ概要とコードベースマップ

WebKit は Apple のすべてのデバイスで Safari を動かし、Mail・Books・News・App Store のエンジンとしても組み込まれており、GTK および WPE ポートを通じて Linux 上でも動作しています。リポジトリは巨大で、数十のトップレベルディレクトリにまたがる数百万行の C++ コードで構成されています。このコードベースに意味のある形で貢献したり、コードをしっかり読んだりするには、まず全体の地図が必要です。本記事では、その地図を提供します。

リポジトリのレイアウト、ビルドシステムに直接エンコードされた厳格な 6 層の依存関係ヒエラルキー、Web コンテンツをシステムの他の部分から隔離するマルチプロセスセキュリティアーキテクチャについて順に解説します。加えて、macOS・iOS・Linux・PlayStation・Windows 向けのビルドを 1 つのソースツリーから実現する条件付きコンパイルマクロも取り上げます。

リポジトリ構成の全体像

トップレベルのディレクトリ構造は、ソースコード・ツール・テストに明確に分かれています。

ディレクトリ 役割
Source/ すべての本番ソースコード — bmalloc から WebDriver までの全コンポーネント
Tools/ ビルドスクリプト、テストランナー、開発者向けユーティリティ、git-webkit CLI
JSTests/ JavaScriptCore 向けの JavaScript 適合性テストおよびリグレッションテスト
LayoutTests/ HTML/CSS/DOM のレンダリングテスト(WebCore の主要テストスイート)
PerformanceTests/ JetStream や MallocBench を含むベンチマーク群
Configurations/ Apple プラットフォームのビルド向け Xcode .xcconfig ファイル
Source/cmake/ Apple 以外のポートで使用される CMake モジュール群

ルートの CMakeLists.txt はプロジェクトを宣言し、モジュールパスを Source/cmake/ に設定したうえで、Source/ をサブディレクトリとして追加します。

cmake_minimum_required(VERSION 3.20)
project(WebKit LANGUAGES C CXX)
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/Source/cmake")

それ以降のすべてはここから始まります。ルートにある Makefile は薄いラッパーで、プラットフォームに応じて Xcode か CMake に処理を委譲します。

ヒント: リポジトリを初めて読む場合は Source/CMakeLists.txt から始めましょう。add_subdirectory() の呼び出し順が、エンジン全体の目次になっています。

6 層のビルドスタック

Source/CMakeLists.txt を開くと、ビルド順に並んだ依存関係のヒエラルキーが一目でわかります。各 add_subdirectory() 呼び出しは、それより前に登場したライブラリしか参照できません。

graph BT
    bmalloc["bmalloc<br/><i>Memory allocator</i>"]
    WTF["WTF<br/><i>Web Template Framework</i>"]
    JSC["JavaScriptCore<br/><i>JS engine</i>"]
    WebCore["WebCore<br/><i>Web standards</i>"]
    WebKit2["WebKit (WebKit2)<br/><i>Multi-process embedding</i>"]
    WebDriver["WebDriver<br/><i>Automation</i>"]
    
    WTF --> bmalloc
    JSC --> WTF
    WebCore --> JSC
    WebKit2 --> WebCore
    WebDriver --> WebKit2

この構造はビルドファイルの冒頭 4 行に明示されています。

add_subdirectory(bmalloc)
add_subdirectory(WTF)
...
add_subdirectory(JavaScriptCore)

これらは条件なしで常に追加されます。すべてのポートで必要だからです。WebCoreWebKitWebDriver などそれ以降のエントリは ENABLE(...) フラグによって制御されており、JS エンジンのみを含む最小構成のビルドも可能です。

各レイヤーの役割は次のとおりです。

レイヤー ディレクトリ 担当範囲
bmalloc Source/bmalloc/ セキュリティのための型分離 IsoHeap ページを持つカスタムメモリアロケータ
WTF Source/WTF/ テンプレートライブラリ:コンテナ(VectorHashMap)、スマートポインタ(RefRefPtrWeakPtr)、スレッドプリミティブ
JavaScriptCore Source/JavaScriptCore/ 4 段階 JIT コンパイルパイプラインを持つ完全な JavaScript エンジン
WebCore Source/WebCore/ HTML/CSS/DOM のパース・レイアウト・レンダリング、および 30 以上の Web API モジュール
WebKit Source/WebKit/ マルチプロセスアーキテクチャ:IPC、プロセス管理、埋め込み API(WKWebView
WebDriver Source/WebDriver/ W3C WebDriver 自動化プロトコルの実装

この厳格な層構造は意図的なアーキテクチャ上の制約です。WebCore が WebKit2 を呼び出すことはなく、JavaScriptCore が WebCore を呼び出すこともありません。依存関係は常に上方向にのみ流れます。

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

WebKit のアーキテクチャ上の最大の特徴はプロセス分離です。すべての Web ページは、アプリケーションとは別のサンドボックス化されたプロセスで動作します。ページがクラッシュしてもアプリケーションは生き続け、攻撃者がレンダラーの脆弱性を突いても、システムへのアクセスが極めて限られたサンドボックスの内側に閉じ込められます。

flowchart LR
    UI["UI Process<br/>(Safari, Mail)"]
    WC1["WebContent Process<br/>(Tab 1)"]
    WC2["WebContent Process<br/>(Tab 2)"]
    NP["Network Process"]
    GP["GPU Process"]
    
    UI <-->|IPC| WC1
    UI <-->|IPC| WC2
    UI <-->|IPC| NP
    UI <-->|IPC| GP
    WC1 <-->|IPC| NP
    WC2 <-->|IPC| NP
    WC1 <-->|IPC| GP

各プロセスの役割は以下のとおりです。

  • UI Process — アプリケーション本体(Safari、Mail、WKWebView を使ったカスタムアプリ)。アドレスバーやタブ、UI クロームを管理します。信頼されていない Web コードを一切実行しません。
  • WebContent Process — 1 つまたは複数の Web ページの HTML パース・CSS レイアウト・JavaScript 実行・レンダリングを担当します。厳しくサンドボックス化されており、macOS/iOS では WebContentServiceEntryPoint.mm に示されるように XPC 経由で起動されます。
  • Network Process — すべての HTTP/HTTPS リクエスト、Cookie、ストレージを処理します。セッション内のすべての WebContent プロセスで共有されます。
  • GPU Process — WebGL、WebGPU、メディアデコード、コンポジットを管理します。GPU ドライバーの攻撃対象領域をレンダラーから分離します。

Apple プラットフォーム向けの WebContent プロセスのエントリポイントファイルは驚くほどシンプルです。

Source/WebKit/WebProcess/EntryPoint/Cocoa/XPCService/WebContentServiceEntryPoint.mm#L44-L61

この関数はメインスレッドを初期化し、環境変数をクリーンアップしたうえで、XPCServiceInitializer<WebKit::WebProcess> を呼び出します。この 1 つのテンプレートインスタンス化が、WebContent プロセス全体を起動します。

ヒント: 各プロセス型は AuxiliaryProcess という基底クラスを継承しており、IPC 接続の管理とメッセージディスパッチの仕組みが提供されています。詳細はパート 5 で取り上げます。

プラットフォームポートと条件付きコンパイル

WebKit は macOS、iOS、watchOS、tvOS、Linux(GTK および WPE ポート)、PlayStation、Windows 向けのビルドをすべて同一のソースツリーから生成します。コンパイル対象を制御するマクロファミリーは 3 種類あります。

マクロファミリー 役割
PLATFORM(X) 対象 OS またはハードウェア PLATFORM(COCOA)PLATFORM(GTK)PLATFORM(IOS_FAMILY)
ENABLE(X) オン/オフ切り替え可能な Web プラットフォーム機能 ENABLE(WEBGL)ENABLE(FTL_JIT)ENABLE(CSS_SELECTOR_JIT)
USE(X) 差し替え可能な実装の選択 USE(TZONE_MALLOC)USE(LIBWEBRTC)USE(SKIA)

これらのマクロはプラットフォームヘッダファイルで定義され、CMake のキャッシュ変数または Xcode のビルド設定から値が渡されます。すべてのソースファイルは冒頭で #include "config.h" をインクルードしており、他のコードよりも先にプラットフォーム定義が読み込まれます。

CMake パスでは、CMakePresets.json によって名前付きビルド構成が定義されます。たとえば GTK ポートには、Ninja ジェネレータと GTK 固有のキャッシュ変数を設定するプリセットがあります。設定は次のコマンドで行います。

cmake --preset gtk-debug

Apple プラットフォームのビルドでは CMake を使わず、Configurations/ ディレクトリにある .xcconfig ファイルを用いた Xcode ワークスペースを使用します。

flowchart TD
    A[Developer runs build-webkit] --> B{Host platform?}
    B -->|macOS/iOS| C[Xcode workspace<br/>Configurations/*.xcconfig]
    B -->|Linux/Windows/PS| D[CMake + Presets<br/>CMakePresets.json]
    C --> E[Build products]
    D --> E

この二重ビルドシステムは実用的な判断の結果です。Apple のエンジニアは日常的に Xcode で作業しますが、Apple 以外のポートのメンテナーには CMake が必要です。Tools/Scripts/ にある build-webkit スクリプトがプラットフォームを自動検出し、適切なバックエンドに処理を委譲します。

どこに何があるか:ソースディレクトリマップ

Source/ 以下の各コンポーネントディレクトリは、一貫した内部構造に従っています。

graph TD
    Source["Source/"]
    Source --> bmalloc["bmalloc/"]
    Source --> WTF["WTF/wtf/"]
    Source --> JSC["JavaScriptCore/"]
    Source --> WC["WebCore/"]
    Source --> WK["WebKit/"]
    Source --> WD["WebDriver/"]
    Source --> TP["ThirdParty/"]
    
    JSC --> parser["parser/"]
    JSC --> bytecode["bytecode/"]
    JSC --> jit["jit/, dfg/, ftl/, b3/"]
    JSC --> runtime["runtime/"]
    JSC --> heap["heap/"]
    
    WC --> dom["dom/"]
    WC --> css["css/"]
    WC --> layout["layout/"]
    WC --> rendering["rendering/"]
    WC --> Modules["Modules/"]
    WC --> platform["platform/"]
    
    WK --> UIProcess["UIProcess/"]
    WK --> WebProcess["WebProcess/"]
    WK --> NetworkProcess["NetworkProcess/"]
    WK --> GPUProcess["GPUProcess/"]
    WK --> IPC["Platform/IPC/"]

構造を読む際に押さえておきたいポイントがいくつかあります。

  • JavaScriptCore はコンパイルフェーズごとに整理されています:parser/bytecompiler/llint/jit/dfg/ftl/b3/
  • WebCore は Web 標準ごとに整理されています:dom/css/html/svg/ があり、さらに 30 以上の Web API 実装を含む Modules/ ディレクトリがあります。
  • WebKit(マルチプロセスレイヤー)はプロセス種別ごとに整理されています:UIProcess/WebProcess/NetworkProcess/GPUProcess/

Source/ThirdParty/ ディレクトリには、WebKit がベンダリングしている外部依存ライブラリが含まれています。ANGLE(OpenGL ES)、libwebrtc、Skia(Apple 以外のポート向けのオプショナルグラフィックスバックエンド)、BoringSSL などが該当します。

シリーズ後半への橋渡し

この地図があれば、どこに何があり、なぜそのような層構造になっているのかが明確になったはずです。次の記事では基盤レイヤー — WTF と bmalloc — に深く入り込み、リポジトリのほぼすべてのファイルに登場するオーナーシップイディオム(RefRefPtrWeakPtrProtectedThis)を詳しく見ていきます。WebKit のコードを効率よく読むには、これらのパターンを理解しておくことが欠かせません。