HTML解释器和DOM模型
DOM模型
DOM标准
DOM全称是文档对象模型,定义的是一组与平台、语言无关的接口,允许编程语言动态访问和更改文档(可以是HTML文档、XML文档、XHTML文档)的内容和结构。
DOM Level 1,,1998年成为W3C推荐标准,包含两个部分:
-
- Core:一组底层接口,接口可以表示任何结构化文档,允许对接口进行扩展,如对XML文档的支持。
- HTML:为了访问HTML文档,W3C在core之上定义了一组上层接口。把HTML中的内容定义为文档(Document)、节点(Node)、属性(Attribute)、元素(Element)、文本(Text)等
DOM Level 2(2000年),包含六个部分:
-
- Core:对DOM 1扩展,最著名的是getElementById,还有很多关于命名空间(namespace)的接口。
- Views:描述跟踪一个文档的各种视图(使用CSS样式设计文档前后)的接口。
- Events:引入对DOM事件的处理。主要是EventTarget、Mouse事件等接口,仍不支持键盘事件。
- Style(CSS):可以修改HTML元素的样式属性。
- Traversal and range:遍历树加上对指定范围的文档修改、删除等操作。
- HTML:允许动态访问和修改HTML文档。
DOM Level 3(2004年),包含五个部分:
-
- Core:加入了新接口adoptNode和textContent。
- Load and Save:允许程序动态加载XML文档并解释成DOM表示的文档结构。
- Validation:允许程序验证文档的有效性。
- Events:加入了对键盘的支持。随着移动平台的星期,触控事件很快会进入标准。
- XPath:使用XPath 1.0来访问DOM树。XPath是一种简单直观的检索DOM树节点的方式。
DOM树
结构模型
DOM结构基本构成要素是“节点”。在DOM中,节点的概念很宽泛,有很多类型:文档节点、元素节点、属性节点、Entity节点、ProcessingIntruction节点、CDataSection节点、注释节点等。
DOM树
众多的节点按照层次组织构成一个DOM树形结构,DOM树的根就是HTMLDocument,HTML网页中的标签是元素节点。
HTML解释器
解释过程
HTML解释器就是将从本地或网络读取到的网页和资源从字节流解释成DOM树结构。大致可以理解为图5-5。
首先是字节流,经过解码之后是字符流,通过词法分析器被解释成词语,之后经过语法分析其构建成节点,最后这些节点被组建成一棵DOM树。
图5-6描述了webkit在构建DOM树时需要用到的主要类。
左边部分是框结构。框对应于Frame类,文档对应于Document类,所以框包含文档。
Document类包含两个子类,HTMLDocument和XMLDocument。
右边部分是webkit为建立网页的框结构所建立的设施。
-
- FrameLoader类:框中内容的加载器,类似资源和资源的加载器。
- DocumentLoader类:帮助加载HTML文档,从字节流到构建的DOM树。
- DocumentWriter类:辅助类,创建DOM树的根节点HTMLDocument对象。
-
-
- 包含两个成员变量:
-
-
-
-
- 用于文档的字符解码的类;
- HTML解释器HTMLDocumentParser类。
-
-
-
- HTMLDocumentParser类:管理类,包含用于各种工作的类,如
-
-
- 字符串到词语需要用到的HTMLTokenizer类;
- 词语需要经过XSSAuditor做安全检查;
- 最后输出到HTMLTreeBuilder类。
-
-
- HTMLTreeBuilder类:能够通过词语创建一个个的节点对象。
- HTMLConstructionSite类:将节点对象构建成一棵DOM树。
如图5-7是从字节流到构建DOM树的时序图。
-
- ResourceLoader和CachedRawResource类在收到网络栈的数据后,
- 调用DocumentLoader类的commitData方法,
- 然后DocumentWriter类会先创建一个根节点HTMLDocument对象,然后将数据局append输送到HTMLDocumentParser对象。
- 后面是像之前描述的将其解释称词语,创建节点对象并创建一棵DOM树。
词法分析
在进行词法分析前,解释器需要检查网页内容使用的编码格式,以便后面使用合适的解码器。
-
- 在网页中找到设置的编码格式,解释器调用对应的解码器将字节流转换成特殊格式的字符串;
- 没有格式,则直接由HTMLTokenizer类进行词法分析。
HTMLTokenizer类是一个状态机——输入的是字符串,输出的是一个个词语。
主要接口是nextToken函数。nextToken每次输出一个词语,同时会标记输入的字符串,表示那些字服已经被处理过了。
对于词语的类别,webkit定义得不多,HTMLToken类定义了6种词语类别:DOCTYPE、StartTag、EndTag、Comment、Character、EndOfFile。
(注,不涉及HTML标签类型等信息,这是语法分析的工作。 )
XSSAuditor验证词语
XSS指的是Cross Site Security。
根据XSS安全机制,解析出来的词语有可能阻碍某些内容的执行,所以XSSAuditor类主要负责过滤这些词语,通过后才做后面的处理。
详细规则和方法见第12章。
词语到节点
从词语到构建节点这一步由HTMLDocumentParser类调用HTMLTreeBuilder类的constructTree函数实现。
该函数利用图5-9中的processToken函数来处理6种词语类型。
节点到DOM树
从节点到创建DOM树,包括为书中的元素创建属性节点等工作,都是由HTMLConstructionSite类来完成。
因为HTML文档的Tag标签都是由开始和结束标记的,所以这一构建过程可以借助栈结构实现。
图5-10显示了主要的节点类。一切的基础是Node类。
-
- Node类继承自EventTarget类(表明Node类能够接收事件)和ScriptWrappable类(与js引擎有关)。
- Node类是其他类的基类。
-
-
- 元素类、文档类、属性类都继承自一个抽象类出来的ContanerNode类,表明能够包含其他的节点对象。
- 回到HTML文档来说,元素和文档对应的类是HTMLElement类和HTMLDocument类。
-
-
-
-
- 注:章节最初提到,文档可以是HTML文档、XML文档、XHTML文档,所以文档和元素可以有多个子类,这里只提到HTML文档相关的。
-
-
网页基础设施
上面介绍了Frame、Document等基础类,都是网页内部的概念。实际上,webkit提供了更高层次的设施,用于表示整个网页的一些类,这些类提供了构建DOM树、布局、渲染等操作。
图5-11描述了webkit中用于表示网页的一些基础设施类,以webkit的chromium移植为例。
图中右边是webkit的webCore提供的类,这些类是公共的,被不同移植共享。
图中左边是webkit的chromium移植使用的接口类,其中RenderViewImpl是chromium使用webkit的主要桥接类,用于Renderer进程。
在chromium移植中,
-
- WebView和WebFrame是chromium项目用于表示网页和框结构的接口类。
- WebViewImpl和WebFrameImpl类是这两个类的子类,负责使用WebCore中的Page、Frame等类来支持两个对外类的接口。
-
-
- WebView类和Page类是一一对应的。
-
-
-
-
- Page类是webkit内部用来表示网页的类。
- WebView类是webkit对外表示网页的类。
-
-
-
-
- 类似的组合还有WebFrame类和Frame类。
-
图中的Chrome不是Google的浏览器Chrome,而是webkit的类,表示的是网页所绘制的与实现相关的一个窗口。必须满足两种需求:
-
- 需要具备获取各平台资源的能力,如webkit可以调用Chrome类创建一个新窗口。
- 需要把webkit内部的状态和进度等信息派发给webkit的使用者。
webkit使用ChromeClient类:
-
- 减少webkit和外部调用者的耦合
- 方便地支持不同的平台。
Chrome类是一些公共的操作流程,ChromeClient在不同的移植有不同的实现,如图中的chromium移植中有
ChromeClientImpl实现类。
ChromeClientImpl类有两类接口:
-
- 用来监听webkit的内部状态信息(回调函数)。
- 实现Chrome类所需要的跟移植相关的工作。
- 图5-12中的两个方法对应以上两条。
Chrome类是webkit与使用者之间的桥梁,主要负责用户界面和渲染相关的需求,实现这些需求会用到平台的相关接口,主要功能如下:
-
- 跟用户界面和渲染相关的,需要各个移植实现的接口集合类。
- 继承自HostWindow类(宿主窗口),包含一系列接口,用来通知重绘或者更新整个窗口、滚动窗口等。
- 窗口相关操作,如显示、隐藏窗口等。
- 显示和隐藏窗口中的工具栏、状态栏、滚动条等。
- 显示JavaScript相关的窗口,如alert、confirm、prompt窗口等。
线程化的解释器
线程化的解释器就是利用单独的线程来解释HTML文档。
在webkit中,网络资源的字节流自IO线程交给渲染线程后,后面的构建、布局、渲染都是由渲染线程完成(不绝对)。
DOM树只能在渲染线程上创建和访问,但从字符串到词语这个阶段可以交给单独的线程来完成,Chrome浏览器就是利用了这个思想。
当字符串传送到HTMLDocumentParser类时,该类会创建一个BackgroundHTMLParser对象来处理,将数据交给该对象。
webkit会检查是否需要创建HTMLParserThread线程(用于解释字符串)。如果存在,webkit九江任务传递给该线程。图5-13描述了上述过程。
在HTMLParserThread线程中,webkit所做的事情(将字符串解释成词语,使用XSSAuditor进行安全检查)与之前介绍的没有什么大的区别,只是在一个新的线程执行。
主要的区别在于,解释称词语后,webkit会分批次地将结果词语传递给渲染线程,图5-14描述了这一过程。(没看懂-_-)
JavaScript的执行
在HTML解释器工作过程中,可能会有JavaScript代码(全局作用域的JavaScript)需要执行,它发生在将字符串解释称词语后、创建各种节点的时候。这也是全局执行的JavaScript代码不能访问DOM树的原因:因为DOM树可能还没有被创建完成。
webkit将DOM树创建过程中需要执行的JavaScript代码交给HTMLScriptRunner类来执行(利用JavaScript引擎执行Node节点中包含的代码)。
关于JavaScript的使用有以下建议:
-
- 在script元素中加上async属性,表明这好是一个可以异步执行的JavaScript代码。如下图中的示例二。
- 将script元素放在body元素后。这样不会阻碍其他资源的并发下载。如下图中的示例三。
- 注,通常script里加defer会比async更合适,详情参考这篇:链接
像示例一,webkit使用预扫描和预加载机制来实现资源的并发下载,而不被JavaScript的执行所阻碍。但还是更推荐示例二和示例三,毕竟不是所有渲染引擎都做了如此的考虑。
当DOM树构建完成,webkit会触发DOMContentLoaded事件。
当所有资源都被加载完了,webkit触发onloade事件。
DOM的事件机制
事件的工作过程
事件在工作过程中使用两个主体:事件(event)和事件目标(eventTarget)。
每个事件都有属性标记该事件的事件目标。当到达事件目标时,就会触发调用在该目标上注册的监听者。监听者的调用顺序不固定,所以不能依赖监听者注册的顺序来决定代码逻辑。
图5-17是eventTarget接口的定义。图中的接口是用来注册和移除监听者的。
事件处理最重要的部分是事件捕获和事件冒泡。图5-18描述了这两个过程(假设img元素是事件的直接目标)。
当渲染引擎接收到一个事件时,webkit会通过HitTest检查哪个元素是直接的事件目标。(注:HitTest是webkit中一种检查触发事件在哪个区域的算法。)
-
- 事件的捕获是自顶向下。
-
-
- 以图5-18为例,顺序是“document->HTML->body->img”。
- 可以使用stopPropagation阻止事件向下传递。
-
-
- 事件的冒泡是自下而上。
-
-
- 默认不冒泡,事件包含一个是否冒泡的属性。
- 可以使用stopPropagation阻止事件向上传递。
-
注:stopPropagation阻止的是,整个事件传递过程中,该节点之后的事件。包括了事件捕获和事件冒泡。参考。
webkit的事件处理机制
DOM的事件分为很多种:
-
- 与用户有关的只是其中一种,称为UIEvent。
-
-
- UIEvent又分为很多种,包括但不限于:FocusEvent、MouseEvent、KeyboardEvent、CompositionEvent等。
-
-
- 其他的包括,CustomEvent、MutationEvent等。
基于webkit的浏览器事件处理过程,首先做HitTest,查找事件发生处的元素,检查该元素有无监听者。
如果有,浏览器会把事件派发给webkit内核处理。同时,浏览器也需要理解和处理这样的事件。主要是因为,有些事件浏览器必须响应,从而对网页做默认处理。
最后了解一下事件从浏览器到达webkit内核之后,webkit内部的调用过程。如图5-20描述了鼠标事件的调用过程。
EventTarget类是处理事件的核心类,除了需要将各种事件传给JavaScript引擎以调用相应的监听者之外,还会识别鼠标事件,来触发调用右键菜单、拖放效果等。此外,还支持网页的多框结构。
影子DOM
什么是影子DOM
影子DOM:使得一些DOM节点在特定范围内可见,而在网页DOM中不可见,但网页渲染结果中包含了这些节点。
HTML5支持了很多新特性,如对视频、音频的支持,这些元素是由很复杂的控制界面组成,这些界面也是使用HTML元素编写,但在DOM树中无法找到对应的节点,这其实就是利用了影子DOM。
图5-21描述了HTML文档对应的DOM树和div元素包含的一个影子DOM子树。
webkit的支持
支持影子DOM的相关类在source/core/dom/shadow目录下,主要类是ShadowRoot,表示影子DOM的根节点。影子DOM同样有Node节点的属性和方法。
webkit的Node类实现中有大量语句,用来检查当前节点是否是ShadowRoot对象。如果是,就把它作为不同DOM树之间的边界。