架构概览与 WebKit 代码库导航
前置知识
- ›对 Web 浏览器工作原理有基本了解(页面加载、HTML/CSS 渲染、JavaScript 执行)
- ›对 C++ 项目结构和构建系统有一定认识
架构概览与 WebKit 代码库导航
WebKit 是现存历史最悠久、影响最深远的开源项目之一。它驱动着每一台 Apple 设备上的 Safari,通过 WKWebView 为数百万 iOS 应用提供渲染能力,并运行在从基于 GTK 的 Linux 桌面到 PlayStation 游戏机等各类平台上。整个代码库涵盖超过五百万行 C++、Objective-C、Perl、Python 和 JavaScript 代码。初次接触时,很容易有一种置身陌生城市、毫无方向感的感觉。
本文就是那张地图。我们将从内存分配器到浏览器界面,逐层梳理整体架构,介绍将 Web 内容隔离在沙盒中的多进程模型,并分享在如此规模的项目中高效导航的实用技巧。
顶层目录结构
代码库在组织上对生产源码、工具链和测试套件做了清晰划分,以下是核心目录说明:
| 目录 | 用途 |
|---|---|
Source/ |
所有生产代码——五个核心组件及第三方依赖 |
Tools/ |
构建脚本(build-webkit、run-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是一份官方贡献指南,篇幅超过 1000 行,是理解各组件职责、构建说明和贡献流程的最佳起点。在深入代码之前,建议先通读一遍。
分层依赖架构
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。提供
Vector、HashMap、String、智能指针(Ref、RefPtr、WeakPtr)以及线程原语。 - JavaScriptCore(JSC)— JavaScript 引擎,具备从解释器到优化型 JIT 的四级编译流水线,同时负责处理 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 内容与宿主应用隔离,同时保障了安全性和稳定性。即使某个网页崩溃,也只会影响对应的标签页,而不会拖垮整个浏览器。
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
四种进程类型各自承担独立的隔离边界:
-
UI 进程 — 运行在宿主应用内(Safari、Mail 等)。包含
WebPageProxy,即每个网页的客户端镜像,负责处理用户交互和导航策略。 -
WebContent 进程 — 通常每个标签页对应一个实例。负责运行所有 Web 内容:JavaScript 执行、DOM 操作、布局和绘制。WebCore 和 JSC 在运行时就驻留在这里。核心类是
WebPage,它封装了一个WebCore::Page。 -
网络进程 — 处理所有 HTTP 网络请求、Cookie 存储和磁盘缓存。将网络操作集中在单一进程中,意味着 WebContent 进程永远不会直接接触网络,从而缩小了攻击面。
-
GPU 进程 — 处理 GPU 加速合成、媒体播放以及 WebGPU/WebGL。将 GPU 驱动的交互隔离在独立进程中,可以防止 GPU 驱动 bug 危及 WebContent 沙盒。
所有跨进程通信均通过一套自定义 IPC 系统完成,该系统在 macOS 上基于 Mach ports,在 Linux 上基于 Unix domain sockets。我们将在第 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 Shell(Source/JavaScriptCore/jsc.cpp)在理解 VM 初始化流程方面非常有价值,因为它不需要完整浏览器的复杂上下文。它包含完整的 JSC 运行时,并提供一个交互式 REPL。
对于浏览器本身,每个子进程都有平台特定的入口点。在 macOS/iOS 上,WebContent 进程以 XPC 服务的形式启动,入口点位于 WebContentServiceEntryPoint.mm。在 Linux 上,则使用较为简单的 WebProcessMain.cpp 入口点。
构建和运行脚本位于 Tools/Scripts/。build-webkit 是主要的构建脚本,run-safari 通过将 DYLD_FRAMEWORK_PATH 指向本地构建产物来启动使用自定义 WebKit 版本的 Safari。
条件编译与平台抽象
WebKit 运行在 macOS、iOS、tvOS、watchOS、Linux(GTK 和 WPE)、PlayStation 以及 Windows 上。跨平台支持通过三类预处理器宏来管理:
| 宏类型 | 用途 | 示例 |
|---|---|---|
PLATFORM(X) |
目标操作系统 | 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/ 子目录和条件编译保护块中。
代码库导航:实用技巧
面对五百万行的代码库,需要有针对性的策略。以下是 WebKit 贡献者们常用的方法。
以 Sources.txt 作为索引。 文件 Source/WebCore/Sources.txt 列出了全部 5270 个 WebCore 源文件。这份平铺列表既作为构建输入(用于统一源文件),也是每个实现文件的可搜索索引。类似地,SourcesCocoa.txt 列出了仅限 macOS/iOS 的文件。
通过 .messages.in 文件追踪消息流。 要了解进程间传递的消息,可以查看 .messages.in 文件。例如,NetworkProcess.messages.in 定义了网络进程能够接收的所有 IPC 消息。其中 DispatchedFrom=UI 这样的头部注解告诉你每条消息由哪个进程发出。
理解 Proxy 模式。 WebKit2 中最重要的架构模式,是 UI 进程 proxy 对象与 WebContent 进程实现对象之间的镜像关系:WebPageProxy ↔ WebPage,NetworkProcessProxy ↔ NetworkProcess。当你对某段代码运行在哪个进程中感到困惑时,先确认自己处于这条分界线的哪一侧。
了解统一源文件机制。 WebKit 不会单独编译每个 .cpp 文件,而是将约 8 个源文件打包成一个翻译单元(即"统一源文件")。这种方式大幅缩短了编译时间,但也有其影响:打包文件之间不能依赖 include 顺序,同一包内的文件之间可能出现静态符号冲突。
提示: 查找某个类的实现时,不要直接在构建系统中寻找
ClassName.cpp文件——它会被编译进某个统一源文件包中。正确的做法是在Sources.txt中搜索文件路径,或直接导航到该文件。
阅读类层次结构注释。 许多核心 WebKit 类都有详尽的注释,说明其在架构中扮演的角色。Node.h、Document.h、RenderObject.h 和 Page.h 都是文档完善的入门起点。
下一步
有了这张地图,我们就可以深入探索了。在第 2 部分,我们将下沉到基础层——WTF 和 bmalloc——理解贯穿 WebKit 每一行代码的内存管理模式。智能指针系统(Ref、RefPtr、WeakPtr)、关键的 protectedThis 模式,以及经过安全强化的内存分配器,这些都不只是实现细节,而是流畅阅读任何 WebKit 代码所必须掌握的基础词汇。