https://www.infoq.cn/article/CS9-WZQlNR5h05HHDo1b
https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#Introduction
下面都以chrome浏览器为例:
浏览器多进程架构
chrome由多个进程组成,每个进程负责自己的核心职责,这些多进程相互配合完成了浏览器的整体功能。
其中一个进程包含多个线程,每个进程中的线程又协同工作。
其中chrome的多进程架构如下:
Browser进程:
- 负责包括地址栏,书签栏,前进后退按钮等部分的工作;
- 负责处理浏览器的一些不可见的底层操作,比如网络请求和文件访问;
Utility 进程:
- 进行”危险操作“(可能会失败导致进程崩溃)的时候。例如图片解码、文件解压缩,为了保证Browser进程的稳定,会开启一个 utility 进程临时做些事。
Renderer进程:
- 负责一个tab标签页关于网页显示的所有事情
Plugin 进程:
- 负责网页用到的一些插件,例如flash等
GPU进程:
- 负责处理与GPU显示浏览器内容相关的事情
下图是不同进程负责的浏览器区域:
chrome页提供了详细的进程显示如下图:(通过「页面右上角的三个点点点 — 更多工具 — 任务管理器」即可打开相关面板)
Chrome 多进程架构的优缺点
优点:
- 某一渲染进程出问题不会影响其他进程
- 更为安全,在系统层面上限定了不同进程的权限
缺点:
- 由于不同进程间的内存不共享,不同进程的内存常常需要包含相同的内容。
- 为了节省内存,Chrome 限制了最多的进程数,最大进程数量由设备的内存和 CPU 能力决定,当达到这一限制时,新打开的 tab 会替换掉之前tab渲染进程。
_tips1: chrome多进程在资源丰富的机器上,进程会划分的很细,比如把Browser进程中的网络请求、文件处理等进程单独开一个进程,这样会让chrome运行更加稳定,但是在资源不足的机器上,会合并到Browser进程中,
tips2: iframe 的渲染 – Site Isolation,Site Isolation 机制从 Chrome 67 开始默认启用。这种机制允许在同一个 Tab 下的跨站 iframe 使用单独的进程来渲染,这样会更为安全。
浏览器角度解析:从输入url到页面显示发生了什么?
浏览器 tab页面外的工作主要由 Browser Process 掌控,Browser Process 又对这些工作进一步划分,使用不同线程进行处理:
- UI thread : 控制浏览器上的按钮及输入框;
- network thread: 处理网络请求,从网上获取数据;
- storage thread: 控制文件等的访问等
导航
浏览器地址栏输入文字 ===>
1.UI thread 需要判断用户输入的是 URL 还是 query ===>
2.点击回车,开始导航,UI thread 通知network thread获取网页内容,并显示tab 上的 spinner(转圈) ===>
3.network thread 会执行 DNS 查询,随后为请求建立 TLS 连接,之后进行tcp连接发出http请求 ===>
4.收到响应,network thread 会依据 Content-Type 及 MIME Type sniffing 判断响应内容的格式。
- 如果响应内容的格式是 HTML ,下一步将会把这些数据传递给 renderer process,
- 如果是 zip 文件或者其它文件,会把相关数据传输给storage thread处理
5.经过了上述过程,数据以及渲染进程都可用了, Browser Process 会给对应的 renderer process 发送 IPC 消息来确认导航,一旦 Browser Process 收到 renderer process 的渲染确认消息,导航过程结束,页面加载过程开始(下节说页面渲染过程)
6.页面渲染结束,当 renderer process 渲染结束(渲染结束意味着该页面内的所有的页面,包括所有 iframe 都触发了 onload 时),会发送 IPC 信号到 Browser process, UI thread 会停止展示 tab 中的 spinner。
_tips3:webAPI内置了一些 renderer 进程与 Browser进程的通信 的一些方法,例如 beforeunload:
页面渲染
renderer 进程几乎负责 Tab 内的所有事情,渲染进程的核心目的在于转换 HTML CSS JS 为用户可交互的 web 页面。渲染进程中主要包含以下线程:
渲染进程包含的线程
- 主线程 Main thread
- 工作线程 Worker thread
- 排版线程 Compositor thread (合成器线程)
- 光栅线程 Raster thread
1.构建DOM
render process接收到html文本信息,根据HTML标准,解析成DOM(DOM TREE),
2.加载次级资源
碰到一些 img link script标签,这些资源需要从缓存或者网上获取,主进程在构建DOM的时候会从上至下,把资源链接发送给 browser proccess 中 network thread下载后返回
3.js执行
碰到js时,主线程会停止解析 HTML,转而去加载、解析和执行JS(JS可能会改变DOM结构)。当然异步加载的如async、defer属性的script不会阻塞 HTML 解析
4.样式处理
主线程基于css选择器,将外部和默认style挂载到DOM tree每个节点上
5.获取布局
遍历dom tree,主线程会构建出每个元素的坐标及盒子大小的布居树(render tree)
6.绘制元素
在painting阶段,主线程会再次遍历 render tree。创建绘制记录。绘制记录可以看做是记录各元素绘制先后顺序的笔记。
7.合成帧
主线程遍历 render tree来创建 layer tree。添加了 will-change
CSS 属性的元素,会被看做单独的一层。
layer tree 创建完成后,每帧渲染的顺序就被确定了,主线程通知合成器线程,将每一层进行栅格化(切成小块),然后将这些小块发送给栅格线程进行光栅化,并发送给 GPU显存中存起来。
之后合成器线程会收集光栅化的小块(绘制四边形磁铁信息)来创建合成帧。
合成帧通过IPC传递到 browser process,然后这些合成帧通过GPU process传递给 GPU进行显示。
合成器的优点在于,其工作无关主线程,合成器线程不需要等待样式计算或者 JS 执行,这就是为什么合成器相关的动画 最流畅,如果某个动画涉及到布局或者绘制的调整,就会涉及到主线程的重新计算,自然会慢很多。
浏览器对事件的处理
用户触发事件的时候,最先收到信息的是 browser process,但是browser process只会感知到哪里出事了,,处理的过程还是要传给 render process进行处理。
事件发生时,浏览器进程会发送事件类型及相应的坐标给渲染进程,渲染进程随后找到事件对象并执行所有绑定在其上的相关事件处理函数。
前文中,我们提到过合成器可以独立于主线程之外通过合成栅格化层平滑的处理滚动。如果页面中没有绑定相关事件,组合器线程可以独立于主线程创建组合帧。如果页面绑定了相关事件处理器,主线程就不得不出来工作了。这时候合成器线程会怎么处理呢?
这里涉及到一个专业名词「理解非快速滚动区域(non-fast scrollable region)」由于执行 JS 是主线程的工作,当页面合成时,合成器线程会标记页面中绑定有事件处理器的区域为 non-fast scrollable region ,如果存在这个标注,合成器线程会把发生在此处的事件发送给主线程,如果事件不是发生在这些区域,合成器线程则会直接合成新的帧而不用等到主线程的响应。
查找事件对象
主线程进行命中测试(hit test)来查找对应事件目标,命中测试会基于渲染过程中生成的绘制记录( paint records )查找事件发生坐标下存在的元素。