Read OSS

WebKitコードベースのアーキテクチャ概要とナビゲーション

中級

前提知識

  • Webブラウザの基本的な動作(ページ読み込み、HTML/CSSのレンダリング、JavaScriptの実行)への理解
  • C++プロジェクトの構造とビルドシステムに関する基本的な知識

WebKitコードベースのアーキテクチャ概要とナビゲーション

WebKitは、現存する最も歴史ある影響力の大きいオープンソースプロジェクトのひとつです。すべてのAppleデバイスでSafariを動かし、WKWebViewを通じて数百万ものiOSアプリのレンダリングエンジンとして機能し、GTKベースのLinuxデスクトップからPlayStationコンソールまで幅広いプラットフォームで稼働しています。コードベースはC++、Objective-C、Perl、Python、JavaScriptを合わせて500万行を超えます。何の予備知識もなしに飛び込むのは、地図なしで大都市へ足を踏み入れるようなものです。

この記事が、その地図です。メモリアロケータからブラウザのUIレイヤーまで、階層化されたアーキテクチャをたどり、Webコンテンツをサンドボックス内に閉じ込めるマルチプロセスモデルを紹介します。さらに、この規模のプロジェクトを効率的にナビゲートするための実践的なテクニックもお伝えします。

リポジトリのトップレベル構造

リポジトリは、本番ソースコード、ツール群、テストスイートが明確に分離されています。主要なディレクトリ構成は次のとおりです。

ディレクトリ 役割
Source/ すべての本番コード。5つのコアコンポーネントとサードパーティの依存関係
Tools/ ビルドスクリプト(build-webkitrun-safari)、テストランナー、ユーティリティ
JSTests/ JavaScriptエンジンのテストスイート(ストレステスト、マイクロベンチマーク)
LayoutTests/ Webプラットフォームの準拠テスト(HTML、CSS、DOMレンダリング)
PerformanceTests/ JetStream、MotionMark、Speedometerベンチマーク
Websites/ webkit.orgおよび関連サイトのコンテンツ

ルートのCMakeLists.txtは非常にシンプルです。CMakeの最低バージョンを設定し、プロジェクトを宣言したあと、すぐにSource/に処理を委譲します。

cmake_minimum_required(VERSION 3.20)
project(WebKit LANGUAGES C CXX)

43行目でSource/をサブディレクトリとして追加しており、そこからアーキテクチャの本体が展開されます。

ヒント: リポジトリルートにあるIntroduction.mdは、1,000行を超える公式のコントリビューターガイドです。コンポーネントの役割、ビルド手順、コントリビューションのワークフローを理解するための最良の出発点です。コードを読み始める前に、まず一読しておきましょう。

階層化された依存関係のアーキテクチャ

WebKitの本番コードは、厳密な依存関係の連鎖として構成されています。各レイヤーは下位のレイヤーにのみ依存し、横方向や上位方向への依存は一切ありません。この制約は、Source/CMakeLists.txtのCMakeビルド順序によって強制されています。

graph TD
    A["<b>bmalloc</b><br/>Custom memory allocator<br/>IsoHeap type segregation"] --> B["<b>WTF</b><br/>Web Template Framework<br/>Containers, smart pointers, threading"]
    B --> C["<b>JavaScriptCore</b><br/>JavaScript & WebAssembly engine<br/>4-tier JIT pipeline"]
    C --> D["<b>WebCore</b><br/>Web standards implementation<br/>DOM, CSS, rendering, bindings"]
    D --> E["<b>WebKit (WebKit2)</b><br/>Multi-process architecture<br/>IPC, process management"]
    D --> F["<b>WebKitLegacy</b><br/>Single-process (deprecated)<br/>WebView / UIWebView"]

CMakeファイルはこの順序を明示的に定義しています。まずadd_subdirectory(bmalloc)(4行目)、次にadd_subdirectory(WTF)(6行目)が続きます。その後、add_subdirectory(JavaScriptCore)(18行目)、add_subdirectory(WebCore)(56行目)、最後にadd_subdirectory(WebKit)(64行目)の順です。

各レイヤーには明確な役割があります。

  • bmalloc — WebKit独自のメモリアロケータです。目玉機能であるIsoHeapは、型混同攻撃を防ぐためにアロケーションを型ごとに分離します。上位のすべてのコンポーネントがこれに依存しています。
  • WTF(Web Template Framework)— C++ STLの多くを置き換える基盤ライブラリです。VectorHashMapString、スマートポインタ(RefRefPtrWeakPtr)、スレッドプリミティブなどを提供します。
  • JavaScriptCore(JSC)— インタープリタから最適化JITまで、4段階のコンパイルパイプラインを備えたJavaScriptエンジンです。WebAssemblyにも対応しています。
  • WebCore — 圧倒的に最大のコンポーネントで、Webの標準仕様を実装しています。HTML/CSSのパース、DOM、レンダリングパイプライン、SVG、Canvas、Web Audio、その他数百のAPIを含みます。
  • WebKit / WebKitLegacy — プロセスアーキテクチャレイヤーです。WebKit2(現在は単に"WebKit")はマルチプロセスモデルを実装しており、WebKitLegacyは旧来のシングルプロセスAPIです。

この階層化は単なる整理上の話ではありません。コンパイル順序、ヘッダの可視性、APIのサーフェスを決定します。WebCoreはJSCのAPIを使用できますが、JSCはWebCoreのヘッダをインクルードできません。

マルチプロセスモデルの概要

WebKit2は、セキュリティと安定性のためにWebコンテンツをホストアプリケーションから分離するマルチプロセスアーキテクチャを導入しました。Webサイトがクラッシュしても、落ちるのはそのタブだけで、ブラウザ全体には影響しません。

flowchart LR
    subgraph UI["UI Process (Safari)"]
        WPP["WebPageProxy"]
    end
    
    subgraph WC["WebContent Process (per tab)"]
        WP["WebPage → WebCore::Page"]
    end
    
    subgraph NP["Network Process"]
        NET["HTTP / Cookies / Cache"]
    end
    
    subgraph GP["GPU Process"]
        GPU["Graphics / Media / WebGPU"]
    end
    
    UI <-->|"IPC"| WC
    UI <-->|"IPC"| NP
    WC <-->|"IPC"| NP
    WC <-->|"IPC"| GP

4種類のプロセスは、それぞれ独立した分離境界を担っています。

  1. UI Process — ホストアプリケーション(Safari、Mailなど)内で動作します。各Webページのクライアント側ミラーであるWebPageProxyを保持し、ユーザーインタラクションとナビゲーションポリシーを処理します。

  2. WebContent Process — 通常、タブごとに1つ起動します。JavaScriptの実行、DOM操作、レイアウト、描画など、すべてのWebコンテンツ処理を担います。WebCoreとJSCが実行時に存在する場所です。中心となるクラスはWebPageで、WebCore::Pageをラップしています。

  3. Network Process — すべてのHTTPネットワーキング、Cookieのストレージ、ディスクキャッシュを処理します。ネットワーク処理を1つのプロセスに集約することで、WebContentプロセスがネットワークに直接アクセスすることがなくなり、攻撃対象領域を縮小できます。

  4. GPU Process — GPUアクセラレーションによるコンポジティング、メディア再生、WebGPU/WebGLを処理します。GPUドライバとのやり取りを分離することで、GPUドライバのバグがWebContentサンドボックスを侵害するリスクを防ぎます。

すべてのプロセス間通信は、Machポート(macOS)またはUnixドメインソケット(Linux)上に構築されたカスタムIPCシステムを通じて行われます。この詳細については第4回で深く掘り下げます。

エントリーポイントとプロセスの起動

WebKitがどのように起動するかを理解すると、コードベース全体の構造が見えてきます。実行するものに応じて、複数の異なるエントリーポイントが存在します。

flowchart TD
    JSC["jsc.cpp<br/>Standalone JS shell"] --> VM["JSC::VM initialization"]
    
    SAFARI["Safari app launch"] --> UIP["UI Process<br/>Creates WebPageProxy"]
    UIP --> |"spawns"| WCS["WebContentServiceEntryPoint.mm<br/>XPC service init"]
    UIP --> |"spawns"| NPS["Network process entry"]
    UIP --> |"spawns"| GPS["GPU process entry"]
    
    BUILD["build-webkit script"] --> |"invokes"| CMAKE["CMake / Xcode build"]
    RUN["run-safari script"] --> |"sets DYLD_FRAMEWORK_PATH"| SAFARI

スタンドアロンのJSCシェルSource/JavaScriptCore/jsc.cpp)は、フルブラウザの複雑さを排してVMの初期化を理解するのに非常に役立ちます。JSCランタイム全体を含み、REPLも提供しています。

ブラウザ本体については、各子プロセスにプラットフォーム固有のエントリーポイントがあります。macOS/iOS上では、WebContentプロセスはXPCサービスとしてWebContentServiceEntryPoint.mmから起動します。Linux上では、よりシンプルなWebProcessMain.cppがエントリーポイントとなります。

ビルドと実行用のスクリプトはTools/Scripts/にあります。build-webkitが主要なビルドスクリプトで、run-safariDYLD_FRAMEWORK_PATHをビルド出力先に設定することで、ローカルビルドのWebKitを使ってSafariを起動します。

条件付きコンパイルとプラットフォームの抽象化

WebKitは、macOS、iOS、tvOS、watchOS、Linux(GTKおよびWPE)、PlayStation、Windowsで動作します。このクロスプラットフォームサポートは、3種類のプリプロセッサマクロファミリーによって管理されています。

マクロファミリー 目的
PLATFORM(X) ターゲットOS PLATFORM(COCOA), PLATFORM(GTK)
ENABLE(X) 機能フラグ ENABLE(WEBGL), ENABLE(CSS_SELECTOR_JIT)
USE(X) 実装の選択 USE(CG) (CoreGraphics), USE(CAIRO), USE(SKIA)

WebKitのすべての.cppファイルは#include "config.h"から始まり、プラットフォーム固有のマクロ定義を読み込みます。このヘッダはビルド時に生成され、対象プラットフォームに適した機能セットが有効化されることを保証します。

ビルド時の特殊化は、プラットフォーム固有のCMakeファイルを通じて行われます。Source/WebCore/PlatformMac.cmake(macOS固有のソースとフレームワーク)とSource/WebCore/PlatformGTK.cmake(GTK固有のソースとライブラリ)を比べてみましょう。これらのファイルでは、プラットフォーム固有のソースファイルを追加し、プラットフォームライブラリとリンクし、機能フラグを設定しています。

flowchart TD
    CONFIG["config.h (generated)"] --> PLAT{"PLATFORM?"}
    PLAT -->|COCOA| MAC["PlatformMac.cmake<br/>CoreGraphics, AVFoundation<br/>SourcesCocoa.txt"]
    PLAT -->|GTK| GTK["PlatformGTK.cmake<br/>Cairo/Skia, GStreamer<br/>SourcesGTK.txt"]
    PLAT -->|WPE| WPE["PlatformWPE.cmake<br/>libwpe, WPEBackend"]
    PLAT -->|PS| PS["PlatformPlayStation.cmake"]

このアーキテクチャのおかげで、WebCoreのコードの大部分はプラットフォームの差異を気にせずに読むことができます。プラットフォーム固有の処理はplatform/サブディレクトリに隔離され、条件付きコンパイルガードで保護されています。

コードベースのナビゲーション:実践的なヒント

500万行のコードベースを読み解くには、特定の戦略が必要です。WebKitのコントリビューターが実際に頼りにしているテクニックを紹介します。

Sources.txtをインデックスとして活用する。 Source/WebCore/Sources.txtには、WebCoreのソースファイル5,270件がすべて列挙されています。フラットなリスト形式で、ビルド入力(unified sources用)としての役割と、すべての実装ファイルを検索できるインデックスとしての役割を兼ねています。同様に、SourcesCocoa.txtにはmacOS/iOS専用のファイルが記載されています。

.messages.inファイルを追う。 プロセス間でどんなメッセージがやり取りされているかを理解するには、.messages.inファイルを確認しましょう。たとえばNetworkProcess.messages.inには、Networkプロセスが受信できるすべてのIPCメッセージが定義されています。DispatchedFrom=UIのようなヘッダアノテーションから、どのプロセスが各メッセージを送信するかがわかります。

Proxyパターンを追う。 WebKit2で最も重要なアーキテクチャパターンは、UIプロセスのproxyオブジェクトとWebContentプロセスの実装オブジェクトの対応関係です。WebPageProxyWebPageNetworkProcessProxyNetworkProcessのように対応しています。コードがどのプロセスで実行されるか迷ったときは、自分がこの境界のどちら側にいるかを確認してください。

unified sourcesを理解する。 WebKitは各.cppファイルを個別にコンパイルしません。代わりに、約8つのソースファイルをまとめて1つの翻訳単位("unified source"ファイル)にバンドルします。これによってコンパイル時間が大幅に短縮されますが、注意点もあります。バンドル内のファイル間でインクルード順序に依存できないこと、そして同じバンドル内のファイル間でstaticシンボルが競合する可能性があることです。

ヒント: クラスの実装を探すとき、ビルドシステム上でClassName.cppファイルを直接探そうとしないでください。そのファイルはunified sourceバンドルの一部としてコンパイルされます。代わりに、Sources.txtでファイルパスを検索するか、ファイルに直接アクセスしましょう。

クラス階層のコメントを読む。 WebKitの主要なクラスの多くには、アーキテクチャにおける役割を詳しく説明するコメントが書かれています。Node.hDocument.hRenderObject.hPage.hはいずれも充実したドキュメントが付いており、読み始めるのに最適な出発点です。

次回予告

全体像が掴めたところで、いよいよ深く潜っていく準備が整いました。第2回では、WebKitコードのあらゆる行に浸透しているメモリ管理パターンを理解するため、基盤となるWTFとbmallocを掘り下げます。スマートポインタシステム(RefRefPtrWeakPtr)、重要なprotectedThisパターン、そしてセキュリティを強化したアロケータは、単なる実装の詳細ではありません。WebKitのコードを流暢に読むために必要な語彙です。