How Blink Works
TL;NR
在 Blink 上开发绝非易事。对于新接触 Blink的开发者来说,要实现一个高效的渲染引擎,需要了解大量Blink特有的概念和编码约定。对于经验丰富的开发者来说亦非易事,因为 Blink 非常庞大,对性能、内存和安全性极为敏感。
本文从全局概述了 “Blink 是如何工作的”,希望有助于开发者快速熟悉Blink的架构:
- 本文并非Blink详细架构与编码规范的开发手册(这些内容可能会变化或过时)。相反地,本文简明扼要地介绍了短期内不会变更的Blink基本原理,并提供了可以进一步阅读的资源(如果你想了解更多的话)
- 本文不解释特定功能(e.g. ServiceWorkers, editing),相反地,本文解释了代码中广泛使用的基本功能(e.g. 内容管理, V8 APIs).
关于Blink开发的更多内容, 参考这里 Chromium wiki page
Blink 做了什么
Blink 是web平台的渲染引擎。简单地说,Blink 实现了浏览器 tab 内的所有内容的渲染:
- 实现web平台规范(e.g HTML标准),包括 DOM, CSS, WebIDL
- 内嵌 V8,并执行 JavaScript
- 向底层网络栈申请资源
- 构建 DOM 树
- 计算样式与布局
- 内嵌 Chrome Compositor,并绘图
通过 content public APIs , 很多客户端嵌入了 Blink, 如 Chromium, Android WebView, Opera 等.
从代码的角度来看,Blink 即 //third_party/blink/
;从工程(project)的角度来看,Blink 即实现Web平台功能的工程。实现Web平台功能的代码分布在 //third_party/blink/
, //content/renderer/
, //content/browser/
及其它地方。
进程/线程架构
进程
Chromium 是多进程架构的。它有一个 浏览器进程(browser process) 与 N 个沙盒化的 渲染进程(renderer process),Blink 运行在 渲染进程中。
那么,N 是多少呢?为安全计,跨站点 documents 间的内存地址隔离非常重要(即站点隔离 Site Isolation)。 理论上讲,一个 渲染进程最多只应该用于一个站点。而事实上,当用户打开了太多的 tab ,或者没有足够的内存时,对 渲染进程与站点做一对一的限制就显得很繁重了。所以,多个 iframe 或从不同站点加载的 tab 可能会共享同一个渲染进程。这意味着一个 tab 内的 iframe 可能在不同的渲染进程中,而不同 tab 内的 iframe 可能在一个渲染进程中。渲染进程,iframe 与 tab 之间不存在 1:1 的映射关系。
对于一个运行在沙盒中的渲染进程, Blink 需要向浏览器进程派发系统调用(e.g. 访问文件,播放媒体),以及访问用户数据(user profile data, e.g. cookie, passwords)。 这种 浏览器-渲染 进程间的通信是基于 Mojo 的(过去使用的是 Chromium IPC, 目前仍有一些地方在使用它,但该方式已被弃用)。在 Chromium 中正在进行服务化(Servicification),并将浏览器进程视为一组 “服务” 的集合。从 BlinK 的角度来看,它只需要使用 Mojo 与服务和浏览器进程交流。
若想了解更多:
- 多进程架构
- Blink 中的 Mojo 编程
线程
一个渲染进程会创建多少个线程呢?
Blink 有一个主线程(main thread), N 个工作线程(worker thread) 以及一堆内部线程(internal thread)。
几乎所有重要的事件都发生在主线程内: 所有的JavsScript(workers 除外),DOM, CSS, 样式与布局计算都运行在主线程内。Blink 经过高度优化以使主线程性能最大化。
Blink 会创建多个工作线程以运行 Web Workers, ServiceWorkers 以及 Worklets.
Blink 及 V8 会创建多个内部线程来处理 网络音频、数据库、GC 等。
对于进程间通信,你必须使用 PostTask APIs
来传递消息。除非个别地方出于性能原因,我们不鼓励使用共享内存编程。这就是为什么你很少在 Blink 代码内见到互斥锁。
若想了解更多:
- Blink 中的线程编程: platform/wtf/ThreadProgrammingInBlink.md
- Workers
Initialization & finalization
Blink 使用 BlinkInitializer::Initialize()
初始化。它必须在任何 Blink 代码调用前执行。
但 Blink 从不终结化(finalized). 比如,渲染进程会不做清理而强制退出。这一方面是基于性能原因。另一方面,优雅有序地完全清理渲染进程的内容是真的难(也不值这么做)。
目录结构
Content public API 以及 Blink public API
Content public API 是使程序能嵌入渲染引擎的 API 层。它是暴露给其它程序的API, 须小心维护。
Blink public API 是 //third_party/blink/
向 chromium 提供功能的 API 层。这是从Webkit 继承而来的 API. 在 Webkit 时代,Chromium 和 Safari 共享 Webkit, 所以这些API 需要向 Chromium 和 Safari 提供功能,而现在 //third_party/blink/
只有 Chromium 在用,所以它不再重要了。我们现在正在消减 Blink public API, 将 web平台的代码从 Chromium 移到 Blink 中(即 “Onion Soup Project”)
目录结构及依赖
//third_party/blink
有如下目录:
platform/
: Blink 底层功能的集合,它们是从core/
中分离出来的, e.g. geometry 与 graphics utilscore/
与modules/
: 所有 web 平台的功能实现都在这里。core/
实现了与 DOM 紧耦合的功能,modules/
则实现了更多自包含的功能, e.g. webaudio, indexedBDbindings/core/
与bindings/modules/
: 理论上bindings/core/
是core/
的一部分,bindings/modules/
是modules/
的一部分。重度使用 V8 API 的文件放在这里controller/
: 使用core/
与modules/
的上层库。 e.g. devtools 前端。
这个文档有更详细的介绍
依赖顺序如下:
- Chromium
controller/
modules/
与bindings/modules/
core/
与bindings/core/
pl