现代浏览器内部工作原理
汇总图
计算机的核心 CPU 和 GPU
CPU:Central Processing Unit(中央处理器), 中心处理器是计算机的大脑, 每个 CPU 核心会逐一执行不同任务现在很多计算机都是多芯片, 多内核的.
GPU:Graphics Processing Unit(图形处理器),GPU 擅长处理跨内核的简单任务, 是为了解决图形而开发的. 在图形环境中,"使用 GPU 支持" 和 "使用 CPU" 都与快速渲染与顺滑渲染有关.
许多带扳手的 GPU 内核说明它们只能执行处理有限任务
当启动电脑时候, 是 CPU 和 GPU 为应用供能. 通常情况下, 应用是通过操作系统提供的机制在 CPU 和 GPU 上运行.
三层计算机系结构: 底部是机器硬件, 中间是操作系统, 顶层是应用程序.
在进程和线程上执行程序 进程 (process) 和线程(thread)
进程作为边界框, 线程为里面抽象的鱼在游动
进程可以被描述为一个应用的执行程序, 线程存在于进程并执行任意部分.
启动应用时, 会创建一个或者多个进程来帮助应用工作, 操作系统为进程提供了一块可以使用的 "内存", 应用的所有状态都保存在该私有内存空间中, 关闭应用, 进程会关闭, 操作系统释放内存.
进程可以请求操作系统的另一个进程来执行不同的任务. 此时, 会分配不同的内存给新进程, 进程之间可以通过 (IPC:Inter Process Communication) 进行通讯. 应用的某个工作进程失去响应, 该进程就可以在不停止应用程序其他进程的情况下, 重启.
独立进程通过 IPC 通信示意图
浏览器架构
不同的浏览器可能是多进程或者单进程的.
Chrome 浏览器架构, 使用多进程的架构
浏览器顶层是浏览器进程(Browser process), 与浏览器其他应用模块进行协调工作.
Chrome 浏览器各个进程的工作:
Browser Process:
地址栏, 书签栏, 前进后退按钮等信息
不可见的一些操作, 请求, 文件访问等
Rederer Process
负责一个网页 tab 的的所有工作
Plugin Process
插件进程, 处理一个网页用到的插件, 例如 Flash 的插件
GPU Process
负责处理 GPU 相关的工作 * 还有其他进程, 例如: 扩展应用, 应用进程
不同进程指向浏览器 UI 的不同部分
谷歌浏览器每一个站点 (网页) 都是独立的进程, 这样可以保证即使网页崩溃了, 其他网页也不会受到影响. 浏览器有多个进程的好处就是安全性跟跟沙箱化. 由于操作系统提供了限制进程权限的方法, 浏览器就可以用沙箱保护某些进程. 由于进程有自己的私有内存空间, 所以每个进程都有公共基础设施的拷贝, 这也意味着会占用更多的内存, 当运行达到极限时候, Chrome 对于同一站点的不同标签页, 会使用同一进程.
当 Chrome 运行在强力硬件上时, 会把不同的服务功能模块分配到不同的进程, 从而提升稳定性, 但是当运行在弱硬件设备时候, 会将一些服务功能模块整合到同一个进程以节约内存, 但是相应的稳定性也会下降.
每个 Iframe 的渲染进程)-- 站点隔离
站点隔离实现每个 Iframe 运行在独立的渲染进程. 每个 tab 站点单独运行一个进程, 站点内的 Iframe 运行一个单独的渲染进程, 这样在不同的站点内, 相同 Iframe 可以共享内存.
同源策略是 web 的安全模型, 也就是某一站点在没有授权的情况下, 其他站点是不能获取其数据的. 进程隔离是分离站点的最高效手段.
站点隔离示意图
导航时候发生了什么?
它以浏览器进程 (Browser Process) 开始
浏览器进程管理一切除 Ta 外之外的一切, 浏览器进程内有很多线程, 例如绘制浏览器的按钮和输入栏的 UI 线程, 处理网络栈以从因特网获取获取的网络线程, 控制文件访问的存储线程. 当输入 URL, 输入由浏览器的进程的 UI 线程处理.
顶部是浏览器 UI, 底部是拥有 UI, 网络和存储线程的浏览器进程图
一个简单的导航
处理输入
浏览器进程的 UI 线程首先确定是搜索查询还是一个 URL.UI 线程决定是搜索内容到搜索引擎还是去一个网站.
UI 线程询问输入内容是搜索查询还是 URL 地址
当按下 Enter 键, UI 线程启用网络去调取获取站点内容, 加载动画会显示在标签页的一角. 网络线程会通过适当的协议, 为请求建立 TLS 连接.
SSL(Secure Sockets Layer 安全套接层), 及其继任者传输层安全 (Transport Layer Security,TLS) 是为网络通信提供安全及数据完整性的一种安全协议. TLS 与 SSL 在传输层对网络连接进行加密.
SSL 协议位于 TCP/IP 协议与各种应用层协议之间, 为数据通讯提供安全支持. SSL 协议可分为两层: SSL 记录协议 (SSL Record Protocol): 它建立在可靠的传输协议(如 TCP) 之上, 为高层协议提供数据封装, 压缩, 加密等基本功能的支持.
安全传输层协议 (TLS) 用于在两个通信应用程序之间提供保密性和数据完整性. 该协议由两层组成: TLS 记录协议 (TLS Record) 和 TLS 握手协议 (TLS Handshake). 较低的层为 TLS 记录协议, 位于某个可靠的传输协议(例如 TCP) 上面, 与具体的应用无关, 所以, 一般把 TLS 协议归为传输层安全协议.
Roger[ˈrɑ:dʒə(r)]: 无线通信答语: 收到. UI 线程告诉网络线程要导航到 http://mysite.com
在这时, 网络线程可能会收到像 HTTP 301 那样的服务器重定向头. 这种情况下, 网络线程会告诉 UI 线程, 服务器正在请求重定向. 然后, 另一个 URL 请求会被启动.
读取响应
包含 Content-Type 的响应头以及作为实际数据的 payload
一旦开始收到响应主体, 网络线程会查看数据流的前几个字节, 响应报文的 Content-Type 字段会声明数据的类型. 但可能存在丢失会错误. 所以有了 MIME 类型嗅探来解决这个问题.
如果响应是一个 html 文件, 那么下一步就会把数据传给渲染进程, 如果是一个下载的文件, 意味着是一个下载请求, 会把它传递给下载器管理器.
网络线程询问一个响应数据是否是从安全网站来的 HTML
此时也会进行 safeBrowsing 检查, 如果域名和数据匹配到一个恶意网站, 那么网络线程会显示一个警告页面. 此外也会进行 Cross Origin Read Bloking (CORB)检查, 以确保敏感的跨域数据不被传给渲染进程.
查找渲染进程
一旦所有的检查处理完毕并且网络线程确定会导航到请求的站点, 网络请求线程会告诉 UI 线程所有的数据请求完毕. UI 线程会寻找渲染进程开始渲染 Web 页面.
网络线程告诉 UI 线程去查找渲染进程
由于网络请求线程会花费时间, 一次可以应用一个优化措施, 当 UI 线程正发送一个 URL 请求给网络线程时候, 可以同时查找开启一个渲染进程待命, 如果导航重定向, 这个渲染进程或许不会用到.
提交导航
现在数据和渲染进程就绪, 浏览器会发送一个 IPC(进程间通信)到渲染进程去提交导航, 他也会传递数据流, 所以渲染进程会保持接受 HTML 数据, 一旦浏览器进程收到渲染进程已经提交的确认信息, 导航完毕并且文档加载解析开始.
这时, 地址栏已经更新, 安全指示器和站点设置 UI 会放映新页面的站点信息, 此标签页的 session 历史记录被更新, 所以前进后退按钮会走向刚导航过的站点, 当你关闭标签页或者窗口, 为了优化 Tab/session 的还原, session 历史被保存在硬盘上.
浏览器和渲染进程间的 IPC, 请求渲染页面.
额外的步骤: 初始加载完毕
一旦导航别提交, 渲染进程开始加载资源和渲染页面, 一旦渲染进程渲染完毕, 会发送 IPC 返回给浏览器进程,(这也会所有 frameh 和 onload 事件已经触发和执行完毕后发生). 这时, UI 线程停止标签页上的加载动画.
导航到另一个站点
简单导航已经完成, 在导航栏在输入一个 URL 时, 浏览器进程会先检查已经渲染的站点是否关心 beforeUnload 事件, 确认没有的话, 就会执行相同的操作, 导航到另一个站点.
befounload 事件会在用户离开或者关闭标签页面时候给予提醒 "离开此站点".
注意: 不要添加无条件的 beforeunload 处理程序, 会产生延时.
浏览器进程向渲染进程发送 IPC 告诉它将要导航到另一个站点
如果渲染进程启动了导航(Windows.location.herf=xxx), 渲染进程会先检查 befoeload 事件处理程序, 会像浏览器处理导航一样执行相同的步骤, 唯一不同的是导航请求是由渲染进程发送到浏览器进程的.
当新导航到新站点时, 会调用一个独立的渲染进程来处理导航, 同时保留当前的渲染进程来处理 unload 事件.
2 个 IPC(从浏览器进程到新渲染进程)告知渲染页面并告知旧渲染进程卸载
Service Worker 待补充
渲染进程的内部机制
渲染进程处理网站内容
渲染进程负责标签内发送的所有事情. 渲染进程中, 主线程处理服务器返回给用户的大部分数据, 如果使用了 Web sworker 或 Service Sorker, 部分 JS 将由工作线程处理. 合成和光栅线程也在渲染进程内运行, 以高效流畅的呈现页面.
渲染进程的核心工作是将 HTML,CSS 和 JavaScript 转换为用户可以与之交互的网页.
渲染进程内部包含主线程, 工作线程, 合成线程和光栅线程
解析(Parsing)
Dom 的构建 当渲染进程收到 HTML 数据时, 主线程解析文本字符串 (HTML) 并将其转换为(DOM). DOM 是页面在浏览器的内部表现, 也是开发人员通过 JS 与之交互的数据结构
HTML 的标签的解析由 HTML Standar https://html.spec.whatwg.org/ 决定, HTML 规范可以很优雅的处理一些标签错误.
子资源加载
网站的图像, CSS,JS 外部资源, 需要从网路或者缓存加载. 解析构建 DOM 时, 主线程会按处理顺序逐个加载, 为了加快速度,"预加载扫描器(preload scanner)" 会同时进行. 如果文档有 < img><link>, 预加载扫描器会在浏览器进程中发送请求.
主线程解析 HTML 并构建 DOM 树
3.JS 阻塞解析 当解析 HTML 时, 遇到 < script > 时, 会停止解析接下来的内容, 加载 JS, 并执行里面的代码.
提示浏览器如何加载资源
可以在 < script > 标签加 async 或者 defer 属性, 实现异步加载 JS, 或者加载 JS 模块, 可以使用 <link rel="preload">告知浏览器当前导航肯定需要该资源, 并且你希望尽快下载.
样式计算
只有 DOM 元素无法确定页面的外观, 需要结合 CSS 样式. 主线程解析 CSS 并且确定每个 DOM 节点计算后的样式.
主线程解析 CSS 以添加计算后样式
布局树(Layout Tree)
知道每个节点已经对应的元素样式, 不足以渲染页面. 布局是计算几何元素形状的过程, 主线程遍历 DOM, 计算样式并创建布局树, 其中包含 xy 坐标和边框大小等信息, 布局树可能与 DOM 树结构树类似, 但它仅包含页面可见内容的信息. 如果一个元素应用了 display:none, 那么该元素不是布局树的一部分. 类似地, 如果应用了如 p::before{content:"Hi!"} 的伪类, 则即使它不在 DOM 中, 也包含于布局树中.
主线程遍历计算样式后的 DOM 树, 以此生成布局树
绘制
有了 DOM, 样式, 布局树还不能画出页面, 还不知道绘制的顺序. 例如, 有的元素有了 z-index, 简单从上到下绘制就会出现图层高低错误.
因为没有考虑 z-index, 页面元素按 HTML 标记的顺序出现, 导致错误的渲染图像
在绘制步骤, 主线程遍历布局树 (Layout Tree) 创建绘制记录(Paint Records), 就像是背景优先, 然后矩形, 文字.
线程遍历布局树并生成绘制记录
更新渲染管道的成本很高
DOM + Style, 布局和绘制树的生成顺序
渲染管道最重要的事情: 每个步骤, 都是前一个操作的结果用于后一个操作的数据. 如果为元素设置动画, 每一帧都要进行相同的处理操作, 大多数浏览器的 1 帧 / 秒 如果浏览器丢失了中间部分帧, 就会让人觉得卡顿.
时间轴上的动画帧
真正绘制一个页面
光栅化
简单光栅处理示意图
现在知道了文档的结构, CSS, 布局树, 绘制顺序. 就是将数据转化为物理设备上的像素了. 这个过程成为光栅化.
合成
合成处理是将页面的各个部分光栅化, 并且合成线程进行图层移动合成.
分层
为了分清哪些元素位于什么图层, 主线程遍历布局树创建图层树, 如果某些部分是单独图层(例如划入式侧面菜单栏), 但没有拆分出来, 可以用 CSS 属性: will-change 提示浏览器.
主线程的光栅化和合成
一旦创建了图层树和确定了绘制顺序, 主线程将会把信息传递给合成线程, 接着, 合成线程会光栅化每个图层, 一个图层可能跟页面一样大, 合成线程将其分块后发送给光栅线程. 光栅线程光栅化每个小块后将他们存储在显存中.
光栅线程创建分块的位图并发送到 GPU
合成线程会不同的光栅化线程设置优先级, 以便视图或者附近区域的画面可以先光栅化显示. 图层还具有不同的分辨率的块, 可以放大显示.
一旦块被光栅化, 合成线程会收集这些块的信息(称为绘制四边形), 创建合成帧.
绘制四边形: 包含块在内存的位置, 以及合成时块在页面中的位置等信息.
合成帧: 一个绘制四边形的集合, 代表一个页面的一帧.
接着, 合成帧通过 IPC 提交给浏览器进程, 此时, 可以在 UI 线程或者其他插件的渲染进程添加一个合成帧, 这些合成器帧被送到 GPU 然后在屏幕上显示. 如果收到滚动事件, 合成帧会创建另一个合成帧到 GPU.
合成线程创建合成帧, 将其发送到浏览器进程, 再接着发送到 GPU
用户输入行为与合成器
用户的任何行为, 对于浏览器来说都是输入行为. 包括点击, 滚动, 触摸屏幕, 滑动鼠标.
例如用户触摸屏幕时, 浏览器进程率先捕捉行为, 浏览器进程所掌握的信息仅限于行为发生的区域, 因为标签内的内容都由渲染进程处理. 浏览器进程会将点击行为以及坐标传达给渲染进程. 渲染进程在进行相应的处理.
合成器接收输入事件
悬于页面图层的视图窗口
理解非立即可滚动区
运行 JS 是渲染进程的主线程的工作, 页面合成之后, 注册了事件的区域叫做 "非立即可滚动区", 合成器线程会通知渲染进程的主线程处理. 没有输入事件没有发生在事件注册区域, 合成器进程则不需要等待主线程, 可以继续合成帧.
设置时间处理应该注意
- document.body.addEventListener('touchstart',
- event => {
- if (event.target === area) {
- event.preventDefault();
- }
- });
事件代理是浏览器常用的事件处理模式, 在顶层元素添加一个事件. 这样带来的问题就是整个页面都被标识为非立即滚动区域, 合成器进程需要每次都询问主线程是否需要处理事件并且等待反馈. 流畅的合成器处理模式就失效了.
你可以给事件监听添加一个 passive:true 选项 , 将这种负面效果最小化. 这会提示浏览器你想继续在主线程中监听事件, 但合成器不必停滞等候, 可接着创建新的合成帧.
- document.body.addEventListener('touchstart', event => {
- if (event.target === area) {
- event.preventDefault()
- }
- }, {passive: true});
不过上述写法可能又会带来另外一个问题, 假设某个区域你只想要水平滚动, 使用 passive: true 可以实现平滑滚动, 但是垂直方向的滚动可能会先于 event.preventDefault()发生, 此时可以通过 event.cancelable 来防止这种情况.
- document.body.addEventListener('pointermove', event => {
- if (event.cancelable) {
- event.preventDefault(); // 阻止默认的滚动行为
- /*
- * 这里设置程序执行任务
- */
- }
- }, {passive:: true});
也可以使用 CSS 属性 touch-action 来完全消除事件处理器的影响, 如:#area{touch-action:pan-x;}
查找事件对象
当组合器线程发送输入事件给主线程时, 主线程首先会进行命中测试 (hit test), 来查找对应的时间目标, 命中测试会基于渲染过程中生成的绘制记录(paint records) 查找事件发生坐标下寻找的元素.
主线程检查绘制记录查询坐标 x,y 处绘制内容
##### 事件的优化
一般我们屏幕的刷新速率为 60fps, 但是某些事件的触发量会不止这个值, 出于优化的目的, Chrome 会合并连续的事件(如 wheel, mousewheel, mousemove, pointermove, touchmove ), 并延迟到下一帧渲染时候执行 . 而如 keydown, keyup, mouseup, mousedown, touchstart, 和 touchend 等非连续性事件则会立即被触发.
使用 getCoalescedEvents 获取帧内事件
事件合并可帮助大多数 Web 应用构建良好的用户体验. 然而, 如果你开发的是一个绘图类应用, 需要基于 touchmove 事件的坐标绘制线路, 那么在你试图画下一根光滑的线条时, 区间内的一些坐标点也可能会因事件合并而丢失. 这时, 你可以使用目标事件的 getCoalescedEvents 方法获取事件合并后的信息.
- Windows.addEventListener('pointermove', event => {
- const events = event.getCoalescedEvents();
- for (let event of events) {
- const x = event.pageX;
- const y = event.pageY;
- // 使用 x,y 坐标画线
- }
- });
参考:
- CPU, GPU, Memory, and multi-process architecture
- What happens in navigation
- Input is coming to the Compositor
- Inner workings of a Renderer Process
浏览器的工作原理: 新式网络浏览器幕后揭秘
来源: https://juejin.im/entry/5c749795e51d45699514f3a5