上一篇文章中,我们说完了浏览器的事件循环机制(☞传送门),这里聊聊浏览器最核心的渲染进程。前端小伙伴们天天和浏览器打交道,如果对其有一定了解但不多,那么咱们可以一起跟随本文来看看。
在这里我们将以大家平日用的最多的Chrome浏览器为例进行探讨。
浏览器中的进程
在Chrome中依次点击:右上角⋮
---> 更多工具
---> 任务管理器
,可以查看它的任务管理器:
如上图所示,浏览器是一个多进程架构,它主要包含以下进程:
Browser
:浏览器主进程,控制浏览器工具栏(如前进后退按钮,地址栏,书签等),处理网络请求,文件访问,以及标签页的新建或关闭等。Renderer
:渲染进程,控制在标签页内显示的网页的所有内容,浏览器核心部分。Plugin
:插件进程,处理网站用到的任何插件。插件进程通常在插件激活时才会创建。GPU
:GPU进程,独立于其他进程,处理与GPU相关的任务。之所以将GPU分离为独立的进程,是因为GPU需要同时处理来自多个应用程序的请求,并在同一个界面绘制。其他进程
:如Extension(扩展程序)进程,Utility(实用程序)进程等。
这里注意,上面提到插件和扩展程序不要混为一谈。
- 插件通常作为独立的应用程序安装在系统上,在需要的时候由浏览器来调用,插件对系统资源的访问权限较高。
- 扩展程序指的是用HTML,CSS和JS开发且安装在浏览器的扩展程序中的程序,用于扩展和增强浏览器的功能。
进程间基于IPC
来进行通信,下图是浏览器界面不同部分对应的的不同进程:
每个标签页对应一个渲染进程?
老规矩先看图
通常情况下,每个标签页会对应一个独立的渲染进程。图中有三个标签页,任意一个标签页崩溃了,都不会影响到另外两个。即使其中两个标签页都崩溃掉了,剩下的独苗:
试想,若图中的三个标签页共用一个进程,那么当其中任意一个标签页崩溃了,其它标签页全部凉凉。所以每个标签页一个渲染进程有助于提高浏览器稳定性。
看起来一个标签页对应一个渲染进程很完美的样子,所以问题的答案是肯定的吗?并不是!
1.先看一个标签页对应多个渲染进程 的例子,站点隔离(Site Isolation)
:
站点隔离是Chrome浏览器后引入的一个功能,它为每个跨站点的iframe运行一个单独的渲染器进程。之前讨论的是每个标签页一个渲染器进程的模型,这允许跨站点的iframe在一个渲染器进程中运行,并在不同网站之间共享内存空间。在同一个渲染器进程中运行a.com和b.com看似可行。同源策略是网络的核心安全模型;它确保一个网站不能在未经同意的情况下访问其他网站的数据。绕过这一策略是安全攻击的主要目标。进程隔离是分离网站的最有效方式。随着Meltdown和Spectre的出现,我们更加明确地认识到需要通过进程来分隔网站。自从Chrome 67起,默认在桌面上启用站点隔离后,标签页中的每个跨站点iframe都获得了一个单独的渲染器进程。
2.再说多个标签对应一个渲染进程的情况:
进程拥有自己的私有内存空间,而渲染进程通常都包含基础架构的副本(如JavaScript引擎v8
),这就意味着内存使用量增加。为了节省内存,Chrome会根据一些策略将同一个网站显示相同的多个标签页共享一个进程。例如当你打开了多个空的浏览器标签页的时候可以发现它们在任务管理中实际上共用一个进程。
多进程的安全性和沙盒化
将浏览器的工作分离成多个进程的一个好处是安全性和沙盒化(Sandboxing)
。由于操作系统提供了限制进程权限的方法,浏览器可以对某些进程的某些功能进行沙盒化。
浏览器中沙盒化非常重要,因为浏览器经常需要处理来自互联网的不可信内容。通过将不同的浏览器任务(如渲染网页、执行JavaScript代码)放在各自独立的沙盒环境中,可以有效地防止恶意代码利用浏览器的漏洞来攻击用户的计算机。
例如,如果一个网页的渲染过程被沙盒化,即使这个网页包含恶意代码,它也无法访问计算机上的其他文件或系统资源,因为它被限制在了一个隔离的环境中。这增加了浏览器的安全性,减少了恶意软件通过浏览器渗透到用户系统的风险。
沙盒化是一种安全技术,用于将一个程序或进程隔离在一个受限的环境中,这个环境通常被称为
沙盒
。在这个沙盒环境中,程序或进程可以执行任务,但它的能力受到限制,不会影响其他的程序或整个操作系统。沙盒化通常用于提高系统的安全性,因为它可以防止恶意软件或程序错误对其他部分的系统造成影响。
渲染进程
渲染进程是浏览器最重要的部分,主要是由主线程
,Worker线程
,合成器线程
,光栅线程
组成。它的核心工作是将HTML、CSS和JavaScript转化为用户可以交互的网页。
大家常说的JavaScript是单线程的 ,这里的单线程指的就是主线程
。主线程是渲染进程中最重要的线程,它负责处理HTML解析,CSS样式计算,JavaScript执行,以及执行布局、重排和垃圾回收 。这意味着长时间运行的JavaScript会阻塞线程,导致页面无响应,从而带来糟糕的用户体验。主线程需要处理的工作越少,该线程就能更多地响应用户事件。
从解析到渲染:
-
构建DOM :当渲染进程收到HTML时,主线程就会开始解析HTML(文本字符串),并根据HTML标准将其转化为文档对象模型(DOM)。
-
加载子资源 :有一些外部资源(图片,css,js)需要从网络或者缓存加载,主线程可以一个个请求这些资源,但为了加快速度,会同时运行
预加载扫描器(preload scanner)
,它预览HTML解析器生成的标记,并向浏览器进程中的网络线程发送请求。
- 资源优先级 :当HTML解析器遇到
<script>
标签时,会暂停解析HTML文档,优先加载解析并执行它的JavaScript代码,因为JavaScript中可以通过document.write()
之类的操作改变文档结构。这种情况可以通过给<script>
标签设置async
或defer
属性来解决,也可以通过<link rel="preload">
来告诉浏览器预加载该资源。
async
用于异步加载JavaScript文件。一旦脚本加载完成,它会立即执行,可能会打断HTML的解析。
defer
用于延迟执行脚本直到HTML解析完成。适用于那些依赖于DOM,或者需要在文档解析完成后运行的脚本。
<link rel="preload">
用于告诉浏览器提前加载特定资源,但并不执行它们,通常用于提前加载CSS、JavaScript、字体文件等资源。
- 样式计算:主线程会解析CSS并确定每个DOM节点的计算样式,即使不提供任何CSS,每个DOM元素也存在一个默认的样式,例如h1标签比h2标签要大。
- 布局 :主线程构建完DOM树和CSSOM树后,会合并为一个渲染树,渲染树创建后,浏览器接着进行布局计算,以创建布局树。它们只包含页面可见内容相关信息,比如应用了
display: none
的元素不会在包含在渲染树和布局树中,但是应用了visibility: hidden
的元素包含在渲染树和布局树中。还有一些伪类,本身不在DOM中也会被包含在渲染树和布局树中,如下代码所示:
css
p::before {
content:'xx'
}
- 绘制:主线程通过遍历布局树来创建绘制记录,绘制记录用于记录绘制过程,比如"先绘制背景,然后文本,再然后矩形"。绘制记录用于告诉浏览器按特定顺序来绘制页面的元素,有助于优化浏览器渲染过程。
- 合成 :现代浏览器通过合成来进行页面的绘制,它会将页面的不同部分分离成多个层,分别进行
光栅化
,然后在合成器线程
中将它们合成为一个合成器帧。这个合成器帧通过IPC
发送给浏览器进程。在浏览器进程中,UI线程可能会添加自己的合成器帧(例如UI变更)。同样浏览器扩展(如果有的话)也可能在其渲染进程中生成合成器帧。这些合成器帧随后被发送到GPU,GPU负责将它们渲染在屏幕上。
浏览器知道了文档的结构,每个元素的样式,页面的几何形状以及绘制顺序,将这些信息转换成屏幕上的像素被称为光栅化。光栅化依赖于
光栅化线程
。
Worker线程
Worker线程
是一种后台线程,这些线程独立于主线程运行,可以进行密集型计算或处理数据,而不会导致页面响应变慢或卡顿。Worker线程通过消息传递与主线程通信,但它不能直接访问DOM。使用Worker线程可以提高页面性能和用户体验。
文章参考
- Inside look at modern web browser(part 1)
- Inside look at modern web browser(part 2)
- Inside look at modern web browser(part 3)
如有错误请帮忙指出,不胜感激!