Chromium中的Blink​

Chromium中的Blink ​Blink 的作用是什么 ​Blink是Web平台的渲染引擎。粗略地说,Blink 实现了在浏览器选项卡中呈现内容的所有内容:

实现Web平台的规范(例如HTML标准),包括DOM、CSS和Web IDL嵌入 V8 并运行 JavaScript向底层网络堆栈请求资源构建 DOM 树计算样式和布局嵌入Chrome Compositor并绘制图形Blink 被 Chromium、Android WebView 和 Opera 等许多客户通过内容公共 API嵌入。

从代码库的角度来看,“Blink”通常表示//third_party/blink/。从项目角度来看,“Blink”通常是指实现Web平台功能的项目。实现Web平台功能的代码跨越//third_party/blink/、//content/renderer/、//content/browser/等地方。

进程/线程架构 ​进程及进程交互 ​Chromium 具有多进程架构。 Chromium 有 1 个浏览器进程和 N 个沙盒渲染器进程。 Blink 在渲染器进程中运行。

创建了多少个渲染器进程?出于安全原因,隔离跨站点文档之间的内存地址区域非常重要(这称为站点隔离)。从概念上讲,每个渲染器进程最多应专用于一个站点。但实际上,当用户打开太多选项卡或设备没有足够的 RAM 时,有时将每个渲染器进程限制到单个站点会过于繁重。然后,渲染器进程可以由从不同站点加载的多个 iframe 或选项卡共享。这意味着一个选项卡中的 iframe 可能由不同的渲染器进程托管,并且不同选项卡中的 iframe 可能由同一渲染器进程托管。渲染器进程、 iframe 和 tab 之间不存在 1:1 映射。

鉴于渲染器进程在沙箱中运行,Blink 需要要求浏览器进程调度系统调用(例如,文件访问、播放音频)并访问用户配置文件数据(例如,cookie、密码)。这种浏览器-渲染器进程通信是由Mojo实现的。 (注意:过去我们使用Chromium IPC,很多地方仍在使用它。但是,它已被弃用并在幕后使用 Mojo。)在 Chromium 方面,服务化正在进行中,并将浏览器进程抽象为一组“服务。从 Blink 的角度来看,Blink 只能使用 Mojo 与服务和浏览器进程进行交互。

多线程架构 ​渲染器进程中创建了多少个线程?

Blink 有一个主线程、N 个工作线程和几个内部线程。

几乎所有重要的事情都发生在主线程上。所有 JavaScript(worker 除外)、DOM、CSS、样式和布局计算都在主线程上运行。 Blink 经过高度优化,可最大限度地提高主线程的性能(假设大部分为单线程架构)。

Blink 可以创建多个工作线程来运行Web Workers、ServiceWorker和Worklets。

Blink 和 V8 可能会创建几个内部线程来处理网络音频、数据库、GC 等。

对于跨线程通信,您必须使用 PostTask API 进行消息传递。除了出于性能原因确实需要使用共享内存编程的几个地方之外,不鼓励使用共享内存编程。这就是为什么您在 Blink 代码库中看不到很多 MutexLock。

Blink 的初始化和结束 ​Blink 由BlinkInitializer::Initialize()初始化。必须在执行任何 Blink 代码之前调用此方法。

另一方面,渲染器进程被强制退出而不被清理。原因之一是性能。另一个原因是,一般来说,以优雅有序的方式清理渲染器进程中的所有内容确实很困难(而且不值得这么做)。

目录结构 ​Content中开放的API和Blink中开放的API ​Content中开放的 API 是使嵌入者能够嵌入渲染引擎的 API 层。Content 中开放的 API 必须小心维护,因为它们会暴露给嵌入者。

Blink 中开放的 API 是向 Chromium 开放的 //third_party/blink/ 功能的 API 层。这个 API 层只是继承自 WebKit 的历史产物。在 WebKit 时代,Chromium 和 Safari 共享 WebKit 的实现,因此需要 API 层将 WebKit 的功能暴露给 Chromium 和 Safari。现在 Chromium 是 //third_party/blink/ 的唯一嵌入者,API 层就没有意义了。我们正在通过将 Web 平台代码从 Chromium 移至 Blink(该项目称为 Onion Soup)来积极减少 Blink 中开放的 API 的数量。

目录结构和依赖关系 ​//third_party/blink/ 有以下目录。

platform/

从整个 core/ 中提取的 Blink 较低级别功能的集合。例如,几何和图形实用程序。core/ 和 modules/

实施规范中定义的所有网络平台功能。 core/ 实现与 DOM 紧密耦合的功能。 module/ 实现了更多独立的功能。例如,webaudio、indexeddb。bindings/core/ 和 bindings/modules/

从概念上讲,bindings/core/ 是 core/ 的一部分,bindings/modules/ 是modules/ 的一部分。大量使用 V8 API 的文件放置在 bindings/{core,modules} 中。controller/

一组使用 core/ 和 module/ 的高级库。例如,devtools 前端。依赖关系按以下顺序流动:

Chromium => controller/ => modules/ and bindings/modules/ => core/ and bindings/core/ => platform/ => low-level primitives such as //base, //v8 and //cc

Blink 需要仔细维护暴露给 //third_party/blink/ 的low-level primitives such。

WTF ​WTF 是一个“Blink 特定基础”库,位于 platform/wtf/。我们正在尝试尽可能统一 Chromium 和 Blink 之间的编码原语,所以 WTF 应该很小。之所以需要这个库,是因为有许多类型、容器和宏确实需要针对 Blink 的工作负载和 Oilpan (Blink GC) 进行优化。如果类型是在 WTF 中定义的,Blink 必须使用 WTF 类型而不是 //base 或 std 库中定义的类型。最流行的是向量、哈希集、哈希图和字符串。 Blink 应该使用 WTF::Vector、WTF::HashSet、WTF::HashMap、WTF::String 和 WTF::AtomicString 而不是 std::vector、std::*set、std::*map 和 std::string 。

内存管理 ​就 Blink 而言,您需要关心三个内存分配器:

PartitionAllocOilpan(又名 Blink GC)malloc(不鼓励)您可以使用 USING_FAST_MALLOC() 在 PartitionAlloc 堆上分配对象:

c++class SomeObject {

USING_FAST_MALLOC(SomeObject);

static std::unique_ptr Create() {

return std::make_unique(); // Allocated on PartitionAlloc's heap.

}

};PartitionAlloc 分配的对象的生命周期应由scoped_refptr<> 或std::unique_ptr<> 管理。强烈建议不要手动管理生命周期。 Blink 中禁止手动删除。

您可以使用 GarbageCollected 在 Oilpan 堆上分配对象:

c++class SomeObject : public GarbageCollected {

static SomeObject* Create() {

return new SomeObject; // Allocated on Oilpan's heap.

}

};Oilpan 分配的对象的生命周期由垃圾收集自动管理。您必须使用特殊的指针(例如,Member<>、Persistent<>)来保存 Oilpan 堆上的对象。请参阅Oilpan API 参考以熟悉有关 Oilpan 的编程限制。最重要的限制是不允许您在 Oilpan 对象的析构函数中触及任何其他 Oilpan 对象(因为无法保证销毁顺序)。

如果既不使用 USING_FAST_MALLOC() 也不使用 GC垃圾回收机制,则对象将在系统 malloc 的堆上分配。 Blink 中强烈建议不要这样做。所有 Blink 对象都应该由 PartitionAlloc 或 Oilpan 分配,如下所示:

默认使用 Oilpan仅当 1) 对象的生命周期非常明确并且 std::unique_ptr<> 就足够了,2) 在 Oilpan 上分配对象会带来很多复杂性或 3) 在 Oilpan 上分配对象会带来很多不必要的压力时才使用 PartitionAlloc到垃圾收集运行时。无论您使用 PartitionAlloc 还是 Oilpan,您都必须非常小心,不要创建悬空指针(注意:强烈建议不要使用原始指针)或内存泄漏。

如果您想了解更多:

如何使用PartitionAlloc如何使用OilpanOilpan GC 设计任务调度 ​为了提高渲染引擎的响应能力,Blink 中的任务应尽可能异步执行。不鼓励同步 IPC / Mojo 和任何其他可能需要几毫秒的操作(尽管有些操作是不可避免的,例如执行用户的 JavaScript 代码)。

渲染器进程中的所有任务都应使用适当的任务类型发布到Blink Scheduler,如下所示:

c++// Post a task to frame's scheduler with a task type of kNetworking

frame->GetTaskRunner(TaskType::kNetworking)->PostTask(..., WTF::Bind(&Function));Blink Scheduler 维护多个任务队列,并智能地确定任务的优先级,以最大限度地提高用户感知的性能。为了让 Blink Scheduler 正确、智能地调度任务,指定正确的任务类型非常重要。

如果您想了解更多:

如何发布任务页面、框架、文档、DOMWindow 等 ​概念 ​Page、Frame、Document、ExecutionContext 和 DOMWindow 是以下概念:

Page 对应于选项卡的概念(如果未启用下面解释的 OOPIF)。每个渲染器进程可能包含多个选项卡。Frame对应于框架(主框架或iframe)的概念。每个页面可以包含一个或多个按树形层次结构排列的框架。DOMWindow 对应于 JavaScript 中的 Window 对象。每个 Frame 有一个 DOMWindow。Document 对应于 JavaScript 中的 window.document 对象。每个 Frame 都有一个 Document。ExecutionContext 是一个抽象 Document(对于主线程)和 WorkerGlobalScope(对于 worker 线程)的概念。Page、Frame、Document、ExecutionContext 和 DOMWindow 的对应关系:

Renderer process : Page = 1 : N.Page : Frame = 1 : M.Frame : DOMWindow : Document (or ExecutionContext) = 1 : 1 : 1

在静态页面中几乎是这样的,但在动态页面中可能会有所变化。例如,考虑以下代码:

jsiframe.contentWindow.location.href = "https://example.com";在本例中,将为https://example.com创建一个新DOMWindow和一个新Document。然而,Frame可以重复使用。

(注:准确的说,有些情况是创建了一个新的Document,但是重复使用了Window和Frame,还有更复杂的情况。)

如果您想了解更多:

Frame CodeOut-of-Process iframes (OOPIF) ​站点隔离使事情变得更加安全,但也更加复杂。 😃 站点隔离的想法是为每个站点创建一个渲染器进程。(站点是页面的可注册域 + 1 标签及其 URL 方案。例如,https://mail.example.com 和 https://chat.example.com 位于同一站点,但 https://oodles.com 和 https://pumpkins.com 则不然。)如果一个页面包含一个跨站点 iframe,则该页面可能由两个渲染器进程托管。考虑以下页面:

html

<正文>

主框架和