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

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

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

相关文章

ORBSLAM3安装

0. C11 or C0x Compiler sudo apt-get install gccsudo apt-get install gsudo apt-get install build-essentialsudo apt-get install cmake1. 依赖 在该目录终端。 1. 1.Pangolin git clone https://github.com/stevenlovegrove/Pangolin.git sudo apt install libglew-d…

Python基础第九篇(Python可视化的开发)

文章目录 一、json数据格式&#xff08;1&#xff09;.转换案例代码&#xff08;2&#xff09;.读出结果 二、pyecharts模块介绍三、pyecharts模块入门&#xff08;1&#xff09;.pyecharts模块安装&#xff08;2&#xff09;.pyecharts模块操作&#xff08;1&#xff09;.代码…

C++力扣题目509--斐波那契数 70--爬楼梯 746--最小花费爬楼梯

509. 斐波那契数 力扣题目链接(opens new window) 斐波那契数&#xff0c;通常用 F(n) 表示&#xff0c;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n) F(n -…

了解WPF控件:PrintDialog常用属性与用法(八)

掌握WPF控件&#xff1a;熟练常用属性&#xff08;八&#xff09; PrintDialog -一个对话框&#xff0c;用于在打印文档时显示打印设置参数供用户选择并确认。通过该控件&#xff0c;用户可以选择打印机、打印的范围、打印的份数、打印质量等。 常用属性描述CurrentPageEnab…

制作编写使用说明书:在结构、风格与内容方面需要注意什么?

如今&#xff0c;一个清晰、简洁、易于理解的使用说明书不仅能够帮助用户正确地使用产品&#xff0c;还能提升用户体验并树立品牌形象。而制作编写一份优质的使用说明书需要我们在结构、风格与内容三个方面下功夫。那么在制作编写使用说明书时需要注意哪些关键要素呢&#xff1…

【JavaWeb】日程管理系统 项目搭建 第二期

文章目录 一、数据库准备二、导入依赖 与 JDBC工具类三、pojo包处理四、daodao包工具类 五、service六、controllerservlet 基类 反射 七、加密工具类 MD5八、页面文件九、业务代码9.1 注册业务处理9.2 登录业务处理 总结 一、数据库准备 创建数据库&#xff1a; SET NAMES …

骨传导耳机综评:透视南卡、韶音和墨觉三大品牌的性能与特点

在当前的蓝牙音频设备领域中&#xff0c;骨传导蓝牙运动耳机以其出色的安全特性和舒适的体验&#xff0c;受到了健身爱好者们的广泛好评。这类耳机不同于我们常见的入耳式耳机&#xff0c;它的工作方式是直接通过振动将声音传递到用户的耳骨中&#xff0c;这样既可以享受音乐&a…

【nowcoder】链表的回文结构

牛客题目链接 链表的回文结构 /* struct ListNode {int val;struct ListNode *next;ListNode(int x) : val(x), next(NULL) {} };*/ #include <cstdlib> // 建议大伙自己对照我的代码画下图&#xff0c;假设A链表是&#xff1a;1 2 3 2 1 class PalindromeList { publi…

【学网攻】 第(7)节 -- 生成树配置

文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学网攻】 第(3)节 -- 交换机配置聚合端口【学网攻】 第(4)节 -- 交换机划分Vlan【学网攻】 第(5)节 -- Cisco VTP的使用【学网攻】 第(6)节 -- 三层交换机实现VLAN间路由 前言 网络已经成为…

vscode 如何指定启动文件?

launch.json 里面可以指定&#xff0c;launch.json 在打开文件夹的时候可以创建&#xff0c;或者手动创建

不合格机器人工程讲师再读《悉达多》-2024-

一次又一次失败的经历&#xff0c;让我对经典书籍的认同感越来越多&#xff0c;越来越觉得原来的自己是多么多么的无知和愚昧。 ----zhangrelay 唯物也好&#xff0c;唯心也罢&#xff0c;我们都要先热爱这个世界&#xff0c;然后才能在其中找到自己所热爱的事业。 ----zh…

vue2项目打包到测试环境之后报错require is not defined

配置打包命令npm run build:test到测试环境之后报错&#xff0c;打包到生产环境没有问题&#xff0c;查找了项目中的require引入似乎也没啥有问题的地方&#xff0c;所以排除是require的原因 环境变量文件&#xff1a; 打包指令&#xff1a; 解决办法&#xff1a; 将.env.tes…

[docker] Docker 网络和Cgroup

一、Docker 网络 1.1 Docker 网络实现原理 Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c;Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为Container-IP&#xff0c;同时Docker网桥是每个容器的默认…

【C/C++】C/C++编程——C++ 开发环境搭建

C的开发环境种类繁多&#xff0c;以下是一些常见的C 集成开发环境&#xff1a; AppCode &#xff1a;构建与JetBrains’ IntelliJ IDEA 平台上的用于Objective-C&#xff0c;C,C&#xff0c;Java和Java开发的集成开发环境CLion&#xff1a;来自JetBrains的跨平台的C/C的集成开…

《动手学深度学习(PyTorch版)》笔记2

Chapter2 Preliminaries 2.1 Automatic Differentiation 让计算机实现微分功能&#xff0c; 有以下四种方式&#xff1a; - 手工计算出微分&#xff0c; 然后编码进代码 - 数值微分 (numerical differentiation) - 符号微分 (symbolic differentiation) - 自动微分&#xff0…

java web mvc-03-JFinal

拓展阅读 Spring Web MVC-00-重学 mvc mvc-01-Model-View-Controller 概览 web mvc-03-JFinal web mvc-04-Apache Wicket web mvc-05-JSF JavaServer Faces web mvc-06-play framework intro web mvc-07-Vaadin web mvc-08-Grails JFinal JFinal 是基于 Java 语言的极…

Shell脚本③条件语句、if命令和case命令

目录 一.条件语句 1.test测试条件表达式 2.整数数值比较 &#xff08;1&#xff09;比较两个整数大小 &#xff08;2&#xff09;查看系统剩余内存是否低于1024M 3.逻辑测试 4.三元运算符 二.if命令 1.单分支结构 2.双分支结构 3.多分支结构 三.case语句 四.脚本 …

代码随想录算法训练营第十三天|层序遍历10,226.翻转二叉树,101.对称二叉树

系列文章目录 代码随想录算法训练营第一天|数组理论基础&#xff0c;704. 二分查找&#xff0c;27. 移除元素 代码随想录算法训练营第二天|977.有序数组的平方 &#xff0c;209.长度最小的子数组 &#xff0c;59.螺旋矩阵II 代码随想录算法训练营第三天|链表理论基础&#xff…

项目成本估算基准的常见步骤

项目成本估算基准是指在项目启动阶段确定的用于衡量和控制项目成本的基准。 基准成本是项目成本估算的依据&#xff0c;也是后续成本控制和决策的依据。它为管理层提供项目预算投资方案等关键投资依据&#xff0c;决定资源的分配情况&#xff0c;有助于优化资源使用效率&#x…

【Linux】文件周边001之系统文件IO

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.C语言文件IO 1.1…