Skip to content

现代Web浏览器

主要围绕浏览器从导航开始直到页面渲染完成,用户开始进行页面交互的过程中浏览器是如何工作的内容展开。

参考WebDev 深入了解现代网络浏览器MDN 渲染页面:浏览器的工作原理两篇文章

进程和线程

在正式介绍浏览器工作原理和架构之前,我们需要先了解进程和线程,这与现代浏览器的架构基础。

进程

进程是操作系统中运行应用程序的实例,是资源分配的最小单位。一个进程拥有独立的内存空间和系统资源。 进程之间是相互隔离的,一个进程的崩溃通常不会影响其他进程。 现代浏览器通常采用多进程架构,即将不同的任务分配给不同的进程,以提高稳定性和安全性。 使用内存空间并存储应用数据的进程示意图

使用内存空间并存储应用数据的进程示意图

进程可要求操作系统启动另一个进程以运行不同的任务。内存的不同部分会分配给新进程,如果两个进程需要通信时,他们可以使用私密通信 (IPC) 来实现。 许多应用按照这种方式工作,如果某个进程无响应,可以将其重启而无需停止运行应用不同部分的其他进程。 使用内存空间并存储应用数据的进程示意图

通过 IPC 进行通信的独立进程示意图

线程

线程是进程中的一个执行路径,是程序执行中的最小单位。一般情况下一个进程至少包含一个线程,即主线程。 同一个进程中的所有线程共享所有资源,如内存、文件句柄等。这使得线程之间的通信更加高效。


启动应用时,系统会创建一个进程。程序可能会创建线程来帮助它运行,但这是可选操作。操作系统为这一过程提供了一个“平台”运行, 并且所有应用状态都保存在该私有内存空间中,关闭该进程也会停止,操作系统也会释放内存。

浏览器架构

不同的浏览器有多种架构实现,这里我们以Chrome为例。 Chrome采用多进程架构,顶层是浏览器进程,它与负责不同部分功能的其他进程相互协调。对于渲染进程,浏览器会尽可能的为每个标签页创建属于其自己的进程。

进程其控制的内容
浏览器控制“Chrome”包括地址栏、书签、返回 前进按钮。还可以处理网络浏览器中不可见的特权部分,例如 网络请求和文件访问
渲染控制标签页内显示网站的一切内容。
插件控制网站使用的所有插件,例如 Flash。
gpu与其他进程分开处理 GPU 任务。它分为不同的进程 因为 GPU 会处理来自多个应用的请求,并在同一 Surface 上绘制它们。

还有一些进程,例如 Extension 进程和实用程序进程。如果您想查看,请点击“选项”菜单图标,选择“更多工具”, 然后选择“任务管理器”系统会打开一个窗口,列出当前正在运行的进程,以及它们的CPU/内存用量。

多进程架构的优势

我们假设硬件性能支持浏览器为每个标签页创建其独立渲染进程,那么当其中某个渲染进程崩溃时,并不会影响到其他标签页,你可以关闭崩溃的标签页并在其他标签页上继续操作。

另一个好处是是安全性和沙箱机制。因为每个进程相对独立,所以即使其中一个进程被恶意攻击利用,一般情况下也不会轻易影响到其他进程。 又例如渲染进程是负责处理交互和显示网页内容的,浏览器对该进程进行沙箱化,限制其对于系统文件的访问权限, 即便某个网站试图利用浏览器漏洞,恶意代码也被限制在沙箱内,不能访问用户的文件系统或操作系统的其他敏感资源。

在导航过程中会发生什么

就像前面提到的,标签页之外的内容由浏览器进程来处理。浏览器进程中有UI线程,它负责处理用户输入、绘制地址栏和其他浏览器界面元素。 有网络线程,负责处理网络请求。有存储线程,负责处理浏览器的存储功能。

当您在地址栏中输入URL时,将由UI线程首先接手处理。

处理输入

在现代浏览器中,地址栏不仅是一个URL输入框,它还是一个搜索框。当您输入内容时,UI 线程需要解析并决定是将内容发送到搜索引擎,还是发送到请求的站点。

开始导航

当输入的是一个站点的地址时,UI线程会将URL发送到网络线程,网络线程会开始加载页面,标签页标题中显示加载动画提示。线程会向DNS服务器发起请求,获取域名对应的真实IP, 在第一次请求之后这个IP地址会被缓存一段时间以加速后续请求。

一旦获取的IP地址,浏览器会通过三次握手建立TCP连接。(四次挥手断开连接)

对于通过 HTTPS 建立的安全连接,还需要另一次 "握手"。这种握手,或者说 TLS 协商, 决定使用哪种密码对通信进行加密,验证服务器,并在开始实际数据传输前建立安全连接。这就需要在实际发送内容请求之前,再往返服务器五次。

NOTE

此时,网络线程可能会收到类似 HTTP 301 的服务器重定向标头。在这种情况下,网络线程会与服务器请求重定向的 UI 线程通信。然后,将启动另一个 URL 请求。

数据响应

成功建立连接之后浏览器会发起GET请求,如果得到的是一个HTML文件,那么下一步是将数据传递给渲染进程,如果他是zip文件或者其他类型的文件,那么将会触发一个下载请求。

浏览器为了提高性能,会对请求的资源使用 缓存策略,如果资源已经在缓存中,那么浏览器会直接从缓存中获取资源,而不是重新请求服务器。

NOTE

具体参考MIME 类型了解浏览器对于不同类型文件的处理方式。

NOTE

在传输过程中有一种流量控制机制,用于在网络连接初始阶段逐步增加数据发送量,以避免拥塞。这种机制称为 TCP 慢启动

数据传输完毕后,网络线程会通知UI线程已准备就绪,UI线程会找到一个渲染进程来继续渲染网页,文档加载阶段开始。

此时,地址栏会更新,安全指示器和站点设置 UI 会反映新页面的站点信息。标签页的会话历史记录将更新,因此前进/后退按钮可以浏览刚刚导航到的站点。 为了方便在关闭标签页或窗口后恢复标签页或会话,会话历史会被存储在磁盘上。

但是如果用户再次在地址栏输入不同的URL会发生什么呢?浏览器进程将再次执行相同的步骤来导航到不同的站点。 但在此之前,它需要检查当前渲染的站点是否注册beforeunload页面生命周期)事件。

beforeunload会创建 "要离开此网站吗?" 提示,在您尝试离开或关闭标签页时发送提醒。 标签页内的所有内容(包括 JavaScript 代码)均由渲染进程处理,因此,收到新导航请求时,浏览器进程必须与当前的渲染进程进行检查。

新导航也可能是由渲染进程发起,传递到浏览器进程。

渲染

NOTE

渲染过程大体可以分为以下五个步骤。

  • 解析:浏览器解析HTML生成DOM树,解析CSS生成CSSOM树。
  • 生成渲染树:结合DOM树和CSSOM树,生成渲染树,包含所有需要显示的元素。
  • 布局(Layout):计算渲染树中每个元素的位置和大小,生成布局树。
  • 绘制(Painting):将布局树中的每个元素绘制到屏幕上的多个图层(layer)。
  • 合成(Compositing):将这些图层组合在一起,最终显示在屏幕上。

渲染进程的核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之互动的网页。

当渲染进程收到导航的提交消息并开始接收 HTML 数据时,主线程开始解析文本字符串 HTML 并将其转换为Document Object Model DOM树

NOTE

单纯的 HTML 并不会造成浏览器渲染错误,例如缺少结束标签或者不正确的标签嵌套。浏览器会尝试修复这些错误,但是这可能会导致不同浏览器之间的渲染结果不同。 如果文档格式良好,则解析它会简单而快速。

DOM 树是浏览器对页面的内部表示,以及 Web 开发人员可以通过 JavaScript 与之交互的数据结构和 API。 树反映了不同标记之间的关系和层次结构。DOM 节点的数量越多,构建 DOM 树所需的时间就越长。

解析进行过程中遇到图片或 CSS 等非阻塞解析的资源时,浏览器会并行下载这些资源和继续解析过程,但是对于<script>标签(特别是没有 asyncdefer 属性的), 因为 JavaScript 可能会修改 DOM 树,所以会阻塞渲染并停止解析过程。尽管浏览器的预加载扫描器加速了这个过程,但过多的脚本仍然是一个重要的瓶颈。

NOTE

CSS 资源不会阻塞解析但是会阻塞渲染,这是为了避免页面的“闪烁”现象,即显示未样式化内容(称为FOUC,Flash of Unstyled Content)。 如果浏览器在CSS文件加载完成之前渲染页面,用户可能会看到页面样式不断变化的过程。

渲染进程同样会对 CSS 进行解析,并构建 CSSOM树。与 DOM 树相似,CSSOM 树是通过遍历 CSS 中的每个规则集,根据 CSS 选择器创建具有父、子和兄弟关系的节点树。

即使你不主动提供 CSS,每个 DOM 节点也会具有默认样式,这是由浏览器默认样式表提供的。

CSSOM 树和 DOM 树结合在一起,形成渲染树。渲染树是一个包含所有可见元素及其视觉样式信息(如颜色、字体、边框等)的树结构。

NOTE

visibility: hidden 的节点会包含在渲染树和布局树中,因为它们会占用空间,但不会被实际绘制出来。

NOTE

CSS 伪元素不在DOM树中,因为它们不是实际的HTML元素,但会出现在渲染树和布局树中,因为它们会影响页面的可见内容和布局。

通过渲染树并不足以绘制出实际页面,还需要计算每个元素的位置和大小,这个过程被称为布局

根据具体显示设备的大小以及系统显示设置的不同,以视口大小为基础,会基于渲染树生成布局树。布局树具有元素的具体几何信息,包括位置和大小等。

第一次生成布局之后如果发生了对于节点大小和位置的重新计算,这一过程被称为重排(又称为回流)

拥有了元素的样式,大小以及位置信息之后,渲染进程就可以开始绘制页面了。这个过程被称为绘制

绘制是按顺序进行的,浏览器会遍历布局树的每个节点,根据节点的样式和内容绘制出对应的图像。这些图像通常被绘制到一个或多个图层(layer)上。

浏览器将矢量信息(如几何形状、文本、图像)转换为位图图像(即像素网格)。这一步通常在GPU上进行,以加速处理速度。每个图层都会被光栅化,生成相应的像素数据。

每个图层的光栅化结果会被存储为纹理,这些纹理会被发送到GPU进行后续的合成。

合成是将绘制好的图层组合在一起的过程。这个过程决定了哪些图层应该在屏幕的哪个位置显示,并将它们组合成最终的图像。

合成器会根据图层的z-index、位置、透明度等属性,确定每个图层的排列顺序和相对位置。合成通常在GPU上完成。GPU负责将多个图层合成到一起,并进行一些硬件加速的特效处理。 合成器会将最终的图像帧传递给显示器,以便显示在用户的屏幕上。

此时页面的初次渲染完成,用户可以开始与页面进行交互,点击链接、填写表单等。

用户使用过程中导致的页面渲染更新会根据实际影响的范围,在上边提到的对应流程中进行部分节点的更新和增量渲染,而不是重新渲染整个页面。

相关知识

关于浏览器的工作原理还有很多内容,例如

基于 Apache-2.0 许可发布