Chrome 浏览器原理

Part 1 浏览器架构

CPU, GPU, Memory, 多进程架构, 通过 IPC(Inter Process Communication)通信。包含的几个进程及内容:

  • Browser process: 地址栏,书签,前进,后退,网络请求和文件
  • Render process: 控制所有 tab 中显示的内容
  • Plugin: 例如 flash
  • GPU: 处理 GPU 任务

通过 Chrome 右上角-更多工具-任务管理器, 可以查看进程, 一般情况下,每个 Tab 页都是一个 render process,使得页面之间互不影响,并且更加安全。如果多个tab页,但网站之间是同域的,还是会共享一个 render process。

Site Isolation 技术实现了即使是跨域的 iframe,也会使用不同的 render process。这意味着一个 Tab 页,可能对应多个 render process。此技术是里程碑式的,没有想象的这么简单,比如 Ctrl + F 必须在多个Render process中搜索。

Part 2 在导航期间发生了什么

导航从 Browser process开始,Browser process 中包含了 UI thread(绘制按钮和搜索框)、Network thread(请求数据)、Storage thread(文件)。

  • (处理输入)当用户在地址栏输入文字时,UI thread 会解析字符串,判断是搜索文字还是URL地址。
  • (开始导航)当用户点击 Enter 按键,UI thread 会初始化网络请求获取内容。Network thread 会进行 DNS查询、建立 TLS连接。
  • (读响应数据)当获取到 Response body时,Network thread 会通过 Content-Type 选择合适的处理方式。如果请求是HTML file,会将数据传递给 Renderer process,如果是 zip 或者其他下载文件,会传递给下载管理器。
  • (找到 renderer process)如果请求返回数据通过了安全检查,Network thread 会通知 UI thread,UI thread 会使用 Renderer process 渲染页面。
  • (提交 navigation)数据会通过IPC从 Browser process 发到 Renderer process。如果 navigation 已经完成,Renderer process 会通知 Browser [Page loaded], 地址栏旁边的spinner将会消失。提交导航后,Renderer process 将会继续加载资源和渲染页面,具体细节,会在后面说到。

Part 3 渲染器进程

渲染器进程处理tab页中所有的事情。main thread处理大部分代码,一些web worker 和 service worker相关的代码会交给 worker thread。compositor thread 和 raster thread 负责渲染页面更有效率和流畅。

renderer process 核心工作是将 HTML,CSS, JS 转变成可交互页面。

main thread 解析HTML文件为 DOM 结构,HTML解析器遇到 script 标签会停止解析,直到加载,解析,执行 js 代码完成之后。因为 js 可能会导致 DOM 结构改变。

得到DOM之后,main thread 解析 CSS 得到每个 DOM Node 的 computed style。

得到 computed style 之后,准备生成 layout tree,获取元素的几何结构,例如元素 x,y坐标,盒尺寸等。

然后是 paint 步骤,决定绘制顺序,比如 z-index。结果是生成绘制指令不是真的绘制。main thread 会遍历 layout tree 生成 paint records(类似 “background first, then text, then rectangle”.)。

得到 paint 指令之后,将这些信息转变为屏幕上的像素被称为栅格化(rasterizing)。在 Chrome 早期的时候,初始化时栅格化 viewport 中的内容,当用户滚动之后,在对新内容进行栅格化。在后期版本中,又增加了一个 合成(compositing)步骤。

Compositing 会将页面分为不同的层,并且对每一层进行栅格化。当有滚动、动画发生时,仅通过合成来获得新的帧。为了判断元素应该被放在哪一层里,主线程会通过遍历 layout tree 去创建 layer tree(可以通过will-change生成新的layer)。

一旦 layer tree 和 paint order 准备就绪,主线程会将信息提交给 compositor thread。然后在栅格化每一层。layer 可能会是整个页面大小,所以 compositor thread 会把layer切分成瓦片(tile)。然后栅格进程对tile进行栅格化,并将结果保存在 GPU中。

当 tile 被栅格化之后,compositor thread 收集 tile 信息,draw quads 和 compositor frame(draw quads 集合)。然后合成帧被提交给浏览器进程。然后被发送给GPU。合成的好处就是不需要主线程参与,就可以生成合成帧,保证动画流畅。

Part 4 处理用户事件输入

当用户touch事件发生时,Browser process 首先接收到,然后发送事件类型和坐标给 Renderer process 中合成线程,然后通知 main thread,其查找事件目标元素和运行事件监听器。

合成线程会将页面中带有事件监听器的区域标记为 Non-Fast Scrollable Region,如果事件在这个区域发生,合成线程会发送给 main thread。如果这个区域没有时间发生,则不需要等待 main thread。所以,大量的事件代理,也会影响性能,因为这样会导致标记区域变大。为了防止这种事情发生,通过设置 passive:true(表示永远不会调用 preventDefault),可以告诉浏览器虽然需要主线程监听,但是合成线程可以继续生成合成帧,不必等待,可以使得滚动更加顺滑。

main thread 通过 x,y 坐标和查找 paint records 来确定事件目标

屏幕一般刷新率为60hz,而move事件一般为 60-120hz,所以 Chrome会合并持续事件(wheel,mousewheel,mousemove,touchmove)到下一个requestAnimationFrame 之前触发,避免无效的事件触发。但是,如果你需要精确的用户事件坐标,如果绘图软件等,那么就需要使用 getCoalescedEvents。

1
2
3
4
5
6
7
8
window.addEventListener('pointermove', event => {
const events = event.getCoalescedEvents();
for (let event of events) {
const x = event.pageX;
const y = event.pageY;
// draw a line using x and y coordinates.
}
});