Browser 原理¶
概览¶
CPU、GPU、内存和多进程体系结构¶
计算机的核心 CPU 和 GPU¶
-
CPU
- 中央处理器 (Central Processing Unit)
- CPU 内部结构复杂:需要通用性处理 不同的数据类型;需要利用分支跳转和中断来处理逻辑判断。
- CPU 擅长逻辑控制,串行的运算。
-
GPU
- 图形处理器 (Graphics Processing Unit)
- GPU 面对的则是 类型高度统一的、相互无依赖 的大规模数据和不需要被打断的纯净的计算环境。
- GPU 擅长同时处理跨内核的简单任务,大规模并发计算。
进程与线程¶
- A process can be described as an application’s executing program. 进程可被描述为一个应用的执行程序。
- A thread is the one that lives inside of process and executes any part of its process's program. 线程存在于进程中,并执行进程内部的任意部分。
- 多个线程之间共享内存;多个进程之间不共享内存,通过 IPC 传递信息。
浏览器架构¶
-
概念
- 图示
- 浏览器可能由一个拥有很多线程的进程,或是通过 IPC 通信的不同线程的进程。
- Chrome 是多进程架构,顶层是 Browser process,来协调其它进程。
-
进程各自控制什么?
- 图示
-
浏览器进程 (Browser Process)
- 控制地址栏、书签、后退和前进按钮。还处理浏览器的不可见的特殊部分,例如网络请求和文件访问。
- UI thread,负责绘制浏览器的按钮和输入字段
- Network thread,负责从互联网接收并处理数据的网络堆栈
- Storage thread,控制对文件的访问
-
渲染进程(Renderer Process)
- 控制 Tab 内可见区;每个选项卡创建一个 Renderer Process。
-
Plugin Process
- 控制网站使用的插件,例如 flash。
-
GPU Process
- 独立于其他 Process 处理 GPU 任务。因为 GPU 处理来自多个应用的请求,它被分成不同的 Process
-
Chrome 多进程架构优点
- 发挥CPU多核优势
-
独立运行,互相影响
- 每个选项卡都有自己的渲染进程,且独立运行。
- 如果一个选项卡长时间无响应,可以关闭这个选项卡,保持其他选项卡功能正常。
-
安全性与沙盒化
- 操作系统可以对不同进程赋予各种权限或限制能力。比如,浏览器限制 Renderer Process 读写任意文件。
- 浏览器对某些进程进行沙箱(sandbox)处理,不允许共享内存。
-
Chrome 多进程内存优化
- 图示
-
限制进程数量
- 当进程达到阈值,Chrome 会将访问统一网站的 Tab, 放在统一个进程里。
-
服务化
- 当 chrome 在强大的硬件上运行时,它会将每个服务拆分为不同的 Process ,从而提供更高的稳定性。
- 但如果在资源受限的设备上运行时,chrome 会将服务整合到同一个 Process 中,从而节省内存占用。
-
站点隔离
- CORB ( Cross-Origin Read Blocking) 可根据其 MIME 类型,防止敏感内容 (如 balance.json) 进入Renderer Process 的内存。
- 为每个标签页(或 iframe)提供一个独立的 Renderer Process 。将敏感数据保留在 Renderer Process 外,防止被不受信任的网站问或窃取数据。
-
CORB 原理
-
从服务器请求两类资源
- 数据资源,如HTML,XML或JSON文档
- 媒体资源,如图像,JavaScript,CSS或字体
-
以下情况 CORB 阻止 Renderer Process 接收跨源数据
- 资源 header 有一个 X-Content-Type-Options: nosniff
- CORS 不允许访问:如 Access-Control-Allow-Origin 不匹配。
-
判断请求类型,阻止请求
- 属于
style
但 MIME 类型不是text/css
; - 属于
script
但 MIME 类型不是 JavaScript MIME 类型。
- 属于
-
导航时发生了什么¶
1)处理输入¶
- 当用户开始输入地址栏时,浏览器进程的 UI thread 解析判断
一次搜索查询 or URL地址?
- 将输入的内容,通过 IPC 通知网络进程 去发送请求;UI thread 让选项卡展示 Spinner。
2)开始导航¶
- 图片
- 网络进程获取站点内容,比 DNS lookup 和 establishing TLS connection。
- Network Process 可能收到 Http 301 的重定向响应头;此时 Network Process 告知 UI thread 对输入框 URL 做修改,启动另一次URL请求。
3)解析响应¶
- 图片
-
判断响应类型
- 响应头 Content-Type 判断数据类型;若丢失或设置错误,则使用 MIME 类型嗅探。(mime sniffer)
-
处理不同数据类型
- 若是 HTML,将数据传给渲染进程;
- 若是 zip 或其他文件,将数据传给下载管理器。
-
同时 SafeBrowsing 检查
- 已知的恶意站点匹配,网络进程显示警告页面。
- 触发 CORB,确保敏感的跨域数据无法进入Renderer Process。
4)查找渲染进程¶
- 图片
网络进程
告诉 UI 线程数据准备完毕,UI 线程
寻找渲染进程
去开始渲染 Web 页面。- 优化措施:UI thread 在通知网络 thread 获取数据的同时,通知预创建一个
渲染进程
;若重定向,则再创建一个 渲染进程。
5)提交导航¶
- 图片
- 数据和渲染进程都就绪,
浏览器进程
通过 IPC 通知指定的渲染进程
提交导航(commit the navigation)。 -
渲染进程
在开始接收 HTML 后,会返回确认信息,然后浏览器进程
会修改 UI。- 比如 HTTPS 的小锁头;
- 地址栏会更新,站点设置 UI 反映新页面的站点信息;
- 后退/前进按钮可以使用 window.history 的数据;
- 选项卡的 session 历史记录更新,保存在磁盘上等。
-
浏览器进程
收到渲染进程
的确认后,本次导航完毕;加载解析文档开始。
6)初始化加载完毕¶
渲染进程
加载资源和渲染页面结束后,触发 onload 事件;同时会发送 IPC 给浏览器进程,UI thread 停止 spinner。
如果有 Service Worker¶
- 浏览器进程中的
网络线程
查找 service worker 作用域。 - 浏览器进程中的 UI 线程启动渲染进程来处理 service workers;然后渲染进程中的
工作线程
从网络请求资源。
导航预加载¶
- 通过与 service worker 启动并行加载异步资源来加速的机制。
渲染进程内部机制¶
渲染进程概念¶
- 渲染进程(Renderer Process),即浏览器内核,核心工作是将 HTML、CSS 和 Javascript 转化为用户可与之交互的网页。
- 渲染进程内部包含主线程、工作线程、合成线程和光栅线程。
关键渲染路径¶
- 渲染关键路径(Critical Render Path):
DOM & CSSOM -> 样式计算 -> 布局 -> 分层 ->绘制 -> 分块 -> 光栅化 -> 合成
1) 解析(Parsing)¶
- 主线程解析 HTML 并构建 DOM 树
-
创建 DOM 树
- 转换 (Conversion):HTML原始字节 -> 单个字符
- 序列化 (Tokenizing):单个字符 -> 标记
- 词法分析 (Lexing): 标记 -> 节点"对象"
- 构建 DOM (DOM construction):对象 -> 树
-
子资源加载
- 渲染进程收到提交导航消息后,
主线程
开始解析文本字符串(HTML),并转化为文档对象模型 (DOM)。 - 在解析构建 DOM 时,
主线程
会按顺序逐个从网络或缓存请求图片、CSS 和 JS。
- 渲染进程收到提交导航消息后,
-
Javascript 阻塞解析
- 遇到
<script>
标记时,暂停解析,开始加载、解析并执行 Javascript 代码。 - 因为内部可能存在
document.write
代码。 - 异步执行
async
: 告诉浏览器,不必等脚本加载和执行完后再加载页面。 - 推迟执行
defer
: 浏览器解析到结束标签</html>
之后,才执行。 preload
: 提前加载较晚出现但对当前页面非常重要的资源,优先级高prefetch
: 提前加载后继路由需要的资源,优先级低
- 遇到
2) 样式计算¶
主线程
解析 CSS,以添加计算后样式
- 构建 CSSOM:
字节 -> 字符 -> 序列化 -> 节点 - > CSS Object Model
。
3) 布局¶
主线程
遍历计算样式后的 DOM 树,以此生成布局树。
- 布局是计算元素几何形状的过程。
- 仅包含页面上可见内容相关信息:
display:none
不在树上;visibility: hidden` 在。
4) 分层¶
主线程
遍历布局树生成图层树
5) 绘制¶
主线程
遍历布局树并生成绘制记录(包含绘制顺序)
主线程
把图层的绘制拆分成绘制指令,生成待绘制列表,提交到合成线程
。
6) 合成¶
-
分块(Tiles)
- 绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。
合成线程
会将图层划分为 图块(tile),这些图块的大小通常是 256x256 或者 512x512。
-
栅格化(Raster)
- 所谓的栅格化,是指将图块转换为位图。
- 然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作就是由栅格化来执行的。
- 通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。
- 先绘制光栅化视窗内的画面,如果用户滚动,移动光栅框。
-
主线程的光栅化和合成
主线程
创建分块的位图并发送到 GPU
-
绘制四边形(draw squad)
- 一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——DrawQuad,然后将该命令提交给浏览器进程。
- 浏览器进程的 viz 组件,根据DrawQuad命令,浏览器进程将页面绘制到内存中,最后将内存先显示在屏幕上
-
合成帧
合成线程
创建合成帧,将其发送到浏览器进程
,再接着发送到 GPU
重绘与回流¶
回流(reflow)¶
- 当 render tree 中一部分因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。称为回流。
-
触发回流的属性
- 盒子模型相关的属性会触发重新布局
- 定位属性及浮动也会触发重新布局
- 改变节点内部文字结构也会触发重布局
-
当元素的几何属性发生变化,浏览器触发重新布局,即解析之后的一系列子阶段。
重绘(repaint)¶
- 当 render tree 中一部分元素需要更新属性,而这些属性只是影响元素的外观,风格,而不影响不布局。称为回流。
- 更新元素的绘制属性,比如背景颜色,触发重绘;跳过布局和分层,直接进行绘制阶段。
合成¶
- CSS3 的 transform、opacity、filter 这些属性会触发合成。
- 非主线程,并没有占用主线程的资源;并在合成线程生成位图的时中利用 GPU 进行加速;相对于重排和重绘,大大提升绘制效率。
- 渲染引擎跳过布局和绘制,直接执行后续的合成。
新建DOM过程¶
- 获取DOM后分割为多个图层
- 对每个图层的节点计算样式结果(recaculate style)
- 为每个节点生成图形和位置(layout-回流)
- 将每个节点绘制填充到图层位图中(Paint-重绘)
- 将图层作为纹理上传至GPU
- 符合多个图层到页面上生成最终屏幕图像(Composite Layers)
优化思路¶
- 将频繁重绘回流的 DOM 元素单独作为一个独立图层,那么这个 DOM 元素的重绘和回流的应用只会在这个图层中。
Chrome 创建图层的条件¶
- 3D 或透视变换 CSS 属性(perspective transform)
- 使用加速视频解码的
video
节点 - 拥有 3D 上线文(WebGL)或加速 2D 上下文(Canvas)的节点
- 混合插件 Flash
- 对自己的 opacity 做CSS 动画或使用一个动画 webkit 变换的元素
- 拥有加速 CSS 过滤器的元素
- 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的图层里)
- 元素有一个 z-index 较低且包含一个复合层的兄弟元素(该元素在复合层上面渲染)
案例¶
- chrome devTools -> More tools -> Rendering -> Paint flashing:查看页面上重绘的区域。
-
chrome devTools -> more tools -> layers: 查看图层树及成为单独图层原因。
* { will-change: transform; }
- 导致所有元素都有单独图层。 Composting Reasons: Has a will-change: transform compositing hint.
优化实战¶
-
用 translate 替代 top
- 使用 translate 减少回流
-
用 opacity 替代 visibility
- opacity 会减少重绘
- opacity 没有 paint 过程:paint 会生成图层,而opacity 直接改变图层的 alpha 通道,不涉及 paint。
-
不要一条一条修改样式,预定定义好 class,修改 className
- DOM 离线后修改,读写分离。比如先
display:none;
,修改100次,然后显示出来。 - 不要将 DOM 的属性放在循环了,成为循环的变量
-
不要使用 table 布局
- table 一个小改动会造成整体table 重新排列
- div 只影响它后边元素的排列
-
动画实现的速度的选择
- 对动画新建图层
- 使用 GPU 加速
多线程附录¶
JS 引擎线程¶
- 负责解析和执行发送给用户的 大部分 js 代码。(一部分交给 worker thread)。
- 注意,GUI渲染线程与JS引擎线程互斥,所以如果JS执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞。
GUI 渲染线程¶
- 负责渲染浏览器界面,解析HTML,CSS,构建DOM 树和 Render 树,布局和绘制等。
- 当页面 Reflow 或 Repaint,该线程就会执行。
- 同样注意,GUI渲染线程与JS引擎线程互斥,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中,等到JS引擎空闲时立即被执行。
Web Worker 线程¶
- JS 的处理是单线程的。如果使用了 web worker 或 service worker,JavaScript 的一部分将由 worker threads 处理。
- 子线程是浏览器开的,完全受主线程控制,而且不能操作DOM。
- JS 引擎线程与 worker 线程间通过 postMessage API 通信(需要序列化对象来与线程交的数据)
事件触发线程¶
- 辅助 JS 引擎,用于控制事件轮询。
- 符合触发条件时,事件会被该线程放到任务队列的队尾,等待 JS 引擎处理。
定时触发线程¶
- setTimeout/setInterval
- 浏览器定时计数器不是由JS引擎计数的
异步Http请求线程¶
- XMLHttpRequest
- 回调函数放到任务队列的队尾
合成线程和 栅格线程¶
- Compositor thread 和 Raster thread 保证高效,流畅地渲染页面。