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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据学习之Flink,Flink的安装部署

Flink部署 一、了解它的关键组件 客户端&#xff08;Client&#xff09; 作业管理器&#xff08;JobManager&#xff09; 任务管理器&#xff08;TaskManager&#xff09; 我们的代码&#xff0c;实际上是由客户端获取并做转换&#xff0c;之后提交给 JobManger 的。所以 …

计算机网络 第2章(物理层)

系列文章目录 计算机网络 第1章&#xff08;概述&#xff09; 计算机网络 第2章&#xff08;物理层&#xff09; 文章目录 系列文章目录1. 物理层的基本概念2. 物理层下面的传输媒体2.1 导引型传输媒体2.2 非导引型传输媒体 3. 传输方式3.1 串行传输和并行传输3.2 同步传输和异…

控制项目进展

优质博文 IT-BLOG-CN 假如一个项目准备工作做的非常周详&#xff0c;现在要做的就是监督项目的进展情况&#xff0c;理想状况下事情应当进展的很顺利&#xff0c;但实际上我们会发现项目永远不会完全按照经计划执行&#xff0c;我们必须进行项目控制。也就是我们需要不断进行调…

AI创作之旅:探索提示工程的奇妙世界

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 在当今信息爆炸的时代&#xff0c;人工智能的发…

开源的测试平台快2千星了,能带来多少收益呢

最近看了下自己去年初开源的测试平台&#xff0c;star一起算的话也到1.7k了&#xff1a; 做开源的初心一方面是想把自己的理解和思想展示出来&#xff0c;另一方面是想进一步打造个人IP&#xff0c;提升影响力&#xff08;其实这个想法很早之前就有了&#xff0c;计划过无数次但…

图灵日记之java奇妙历险记--异常包装类泛型

目录 异常概念与体系结构异常的分类异常的处理防御式编程异常的抛出异常的捕获异常声明throwstry-catch捕获并处理 自定义异常类 包装类基本数据类型及其对应包装类装箱和拆箱 泛型泛型使用类型推导 裸类型说明 泛型的编译机制泛型的上界语法 异常概念与体系结构 在java中,将程…

【JavaEE进阶】MyBatis⼊⻔

文章目录 &#x1f332;什么是MyBatis?&#x1f333;准备⼯作&#x1f6a9;创建⼯程&#x1f6a9;数据准备&#x1f6a9;配置数据库连接字符串&#x1f6a9; 在项⽬中,创建持久层接⼝UserInfoMapper &#x1f343;单元测试&#x1f6a9;使⽤Idea⾃动⽣成测试类 &#x1f340;打…

LabVIEW电路板插件焊点自动检测系统

LabVIEW电路板插件焊点自动检测系统 介绍了电路板插件焊点的自动检测装置设计。项目的核心是使用LabVIEW软件&#xff0c;开发出一个能够自动检测电路板上桥接、虚焊、漏焊和多锡等焊点缺陷的系统。 系统包括成像单元、机械传动单元和软件处理单元。首先&#xff0c;利用工业相…

nvm切换node版本报错

1. 问题描述 使用 nvm use (node版本号) 命令时报错 2. 解决方法 权限不够&#xff0c;以管理员身份运行cmd 具体操作&#xff1a; &#xff08;1&#xff09;点击电脑左下方搜索 命令提示符 &#xff0c;点击 以管理员身份运行 &#xff08;2&#xff09;重新输入nvm use …

创建SERVLET

创建SERVLET 要创建servlet,需要执行以下任务: 编写servlet。编译并封装servlet。将servlet部署为Java EE应用程序。通过浏览器访问servlet。编写servlet 要编写servlet,需要扩展HttpServlet接口的类。编写servlet是,需要合并读取客户机请求和返回响应的功能。 读取和处…

基于jQuery与Spring MVC实现用户密码异步修改的实战演示

文章目录 一、实战概述二、实战步骤&#xff08;一&#xff09;创建表单1、表单界面2、表单代码3、脚本代码 &#xff08;二&#xff09;后端控制器&#xff08;三&#xff09;测试代码&#xff0c;查看效果1、弹出更改密码表单2、演示更改密码操作 三、实战总结 一、实战概述 …

如何正确判断一个字符串是数值

在网页中&#xff0c;我们从用户输入的内容中获取的值通常是字符串&#xff0c;但是有时候我们希望用户输入的内容一定要能转成数值&#xff1a; userInput.addEventListener(change, (e) > {const value e.target.value;console.log(typeof value); // stringconsole.ass…

健康成长的基石:新生儿补充镁的关键

引言&#xff1a; 镁是人体内的重要矿物质之一&#xff0c;对于新生儿的生长发育和健康维护至关重要。在新生儿期间&#xff0c;适量补充镁有助于促进神经、骨骼和心血管系统的健康发展。然而&#xff0c;在进行镁的补充时&#xff0c;家长需要特别注意一系列事项&#xff0c;…

Android 通过adb命令查看应用流量

一. 获取应用pid号 通过adb shell ps -A | grep 包名 来获取app的 pid号 二. 查看应用流量情况 使用adb shell cat /proc/#pid#/net/dev 命令 来获取流量数据 备注&#xff1a; Recevice: 表示收包 Transmit: 表示发包 bytes: 表示收发的字节数 packets: 表示收发正确的…

move_base+自己的定位程序(攻略篇) --- 成功实现ESKF的lidar+imu以及move_base的路径规划

临近放假&#xff0c;老板要求回去之前找其汇报进展&#xff0c;无奈近几月忙于毕业论文的编写&#xff0c;实在是没有多少可以汇报的内容&#xff0c;想来自己弄得定位程序只能实现定位&#xff0c;要不自己再加一个路径规划&#xff0c;直接干&#xff01; 本文的文字量较大…

centos 7.6 进入单用户模式

1、重启服务器&#xff0c;在选择内核界面使用上下箭头移动 2、选择内核并按“e” 将“RO”改成 rw ,删除 rhgb quiet 添加 init/bin/bash Ctrl X 进入单用户模式 为防止乱码&#xff0c;修改语言为英语 修改完密码建议输入&#xff1a;touch /.autorelabel 更新系统信…

websocket服务端本地部署

文章目录 1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功,暴露端口默认99995. 创建隧道映射内网端口6. 查看状态->在线隧道,复制所创建隧道的公网地址加端口号7. 以…

Unity3d引擎中使用AIGC生成的360全景图(天空盒)

前言 在这里与Skybox AI一起&#xff0c;一键打造体验无限的360世界&#xff0c;这是这个AIGC一键生成全景图的网站欢迎语。 刚使用它是23年中旬&#xff0c;在没有空去给客户实地拍摄全景图时&#xff0c;可以快速用它生成一些相关的全景图&#xff0c;用作前期沟通的VR de…

因谷歌Play Store审核超过7天和联系他们的方式

三种联系他们的方式 1.让他们打电话过来 英语好不好没关系&#xff0c;主要是他们讲着一口浓厚的印度口音英语&#xff0c;很难听懂 2.在线实时聊天沟通 可以选择英文、中文、但是英文肯定容易约上 3.发送邮件 回复太慢了&#xff0c;1-2天回复你一次 传送门&#xff1…

【Java数据结构 -- 队列:队列有关面试oj算法题】

队列、循环队列、用队列模拟栈、用栈模拟队列 1.队列1.1 什么是队列1.2 创建队列1.3 队列是否为空和获取队头元素 empty()peek()1.4 入队offer()1.5 出队&#xff08;头删&#xff09;poll() 2. 循环队列2.1 创建循环队列2.2 判断是否为空isEmpty()和满isFull()2.3 入队enQueue…