“在浏览器里,从输入 URL 到页面展示,这中间发生了什么?”

date
May 27, 2023
slug
what-happens-between-entering-the-URL-and-displaying-the-page-in-a-browser
status
Published
tags
Code
Question
summary
一道经典的面试题
type
Post
Created Time
Oct 28, 2023 01:45 PM
Updated Time
Oct 28, 2023 01:45 PM
AI summary
Status
去年,去某知名游戏公司面试,面试到第三轮时,CTO 问了我一个问题:“你未来五年的职业生涯规划是什么?”,我说:成为一名优秀的全栈开发工程师。CTO 笑了笑,说:“好,我最后问你一个问题。在浏览器里,从输入 URL 到页面展示,这中间发生了什么?”
这是一道经典的面试题,考察的技术点很多、很杂,为了方便理解和加深记忆,我在极客时间上找到了这张图:
notion image
可以看到,从输入 URL 到页面展示,整个过程需要三个进程互相配合,其中:
  • 浏览器进程主要负责页面展示、用户交互、进程管理和文件存储等功能;
  • 网络进程主要负责网络资源的加载;
  • 渲染进程主要负责资源的解析,如 HTML、JavaScript、CSS、图片等。为了保证安全,渲染进程通常运行在安全沙箱里

1. 用户输入

用户在浏览器地址栏输入 URL,点击回车键之后,浏览器会执行当前页面的 onbeforeunload 事件,弹出对话框,提示用户是继续浏览页面还是离开当前页面,或直接执行数据清理;

2. 处理输入信息

浏览器根据输入的信息,判断是搜索还是跳转网址,如果是搜索内容,则将搜索内容和默认的搜索引擎组合,形成新的 URL,执行搜索操作,如果是合法的 URL,则直接跳转至输入的 URL 地址;

3. 开始导航

这部分内容分为三步,依次是查找强缓存→本地域名解析→ DNS 解析,如果上一步成功了,就不会执行下一步了。

a. 查找强缓存

浏览器根据 ExpiresCache-Control 字段,判断是否命中强缓存,如果命中,直接返回结果,如果没命中,执行本地域名解析。

b.本地域名解析

查找本地 hosts 文件,找到对应的 ip 地址,如果找到了,就直接使用 hosts 文件里的 ip 地址,如果没找到,执行 DNS 解析。

c.DNS 解析

本地 DNS 服务器首先查询它自己的缓存记录,如果命中缓存,直接返回结果,如果没有命中,就会以递归的方式向根 DNS 服务器查询结果,如果都没有命中,就会向域服务器查询,直到命中结果(同时将对应关系保存在缓存中)或返回 Not Found。

4. 发起 URL 请求

浏览器进程通过进程间通信(IPC),将 URL 请求发送至网络进程,网络进程接收到 URL 请求后,会首先查找本地缓存,如果命中,直接返回结果,如果没有命中,就进入网络请求流程。
网络请求的过程大致会经历建立连接→构建请求头的过程,但也有可能会重定向,重新建立新的连接。

a. 建立连接

如果请求协议是 HTTPS,需要首先建立 TLS 连接,如果是 HTTP 服务,则会直接通过“三次握手”建立 TCP 连接。

b. 构建请求头

建立连接之后,浏览器会构建请求头、请求体等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,向服务器发送构建的请求信息。

c. 重定向

在接收到服务器返回的响应头后,网络进程开始解析响应头,如果发现返回的状态码是 301 或者 302,网络进程就会从响应头的 Location 字段里读取重定向的地址,再次发起新的 HTTP 或者 HTTPS 请求。

d. 响应数据类型

网络进程根据服务器返回的响应头中的 Content-Type 字段,区分请求类型,如果是下载类型,该请求会被直接提交给浏览器的下载管理器,URL 请求结束。但如果是 HTML,那么浏览器则会进入下一个流程,准备渲染进程。

5.准备渲染进程

默认情况下,Chrome 会为每个页面分配一个渲染进程,但对于满足同源策略(域名、协议和端口都相同)的两个页面,则会使用同一个渲染进程,因此准备渲染进程的过程可以分为两步:
  1. 判断新打开的页面和当前页面是否满足同源策略;
  1. 如果跨域(不满足同源策略),创建新的渲染进程,如果同源(满足同源策略),复用当前页面的渲染进程。

6.提交文档并进行确认

提交文档,就是将网络进程接收到的 HTML 数据提交给渲染进程,具体流程如下:
  1. 浏览器进程接收到网络进程向渲染进程发起“提交文档”的消息;
  1. 渲染进程接收到“提交文档”的消息后,和网络进程建立传输数据的“管道”;
  1. 文档数据通过“管道”传输完成后,渲染进程返回“确认提交”消息给浏览器进程;
  1. 浏览器在接收到“确认提交”的消息后,更新浏览器的界面状态,包括安全状态、地址栏的 URL、前进后退的历史状态,最后更新 Web 页面。

7.页面解析和资源加载

渲染进程在接收到网络进程的数据后,会依次执行以下几个过程:构建 DOM 树、样式计算、布局阶段、分层、图层绘制、栅格化、合成和显示。其中,每个阶段都会履行相应职责:将上一步输入的内容,经过处理,输出新的内容。

a. 构建 DOM 树

构建 DOM 过程的输入内容是网络进程传输过来的 HTML 文件,输出的内容是一棵 DOM 树。
这个过程的执行者是 HTML 解析器,该过程执行完成后,DOM 和 HTML 的内容几乎是一样的,唯一不同的是,DOM 是保存在内存中的树状结构,可以通过 JavaScript 查询和修改其内容。

b. 样式计算

样式计算的输入内容三处来源:
  • <link> 标签引用的外部 CSS 文件;
  • <style> 标签内的 CSS 内容;
  • HTML 元素的 style 属性中的 CSS 内容;
输出内容为每个 DOM 节点的样式,结果最终被保存在 ComputedStyle 结构体内。
这个过程的执行分为三步:
  1. 渲染引擎将接收到的 CSS 文本转换为浏览器可以理解的 styleSheets 结构;
  1. 将 CSS 文本中的所有属性值转换为渲染引擎容易理解的、标准化的计算值;
  1. 遵守 CSS 的继承和层叠规则进行计算,输出 DOM 节点的样式,将结果保存在 ComputedStyle 结构体内。

c. 布局阶段

布局阶段的输入内容是 DOM 树,输出内容也是 DOM 树。
Chrome 在布局阶段需要完成两个任务:创建布局树和布局计算。

创建布局树

为了构建布局树,浏览器大体上完成了下面这些工作:
  • 遍历 DOM 树中的所有可见节点,并把这些节点加到布局树中;
  • 忽略掉计算属性中不可见的节点;

布局计算

计算每个元素的几何坐标位置,将信息保存在布局树中。

d. 分层

分层阶段的输入内容是 DOM 树,输出内容是图层树。
渲染引擎为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。
满足下面两点中任意一点的元素就会被提升为单独的一个图层:
  • 拥有层叠上下文属性,如定位属性、透明属性、CSS 滤镜等;
  • 需要剪裁(clip),如滚动条、overflow hidden 等;

e. 图层绘制

图层绘制的输入内容是图层树,输出内容是一个待绘制列表。
渲染引擎将图层进行拆分,分解成多个独立的绘制指令,再将这些指令按照顺序组成一个待绘制列表。

f. 栅格化(raster)

栅格化操作的输入内容是图层,输出内容是位图。
合成线程将图层划分为图块(title),在 GPU 中,将图块转换为位图,并将最终的结果保存在 GPU 中。

g. 合成和显示

合成和显示输入内容是位图,输出内容是页面。
合成线程会根据位图生成绘制图块的命令——“DrawQuad”,浏览器进程里面的 viz 组件接收并执行该命令,将页面内容绘制到内存中,最终显示在屏幕上。
 
最后,需要指出的是,“重排“、“重绘”和“合成”会极大的影响浏览器的渲染速度。
  • 重排:通过 JavaScript 或者 CSS 修改元素的几何位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,执行一系列子阶段,这个过程就叫做重排。重排需要更新完整的渲染流水线,所以开销也是最大的
  • 重绘:通过 JavaScript 更改某些元素的背景颜色,渲染流水线就会在执行完一系列过程之后,再重新进行绘制,这个过程就叫做重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些
  • 合成:更改一个既不要布局也不要绘制的属性,渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成,如使用 CSS 的 transform 来实现动画效果。因为合成是在非主线程上完成的,同时避开了布局和绘制两个子阶段,所以,相对于重绘和重排,合成的效率较高。
 

© 孙东辉 2022 - 2025