《WebKit 技术内幕》学习之五(2): HTML解释器和DOM 模型

news2024/11/15 17:50:14

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代码中都可以被访问或者调用、

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1403360.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

终端(命令提示符或Windows PowerShell或Azure Cloud Shell)概述

终端&#xff08;命令提示符或Windows PowerShell或Azure Cloud Shell&#xff09;是一种很 不 好用的东西 就是要背&#xff0c;很 不 爽 介绍 Windows 终端是一个新式主机应用程序&#xff0c;它面向你喜爱的命令行 shell&#xff0c;如命令提示符、PowerShell 和 bash&…

力扣刷MySQL-第五弹(详细讲解)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;力扣刷题讲解-MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出…

Python 算法交易实验67 第一次迭代总结

说明 在这里对第一次迭代&#xff08;2023.7~ 2024.1&#xff09;进行一些回顾和总结&#xff1a; 回顾&#xff1a; 1 实现了0~1的变化2 在信息隔绝的条件下&#xff0c;无控制的操作&#xff0c;导致被套 总结&#xff1a; 思路可行&#xff0c;在春暖花开的时候&#x…

设备对象(DEVICE_OBJECT)

设备对象(DEVICE_OBJECT) 每个驱动程序会创建一个或多个设备对象&#xff0c;用DEVICE_OBJECT数据结构表示。每个设备对象都会有一个指针指向下一个设备对象&#xff0c;因此就形成一个设备链。设备对象链的第一个设备是由DRIVER_OBJECT结构体中指明的。设备对象保存设…

C++ 之LeetCode刷题记录(十五)

&#x1f604;&#x1f60a;&#x1f606;&#x1f603;&#x1f604;&#x1f60a;&#x1f606;&#x1f603; 开始cpp刷题之旅。 依旧是追求耗时0s的一天。 94. 二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; …

C#,入门教程(28)——文件夹(目录)、文件读(Read)与写(Write)的基础知识

上一篇&#xff1a; C#&#xff0c;入门教程(27)——应用程序&#xff08;Application&#xff09;的基础知识https://blog.csdn.net/beijinghorn/article/details/125094837 C#知识比你的预期简单的多&#xff0c;但也远远超乎你的想象&#xff01; 与文件相关的知识&#xf…

跟着我学Python进阶篇:03. 面向对象(下)

往期文章 跟着我学Python基础篇&#xff1a;01.初露端倪 跟着我学Python基础篇&#xff1a;02.数字与字符串编程 跟着我学Python基础篇&#xff1a;03.选择结构 跟着我学Python基础篇&#xff1a;04.循环 跟着我学Python基础篇&#xff1a;05.函数 跟着我学Python基础篇&#…

# Jenkins:一键部署与备份的终极解决方案

Jenkins&#xff1a;一键部署与备份的终极解决方案 引言 在持续集成和持续部署&#xff08;CI/CD&#xff09;的世界中&#xff0c;Jenkins 作为一个开源自动化服务器&#xff0c;扮演着至关重要的角色。但是&#xff0c;部署和维护 Jenkins 服务往往需要一定的技术知识和时间…

卷积和滤波对图像操作的区别

目录 问题引入 解释 卷积 滤波 问题引入 卷积和滤波是很相似的&#xff0c;都是利用了卷积核进行操作 那么他们之间有什么区别呢&#xff1f; 卷积&#xff1a;会影响原图大小 滤波&#xff1a;不会影响原图大小 解释 卷积 我们用这样一段代码来看 import torch.nn as …

【C++】入门(一)

前言&#xff1a; 本篇博客将带大家认识C&#xff0c;熟悉基本语法 文章目录 认识CC的诞生与发展C 在行业中的运用 一、命名空间1.1 命名空间的定义1.2 命名空间的使用1.3 命名空间的访问 二、C输入&输出输出操作符 <<输入操作符 >>换行符和刷新输出缓冲区关键…

C++入门学习(十二)字符串类型

上一节&#xff08;C入门学习&#xff08;十一&#xff09;字符型-CSDN博客&#xff09;中我们学到如何表示和使用一个字符串&#xff0c;本篇文章是字符串&#xff08;多个字符&#xff09;。 定义字符串主要有两种方式&#xff1a; 第一种&#xff1a; char str[] "…

openGauss学习笔记-203 openGauss 数据库运维-常见故障定位案例-修改索引时只调用索引名提示索引不存在

文章目录 openGauss学习笔记-203 openGauss 数据库运维-常见故障定位案例-修改索引时只调用索引名提示索引不存在203.1 修改索引时只调用索引名提示索引不存在203.1.1 问题现象203.1.2 原因分析203.1.3 处理办法 openGauss学习笔记-203 openGauss 数据库运维-常见故障定位案例-…

28、web攻防——通用漏洞SQL注入HTTP头XFFCOOKIEPOST请求

文章目录 $_GET&#xff1a;接收get请求&#xff0c;传输少量数据&#xff0c;URL是有长度限制的&#xff1b; $_POST&#xff1a;接收post请求&#xff1b; $_COOKIE&#xff1a;接收cookie&#xff0c;用于身份验证&#xff1b; $_REQUEST&#xff1a;收集通过 GET 、POST和C…

Web开发3:数据库使用

欢迎来到Web开发系列的第三篇&#xff01;今天我们将探讨如何在Web开发中使用数据库。数据库是存储和管理数据的重要工具&#xff0c;它在现代应用程序中起着至关重要的作用。无论是社交媒体应用、电子商务平台还是博客网站&#xff0c;数据库都是不可或缺的一部分。 什么是数…

基于DUP的网络聊天室

基于UDP的网络聊天室的使用&#xff08;select&#xff09;完成的服务器端 #include<head.h> typedef struct de {char name[10];struct sockaddr_in cin;struct de* next; }*linklist; //创建节点 linklist a_creat() {linklist p(linklist)malloc(sizeof(struct de));…

C++:基于C的语法优化

C&#xff1a;基于C的语法优化 命名空间命名空间域域作用限定符展开命名空间域 输入输出缺省参数全缺省参数半缺省参数 函数重载参数类型不同参数个数不同参数类型的顺序不同 引用基本语法按引用传递返回引用引用与指针的区别 内联函数autoauto与指针和引用结合 范围for循环nul…

统计灰度图像的灰度值分布并绘制

1、numpy方法 函数&#xff1a; numpy.histogram(a, bins10, rangeNone, normedNone, weightsNone, densityNone) 参数说明&#xff1a; a:输入数据数组&#xff1b;bins:指定统计的区间个数&#xff0c;可以是一个整数&#xff0c;也可以是一个数组&#xff0c;默认值为10…

续签KES证书

MiniO KES&#xff08;密钥加密服务&#xff09;是 MinIO 开发的一项服务&#xff0c;旨在弥合在 Kubernetes 中运行的应用程序与集中式密钥管理服务 &#xff08;KMS&#xff09; 之间的差距。中央 KMS 服务器包含所有状态信息&#xff0c;而 KES 在需要执行与获取新密钥或更新…

最小生成树(Java实现)

一、Prim算法 Prim算法基本思想为&#xff1a;从联通网络 N{V,E}中某一顶点 v0 出发&#xff0c;此后就从一个顶点在 S 集中&#xff0c; 另一个顶点不在 S 集中的所有顶点中选择出权值最小的边&#xff0c;把对应顶点加入到 S 集 中&#xff0c; 直到所有的顶点都加入到 S 集中…

RK3568平台 TinyAlsa集成第三方音频算法

一.tinyalsa介绍 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;是一个开源项目&#xff0c;涵盖了用户空间和内核空间对音频设备的操作接口&#xff0c;通过应用层使用alsalib可以实现对音频设备的控制 TinyAlsa是android推出的一个精简的ALSA库&#xff0c…