跳到主要内容
浏览器
导航
内核与技术专题

排版引擎架构:模块划分与协作

拆解排版引擎把字节流转换为可交互页面所经历的分层链路:网络与资源加载、解析、样式、布局、绘制与合成各模块的输入输出,多进程模型下的职责划分与进程间通信,以及内存管理、任务调度与事件分发等公共设施。

发布于 4 分钟阅读 1469 字

排版引擎(也常被称作浏览器内核)负责把网络接收到的资源转换成可交互的页面。它并不是一块单一的程序,而是由若干职责明确的模块按层组织,模块之间通过约定好的数据结构传递结果。理解每一层的输入与输出,是阅读引擎实现或分析页面行为的起点。

多数实现把内核划分为资源获取、解析、样式、布局、绘制与合成等几个部分,并由一套任务调度与内存管理设施在背后支撑。层的划分思路在不同实现之间大致相似,差异主要集中在模块边界的划定与内部数据结构的选择上。

分层的职责划分

按数据流动方向,可以把内核划分为如下几层:

层主要输入主要输出
网络与资源加载URL、请求参数字节流、解码后的资源
解析字节流DOM 树、CSSOM、脚本
样式DOM 树、CSSOM带计算样式的渲染树
布局渲染树带几何信息的盒树
绘制盒树绘制指令列表
合成绘制指令、图层纹理与合成帧

每一层的输出会被缓存并在后续复用,只有当输入发生变化时,对应的层才需要重新计算。这种「按层失效」的组织方式,使得一次局部变更不必推动整条链路重跑。

网络与资源加载

资源加载层接受解析层提出的请求,按优先级排队并复用连接。它处理的资源类型包括 HTML 文档、样式表、脚本、字体与图片。加载层需要决定请求的先后顺序:文档与阻塞渲染的样式表通常被赋予较高优先级,视口内的图片优先级高于视口外的图片。

GET /index.html HTTP/2
accept: text/html
accept-encoding: gzip, br

响应到达后,加载层负责解压、确定字符编码,并把字节流交给解析层。对于可流式处理的资源,字节流边到达边交付,不必等待完整响应。

解析与样式

解析层包含 HTML 解析器、CSS 解析器以及脚本引擎的调用入口。HTML 解析器以流式方式消费字符,维护一棵 DOM 树与一个待处理记号栈。遇到 <script> 时,解析器需要根据脚本的加载方式决定是否暂停自身。

样式层把 CSSOM 与 DOM 树结合,为每个元素确定生效声明。选择器匹配需要沿祖先链比对,因此引擎通常会维护若干索引(例如按 id、类名、标签名建立的映射)来缩小候选范围,避免每次都遍历整棵树。

布局、绘制与合成

布局层根据计算样式与内容生成盒模型树,输出每个盒的位置与尺寸。布局的求解依赖父级给出的约束,因此一次自上而下的遍历配合自下而上的尺寸回传,即可完成多数情况下的计算。

绘制层不直接写像素,而是产生有序的绘制指令;合成层把指令按图层栅格化为纹理,再交给图形接口合成输出。把页面按层叠上下文划分为独立图层,可以让局部变更只触发该图层的重新栅格化。

多进程模型与进程间通信

现代内核普遍采用多进程组织:浏览器主进程负责窗口、导航与权限,渲染进程负责某个站点(或某组站点)的文档渲染,此外通常还有网络进程与图形进程各自承担专门的工作。

进程之间不共享地址空间,因此需要通过进程间通信传递消息。渲染进程一般不会直接发起网络请求,而是把请求交给网络进程;与合成相关的任务则交给图形进程。消息的序列化与线程切换都有成本,因此引擎倾向于把高频操作合批处理,减少往返次数。

站点隔离 的做法是让不同站点运行在不同的渲染进程中,使一个页面的渲染进程无法直接读取另一个站点的内存。代价是进程数量增加,引擎需要在资源占用与隔离强度之间做出权衡。

公共基础设施

跨模块复用的设施大致有三类:

  • 任务调度:把来自解析、布局、脚本、定时器与用户输入的工作拆成任务,按优先级与时限排队执行,与 事件循环 的模型相互对应。
  • 内存管理:为 DOM 节点、样式对象与布局对象提供统一的生命周期管理,多数实现以垃圾回收配合分代策略或引用计数来处理。
  • 事件分发:把输入设备的事件按命中测试结果派发到目标元素,并在捕获与冒泡阶段依次调用已注册的监听器。

小结

内核的架构可以概括为「分层加多进程」:分层确定了单次渲染的数据流向,多进程确定了稳定性与安全性的边界。在分析具体行为时,先定位它发生在哪一层,再确认它跨越了哪些进程边界,问题的范围通常就能收敛到一个可验证的假设。

内核与技术专题 · 相关文章