问题
构建一个不崩溃或者绝对安全的渲染引擎几乎是不可能的.
在2006年左右,浏览器的状态类似于过去的单用户,协作式操作系统.由于一些程序的错误行为可能导致整个系统都崩溃,同样某些页面的错误操作会使整个浏览器奔溃,这只是一个页面的错误或者插件的bug就会导致整个浏览器关闭和所有的Tabs.
现在的操作系统往往都更健壮,因为它将不同的程序放到了彼此隔离的单独进程中.一个应用崩溃通常不会影响其他应用或者整个系统,并且每个用户访问其他用户的数据是受限的.
架构概览
我们对浏览器的Tab页使用单独进程,可以避免整个应用受到渲染引擎的bug或者小故障的影响.我们也限制每个渲染引擎进程访问其他进程和系统的其他部分.这为浏览器带来了内存保护和访问控制操作系统带来了好处.
我们将运行UI的主线程和管理Tabs和插件进程的作为"浏览器进程"或者"浏览器".同样的,特定Tab的进程称为"渲染进程"或者"渲染器".渲染器使用Blink开源布局引擎来解释和布局HTML.
管理渲染引擎
每个渲染过程都有一个全局RenderProcess对象,该对象管理与父浏览器进程的通信并维护全局状态。 浏览器为每个渲染进程维护一个相应的RenderProcessHost,它管理渲染器的浏览器状态和通信。 浏览器和渲染器使用Chromium的IPC系统进行通信。
管理视图
每一个Tab的渲染引擎都至少有一个RenderView对象,由RenderProcess管理.相应的RenderProcessHost维护着与渲染器中每个视图对应的RenderViewHost. 每个视图都有一个视图ID,用于区分同一渲染器中的多个视图.每个渲染过程都有一个或多个RenderView对象,由RenderProcess管理,对应于内容选项卡。 相应的RenderProcessHost维护与渲染器中每个视图对应的RenderViewHost.每个视图都有一个视图ID,用于区分同一渲染器中的多个视图.这些ID在同一个渲染器中是唯一的,但在整个浏览器中不是唯一的,因此识别视图需要RenderProcessHost和视图ID.从浏览器到特定Tab的通行是通过这些RenderViewHost对象完成的.这些RenderProcessHost对象知道如何将消息发送给RenderProcess和RenderView.
组件和接口
渲染进程:
- RenderProcess在浏览器中使用相应的RenderProcessHost处理IPC.每个渲染过程只有一个RenderProcess对象.这就是所有浏览器↔渲染器通信的发生方式。
- RenderView对象在浏览器进程(通过RenderProcess)和我们的WebKit嵌入层与其对应的RenderViewHost进行通信。 此对象表示Tab或弹出窗口中的一个网页内容
浏览器进程
- Browser对象代表了顶层的浏览窗口.
- RenderProcessHost对象代表了单个浏览器端↔渲染器的IPC连接, 每个渲染进程在浏览器进程中都有一个RenderProcessHost对象.
- RenderViewHost对象封装了与远程RenderView的通信,RenderWidgetHost在浏览器中处理RenderWidget的输入和绘制.
有关嵌入层是如何工作的更多详细信息,请参阅How Chromium displays web pages。
共享渲染进程
通常,每个新窗口或Tab都会在新进程中打开.浏览器将生成一个新进程并创建一个新的RenderView。
有时,我们希望在能在Tab或窗口之间共享渲染进程. Web应用程序打开新窗口,期望与其同步通信,例如,使用JavaScript中的window.open. 如果进程总数太大,或者用户已经打开到该域的导航,在这种情况下,当我们创建一个新窗口或Tab时,我们需要重用已经打开窗口的进程. 我们还有策略为现有进程分配新Tab.Process Models中描述了这些策略.
检测崩溃和渲染器的异常行为
每个与浏览器进程的IPC连接都会监视进程句柄.如果这些句柄发出信号,则渲染进程已经崩溃,并且会向Tab通知崩溃.现在,我们会显示一个“sad tab”在屏幕上,通知用户渲染器已经崩溃. 可以重新加载按钮或开始可以打开一个新的导航. 发生这种情况时,我们要注意,浏览器并没有任何一个进程并且也不会创建一个新进程.
沙箱渲染器
考虑到渲染器在单独的进程中运行,我们有机会通过沙盒限制其对系统资源的访问. 例如,我们可以确保渲染器只能通过其父浏览器进程访问网络.同样,我们可以使用主机操作系统的内置权限来限制其对文件系统的访问.
恢复内存
考虑到渲染器在单独的进程中运行,那么将隐藏Tab,这种较低优先级的操作变得简单明了.通常,Windows上最小化的进程会将其内存自动放入“可用内存”池中.在内存不足的情况下,Windows会在将此内存转到到硬盘上,而不是转移优先级较高的内存,从而有助于保持用户可见程序的响应速度.我们可以将相同的原则应用于隐藏的Tab.当渲染进程没有顶级Tab时,我们可以释放该进程的“工作集”,作为给系统的提示,以便在必要时首先将该内存转移到磁盘。因为我们发现当用户在两个标签之间切换时,减小工作集大小也会降低Tab切换性能,所我们会逐步释放这个内存.这意味着如果用户切换回最近使用的Tab时,该Tab的内存比最近使用的Tab更容易被换入.当有足够内存来运行所有程序时,用户根本不会注意到这个过程:Windows只会在需要时才会回收这些数据,因此在内存充足时不会有性能损失.
这有助于我们在低内存情况下获得更佳的内存占用. 与很少使用的后台Tab相关联的内存可以完全换出,而前台Tab的数据可以完全加载到内存中。 相比之下,单进程浏览器将所有选项卡的数据随机分布在其内存中,并且不可能如此干净地分离已使用和未使用的数据,从而浪费内存和性能。
插件和扩展程序
Firefox风格的NPAPI插件在自己的进程中运行,与渲染器分开。 这在Plugin Architecture中有详细描述。
Site Isolation项目旨在提供渲染器之间的更多隔离,此项目的早期成果包括在隔离进程中运行Chrome HTML / JavaScript内容的扩展程序。