浏览器运作原理


结构分析

浏览器的结构可以简单地划分为以下模块:

  • 用户界面
  • 浏览器引擎:其中包括数据持久层
  • 渲染引擎:其中包括网络、JS解析器等

其中渲染引擎可以说是一个浏览器的核心灵魂,也就是我们常说的浏览器内核

渲染引擎

  • IE浏览器:Trident内核
  • Firefox浏览器:Gecko内核
  • Safari浏览器:Webkit内核(已开源)
  • Chrome/Opera/Edge浏览器:Blink内核

本文以Chrome内核为基础进行解释。

从单进程进化到多进程结构

以前的浏览器是单进程浏览器,一个进程中大概有页面线程负责页面,JS线程负责执行JS代码,还有其他各种线程。单进程的浏览器引发了很多问题:

  1. 其中一个线程的卡死可能会导致整个进程出问题。
  2. 安全性堪忧,多个线程间共享数据。
  3. 流畅度差,一个进程需要负责太多事情,导致浏览器运行效率低。

目前的浏览器采用多进程结构,分类如下:

  • 浏览器进程

    1. 负责控制除标签页外的用户界面,包括地址栏、书签、后退和前进按钮。
    2. 负责与浏览器的其他进程协调工作。
  • 渲染进程

    1. 负责控制显示tab标签内的所有内容,将其转换为用户可视化的内容。
    2. 负责与浏览器进程通信,处理用户输入和鼠标操作等。

    浏览器默认情况下会为每个标签页创建一个进程。浏览器默认情况下会为每个标签页创建一个进程。这种进程模型会占用更多内存,但是更加安全,各个标签页相互独立,当其中一个标签页渲染器进程卡死,并不会影响其他标签。

    注意:Chrome一共有4种进程模型,默认为每个实例创建一个渲染器进程,详见官方文档。

  • 缓存进程

    1. 负责管理浏览器的缓存数据,包括将访问过的网页和资源文件存储在本地缓存中,下次访问同一网页时可以更快地加载页面。缓存进程还可以根据用户的使用习惯,预加载可能需要访问的网页和资源文件,以提升用户的体验。
    2. 负责在后台执行一些优化操作,如缓存常用的资源文件,以提高浏览器的性能和响应速度。
  • 网络进程

    负责处理网络请求,包括DNS解析、建立TCP连接、SSL认证等。每个标签页都有一个独立的网络进程,这样可以避免一个标签页的网络请求影响到其他标签页的性能。

  • GPU进程

    负责处理GPU相关的任务,如图形渲染、图像处理等。这个进程在Windows和Linux上是独立的进程,而在macOS上则与浏览器进程合并,称为“GPU进程”。

  • 插件进程

    负责运行浏览器插件,如Flash、PDF阅读器等。插件进程是一个独立的进程,与浏览器进程和渲染进程分别通信。

浏览器内部工作原理

当你在浏览器地址栏输入地址时,浏览器进程的UI线程会捕捉你的输入内容:

  • 如果输入内容是网址,则UI线程会启动一个网络线程来请求DNS进行域名解析,接着开始连接服务器获取数据。
  • 如果输入内容不是网址而是一串关键词,浏览器知道你是要搜索,于是会使用默认搜索引擎来进行搜索。

当网络线程获取数据之后:

  1. 当网络线程获取数据之后,会通过SafeBrowsing来检查网站是否是恶意站点。

  2. 当网络数据接收完毕,安全检验通过后,网络线程会通知UI线程。

  3. UI线程创建渲染器进程来渲染页面。浏览器进程通过IPC管道将数据传递给渲染器进程。

  4. **(Dom)**渲染器进程的主线程对HTML内容进行解析,构造DOM数据结构(DOM是文档对象模型,是浏览器对页面在其内部的表示形式,是web开发程序员可以通过JS与之交互的数据结构和API):

    1. HTML首先通过Tokeniser通过词法分析将输入的html内容解析成多个标记。根据识别后的标记进行DOM树构造。

    2. 在DOM树构造过程中会创建document对象,然后以document的为根节点的DOM树不断进行修改,向其中添加各种元素。

    3. HTML代码中往往会引入一些额外的资源,例如图片、CSS、JS脚本等。图片和CSS资源需要通过网络下载或者从缓存中直接加载。这些资源不会阻塞html的解析,因为它们不会影响DOM的生成。但当html解析中遇到script标签,就会停止html解析流程,转而去加载解析并且执行JS。

      为什么不跳过JS解析,等html解析完成了再去加载运行JS呢?

      这是因为浏览器并不知道JS执行是否会改变当前页面的HTML结构,如果JS代码里调用了document.write方法来修改html,那么之前的html解析就没有任何意义了。

      这也是为什么script标签的位置,使用async或defer属性来异步加载JS的重要性。

  5. **(Style)**在script标签加载完成后,我们会获得一个DOM树,但我们仍然不知道每个树节点上的样式。主线程需要解析CSS,并确定每个DOM节点的计算样式。

  6. **(Layout)**接下来需要知道每个节点放在页面上哪个位置,即节点坐标以及该节点需要占用多大的区域。

  7. 主线程通过遍历DOM和计算好的样式来生成Layout树。LAYOUT树上的每个节点都记录了x,y坐标和边框尺寸。

    注意:DOM树和Layout树并不是一一对应的。

    • 设置了display:none的节点不会出现在Layout树上。
    • 在before伪类中添加了content值的元素,content里的内容会出现在Layout树上,而不会出现在DOM树中。这是因为DOM是通过HTML解析获得,并不关心样式。
  8. **(Paint)**我们还需知道以何种顺序来绘制节点。例如z-index属性会影响节点绘制的层级关系,如果按照DOM的层级结构来绘制页面则会导致错误的渲染。所以主线程遍历Layout树创建一个绘制记录表,代表了绘制的顺序,这个阶段称为绘制。

  9. **(Rastering)**在知道文档的绘制顺序后,终于到了该把这些信息转化为像素点的时候了。这种行为被称为栅格化。

    Chrome最早使用了一种很简单的方式,只栅格化用户可视区域的内容,当用户滚动页面时,再栅格化更多的内容来填充缺失的部分。这种方式带来的缺点显而易见,会导致显示延迟。

    随着不断的优化升级,现在的Chrome使用了一种更复杂的栅格化流程,叫做合成(Composting)。合成是一种将页面的各个部分分成多个图层,分别对其进行栅格化,并在合成器线程中单独进行合成页面的技术。

  10. **(Layer)**主线程遍历Layout树生成Layer树,当Layer树生成完毕和绘制顺序确定后,主线程将这些信息传递给合成器线程。合成器线程将每个图层栅格化,由于一层可能像页面的整个长度一样大,因此合成器线程将他们切分为许多图块,然后将每个图块发送给栅格化线程(Raster Thread)。

  11. 栅格化线程栅格化每个图块,并将它们存储在GPU内存中,当图块栅格化完成后,合成器线程将收集称为“draw quads”的图块信息。这些信息里记录了图块在内存中的位置和在页面的哪个位置绘制图块的信息,根据这些信息合成器线程生成了一个合成器帧。

  12. 合成器帧通过IPC传送给浏览器进程,接着浏览器进程将合成器帧传送到GPU,然后GPU渲染展示到屏幕上。

总体流程

①浏览器进程中的网络线程请求获取到html数据——②通过IPC将数据传给渲染器进程的主线程——③主线程将html解析构造DOM树——④样式计算——⑤根据DOM树和生成好的样式生成Layout树——⑥通过遍历Layout树生成绘制顺序表——⑦通过遍历Layout树生成Layer树——⑧主线程将Layer树和绘制顺序表一起传给合成器线程——⑨合成器线程根据规则划分图层——⑩将图层分为更小的图块(tiles)传给栅格化线程进行栅格化——⑪栅格化完成后合成器线程会获得栅格化线程传过来的“draw quads”图块信息——⑫根据图块信息合成器线程上合成了一个合成器帧——⑬该合成器帧通过IPC传回给浏览器进程——⑭浏览器进程再传给GPU进行渲染——⑮将图像展示在屏幕上

image-20230525174756341


文章作者: QT-7274
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 QT-7274 !
评论
  目录