【多学多看】现代浏览器如何工作

1,789 阅读11分钟

本系列分4个部分来讲述Chrome浏览器是如何工作的。
如果你想知道浏览器如何将你写好的代码(HTML/CSS/JAVASCRIPT)变成你所看到的网页这一过程所发生的细节,那么本文章适合你。

目录如下

  1. Chrome浏览器的架构
    • 介绍计算机的核心术语以及Chrome浏览器的架构--->【多进程多线程架构】
  2. 浏览器如何展示一个页面
    从浏览器的【导航栏(UI Thread)】出发,深挖在【多进程多线程】的架构下  
        各个进程 :: Process   
            各个线程 :: Thread  
                之间如何【通信】  
        ------⏬------  
        来最终展示网页
    
  3. 浏览器内核是如何工作的
    • Chrome浏览器内核---> 渲染进程(Renderer)是如何工作的
  4. 浏览器如何与用户交互
    • 浏览器如何处理与用户的交互,并反馈至网页

1. Chrome浏览器的架构

准备工作

浏览器是一个应用程序(Application),在一个计算机上运行一个应用程序,都需要哪些东西呢?
最基本的3点(把大象装冰箱):

  1. 硬件(Machine Hardware)
  2. 操作系统(Operating System)
  3. 应用程序(Application)
    alt
    tips: 计算机体系结构的三层。机器硬件在底部,操作系统在中间,应用程序在顶部

浏览器的工作离不开硬件和操作系统的支持,关于这块,我们需要知道的知识有:

  1. CPU (中央处理器)
  2. GPU (图形处理单元)
  3. Process (进程)
  4. Thread (线程)

这里可以做一个简单的解释(看图说话)
具体细节可以自行去了解,特别是进程与线程的概念。

  • CPU && GPU
    alt
  • Process && Thread
    alt

正式开始

知道了进程和线程的概念。
那么如何使用进程和线程构建Web浏览器呢?
其实答案并不是唯一的

  • 可以使用一个进程包含许多线程(图左👈)
  • 多个进程,每个进程包含一些线程,进程之间相互通信(图右👉)
    alt

这里我们研究的是最近的架构,即多进程多线程架构。

alt
如图所示。
浏览器的主要进程有:

  • Browser Process
    • 控制应用程序的chrome部分,包括地址栏、书签、后退和前进按钮(UI thread)
    • 处理Web浏览器一些UI上未显示的功能,例如网络请求和文件访问等(network thread)
  • Renderer Process
    • 控制显示网站的标签内的所有内容
  • Plugin Process
    • 网站使用的所有插件
  • GPU Process
    • 与其他进程隔离地处理GPU任务。GPUs处理来自多个不同应用程序的请求并把它们画在屏幕上

其中最顶部是【Browser Process】,其负责协调应用程序的其他进程。
对于渲染进程【Renderer Process】,每一个Tab栏都可理解为创建了一个渲染进程。

alt

整个浏览器还有其他的进程,例如Extension process 和 utility process。
具体查看浏览器运行的时候都开启了哪些进程,可以点击右上角的:按钮,选择【更多工具】,点击【任务管理器】。这里可以看到浏览器开启的所有进程。

浏览器多进程多架构的好处

有哪些好处其实随便想想就有很多(暂不详述)。

  1. 单个页面崩溃不会影响其他页面
  2. 安全性
  3. 健壮性

节约内存

Chrome正在进行架构更改,以将浏览器程序的每个部分作为一项服务运行,从而可以轻松拆分为不同的进程或整合为一个进程。

alt

站点隔离

网站隔离是Chrome最近引入的功能,可为每个跨网站iframe运行单独的渲染器进程。 自Chrome 67起,默认情况下在桌面上启用了网站隔离功能,标签中的每个跨网站iframe都会获得一个单独的渲染器进程(可通过chrome内部的【任务管理器】查看)

alt

小结

本节只需要理解:

  1. Chrome浏览器是多进程多线程架构的
  2. Chrome浏览器的主要进程有
    • Browser Process(负责统筹全局)
    • GPU Process(负责图像处理)
    • Plugin Process(负责运行插件)
    • Renderer Process(负责渲染页面)
  3. Chrome浏览器多进程多架构的好处
    • 页面渲染互不影响
    • 安全性,沙箱
    • 健壮性
  4. 站点隔离
    • 每个页面中引入iframe都会开启一个进程

2. 浏览器如何展示一个页面

有一道很常见的面试题: 【从地址栏输入url都发生了什么】
我们就从浏览器的导航开始,从浏览器的地址栏输入url,都发生了什么:

从 Browser process开始

  • 当地址栏中输入URL时,输入将由浏览器进程的UI线程处理

Step1: 处理输入

  • 浏览器会判断你在地址栏的输入是一个搜索查询还是一个URL,以便决定是将你的输入放入搜索引擎去查询,还是帮你发送一个网页请求。
    alt

2.Step2:开始导航

  • 键盘敲入enter,UI线程启动网络调用以获取网站内容。Tab项的左端Loading的那个小logo就会开始转圈,并且network线程会通过适当的协议(UDP)进行DNS查询并且建立TLS连接。
  • 此时如果network请求收到来自服务端的响应例如HTTP重定向301,那么就会跟UI Thread通信,告诉UI Thread服务端redirect了,那么另一个URL请求将会被启动。重复进行这一步骤。

3.Step3:处理响应

  • 一旦响应主体(有效负载)开始出现,网络线程将在必要时查看流的前几个字节,响应的Content-Type标头应说明数据的类型,但由于可能丢失或错误,因此在此处进行MIME类型嗅探。
  • 如果响应是HTML文件,则下一步是将数据传递到渲染器进程,但是如果是zip文件或其他文件,则意味着这是下载请求,因此他们需要将数据传递到下载管理器
    alt
  • network线程从安全站点询问响应数据是否为HTML
  • 这也是进行安全浏览检查的地方
  • 如果域和响应数据与已知的恶意站点匹配,则network线程发出警报以显示警告页面
  • 此外,还会进行跨源读取阻止(CORB)检查,以确保敏感的跨站点数据不会进入渲染器进程
    alt
    tips: 响应标头,其中包含Content-Type和有效载荷,这是实际数据

4. Step4:找到渲染引擎

  • 一旦完成所有检查,并且Network线程确信浏览器应导航到请求的站点,则Network线程将告知UI线程数据已准备就绪。然后,UI线程找到一个渲染器进程来进行网页渲染
    alt
    tips: network线程告诉UI线程查找渲染器进程
  • 由于网络请求可能需要数百毫秒才能获得响应,因此将应用优化来加快此过程。当UI线程在步骤2向网络线程发送URL请求时,它已经知道他们正在导航到哪个站点。 UI线程尝试与网络请求并行地主动查找或启动渲染器进程。这样,如果一切按预期进行,则当网络线程接收到数据时,渲染器进程已经处于备用位置。如果导航重定向跨站点,则可能不会使用此备用过程,在这种情况下,可能需要其他过程

5. Step5:更新导航

  • 现在已经准备好数据和渲染器进程,将IPC从浏览器进程发送到渲染器进程以提交导航。它还会传递数据流,因此渲染器进程可以继续接收HTML数据。一旦浏览器进程听到确认提交已在渲染器进程中完成,导航就完成了,文档加载阶段开始了
  • 此时,地址栏已更新,安全指示符和站点设置UI反映了新页面的站点信息。标签的会话历史记录将被更新,因此后退/前进按钮将逐步浏览刚刚导航到的站点。为了在关闭选项卡或窗口时方便选项卡/会话还原,会话历史记录存储在磁盘上
    alt
    tips:浏览器和渲染器进程之间的IPC,请求渲染页面

6. Step6:一旦导航更新以后,渲染引擎拿到了所有资源然后开始渲染页面。下一节会讲。

导航到其他站点

简单的导航就完成了!但是,如果用户再次将不同的URL放入地址栏会发生什么?
浏览器过程将通过相同的步骤导航到不同的站点。
但在此之前,它需要与当前渲染的站点进行检查,看看他们是否注册了beforeunload事件

beforeunload可以创建“离开此网站?”尝试导航或关闭选项卡时发出警报。选项卡内的所有内容(包括您的JavaScript代码)都由渲染器进程处理,因此,当有新的导航请求出现时,浏览器进程必须与当前渲染器进程进行核对

不要添加无条件的beforeunload处理程序。它创建了更多的延迟,因为需要在甚至开始导航之前执行处理程序。仅在需要时才添加此事件处理程序,例如,如果需要警告用户,他们可能会丢失在页面上输入的数据

alt
tips:从浏览器进程到渲染器进程的IPC告诉它即将导航到其他站点

如果导航是从渲染器进程启动的(例如用户单击链接或客户端JavaScript已运行window.location =“ newsite.com”),则渲染器进程首先检查beforeunload处理程序。然后,它经历与浏览器过程启动的导航相同的过程。唯一的区别是导航请求从渲染器进程到浏览器进程开始了

当新的导航与当前渲染的站点不在同一个站点上时,将调用一个单独的渲染过程来处理新的导航,而当前的渲染过程将保留下来以处理诸如卸载之类的事件.

alt
tips: 从浏览器进程到新渲染器进程的IPC,告诉渲染页面并告诉旧渲染器进程卸载

小结

浏览器如何渲染一个页面(六脉神剑):

  1. 处理输入(Handling input) UI Thread 判断是search query 还是 URL
  2. 开始导航(Start navigation)
    • UI Thread 把 URL 给 network Thread
    • DNS && TLS
    • 如果响应的 HTTP码是 301,拿到的URL传给UI Thread,更新navigation.重新开启DNS&&TLS
  3. 读取响应(Read response: check with response)
    • 拿到响应数据(结果)
    • Network thread 检查数据是否是从安全站点返回的,如果不是则显示警告页
    • Network thread CORB check,检查跨域问题
    • Network check 响应数据的Content-type
    • 如果是: HTML data --> Renderer process
    • 如果是: zip file or other file --> download manager
  4. 找到渲染引擎(Find a renderer process)
    • UI thread 收到 network thread成功返回数据的信号
    • UI thread 开启一个 renderer thread
    • (优化==>UI thread拿到URL,传给network thread以后就直接做开启renderer process 的准备,等network返回数据完毕后,开的renderer process直接使用)
  5. 更新导航(Commit navigation)
    • network thread 把拿到的数据给 renderer process
    • UI thread receive 收到 renderer process 确认拿到数据的信号
    • navigation completed and the document loading phase begins.
  6. 初始加载完成(Initial load complete)
    • renderer process(loading finished),然后通知browser process
    • UI thread(UI 线程属于Browser 线程) stops the loading spinner on the tab
  7. 导航到其他站点
      1. 如果从导航栏输入其他站点的URL,先在当前renderer process中去检查是否有beforeunload事件
    • 2.如果从当前render process内做跳转,也就是当前页面脚本中有类似window.href="xxx"这段代码
      • 首先检查当前页面是否有beforeunload事件
      • 1.2点的区别就在于导航请求是从外部(Browser process)还是内部(Renderer process)发起的。
      1. 当新的导航与当前渲染的站点不在同一个站点上时,将调用一个单独的渲染过程来处理新的导航,而当前的渲染过程将保留下来以处理诸如卸载unload之类的事件
        这里涉及到页面生命周期API可以参考

alt

3. 浏览器内核是如何工作的

4. 浏览器如何与用户交互