《WebKit 技术内幕》之五(2): HTML解释器和DOM 模型
2.HTML 解释器
2.1 解释过程
HTML 解释器的工作就是将网络或者本地磁盘获取的 HTML 网页和资源从字节流解释成 DOM 树结构。
这一过程中,WebKit 内部对网页内容在各个阶段的结构表示。 WebKit 中这一过程如下:首先是字节流,经过解码之后是字符流,然后通过词法分析器会被解释成词语(Tokens),之后经过语法分析器构建成节点,最后这些节点被组建成一棵 DOM 树。
WebKit为完成这一过程,引入比较复杂的基础设施类。
左边部分网页框结构,框对应于Frame类,而文档对应于HTMLDocument类,所以框包含文档、HTMLDocument类继承自Document类。也是遵循DOM标准的,因为Document有两个子类,另外一个是XMLDocument。这里没有描述内嵌的复杂框结构,但是足以说明网页基本结构内部表示。在实际应用中,网页内嵌框也会重复这样的动作。
右边部分是WebKit为建立网页框结构所建立的设施,先看FrameLoder类,它是框中内容的加载器,类似于资源和资源加载器。因为Frame对象中包含Document对象,所以WebKit同样需要DocumentLoader类帮助加载HTML文档并从字节流到构建的DOM树,DocumentWriter类是一个辅助类,它会创建DOM树的根节点HTMLDocument对象,同时该类包括两个成员变量,一个用于文档的字符解码类,另一个就是HTML解释器HTMLDocumentParser类。
HTMLDocumentParser类是一个管理类,包括了用于各种工作的其他类,如字符串到词语需要用到词法分析器HTMLTokenizer类。该管理类读入字符串,输出一个词语,这些词语经过XSSAuditor做完安全检查之后,就会输出到HTMLTreeBuilder类。
HTML Tree Builder类负责DOM树的建立,它本身能够通过词语创建一个个的节点对象,然后,借由HTMLConstructionSite类来将这些点对象构建成一颗树。
下图是WebKit收到网络回复的字节流的时候,从字节流到构建DOM树的时序图,详述了调用过程,但省略了一些次要调用。ResourceLoader类和CachedRawResource类收到网络栈的数据后,调用DocumentLoader类的commitData方法,然后DocumentWriter类会创建一个根节点HTMLDocument对象,然后将数据append输送到HTMLDocumentParser对象,后面就是将其解释成词语,创建节点对象然后建立以HTMLDocument为根的DOM树。
2.2 词法分析
在进行词法分析之前,解释器首先要做的事情就是检查该网页内容使用的编码格式,以便后面使用合适的解码器。如果解释器在 HTML 网页中找到了设置的编码格式, WebKit 会使用相应的解码器来将字节流转换成特定格式的字符串。如果没有特殊格式,词法分析器 HTMLTokenizer 类可以直接进行词法分析。
词法分析的工作都是由 HTMLTokenizer 来完成 ,简单来说,它就是一个状态机—输入的是字符串,输出的是一个个词语。因为字节流可能是分段的,所以输入的字符串可能也是分段的,但是这对词法分析器来说没有什么特别之处,它会自己维护内部的状态信息。
词法分析器的主要接口是 “nextToken” 函数,调用者只需要关键字符串传入,然后就会得到一个词语,并对传入的字符串设置相应的信息,表示当前处理完的位置,如此循环,如果词法分析器遇到错误,则报告状态错误码,主要逻辑在图中给予了描述。
对于 “nextToken” 函数的调用者而言,它首先设置输入需要解释的字符串,然后循环调用 NextToken 函数,直到处理结束。 “nextToken” 方法每次输出一个词语,同时会标记输入的字符串,表明哪些字符已经被处理过了。因此,每次词法分析器都会根据上次设置的内部状态和上次处理之后的字符串来生成一个新的词语。 “nextToken” 函数内部使用了超过 70 种状态,图中只显示了 3 种状态。对于每个不同的状态,都有相应的处理逻辑。
而对于词语的类别,WebKit只定义了很少,HTMLToken类定义了6种词语类别,包括DOCTYPE、StartTag、EndTag、Comment、Character和EndOfFile,这里不涉及HTML的标签类型等信息,那是后面语法分析的工作。
2.3 XSSAuditor 验证词语
当词语生成之后,WebKit 需要使用 XSSAuditor 来验证词语流(Token Stream)。XSS 指的是 Cross Site Security , 主要是针对安全方面的考虑。
根据 XSS 的安全机制,对于解析出来的这些词语,可能会阻碍某些内容的进一步执行,所以 XSSAuditor 类主要负责过滤这些被阻止的内容,只有通过的词语才会作后面的处理。
2.4 词语到节点
经过词法分析器解释之后的词语随之被 XSSAuditor 过滤并且在没有被阻止之后,将被 WebKit 用来构建 DOM 节点。从词语到构建节点的步骤是由 HTMLDocumentParser 类调用 HTMLTreeBuilder 类的 “constructTree” 函数来实现。该函数实际上是利用ProcessToken函数来处理6种词语类型。
2.5 节点到 DOM 树
从节点到构建 DOM 树,包括为树中的元素节点创建属性节点等工作由 HTMLConstructionSite 类来完成。正如前面介绍的,该类包含一个 DOM 树的根节点 ——HTMLDocument 对象,其他的元素节点都是它的后代。
因为 HTML 文档的 Tag 标签是有开始和结束标记的,所以构建这一过程可以使用栈结构来帮忙。HTMLConstructionSite 类中包含一个 “HTMLElementStack” 变量,它是一个保存元素节点的栈,其中的元素节点是当前有开始标记但是还没有结束标记的元素节点。想象一下 HTML 文档的特点,例如一个片段 “<body><div><img></img></div><.body>”,当解释到 img 元素的开始标记时,栈中的元素就是 body 、div 和 img ,当遇到 img 的结束标记时,img 退栈, img 是 div 元素的子女;当遇到 div 的结束标记时,div 退栈,表明 div 和它的子女都已处理完,以此类推。
根据DOM标准中的定义,节点有很多类型,如元素节点、属性节点等。同 DOM 标准一样,一切的基础都是 Node 类。在 WebKit 中, DOM 中的接口 Interface 对应于 C++ 的类,Node 类是其他类的基类,在下面的图中 显示了 DOM 的主要相关节点类。图中的 Node 类实际上继承自 EventTarget 类,它表明 Node 类能够接受事件,这个会在 DOM 事件处理中介绍。Node 类还继承自另外一个基类 ——ScriptWrappable,这个跟 JavaScript 引擎相关。
Node 的子类就是 DOM 中定义的同名接口,元素类,文档类和属性类均继承自一个抽象出来的 ContainerNode 类,表明它们能够包含其他的节点对象。回到 HTML 文档来说,元素和文档对应的类注是 HTMLElement 类和 HTMLDocument 类,实际上 HTML 规范还包含众多的 HTMLElement 子类,用于表示 HTML 语法中众多的标签。
2.6 网页基础设施
上面介绍了 Frame 、Document 等 WebKit 中的基础类,这些都是网页内部的概念,实际上,WebKit 提供了更高层次的设施,用于表示整个网页的一些类,WebKit 中的 接口部分 就是基于它们来提供的,表示网页的类既提供了构建 DOM 树等操作,同时也提供了接口用于布局。渲染等操作。
在上图中右边的部分还有一些WebKit表示网页的一些基础设施类,来描述WebKit是如何被该移植使用从而提供对外使用的接口,它们实际上是Chromium项目调用WebKit项目的接口。右边是WebKit中的WebCore提供的部分类,这些类都是公共的,也就是被不同的WebKit移植所共享。如Frame、Chrome(是个类)、ChromeClient和Page类。图中左边是WebKit的Chromium移植实现使用的接口类,其中RenderViewImpl来自Chromium项目,是Chromium使用WebKit的主要桥接类,用于Renderer进程。在WebKit的Chromium移植中,WebView和WebFrame是不得不提的两个类,它们是Chromium项目用于表示网页和网页框的接口类,另外两个类WebViewImpl和WebFrameImpl是这两个类的子类,它们负责使用Page、Frame等WebCore中类来支持两个对外类的接口。
图中右上角是Chrome(类)和ChromeClient类,这两个类很重要,它们使用一个WebKit普遍使用设计模式:
- Chrome类需要具备获取各个平台资源的能力,可以调用Chrome类来创建一个新窗口。
- Chrome类需要把WebKit的状态和进度等信息派发给外部的调用者或者说是WebKit的使用者。
WebKit内部同这两类需求相关的要求都是通过Chrome类的接口来完成的,WebKit使用ChromeClient抽象类ChromeClient抽象类来解决既让webKIt和外部调用者既不紧密耦合,又能方便地支持不同的平台 。Chrome类是一些公共的操作流程,而ChromeClient类是Chrome需要用到的一些接口,这些接口在不同的移植上必须有不同的实现。WebKit的Chromium移植中有ChromeClientImpl实现类。ChromeClient类可以有两类接口,第一类用来监听WebKit的内部状态信息,这其实是回调函数;第二类用来实现Chrome类所需要的跟移植相关的工作。
当Chrome类接收到网页的大小发生改变的消息时,它就会调用ChromeClient类的contentSizeChanged函数来通知Chromium浏览器。而当Chrome类需要创建一个窗口的时候,WebKit同样使用ChromeClient类来帮助创建,因为Chrome类本身没有能力创建与移植相关的这些信息。
综上所述Chrome类,它是一个非常重要的类,是WebKit与它的使用者之间的桥梁,主要负责用户界面和渲染相关的需求,实现这些需求会用到平台的接口,以上是它的这些功能。
- 跟用户界面和渲染显示相关的需要各个移植实现接口的结合类,
- 继承自HostWindow(宿主窗口)类,该类包含一系列接口,用来通知重绘或者更新整个窗口、滚动窗口等。
- 窗口相关操作,如显示、隐藏窗口等。
- 显示和隐藏窗口中的工具栏、状态栏、滚动条等。
- 显示JavaScript相关的窗口,如JavaScript的alert、confirm、prompt窗口。
2.7 线程化的解释器
在 Renderer 进程中有一个线程,该线程用来处理 HTML 文档的解释任务,在 HTML 解释器的步骤中,WebKit 的 Chromium 移植跟其他的 WebKit 移植也存在不同之处。
线程化的解释器就是利用单独的线程来解释 HTML 文档。因为在WebKit 中,网络资源的字节流自 IO 线程传递给渲染线程之后,后面的解释、布局和渲染等工作基本上就是工作在该线程,也就是渲染线程完成的(这不是绝对的)。因为 DOM 树只能在渲染线程上创建和访问,这也就是说构建 DOM 树的过程只能在渲染线程中进行。但是,从字符到词语这个阶段可以交给单独的线程来做,Chromium 浏览器使用的就是这个思想。
具体的实现过程:
字符串 (传给)=> HTMLDocumentParser类 (创建一个新的对象)=> BackgroundHTMLParser 来负责处理 (交给)=> 前一步创建的对象
WebKit 会检查是否需要创建用于解释字符串的线程 HTMLParserThread 。如果该线程已存在,WebKit 就将刚刚的任务传递给这一新线程, 下图描述了这一过程。
在 HTMLParserThread 线程中,WebKit 所做的事情包括将字符串解释成一个个词语,然后使用之前提到的 XSSAuditor 进行安全检查。这是在一个新的线程中执行。主要区别在于解释成词语之后,WebKit 会分批次地将结果词语传递给渲染线程。
2.8 JavaScript 的执行
在 HTML 解释器的工作过程中,可能会有 JavaScript 代码(全局作用域的代码)需要执行,它发生在将字符串解释成词语之后、创建各种节点的时候。这也是全局执行的 JavaScript 代码不能访问 DOM 树的原因——因为 DOM 树还没有被创建完。
WebKit将DOM树创建过程中需要执行的JavaScript代码交由HTMLScriptRunner类来负责。工作方式很简单,就是利用JavaScript引擎来执行Node节点中包含的代码。细节见HTML Script Runner::excuteParsingBlockingScript方法。
因为JavaScript代码可能会调用如“document.write()”来修改文档结构,所以JavaScript代码的执行会阻碍后面节点的创建,同时当然也会组在后面资源的下载,这时候WebKit对需要什么资源一无所知,这导致了资源不能够并发地下载这一严重影响性能的问题。下图示例一,JavaScript代码的执行阻碍了后面img图片的下载。当该Script节点使用src属性的时候,情况变得更差,因为WebKit还需要等待网络获取JavaScript文档,所以关于JavaScript的使用有以下两点建议。
1、将 “script” 元素加上 “async” 属性,表明这是一个可以异步执行的 JavaScript 代码。在HTMLScriptRunner类中就有相应的函数执行异步的JavaScript代码,图中示例二给出了使用方法。
2、 将 “script” 元素放在 “body” 元素的最后,这样它不会阻碍其他资源的并发下载。
但是不这样做的时候,WebKit 使用预扫描和预加载机制来实现资源的并发下载而不被 JavaScript 的执行所阻碍。
具体做法是:当遇到需要执行 JavaScript 代码的时候,WebKit 先暂停当前 JavaScript 代码的执行,使用预先扫描器 HTMLPreloadScanner 类来扫描后面的词语。如果 WebKit 发现它们需要使用其他资源,那么使用预资源加载器 HTMLPreloadScanner 类来发送请求,在这之后,才执行 JavaScript代码。预先扫描器本身并不创建节点对象,也不会构建 DOM 树,所以速度比较快。
当 DOM 树构建完之后,WebKit 触发 “DOMContentLoaded” 事件,注册在该事件上的 JavaScript 函数会被调用。当所在资源都被加载完之后,WebKit 触发 “onload” 事件。
2.9 实践:理解DOM树
实际查看网页的DOM树结构,具体的步骤如下:
(1)打开Chrome浏览器的开发者工具,点击console控制台按钮。
(2)在控制台中输入“document”,可以看到整个文档,而且有“#document”表示的根节点,这个是额外附加在上面的,表示的是HTMLDocumen。下图左边第一行显示的就是document节点。另外,还可以尝试输入”document.firstChild“,显然是html节点。接下来就是对DOM树的遍历,可以使用firstChild获得第一个子女,然后使用nextSibling来获得子女的兄弟。
(3)在控制台中输入”document“,会看到Chrome浏览器提供一个候选列表,该列表中列举了所有的Document对象的属性和方法,可以得到一个非常长的列表,这些方法和属性在JavaScript代码中都可以被访问或者调用、
相关文章:

《WebKit 技术内幕》之五(2): HTML解释器和DOM 模型
2.HTML 解释器 2.1 解释过程 HTML 解释器的工作就是将网络或者本地磁盘获取的 HTML 网页和资源从字节流解释成 DOM 树结构。 这一过程中,WebKit 内部对网页内容在各个阶段的结构表示。 WebKit 中这一过程如下:首先是字节流,经过解码之…...

Spring Boot多环境配置
Spring Boot的针对不同的环境创建不同的配置文件, 语法结构:application-{profile}.properties profile:代表的就是一套环境 需求 application-dev.yml 开发环境 端口8090 application-test.yml 测试环境 端口8091 application-prod.yml 生产环境 端口80…...

常用的目标跟踪有哪些
目标跟踪是计算机视觉领域的一个重要研究方向,主要用于实现视频监控、人机交互、智能交通等领域。下面介绍几种常用的目标跟踪方法: 特征匹配法 特征匹配法是目标跟踪中最基本的方法之一,其基本原理是通过提取目标的特征,然后在…...

python222网站实战(SpringBoot+SpringSecurity+MybatisPlus+thymeleaf+layui)-帖子详情页实现
锋哥原创的SpringbootLayui python222网站实战: python222网站实战课程视频教程(SpringBootPython爬虫实战) ( 火爆连载更新中... )_哔哩哔哩_bilibilipython222网站实战课程视频教程(SpringBootPython爬虫实战) ( 火…...

11、Kafka ------ Kafka 核心API 及 生产者API 讲解
目录 Kafka核心API 及 生产者API讲解★ Kafka的核心APIKafka包含如下5类核心API: ★ 生产者APIKafka 的API 文档 ★ 使用生产者API发送消息 Kafka核心API 及 生产者API讲解 官方文档 ★ Kafka的核心API Kafka包含如下5类核心API: Producer API&#x…...

MySQL 8.3 发布, 它带来哪些新变化?
1月16号 MySQL 官方发布 8.3 创新版 和 8.0.36 长期支持版本 (该版本 没有新增功能,更多是修复bug ),本文基于 官方文档 说一下 8.3 版本带来的变化。 一 增加的特性 1.1 GTID_NEXT 支持增加 TAG 选项。 之前的版本中 GTID_NEXTUUID:number ÿ…...

【数据结构】详谈队列的顺序存储及C语言实现
循环队列及其基本操作的C语言实现 前言一、队列的顺序存储1.1 队尾指针与队头指针1.2 基本操作实现的底层逻辑1.2.1 队列的创建与销毁1.2.2 队列的增加与删除1.2.3 队列的判空与判满1.2.4 逻辑的局限性 二、循环队列2.1 循环队列的实现逻辑一2.2 循环队列的实现逻辑二2.3 循环队…...

为什么 HTTPS 协议能保障数据传输的安全性?
HTTP 协议 在谈论 HTTPS 协议之前,先来回顾一下 HTTP 协议的概念。 HTTP 协议介绍 HTTP 协议是一种基于文本的传输协议,它位于 OSI 网络模型中的应用层。 HTTP 协议是通过客户端和服务器的请求应答来进行通讯,目前协议由之前的 RFC 2616 拆…...

使用 Node 创建 Web 服务器
Node.js 提供了 http 模块,http 模块主要用于搭建 HTTP 服务端和客户端,使用 HTTP 服务器或客户端功能必须调用 http 模块,代码如下: var http require(http); 以下是演示一个最基本的 HTTP 服务器架构(使用 8080 端口)&#x…...

leetcode 151反转字符串如何原地去除多余空格
题目:https://leetcode.cn/problems/reverse-words-in-a-string/description/ 完整题解:https://leetcode.cn/problems/reverse-words-in-a-string/solutions/2611893/chu-li-kong-ge-ku-han-shu-reversefan-zhu-bioo 思路来自代码随想录,对其中的除去多…...

面试问题记录【深圳,共三面,A 轮公司】
问题记录 一面: 自我介绍项目介绍项目中用到的本地缓存是否涉及数据不一致问题,如何解决?项目中用到了 RTree 和普通的 B 树和 B树的数据结构的区别是什么?mysql 数据库中几种日志的用法和区别?redis 中缓存三兄弟存…...

Mysql数据库cpu飙升怎么解决
排查过程 (1)使用top命令观察,确定是mysql导致还是其他原因。 (2)如果是mysql导致的,show processlist,查看session情况,确定是不是有消耗资源的sql在运行。 (3…...

PHP反序列化漏洞-POP链构造
POP链构造 POP链(Property-Oriented Programming)是一种常用于构造特定调用链的方法,用于从现有运行环境中寻找一系列代码或指令调用。它的目的是构成一组连续的调用链,最终达到攻击者恶意利用的目的。POP链实质上是通过控制对象的可控属性来控制程序的执行流程,从而利用…...

CentOS 7安装Java并配置环境
一、安装Java环境 1、检查系统是否安装Java [rootlocalhost ~]# java -version 2、更新系统软件包 [rootlocalhost ~]# yum update #遇到[y/n],选择y并回车,耐心等待下载完毕,之后系统会自动检验更新的软件包遇到 /var/run/yum.pid 已被锁定 /var/…...

Vagrant创建Oracle RAC环境示例
利用Vagrant安装Oracle RAC(默认为non-CDB模式),生成2台虚机,耗时约1小时。 node1: -----------------------------------------------------------------node1: INFO: 2024-01-11 18:25:54: Make create database commandnode1: …...

鸿蒙 HarmonyOS ArkTS ArkUI 动画 中心缩放、顶部缩放、纵向缩放
EntryComponentstruct Index {State widthA: number 200State heightA: number 200onPageShow():void{animateTo ( {duration: 2000,iterations: -1,curve:Curve.Linear}, () > {this.widthA 0this.heightA 0} )}build() {Column() {// 中心缩放Column(){}.width(this.wi…...

基于python socket实现TCP/UDP通信
两个应用程序如果需要进行通讯最基本的一个前提就是能够唯一的标示一个进程,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中…...

指针的运算
指针的运算 1.指针-整数 #define N_VALUES 5 float values[N_VALUES]; float* vp; //指针-整数:指针的关系运算 int main() { for (vp &values[0]; vp < &values[N_VALUES];) { *vp 0;//指针每自增一次,就是指向下一个元素的地址 } return …...

记录一次QT乱码问题
问题描述 在敲陆文周的书《QT5开发及实例》的示例代码时,出现乱码,如下图所示 具体代码如下 Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);ui->treeWidget->clear();int groupSize 2;int ite…...

怎么提升搜狗网站排名
在当今数字化时代,网站排名对于品牌、企业以及个人都至关重要。而对于许多网站来说,搜狗搜索引擎是一个重要的流量来源。为了在搜狗上取得更好的排名,不仅需要优化网站内容,还需要巧妙运用一些工具和技巧。在本文中,我…...

搜索经典题——填充 9*9矩阵
题目:给定一个九行九列矩阵,填充矩阵元素,要求: 1、每一行每一列,每个小九宫格(图片画粗的地方就是)不能包含相同元素 2、每一行,每一列,每个小九宫格均会完整出现1-9的数…...

Vue待办事项(组件,模块化)
//html页面代码 <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title></title> <style> * { padding: 0; margin: 0; }…...

Vue中的组件
在应用程序的开发中,组件是不可缺少的。在Vue的使用中,同样也会用到组件。 vue组件的一般知识点: 1、组件的名字唯一; 2、组件以Html形式书写; 3、组件可以复用; 4、组件可以嵌套&…...

svg矢量图标在wpf中的使用
在wpf应用程序开发中,为支持图标的矢量缩放,及在不同分辨率下界面中图标元素的矢量无损缩放,所以常常用到svg图标,那么如果完 美的将svg图标运用到wpf日常的项目开发中呢,这里分享一下我的个人使用经验和详细步骤。 步…...

如何在云端加速缓存构建
缓存是指将某类数据存储起来以便以后重复使用的过程,它的运用在开发场景中非常普遍。类似于你习惯把最常用的调料放在厨房台面上,而不是橱柜里,这样你在准备大餐时就可以轻松取用。 但对于一个更为技术性、更精确的用例,比如像谷…...

JavaWeb-Cookie与Session
一、概念 是否还记得我们在HTTP概念中提到:HTTP的一大特点是无状态,这意味着多次HTTP请求之间是无法共享数据的。而在请求之间共享一些数据又是我们期望达到的效果。(例如登录的记住我功能)于是便有了会话跟踪技术,而…...

ZABBIX根据IP列表,主机描述,或IP子网批量创建主机的维护任务
有时候被ZABBIX监控的主机可能需要关机重启等维护操作,为了在此期间不触发告警,需要创建主机的维护任务,以免出现误告警 ZABBIX本身有这个API可供调用(不同版本细节略有不同,本次用的ZABBIX6.*),实现批量化建立主机的维护任务 无论哪种方式(IP列表,主机描述,或IP子网)创建维护…...

PMIS_ENT_STD
...

32 登录页组件
效果演示 实现了一个登录页面的样式,包括一个容器、左侧和右侧部分。左侧部分是一个背景图片,右侧部分是一个表单,包括输入框、复选框、按钮和忘记密码链接。整个页面的背景色为白色,容器为一个圆角矩形,表单为一个半透…...

Docker(一)简介和基本概念:什么是 Docker?用它会带来什么样的好处?
作者主页: 正函数的个人主页 文章收录专栏: Docker 欢迎大家点赞 👍 收藏 ⭐ 加关注哦! 一、简介 本章将带领你进入 Docker 的世界。 什么是 Docker? 用它会带来什么样的好处? 好吧,让我们带…...