Skip to content

Browser 原理

概览

x

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 传递信息。

浏览器架构

  • 概念

    • 图示

    x

    • 浏览器可能由一个拥有很多线程的进程,或是通过 IPC 通信的不同线程的进程。
    • Chrome 是多进程架构,顶层是 Browser process,来协调其它进程。
  • 进程各自控制什么?

    • 图示

    x

    • 浏览器进程 (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 多进程内存优化

    • 图示

    x

    • 限制进程数量

      • 当进程达到阈值,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)开始导航

  • 图片

x

  • 网络进程获取站点内容,比 DNS lookup 和 establishing TLS connection。
  • Network Process 可能收到 Http 301 的重定向响应头;此时 Network Process 告知 UI thread 对输入框 URL 做修改,启动另一次URL请求。

3)解析响应

  • 图片

x

  • 判断响应类型

    • 响应头 Content-Type 判断数据类型;若丢失或设置错误,则使用 MIME 类型嗅探。(mime sniffer)
  • 处理不同数据类型

    • 若是 HTML,将数据传给渲染进程;
    • 若是 zip 或其他文件,将数据传给下载管理器。
  • 同时 SafeBrowsing 检查

    • 已知的恶意站点匹配,网络进程显示警告页面。
    • 触发 CORB,确保敏感的跨域数据无法进入Renderer Process。

4)查找渲染进程

  • 图片

x

  • 网络进程告诉 UI 线程数据准备完毕,UI 线程寻找渲染进程去开始渲染 Web 页面。
  • 优化措施:UI thread 在通知网络 thread 获取数据的同时,通知预创建一个渲染进程;若重定向,则再创建一个 渲染进程。

5)提交导航

  • 图片

x

  • 数据和渲染进程都就绪, 浏览器进程通过 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 转化为用户可与之交互的网页。
  • 渲染进程内部包含主线程、工作线程、合成线程和光栅线程。

x

关键渲染路径

  • 渲染关键路径(Critical Render Path):DOM & CSSOM -> 样式计算 -> 布局 -> 分层 ->绘制 -> 分块 -> 光栅化 -> 合成

x

1) 解析(Parsing)

  • 主线程解析 HTML 并构建 DOM 树

x

  • 创建 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,以添加计算后样式

x

  • 构建 CSSOM:字节 -> 字符 -> 序列化 -> 节点 - > CSS Object Model

3) 布局

  • 主线程遍历计算样式后的 DOM 树,以此生成布局树。

x

  • 布局是计算元素几何形状的过程。
  • 仅包含页面上可见内容相关信息:display:none 不在树上;visibility: hidden` 在。

4) 分层

  • 主线程遍历布局树生成图层树

x

5) 绘制

  • 主线程遍历布局树并生成绘制记录(包含绘制顺序)

x

  • 主线程把图层的绘制拆分成绘制指令,生成待绘制列表,提交到合成线程

6) 合成

  • 分块(Tiles)

    • 绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。
    • 合成线程会将图层划分为 图块(tile),这些图块的大小通常是 256x256 或者 512x512。
  • 栅格化(Raster)

    • 所谓的栅格化,是指将图块转换为位图。
    • 然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作就是由栅格化来执行的。
    • 通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。
    • 先绘制光栅化视窗内的画面,如果用户滚动,移动光栅框。

    x

  • 主线程的光栅化和合成

    • 主线程创建分块的位图并发送到 GPU

    x

  • 绘制四边形(draw squad)

    • 一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——DrawQuad,然后将该命令提交给浏览器进程。
    • 浏览器进程的 viz 组件,根据DrawQuad命令,浏览器进程将页面绘制到内存中,最后将内存先显示在屏幕上
  • 合成帧

    • 合成线程创建合成帧,将其发送到浏览器进程,再接着发送到 GPU

    x

重绘与回流

回流(reflow)

  • 当 render tree 中一部分因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。称为回流。
  • 触发回流的属性

    • 盒子模型相关的属性会触发重新布局
    • 定位属性及浮动也会触发重新布局
    • 改变节点内部文字结构也会触发重布局
  • 当元素的几何属性发生变化,浏览器触发重新布局,即解析之后的一系列子阶段。

x

重绘(repaint)

  • 当 render tree 中一部分元素需要更新属性,而这些属性只是影响元素的外观,风格,而不影响不布局。称为回流。
  • 更新元素的绘制属性,比如背景颜色,触发重绘;跳过布局和分层,直接进行绘制阶段。

x

合成

  • CSS3 的 transform、opacity、filter 这些属性会触发合成。
  • 非主线程,并没有占用主线程的资源;并在合成线程生成位图的时中利用 GPU 进行加速;相对于重排和重绘,大大提升绘制效率。
  • 渲染引擎跳过布局和绘制,直接执行后续的合成。

x

新建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 保证高效,流畅地渲染页面。