Read OSS

架构概览与 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-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 是一份官方贡献指南,篇幅超过 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。提供 VectorHashMapString、智能指针(RefRefPtrWeakPtr)以及线程原语。
  • 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

四种进程类型各自承担独立的隔离边界:

  1. UI 进程 — 运行在宿主应用内(Safari、Mail 等)。包含 WebPageProxy,即每个网页的客户端镜像,负责处理用户交互和导航策略。

  2. WebContent 进程 — 通常每个标签页对应一个实例。负责运行所有 Web 内容:JavaScript 执行、DOM 操作、布局和绘制。WebCore 和 JSC 在运行时就驻留在这里。核心类是 WebPage,它封装了一个 WebCore::Page

  3. 网络进程 — 处理所有 HTTP 网络请求、Cookie 存储和磁盘缓存。将网络操作集中在单一进程中,意味着 WebContent 进程永远不会直接接触网络,从而缩小了攻击面。

  4. 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 ShellSource/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 进程实现对象之间的镜像关系:WebPageProxyWebPageNetworkProcessProxyNetworkProcess。当你对某段代码运行在哪个进程中感到困惑时,先确认自己处于这条分界线的哪一侧。

了解统一源文件机制。 WebKit 不会单独编译每个 .cpp 文件,而是将约 8 个源文件打包成一个翻译单元(即"统一源文件")。这种方式大幅缩短了编译时间,但也有其影响:打包文件之间不能依赖 include 顺序,同一包内的文件之间可能出现静态符号冲突。

提示: 查找某个类的实现时,不要直接在构建系统中寻找 ClassName.cpp 文件——它会被编译进某个统一源文件包中。正确的做法是在 Sources.txt 中搜索文件路径,或直接导航到该文件。

阅读类层次结构注释。 许多核心 WebKit 类都有详尽的注释,说明其在架构中扮演的角色。Node.hDocument.hRenderObject.hPage.h 都是文档完善的入门起点。

下一步

有了这张地图,我们就可以深入探索了。在第 2 部分,我们将下沉到基础层——WTF 和 bmalloc——理解贯穿 WebKit 每一行代码的内存管理模式。智能指针系统(RefRefPtrWeakPtr)、关键的 protectedThis 模式,以及经过安全强化的内存分配器,这些都不只是实现细节,而是流畅阅读任何 WebKit 代码所必须掌握的基础词汇。