✨「前端基础」WebKit技术内幕

2023年 9月 23日 123.6k 0

前言

本文章将从以下三点来讲述webkit的工作流程及一些实现原理,帮助你更好地理解浏览器工作流程:

  • 浏览器内核

  • 网页渲染

  • JavaScript引擎

  • 下面,让我们开启webKit探索之旅吧~

    一、浏览器内核

    • 浏览器特性

    随着B/S架构的流行,浏览器变得越来越重要。而一个浏览器往往需要以下这些功能:

  • 网络:浏览器通过网络模块下载资源
  • 资源管理:管理下载后的资源,缓存资源
  • 网页浏览:将网络资源转变为可视化结果
  • 多页面管理:如何解决多页面同时加载的影响,多线程或多进程?
  • 插件与拓展:网页功能的拓展
  • 安全机制:提供安全的浏览环境
  • 开发者工具:对开发者更好地调试网页
    • 用户代理(User Agent)

    User Agent用于表明浏览器身份,因为一个网页在不同的浏览器往往有不同的展示,所以需要根据浏览器身份发送不同的网页内容。浏览器控制台输入 navigator.userAgent 就会得到以下字符串

    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36"
    

    此次测试(使用Chrome)浏览器不仅包含Chrome 还加入了Mozilla ,AppleWebKit , Safari的额外代理,表示浏览器也能兼容其定制内容的页面,这样就能拿到最新功能的页面了。

    • 浏览器内核

    在浏览器,将网络资源转换为可视化界面的模块就称为浏览器内核(也叫渲染引擎)。

    image-20201009215822793.png
    目前主流内核:

    浏览器 Chrome IE 火狐 Safari
    内核 Blink(基于WebKit) Trident Gecko WebKit

    渲染引擎主要由以下组成:

    image-20201009220454268.png

    渲染引擎在基础模块的基础上主要包含:

  • HTML解释器:构建DOM树
  • CSS解释器:构建CSSDOM树
  • 布局:DOM树加CSS树,结合布局构建内部模型
  • JavaScript引擎:执行JavaScript代码
  • 绘图:使用图像库将布局后计算后的节点绘制图像
  • 渲染过程:

    image-20201009221658871.png
    上图大体概括了浏览器渲染的大体过程,这也很好解释了为什么不在DOM里面夹杂JavaScript代码,容易造成线程切换。

    webKit的渲染过程大致可分成三大阶段(三阶段后面又细分具体过程):

  • 从URL到构建完DOM树

    • 用户输入网页URL时,webKit调用资源加载器加载对应资源
    • 加载器依赖网络模块建立连接,发送请求
    • 收到的网页被HTML解释器转变成DOM树
    • 如果遇到JavaScript代码,调用js引擎解释执行
    • DOM树构建完触发DOMContent事件,DOM树构建完成及网页依赖资源都加载完成后触发onload事件。
  • 从DOM树构建完webKit的绘图上下文

    image-20201010110521704.png

    • css文件被解释成内部结构,附加在DOM树形成renderObject渲染树。
    • 渲染树创建,webKit同时会构建renderLayer层次树和一个虚拟的绘图上下文。
  • 从绘图上下文到生成图像

    image-20201010110542995.png

    • 绘图实现类结合图像库将绘制的结果返回给浏览器
    • webKit

    webKit是苹果公司开源的一个项目,被很多浏览器采用为内核,Chrome的Blink就是后面从webKit分支出去发展的。其整体架构如下图:

    image-20201010110643244.png

    而webKit2则是在webKit基础上一组支持新架构的接口层。该接口与网页渲染工作代码不在同一个进程,实现了chromium多进程的优点。webKit2接口使用不需要接触背后的多进程机制。

    image-20201009223118470.png
    如上图,网页渲染在web进程与webKit2所在的UI进程不是同一进程。

    • chromium

    chromium浏览器使用的也是基于webKit的Blink引擎,它相当于chrome的创新版,一些新技术都会先在chromium上实验。在chromium,webKit只是它的一部分。其中content模块和接口是对chromium渲染网页功能的抽象,它在webKit的上层渲染网页,以便可以使用沙箱模型和跨进程GPU等机制。相当于封装内层,提供content接口层让人调用。

    image-20201010111203540.png

    • 多进程模型

    多进程模型的优势:

  • 避免单个页面的崩溃而影响浏览器的稳定性。
  • 避免第三方插件崩溃影响浏览器的稳定性。
  • 方便安全模型的实施。
  • image-20201010114502992.png

    上图是chromium的多进程模型,其中连线代表IPC进程间通信,chromium浏览器主要进程类型有:

  • Browser进程
    • 浏览器的主进程,负责页面的显示和各页面的管理,是所有其他类型线程的祖先,负责它们的创建与销毁,有且仅有一个。
  • Renderer进程
    • 网页的渲染线程,可能有多个,不一定和网页数量相同
  • NPAPI插件进程
    • 进程为创建NPAPI类型的插件创建,每种类型插件只会创建一次,插件进程共享
  • GPU进程
    • 最多只有一个,GPU硬件加速时才创建
  • Pepper插件进程
    • 同NPAPI插件进程 ,为创建Pepper类型的插件创建。
  • 其他类型进程
    • 分场景使用,例如Linux的”Zygote“进程,”Sandbox“准备进程。
  • 多进程模型下网页的渲染:

    image-20201010140830364.png

  • Browser进程收到请求,由UI线程处理,将对应任务转给I/O线程,再传递给Render进程
  • Render进程的IO线程处理后交给渲染线程渲染,最后render进程将结果由IO线程传递给Browser进程
  • Browser进程收到结果并绘制出来。
  • Renderer创建方式

    Chromium允许用户配置 Renderer 进程的创建方式,有以下四种方式:

    • Process-per-site-instance:每个页面都创建一个独立的渲染线程
    • Process-per-site:同一个域的页面共享同一线程
    • Process-per-tab(Chromium默认):每个标签页都创建一个独立的渲染线程
    • Single process:不为页面创建任何独立线程,渲染在Browser进程进行。主要在Android WebView使用。

    下图为WebKit由内到外的交互:

    image-20201010135959622.png

    • webKit2与chromium的区别

      首先,两者都是多进程架构的模型,两者的根本目的都要实现UI和渲染的分离,区别在于设计理念:

  • Chromium 从浏览器角度出发,使用的仍是webKit接口,在webKit上构建多进程架构实现,成本低,对移动资源消耗大。
  • webKit2 定位为渲染引擎,尽量将多进程架构隐藏,只暴露相应接口,但实现代价高。
  • 二、网页加载与渲染

    • webKit资源加载机制

  • 资源缓存

    当webKit请求资源时,先从资源池查找是否存在相应的资源(通过URL,不同的URL被认为不同的资源。),如果有,直接取出使用,否则创建一个新的CachedResource子类的对象并真正发送请求给服务器,当webKit收到资源后将其设置到该资源类的对象中去,以便内存缓存后下次使用。

  • 资源加载器

    webKit共有三种类型的加载器:

    • 针对每种资源类型的特定加载器,仅加载某一种资源:例如ImageLoader
    • 资源缓存机制的资源加载器,所有的特定加载器都可以共享它所查找出来的缓存资源--CacheResourceLoader类。
    • 通用的资源加载器--resourceLoader类,webKit使用该类只负责获取资源的数据,属于CachedResource类,但不是继承CacheResourceLoader类。

    image-20201011195137246.png

  • 通常资源的加载是异步执行的,这样不会阻碍WebKit的渲染过程。webKit能够并发下载资源以及下载JavaScript代码。

    • DOM树

    DOM结构的基本要素就是“节点”,整个文档(Document)也是一个节点,称文档节点。除了文档节点还有元素节点,属性节点,注释节点等等。

    image-20201011201834146.png

    Document继承Node,具有一些属性和方法。

    • HTML解释器

    HTML解释器的工作就是讲网页资源由字节流解释成DOM树结构。

    image-20201011202204013.png

    大体概括为:

  • 词法分析,解释器检查网页编码格式,通过HTMLTokenizer类将字节流转化为一个个词语。
  • XSSAuditor验证词语,过滤不安全内容。
  • 词语到节点,调用HTMLTreeBuilder类来创建
  • 节点到DOM树,调用HTMLConstructionSite类来完成
    • 事件机制

      事件在工作分为两主体,一是事件,二是事件目标。Node节点继承EventTarget类。下图接口用来注册和移除监听。

      image-20201011203936940.png

    当渲染引擎收到事件,它会检查哪个元素是直接的事件目标,事件会经过自顶向下捕获和自底向上冒泡的两个过程。
    image.png

    • RenderObject树

      在DOM树构建完成后,webKit要为DOM树节点构建RenderObject树,一个RenderObject对应绘制DOM节点所需要的各种信息。那么问题来了,DOM树哪些节点需要创建RenderObject呢?大概有以下三类:

    • document节点
    • 可视节点,如body,div 。(ps: 不可视节点比如head script等)
    • 匿名的RenderObject 不对应任何DOM,例如RenderBlock
    • webKit布局

    当webKit创建RenderObject对象后,根据框模型计算各对象的位置,大小等信息的过程称为布局计算。

    image-20201011210323217.png

    Frame类用于表示网页的框结构,每个框都有一个frameView类表示框的视图结构。其中layout和needslayout用来计算布局和是否需要重新布局。布局计算先递归子女节点的位置和大小,最后得出自己节点布局。当可视区域发生变化后,webKit都需要重新计算布局。

    • 渲染方式

    网页渲染的方式主要有三种:

  • 软件渲染:CPU完成,处理2D方面的操作,适合简单网页

    RenderObject图像的绘制又分为以下三阶段:

    • 绘制层中背景和边框
    • 绘制浮动内容
    • 绘制前景,即内容部分等
  • 硬件加速渲染:GPU完成,适合3D绘图,但消耗内存资源

  • 混合渲染:既有CPU也有GPU

  • CPU绘制使用缓存机制可以减少重绘开销,每个renderLayer对象对应图像的一个图层,浏览器把所有图层合成图像就叫做合成化渲染。

    三、JavaScript引擎

    • 解释性语言

    JavaScript属于解释性语言,它的语言特性让我们在编译时无法确定变量的类型(不能做偏移信息查找,偏移信息共享等编译优化),同时在运算时计算和决定类型会带来性能损失,这也是它比静态语言慢的原因。但现在JavaScript引擎做了很多优化,加入JIT等等,已经十分接近静态语言的性能了。

    JavaScript与静态语言编译优化的区别:

  • 编译确定位置:静态语言在编译阶段就确定的对象的属性和偏移信息,而JavaScript没有类型,只有在对象创建时才有信息,也就是执行阶段才确定。
  • 偏移信息共享:静态语言所有对象按类型确定,这些对象共享偏移信息,访问它们只需按照编译时确定的偏移量。而JavaScript每个对象都是自描述,属性和位置偏移都包含在自身的结构中。
  • 偏移信息查找:静态语言对使用到的成员变量直接设置偏移量,而JavaScript使用到一个对象需要通过属性名匹配才能找到对应的值。
    • JIT(just in time)

    JavaScript为了提高运行速度加入了JIT技术,当解释器将源代码解释成内部表示时,JavaScript执行环境不仅解释这些内部表示而且将使用率较高的一些字节码转成本地代码(汇编)让CPU直接执行,而不是解释执行。这在Java虚拟机中也有应用。

    • JavaScript引擎

    JavaScript引擎就是能够将JavaScript代码处理并执行的运行环境。

    image-20201013091817685.png

    js引擎先将源代码转为抽象语法树,抽象语法树再转为中间表示(字节码表示),然后JIT转成本地代码运行。当然V8引擎可以直接从抽象语法树到本地代码,不经过中间表示,更加提高效率。

    一个JavaScript引擎通常包括:

  • 编译器:将源代码编译成抽象语法树
  • 解释器:解释执行字节码
  • JIT工具:将字节码或抽象语法树转换为本地代码
  • 垃圾回收器:负责垃圾回收
    • js引擎与渲染引擎

    JavaScript引擎需要能够访问渲染引擎构建的DOM树,这往往通过桥接的接口。通过桥接接口这对性能来说是很大的损失,所以应避免JavaScript频繁地访问DOM。

    image-20201013093217698.png

    • V8引擎

    V8是谷歌的一个开源项目,是高性能JavaScript引擎的实现。它支持window,linux,mac等操作系统,也支持X64,arm,IA32等硬件架构。其中node就是基于V8的引擎。在v8中,数据表示分为数据实际内容和数据的句柄两部分,内容是变长的,类型也不一样,而句柄定长,包含指向数据的指针。

    JavaScript对象在V8的内部表示有三个成员:

    image-20201013094945684.png

  • 隐藏类指针:v8为JavaScript对象创建的隐藏类(用于对相同属性名和属性值的对象构建类型信息,划分相同的组,组内共享信息)
  • 属性表指针指向对象包含的属性值
  • 元素表指针指向对象包含的元素
  • 隐藏类的例子:

    image-20201013100241250.png

    • 内存管理

    对于V8的内存划分,Zone类先申请一块内存,然后管理和分配一些小内存。小内存被分配后,不能被Zone回收,只能一次性回收分配的所有小内存。这有个缺陷,如果Zone分配了大量内存,但又不能够释放就会导致内存不足。

    V8用堆来管理JavaScript数据,为了方便垃圾回收,v8堆主要分成三部分:

    image-20201013103607822.png

    • 年轻代:主要为新对象分配内存空间
    • 年老代:较少地做垃圾回收
    • 大对象:需要较多内存的大对象

    对于垃圾回收,主要采用标记清除法(标记清除也分多种,如下所示)。

    image-20200928215651219.png
    image.png

    • V8为什么快?

  • 针对上下文的快照(Snapshot)技术

    快照技术即将内置对象和函数加载之后的内存保存并序列化,缩短启动时间。打开snapshot=on即可开启快照机制(但快照代码没法被优化编译器优化)。上下文(Contexts)则是JS应用程序的运行环境,避免应用程序的修改相互影响,例如一个页面js修改内置对象方法,不应该影响到另外页面。chrome浏览器每个process只有一个V8引擎实例,浏览器中的每个窗口、iframe都对应一个上下文。

  • Built-in代码

    利用JS自表达内置对象、方法。

  • AST的内存管理

    针对AST建立过程中多结点内存申请和一次性回收的特点,V8使用了内存段链表管理,并结合scopelock模式,实现少数申请(Segment,8KB~1MB)、多次分配AST结点、一次回收各个Segment的管理方式,既能避免内存碎片,又可以避免遍历AST结点逐个回收内存。

  • ComplieCache避免重复编译

    对于一段JS代码,在开始进行词法分析前,会从编译缓存区CompilationCache查找该段代码是否已经被编译过,如果是,则直接取出编译过的机器代码,并返回,这样降低CPU的使用率。

  • 属性快速访问

    V8没有使用词典结构或红黑树实现的map来管理属性,而是在每个对象附加一个指针,指向隐藏类hidden class(如果第一次创建该类型对象,则新建hidden class);当对象每添加一个属性时,将新建一个class(记录了每个属性的位移/位置),而原来的class指向新class,即建立起一个hidden class的转换链表。

  • 一次性编译生成机器语言

    V8一次性把AST编译为机器语言,没有中间表示(通常先编译为字节码)。

  • 相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论