chrome多进程架构

问题

构建几乎不会崩溃或挂起的渲染引擎几乎是不可能的。构建完全安全的渲染引擎几乎也是不可能的。

在某种程度上,2006年左右的网络浏览器的状态类似于过去的单用户,协作多任务操作系统。由于此类操作系统中的行为异常的应用程序可能会破坏整个系统,因此Web浏览器中的行为异常的网页也可能因此而崩溃。它只需要一个浏览器或插件错误即可关闭整个浏览器和所有当前运行的选项卡。

现代操作系统更强大,因为它们将应用程序置于相互隔离的单独进程中。一个应用程序中的崩溃通常不会损害其他应用程序或操作系统的完整性,并且每个用户对其他用户数据的访问受到限制。

建筑概述

我们对浏览器选项卡使用单独的过程,以保护整个应用程序免受渲染引擎中的错误和干扰。我们还限制了每个渲染引擎进程对其他进程以及系统其余部分的访问。在某些方面,这为Web浏览带来了内存保护和访问控制带给操作系统的好处。

我们将运行UI并管理选项卡和插件过程的主要过程称为“浏览器过程”或“浏览器”。同样,特定于选项卡的过程称为“渲染过程”或“渲染器”。渲染器使用Blink开源布局引擎来解释和布局HTML。

管理渲染过程

每个渲染进程都有一个全局RenderProcess对象,该对象管理与父浏览器进程的通信并维护全局状态。浏览器RenderProcessHost 为每个渲染进程维护一个对应项,该进程管理浏览器状态和渲染器的通信。浏览器和渲染器使用Chromium的IPC系统进行通信。

管理视图

每个渲染过程都有一个或多个RenderView对象,由管理RenderProcess,这些对象对应于内容的选项卡。对应项 在渲染器中RenderProcessHost维护RenderViewHost与每个视图对应的内容。每个视图都有一个视图ID,该ID用于区分同一渲染器中的多个视图。这些ID在一个渲染器中是唯一的,但在浏览器中不是唯一的,因此要识别视图,需要一个RenderProcessHostID和一个View ID。通过这些RenderViewHost对象可以完成从浏览器到特定内容选项卡的通信,这些对象知道如何将消息通过它们发送RenderProcessHostRenderProcessRenderView

组件和接口

在渲染过程中:

  • 在浏览器中RenderProcess使用相应的句柄处理IPC RenderProcessHostRenderProcess每个渲染过程只有一个对象。这就是所有浏览器↔渲染器通信的发生方式。
  • RenderView对象RenderViewHost在浏览器进程中(通过RenderProcess)和我们的WebKit嵌入层与其对应的对象通信。此对象表示选项卡或弹出窗口中一个网页的内容

在浏览器过程中:

  • Browser对象表示顶级浏览器窗口。
  • RenderProcessHost对象表示单个浏览器↔渲染器IPC连接的浏览器端。有一个RenderProcessHost在每个渲染过程中的浏览器进程。
  • RenderViewHost对象封装的通信与远程RenderViewRenderWidgetHost处理用于输入和绘画RenderWidget在浏览器中。

有关此嵌入的工作方式的更多详细信息,请参阅Chromium如何显示网页  设计文档。

共享渲染过程

通常,每个新窗口或选项卡都会在新过程中打开。浏览器将产生一个新进程,并指示它创建一个RenderView

有时有必要或希望在选项卡或窗口之间共享渲染过程。Web应用程序会打开一个新窗口,希望与之进行同步通信,例如,使用  JavaScript中的window.open。在这种情况下,当我们创建新的窗口或选项卡时,我们需要重用打开窗口的过程。如果进程总数太大,或者用户已经打开一个导航到该域的进程,我们还可以为新进程分配新选项卡的策略。这些策略在过程模型中进行了描述。

检测崩溃或行为异常的渲染器

与浏览器进程的每个IPC连接都会监视进程句柄。如果发出了这些句柄的信号,则表示渲染过程已崩溃,并且向选项卡通知了崩溃。现在,我们显示一个“悲伤的标签”屏幕,通知用户渲染器崩溃了。可以通过按下重新加载按钮或开始新的导航来重新加载页面。发生这种情况时,我们注意到没有任何流程,而是创建一个新的流程。

沙盒渲染器

鉴于渲染器在单独的进程中运行,我们有机会通过sandboxing限制其对系统资源的访问。例如,我们可以确保渲染器对网络的唯一访问是通过其父浏览器进程进行的。同样,我们可以使用主机操作系统的内置权限来限制其对文件系统的访问。

除了限制渲染器对文件系统和网络的访问之外,我们还可以限制对渲染器对用户的显示和相关对象的访问。我们在用户看不见的单独Windows“ 桌面 ” 上运行每个渲染过程。这样可以防止受损的渲染器打开新窗口或捕获击键。

回馈记忆

给定渲染器在单独的进程中运行,将隐藏的选项卡视为较低优先级就变得很简单。通常,Windows上最小化的进程会将其内存自动放入“可用内存”池中。在低内存情况下,Windows将在交换出更高优先级的内存之前将该内存交换到磁盘上,从而有助于使用户可见的程序具有更高的响应速度。我们可以将相同的原理应用于隐藏的标签。当渲染进程没有顶级选项卡时,我们可以释放该进程的“工作集”大小,以提示系统在必要时先将该内存换出到磁盘。因为我们发现减小工作集大小也会降低用户在两个选项卡之间切换时的选项卡切换性能,所以我们逐渐释放此内存。这意味着,如果用户切换回最近使用的选项卡,则与最近使用的选项卡相比,该选项卡的内存更有可能被分页。具有足够内存来运行所有程序的用户根本不会注意到此过程:Windows仅会在需要时才真正回收这些数据,因此,如果有足够的内存,则不会对性能造成任何影响。

这有助于我们在低内存情况下获得更好的内存占用。与很少使用的背景标签相关联的内存可以完全换出,而前景标签的数据可以完全加载到内存中。相比之下,单进程浏览器会将所有选项卡的数据随机分布在其内存中,并且不可能如此干净地分离使用和未使用的数据,从而浪费内存和性能。

插件和扩展

Firefox风格的NPAPI插件在其自身的进程中运行,与渲染器分开。插件体系结构中对此进行了详细描述。 

“ 网站隔离”项目旨在提供渲染器之间的更多隔离,该项目的早期交付成果包括在隔离的进程中运行Chrome的HTML / JavaScript内容扩展。

chrome渲染引擎blink如何工作

眨眼的工作原理

bit.ly/how-blink-works

作者:haraken @

最后更新:2018年8月14日

状态:PUBLIC

 

在眨眼上工作并不容易。对于新的Blink开发人员而言,这并不容易,因为已经引入了许多特定于Blink的概念和编码约定来实现非常快速的渲染引擎。即使对于有经验的Blink开发人员来说,这也不容易,因为Blink庞大且对性能,内存和安全性极为敏感。

 

本文档旨在提供1 万英尺的“ Blink的工作原理”概述,我希望它将有助于Blink开发人员快速熟悉该体系结构:

 

  • 该文档不是有关Blink详细架构和编码规则(可能会更改和过时)的详尽教程。相反,该文档简明扼要地描述了Blink的基本原理(短期内不太可能改变),并指出了您想了解更多信息时可以阅读的资源。
  • 该文档未解释特定功能(例如ServiceWorkers,编辑)。而是该文档解释了广泛的代码库所使用的基本功能(例如,内存管理,V8 API)。

 

有关Blink开发的更多常规信息,请参阅Chromium Wiki页面

 

眨眼做什么

流程/线程架构

工艺流程

线程数

闪烁的初始化

目录结构

内容公共API和Blink公共API

目录结构和依赖性

WTF

内存管理

任务调度

页面,框架,文档,DOMWindow等

概念

OOPIF

分离的框架/文件

Web IDL绑定

V8和闪烁

隔离,上下文,世界

V8 API

V8包装器

渲染管线

有什么问题吗

 

眨眼做什么

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

 

  • 实施Web平台的规范(例如HTML标准),包括DOM,CSS和Web IDL
  • 嵌入V8并运行JavaScript
  • 从基础网络堆栈请求资源
  • 建立DOM树
  • 计算样式和布局
  • 嵌入Chrome合成器并绘制图形

 

许多用户(例如Chromium,Android WebView和Opera)通过内容公开API嵌入了Blink 

 

从代码库的角度来看,“闪烁”通常是指// third_party / blink /。从项目角度来看,“闪烁”通常表示实现Web平台功能的项目。实现Web平台功能的代码跨越// third_party / blink /,// content / renderer /,// content /浏览器/和其他地方。

流程/线程架构

工艺流程

铬具有多工艺体系结构。Chromium具有一个浏览器进程和N个沙盒渲染器进程。闪烁在渲染器进程中运行。

 

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

 

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

 

如果您想了解更多信息:

 

  • 多进程架构
  • Blink中的Mojo编程:platform / mojo / MojoProgrammingInBlink.md

线程数

在渲染器进程中创建了多少个线程?

 

眨眼有一个主线程,N个工作线程和几个内部线程。

 

几乎所有重要的事情都在主线程上发生。所有JavaScript(工人除外),DOM,CSS,样式和布局计算都在主线程上运行。假设主要是单线程体系结构,Blink进行了高度优化以最大化主线程的性能。

 

眨眼可能会创建多个工作线程来运行Web WorkersServiceWorkerWorklets

 

Blink和V8可能会创建几个内部线程来处理webaudio,数据库,GC等。

 

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

 

如果您想了解更多信息:

 

闪烁的初始化和完成

眨眼由BlinkInitializer :: Initialize()初始化。在执行任何Blink代码之前必须调用此方法。

 

另一方面,Blink从未完成。也就是说,渲染器进程被强制退出而不进行清理。原因之一是性能。另一个原因是,通常很难以一种有序的方式清理渲染器进程中的所有内容(这是不值得的工作)。

目录结构

内容公共API和Blink公共API

内容公共API是使嵌入程序嵌入呈现引擎的API层。内容公共API必须小心维护,因为它们会暴露在嵌入程序中。

 

眨眼公共API是将// // third_party / blink /的功能公开给Chromium的API层。该API层只是从WebKit继承的历史工件。在WebKit时代,Chromium和Safari共享WebKit的实现,因此需要API层才能将WebKit的功能公开给Chromium和Safari。既然Chromium是// third_party / blink /的唯一嵌入者,那么API层就没有意义了。通过将网络平台代码从Chromium移到Blink(该项目称为Onion Soup),我们正在积极减少Blink公共API的数量。

 

目录结构和依赖性

// third_party / blink /具有以下目录。有关这些目录的更详细定义,请参阅此文档

 

  • 平台/
    • Blink的较低级功能的集合,这些功能是从整体内核中剔除的。例如,几何和图形工具。
  • 核心/和模块/
    • 规范中定义的所有Web平台功能的实现。core /实现与DOM紧密结合的功能。模块/实现更多独立功能。例如webaudio,indexeddb。
  • 绑定/核心/和绑定/模块/
    • 从概念上讲,bindings / core /是core /的一部分,而bindings / modules /是modules /的一部分。大量使用V8 API的文件被放在bindings / {core,modules}中。
  • 控制器/
    • 一组使用core /和modules /的高级库。例如devtools前端。

 

依存关系按以下顺序流动:

 

  • 铬=>控制器/ =>模块/和绑定/模块/ =>核心/和绑定/核心/ =>平台/ =>底层基元,例如// base,// v8和// cc

 

Blink仔细维护暴露于// third_party / blink /的低级基元列表。

 

如果您想了解更多信息:

 

WTF

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

 

如果您想了解更多信息:

 

内存管理

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

 

 

您可以使用USING_FAST_MALLOC()在PartitionAlloc的堆上分配一个对象:

 

类SomeObject {

  USING_FAST_MALLOC(SomeObject);

  静态std :: unique_ptr <SomeObject> Create(){

    返回std :: make_unique <SomeObject>(); //分配在PartitionAlloc的堆上。

}

};

 

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

 

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

 

类SomeObject:公共GarbageCollected <SomeObject> {

  静态SomeObject * Create(){

    返回新的SomeObject; //分配在Oilpan的堆上。

  }

};

 

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

 

如果您既不使用USING_FAST_MALLOC()也不使用GarbageCollected,则在系统malloc的堆上分配对象。在眨眼中强烈建议不要这样做。所有Blink对象应由PartitionAlloc或Oilpan分配,如下所示:

 

  • 默认情况下使用Oilpan。
  • 仅在以下情况下才使用PartitionAlloc:1)对象的生存期非常明确并且std :: unique_ptr <>或scoped_refptr <>足够,2)在Oilpan上分配对象会带来很多复杂性,或者3)在Oilpan上分配对象会导致给垃圾收集运行时带来了不必要的压力。

 

无论使用PartitionAlloc还是Oilpan,都必须非常小心,不要创建悬空的指针(注意:强烈建议不要使用原始指针)或内存泄漏。

 

如果您想了解更多信息:

 

任务调度

为了提高渲染引擎的响应速度,Blink中的任务应尽可能异步执行。不鼓励同步IPC / Mojo和任何其他可能花费几毫秒的操作(尽管某些操作是不可避免的,例如用户的JavaScript执行)。

 

呈现器进程中的所有任务都应使用正确的任务类型发布到Blink Scheduler,如下所示:

 

//使用kNetworking的任务类型将任务发布到框架的调度程序

frame-> GetTaskRunner(TaskType :: kNetworking)-> PostTask(…,WTF :: Bind(&Function));

 

Blink Scheduler维护多个任务队列,并巧妙地对任务进行优先级排序,以最大化用户感知的性能。重要的是要指定适当的任务类型,以使Blink Scheduler能够正确,智能地调度任务。

 

如果您想了解更多信息:

 

  • 如何发布任务:platform / scheduler / PostTask.md

页面,框架,文档,DOMWindow等

概念

页面,框架,文档,ExecutionContext和DOMWindow是以下概念:

 

  • 页面与选项卡的概念相对应(如果未启用下面说明的OOPIF)。每个渲染器进程可能包含多个选项卡。
  • 框架对应于框架(主框架或iframe)的概念。每个页面可以包含一个或多个以树状层次结构排列的框架。
  • DOMWindow对应于JavaScript中的窗口对象。每个框架都有一个DOMWindow。
  • Document对应于JavaScript中的window.document 对象。每个框架都有一个文档。
  • ExecutionContext是一个抽象文档(用于主线程)和WorkerGlobalScope(用于工作线程)的概念。

 

渲染过程:页面= 1:N

 

页:框架= 1:M.

 

框架:DOMWindow:文档(或ExecutionContext)= 1:1:1在任何时间点,但映射可能随时间而变化。例如,考虑以下代码:

 

iframe.contentWindow.location.href =“ https://example.com”;

 

在这种情况下,将为https://example.com创建一个新的DOMWindow和一个新的Document 。但是,可以重复使用该框架。

 

(注意:确切地说,在某些情况下会创建一个新的Document,但是DOMWindow和Frame会被重用。甚至还有一些更复杂的情况。)

 

如果您想了解更多信息:

 

  • 核心/框架/FrameLifecycle.md

进程外iframe(OOPIF)

站点隔离使事情变得更加安全,但更加复杂。🙂站点隔离的想法是为每个站点创建一个渲染器进程。(网站是页面的可注册域+ 1标签及其URL方案。例如,https://mail.example.comhttps://chat.example.com在同一网站中,但https:// noodles.comhttps://pumpkins.com都没有。)如果一个页面包含一个跨站点IFRAME,该页面可以由两个渲染过程托管。考虑以下页面:

 

<!– https://example.com –>

<身体>

<iframe src =” https://example2.com”> </ iframe>

</ body>

 

主框架和<iframe>可以由不同的渲染器进程托管。渲染器进程本地的帧由LocalFrame表示,而不是渲染器进程本地的帧由RemoteFrame表示。

 

从主框架的角度来看,主框架是LocalFrame,而<iframe>是RemoteFrame。从<iframe>的角度来看,主框架是RemoteFrame,而<iframe>是LocalFrame。

 

本地框架和远程框架(可能存在于不同的渲染器进程中)之间的通信是通过浏览器进程进行处理的。

 

如果您想了解更多信息:

 

分离的框架/文件

相框/文档可能处于分离状态。考虑以下情况:

 

doc = iframe.contentDocument;

iframe.remove(); //将iframe与DOM树分离。

doc.createElement(“ div”); //但是您仍然可以在分离的框架上运行脚本。

 

棘手的事实是,您仍然可以在分离的框架上运行脚本或DOM操作。由于框架已经分离,大多数DOM操作将失败并引发错误。不幸的是,分离框架上的行为在浏览器之间并不能真正实现互操作,在规范中也没有明确定义。基本上,人们期望JavaScript可以继续运行,但是大多数DOM操作应该会因某些适当的异常而失败,例如:

 

无效someDOMOperation(…){

  if(!script_state _-> ContextIsValid()){//框架已经分离

    …;//设置例外等

    返回;

}

}

 

这意味着在通常情况下,当框架分离时,Blink需要执行一系列清理操作。您可以通过从ContextLifecycleObserver继承来做到这一点,如下所示:

 

类SomeObject:公共GarbageCollected <SomeObject>,公共ContextLifecycleObserver {

  void ContextDestroyed()覆盖{

    //在此进行清理操作。

}

  〜SomeObject(){

    //在这里进行清理操作不是一个好主意,因为现在进行清理已经太迟了。此外,不允许析构函数接触Oilpan堆上的任何其他对象。

  }

};

Web IDL绑定

当JavaScript访问node.firstChild时将调用node.h 中的Node :: firstChild()。它是如何工作的?让我们看一下node.firstChild的工作方式。

 

首先,您需要根据规范定义一个IDL文件:

 

// node.idl

接口Node:EventTarget {

  […]只读属性Node?第一个孩子;

};

 

Web IDL的语法在Web IDL规范中定义[…] 称为IDL扩展属性。一些IDL扩展属性是在Web IDL规范中定义的,而另一些是特定于Blink的IDL扩展属性。除了特定于闪烁的IDL扩展属性外,IDL文件应以符合规范的方式编写(即,仅从规范中复制并粘贴)。

 

其次,您需要为Node定义一个C ++类,并为firstChild实现一个C ++ getter:

 

class EventTarget:public Sc​​riptWrappable {//所有暴露给JavaScript的类都必须从ScriptWrappable继承。

…;

};

 

类Node:public EventTarget {

  DEFINE_WRAPPERTYPEINFO(); //所有具有IDL文件的类都必须具有此宏。

  节点* firstChild()const {return first_child_; }

};

 

在通常情况下,就是这样。生成node.idl时,IDL编译器会自动为Node接口和Node.firstChild生成Blink-V8绑定。自动生成的绑定是在//src/out/{Debug,Release}/gen/third_party/blink/renderer/bindings/core/v8/v8_node.h中生成的。当JavaScript调用node.firstChild时,V8会调用v8_node.h中的V8Node :: firstChildAttributeGetterCallback(),然后会调用您在上面定义的Node :: firstChild()。

 

如果您想了解更多信息:

 

V8和闪烁

隔离,上下文,世界

当您编写涉及V8 API的代码时,了解隔离,上下文和世界的概念很重要。它们分别在代码库中由v8 :: Isolate,v8 :: Context和DOMWrapperWorld表示。

 

隔离对应于物理线程。隔离:闪烁中的物理线程= 1:1。主线程具有自己的隔离。辅助线程具有其自己的隔离。

 

上下文对应于全局对象(在使用框架的情况下,它是框架的窗口对象)。由于每个框架都有其自己的窗口对象,因此渲染器进程中存在多个上下文。调用V8 API时,必须确保您使用的是正确的上下文。否则,v8 :: Isolate :: GetCurrentContext()将返回错误的上下文,在最坏的情况下,它将最终导致对象泄漏并导致安全问题。

 

World是支持Chrome扩展程序的内容脚本的概念。世界与Web标准中的任何内容都不对应。内容脚本希望与网页共享DOM,但是出于安全原因,必须将内容脚本的JavaScript对象与网页的JavaScript堆隔离。(还必须将一个内容脚本的JavaScript堆与另一个内容脚本的JavaScript堆隔离。)为了实现隔离,主线程为网页创建了一个主世界,为每个内容脚本创建了一个隔离世界。主世界和孤立世界可以访问相同的C ++ DOM对象,但是它们的JavaScript对象是孤立的。通过为一个C ++ DOM对象创建多个V8包装器来实现这种隔离。也就是说,每个世界一个V8包装器。

 

上下文,世界和框架之间有什么关系?

 

想象一下,在主线程上有N个世界(一个主世界+(N – 1)个孤立世界)。然后,一个框架应具有N个窗口对象,每个窗口对象用于一个世界。上下文是与窗口对象相对应的概念。这意味着,当我们有M个框架和N个世界时,我们有M * N个上下文(但是这些上下文是惰性创建的)。

 

如果是工人,则只有一个世界和一个全局对象。因此,只有一个上下文。

 

同样,当您使用V8 API时,应该非常小心使用正确的上下文。否则,您最终将在孤立的世界之间泄漏JavaScript对象并造成安全灾难(例如,来自A.com的扩展程序可以操纵来自B.com的扩展程序)。

 

如果您想了解更多信息:

 

V8 API

//v8/include/v8.h中定义了许多V8 API 。由于V8 API是低级的并且难以正确使用,因此platform / bindings /提供了一堆包装V8 API的帮助程序类。您应该考虑尽可能使用助手类。如果您的代码必须大量使用V8 API,则应将文件放在bindings / {core,modules}中。

 

V8使用句柄指向V8对象。最常见的句柄是v8 :: Local <>,用于从计算机堆栈指向V8对象。在计算机堆栈上分配v8 :: HandleScope之后,必须使用v8 :: Local <>。v8 :: Local <>不应在机器堆栈之外使用:

 

void function(){

  v8 :: HandleScope范围;

  v8 :: Local <v8 :: Object> object =…; // 这是对的。

}

 

类SomeObject:公共GarbageCollected <SomeObject> {

  v8 :: Local <v8 :: Object> object_; //这是错误的。

};

 

如果要从计算机堆栈外部指向V8对象,则需要使用包装器跟踪。但是,您必须非常小心,不要用它创建参考循环。通常,V8 API很难使用。如果您不确定自己在做什么,请询问blink-review-bindings @

 

如果您想了解更多信息:

 

  • 如何使用V8 API和帮助程序类:platform / bindings / HowToUseV8FromBlink.md

V8包装器

每个C ++ DOM对象(例如Node)都有其对应的V8包装器。确切地说,每个C ++ DOM对象每个世界都有其对应的V8包装器。

 

V8包装器对其相应的C ++ DOM对象有很强的引用。但是,C ++ DOM对象仅对V8包装程序具有弱引用。因此,如果您想让V8包装器存活一段时间,则必须明确地做到这一点。否则,将过早收集V8包装器,并且V8包装器上的JS属性将丢失。

 

div = document.getElementbyId(“ div”);

child = div.firstChild;

child.foo =“酒吧”;

child = null;

GC(); //如果不执行任何操作,则| firstChild |的V8包装器 由GC收集。

assert(div.firstChild.foo ===“ bar”); // …这将失败。

 

如果我们什么都不做,GC会收集child ,因此child.foo 会丢失。为了使div.firstChild 的V8包装器保持活动状态,我们必须添加一种机制,“ 只要div 所属的DOM树可以从V8到达,则使div.firstChild 的V8包装器保持活动状态”。

 

有两种方法可以使V8包装器保持活动状态:ActiveScriptWrappable包装器跟踪

 

如果您想了解更多信息:

 

渲染管线

从将HTML文件发送到Blink到在屏幕上显示像素还有很长的一段路要走。渲染管道的结构如下。

 

阅读这个出色的资料,以了解渲染管线的每个阶段的功能。(我认为我能写出比甲板更好的解释🙂

 

如果您想了解更多信息,请联系:GC收集。

 

assert(div.firstChild.foo ===“ bar”); // …这将失败。

 

如果我们什么都不做,GC会收集child,因此child.foo会丢失。为了使div.firstChild的V8包装器保持活动状态,我们必须添加一种机制,“只要div所属的DOM树可以从V8到达,则使div.firstChild的V8包装器保持活动状态”。

 

有两种方法可以使V8包装器保持活动状态:ActiveScriptWrappable和包装器跟踪。

 

如果您想了解更多信息:

 

如何管理V8包装器的生命周期:bindings / core / v8 / V8Wrapper.md

 

如何使用包装程序跟踪:platform / bindings / TraceWrapperReference.md

 

渲染管线

从将HTML文件发送到Blink到在屏幕上显示像素还有很长的一段路要走。渲染管道的结构如下。

Chrome如何显示网页

概念应用层

(此插图的原始Google文档为http://goo.gl/MsEJX,任何@ chromium.org均可开放对其进行编辑)
每个框代表一个概念性的应用程序层。任何层都不应该了解或依赖任何更高层。
  • WebKit:在Safari,Chromium和所有其他基于WebKit的浏览器之间共享的渲染引擎。该港口是WebKit的一部分,与平台相关的系统集成服务,如资源加载和图形。
  • 胶水:将WebKit类型转换为Chromium类型。这是我们的“ WebKit嵌入层”。它是两个浏览器Chromium和test_shell(允许我们测试WebKit)的基础。
  • 渲染器/渲染主机:这是Chromium的“多进程嵌入层”。它跨流程边界代理通知和命令。
  • WebContents:一个可重用的组件,它是Content模块的主要类。它很容易嵌入,以允许将HTML的多进程呈现到视图中。有关更多信息,请参见内容模块页面
  • 浏览器: 代表浏览器窗口,其中包含多个WebContent。
  • 选项卡助手:可以附加到WebContents的单个对象(通过WebContentsUserData mixin)。浏览器将其中的各种附加到它所拥有的WebContentses(一个用于收藏夹图标,一个用于信息栏等)。

WebKit

我们使用WebKit开源项目来布局网页。此代码从Apple中提取,并存储在/ third_party / WebKit目录中。WebKit主要由代表核心布局功能的“ WebCore”和运行JavaScript的“ JavaScriptCore”组成。我们仅出于测试目的运行JavaScriptCore,通常我们将其替换为高性能的V8 JavaScript引擎。我们实际上并没有使用Apple称为“ WebKit”的层,它是WebCore和OS X应用程序(例如Safari)之间的嵌入API。为了方便起见,我们通常将Apple的代码统称为“ WebKit”。

WebKit端口

在最低级别,我们有WebKit“端口”。这是我们所需的特定于平台的功能的实现,该功能可与独立于平台的WebCore代码对接。这些文件位于WebKit树中,通常位于目录中或作为带有铬后缀的文件。我们的许多端口实际上并不是特定于操作系统的:您可以将其视为WebCore的“ Chromium端口”。对于每个平台,某些部分(如字体渲染)必须以不同的方式处理。

  • 网络流量是由我们的多进程资源加载系统处理的,而不是直接从渲染过程传递给OS的。
  • 图形使用为Android开发的Skia图形库。这是一个跨平台的图形库,可处理除文本以外的所有图像和图形基元。Skia位于t hird_party / skia中。图形操作的主要入口点是/webkit/port/platform/graphics/GraphicsContextSkia.cpp。它使用同一目录以及/ base / gfx中的许多其他文件。

WebKit胶

与第三方WebKit代码相比,Chromium应用程序使用不同的类型,编码样式和代码布局。WebKit“胶水”为使用Google编码约定和类型的WebKit提供了更方便的嵌入API(例如,我们使用std :: string代替WebCore :: StringGURL代替KURL)。粘合代码位于/ webkit / glue中。胶合对象的名称通常类似于WebKit对象,但开头带有“ Web”。例如,WebCore :: Frame变为WebFrame

WebKit的“胶水”层将其余的Chromium代码库与WebCore数据类型隔离开来,以帮助最小化WebCore更改对Chromium代码库的影响。因此,Chromium永远不会直接使用WebCore数据类型。当Chromium需要戳入某些WebCore对象时,API会被添加到WebKit的“胶水”中。

“测试外壳”应用程序是一个准系统的Web浏览器,用于测试我们的WebKit端口和粘合代码。它使用与Chromium相同的粘合接口与WebKit进行通信。它为开发人员提供了一种更简单的方法来测试新代码,而无需具有许多复杂的浏览器功能,线程和进程。此应用程序还用于运行自动化的WebKit测试。但是,“测试外壳”的缺点是,它不像Chromium那样以多进程方式使用WebKit。内容模块嵌入在称为“内容外壳”的应用程序中,该应用程序将很快运行测试。

渲染过程

Chromium的渲染过程使用胶水接口嵌入了我们的WebKit端口。它不包含太多代码:它的工作主要是作为浏览器IPC通道的呈现器端。

渲染器中最重要的类是RenderView,它位于/content/renderer/render_view_impl.cc中。该对象表示一个网页。它处理往返于浏览器进程的所有与导航有关的命令。它源自提供绘画和输入事件处理的RenderWidget。所述的RenderView经由全球浏览器进程通信(每呈现进程)RenderProcess对象。

FAQ:RenderWidget和RenderView有什么区别?RenderWidget通过在称为WebWidgetDelegate的粘合层中实现抽象接口,映射到一个WebCore :: Widget对象。基本上,这是屏幕上的一个窗口,用于接收输入事件并绘制到该窗口中。甲的RenderView从继承RenderWidget并且是选项卡或弹出窗口的内容。除了小部件的绘制和输入事件外,它还处理导航命令。仅在一种情况下存在RenderWidget而没有RenderView ,这是网页上的选择框。这些是带有向下箭头的框,这些框会弹出一个选项列表。选择框必须使用本机窗口渲染,以便它们可以显示在其他所有框的上方,并在必要时弹出框。这些窗口需要绘制并接收输入,但是没有单独的“网页”(RenderView)。

渲染器中的线程

每个渲染器都有两个线程(有关图表,请参见多进程体系结构页面;有关如何编程,请参见Chromium中的线程)。渲染线程是主要对象(例如RenderView)所在的位置并运行所有WebKit代码。当它与浏览器通信时,消息首先发送到主线程,然后主线程将消息分派给浏览器进程。除其他外,这使我们能够将消息从渲染器同步发送到浏览器。发生这种情况的原因是少数操作需要浏览器的结果才能继续。一个示例是在JavaScript请求时获取页面的Cookie。渲染器线程将阻塞,并且主线程将对所有收到的消息进行排队,直到找到正确的响应为止。随后将任何同时接收到的消息发布到渲染器线程以进行正常处理。

浏览器过程

 

低级浏览器过程对象

与渲染过程的所有IPC通信都是在浏览器的I / O线程上完成的。该线程还处理所有网络通信,以防止其干扰用户界面。

在主线程(运行用户界面的地方)上初始化RenderProcessHost时,它将创建新的渲染器进程和带有指向渲染器的命名管道的ChannelProxy IPC对象。该对象在浏览器的I / O线程上运行,侦听到渲染器的命名管道,并自动将所有消息转发回UI线程上的RenderProcessHost。甲ResourceMessageFilter将被安装在该通道中,这将筛选出可直接在I / O线程上处理诸如网络请求的某些消息。此过滤发生在ResourceMessageFilter :: OnMessageReceived中

所述RenderProcessHost在UI线程上是负责分派所有视图专用消息到适当的RenderViewHost(它处理非观看专用消息本身的有限的数目)。这种调度发生在RenderProcessHost :: OnMessageReceived中

高级浏览器过程对象

特定于视图的消息进入RenderViewHost :: OnMessageReceived。大多数消息都在这里处理,其余消息转发到RenderWidgetHost基类。这两个对象映射到渲染器中的RenderViewRenderWidget(有关这些含义,请参见上面的“ Render Process”)。每个平台都有一个视图类(RenderWidgetHostView [Aura | Gtk | Mac | Win]),以实现与本机视图系统的集成。

RenderView / Widget上方是WebContents对象,大多数消息实际上最终都作为对该对象的函数调用而结束。一个WebContents代表网页的内容。它是内容模块中的顶级对象,并负责以矩形视图显示网页。有关更多信息,请参见内容模块页面

所述WebContents对象被包含在TabContentsWrapper。那是chrome /并负责制表符。

说明性例子

围绕Chromium源代码 涵盖了导航和启动的其他示例。

“设置游标”消息的寿命

设置光标是从渲染器发送到浏览器的典型消息的示例。在渲染器中,将发生以下情况。

  • 设置光标消息是由WebKit在内部生成的,通常是响应输入事件而生成的。设置光标消息将从content / renderer / render_widget.cc中的RenderWidget :: SetCursor开始
  • 它将调用RenderWidget :: Send来分派消息。RenderView也使用此方法将消息发送到浏览器。它将调用RenderThread :: Send 
  • 这将调用IPC :: SyncChannel,它将在内部将消息代理到渲染器的主线程,并将其发布到命名管道以发送到浏览器。

然后浏览器控制:

  • RenderProcessHost中的IPC :: ChannelProxy在浏览器的I / O线程上接收所有消息。它首先通过ResourceMessageFilter发送它们,后者直接在I / O线程上调度网络请求和相关消息。由于我们的消息没有被过滤掉,因此它将继续进入浏览器的UI线程(IPC :: ChannelProxy在内部执行此操作)。
  • RenderProcessHost :: OnMessageReceived内容/浏览器/ renderer_host / render_process_host_impl.cc获取相应的渲染过程的所有意见的消息。它直接处理几种类型的消息,其余的则转发到与发送消息的源RenderView对应的适当的RenderViewHost
  • 该消息到达RenderViewHost :: OnMessageReceived内容/浏览器/ renderer_host / render_view_host_impl.cc。很多消息都在这里处理,但我们是不是因为它是从发送的消息RenderWidget和处理由RenderWidgetHost
  • RenderViewHost中所有未处理的消息都将自动转发到RenderWidgetHost,包括我们设置的光标消息。
  • 内容/浏览器/renderer_host/render_view_host_impl.cc中的消息映射  最终在RenderWidgetHost :: OnMsgSetCursor中接收到消息,并调用适当的UI函数来设置鼠标光标。

“鼠标单击”消息的寿命

发送鼠标单击是从浏览器到渲染器的典型消息示例。

  • Windows消息由RenderWidgetHostViewWin :: OnMouseEvent在浏览器的UI线程上接收,然后在同一类中调用ForwardMouseEventToRenderer
  • 转发器功能将输入事件打包到一个跨平台的WebMouseEvent中,并最终将其发送到与之关联的RenderWidgetHost
  • RenderWidgetHost :: ForwardInputEvent创建一条IPC消息ViewMsg_HandleInputEvent,将WebInputEvent序列化为其,然后调用RenderWidgetHost :: Send
  • 这只是转发给拥有的RenderProcessHost :: Send函数,后者将消息提供给IPC :: ChannelProxy
  • 在内部,IPC :: ChannelProxy将消息代理到浏览器的I / O线程,并将其写入相应渲染器的命名管道。

请注意,WebContents中还会创建许多其他类型的消息,尤其是导航消息。它们遵循从WebContentsRenderViewHost的相似路径。

然后渲染器控制:

  •  渲染器主线程上的IPC :: Channel读取浏览器发送的消息,而IPC :: ChannelProxy代理到渲染器线程。
  • RenderView :: OnMessageReceived获取消息。许多类型的消息直接在这里处理。由于单击消息不是,因此它(与所有其他未处理的消息一起)进入RenderWidget :: OnMessageReceived  ,后者又将其转发到  RenderWidget :: OnHandleInputEvent
  • 将输入事件提供给  WebWidgetImpl :: HandleInputEvent,在此将其转换为WebKit PlatformMouseEvent类,并提供给WebKit 中的WebCore :: Widget类。