新时代新潮流 WebOS【20】WebKit 的结构与解构

2009-05-25 20:54

从指定一个 HTML 文本文件,到绘制出一幅布局复杂,字体多样,内含图片音频视 频等等多媒体内容的网页,这是一个复杂的过程。在这个过程中 Webkit 所做的一切,都是围绕 DOM Tree 和 Rendering Tree 这两个核心。上一章我们谈到这两棵树各自的功用,这一章,我们借一个简单的 HTML 文件,展示一下 DOM Tree 和 Rendering Tree 的具体构成,同时解剖一下 Webkit 是如何构造这两棵树的。

From HTML to webpage, and the underlying DOM tree and rendering tree

Figure 1. From HTML to webpage, and the underlying DOM tree and rendering tree.

1. DOM Tree 与 Rendering Tree 的结构

Figure 1 中左上是一个简单的 HTML 文本文件,右上是 Webkit rendering engine 绘制出来的页面。页面的内容包括一个标题,“AI”,一行正文,“Ape’s Intelligence”,以及一幅照片。整个页面分成前后两个层面,标题和正文绘制在前一个层面,照片处于后一个层面。L 君和我亦步亦趋地跟踪了,从解析这个 HTML 文本文件,到生成 DOM Tree 和 Rendering Tree 的整个流程,目的是为了了解 DOM Tree 和 Rendering Tree 的具体成份,以及构造的各个步骤。

先说 Figure 1 中左下角的 DOM Tree。基本上 HTML 文本文件中每个 tag,在 webkit/webcore/html 中都有一个 class 与之对应。譬如<HTML> tag 对应 HTMLHtmlElement,<HEAD> tag 对应 HTMLHeadElement,<STYLE> tag 对应 HTMLStyleElement 等等。比较特别的是 DOM Tree 的根节点,HTMLDocument,在 HTML 文本文件中没有哪个 tag 与之对应。关于 HTMLDocument 的作用,我们稍后介绍。整个 DOM Tree 的结构,与 HTML 文本文件中各个 tags 的嵌套关系也一一对应。一言以蔽之,DOM Tree 就是把 HTML 文本文件翻译成 object 树状结构。

需要强调的是,DOM Tree 是一个通用数据结构,任何 XML 文本文件都可以翻译成 DOM Tree,而不仅仅限于 HTML 文本文件。webkit/webcore/html 中林林总总 html classes,基本上都是 webkit/webcore/dom 中的某个 class 的子类,也就是说,/html 是 /dom 的一个特例。这样的设计,为将来把 Webkit 拓展到 HTML 格式以外的页面的布局和渲染,埋下了伏笔。所以严格地讲,Figure 1 中左下的 DOM Tree,实际上是一个 HTML DOM Tree。

再看 Rendering Tree,显著的特点在于,

a. 整个 Rendering Tree 树状结构,与 HTML DOM Tree 树状结构一一对应。也就是说,几乎每个 HTML DOM Tree 中的节点,在 Rendering Tree 中都有对应的节点。节点与节点之间的父子或兄弟关系也一一对应。

例外的是,在 HTML DOM Tree 有 HTMLStyleElement 叶子节点,而在 Rendering Tree 中,没有相应的叶子节点。原因是,Rendering Tree 各个节点,都涉及页面中某块区域的布局和渲染。而 HTMLStyleElement,并不直接涉及某块区域的布局和渲染,HTML DOM Tree 中 HTMLStyleElement 叶子节点包含的内容,已经融入 Rendering Tree 中 RenderImage 叶子节点的属性中去了。另外,因为 Rendering Tree 中不存在与 HTMLStyleElement 相应的叶子节点,所以,与 HTMLHeadElement 对应的节点也没有必要存在。

b. webkit/webcore/rendering 中各个 class 与 HTML tags 并没有一一对应的关系。
Rendering Tree 是一个通用的规划页面布局和渲染的机制,这个通用机制可以服务于 HTML 页面,但是并不仅仅限于为 HTML 页面服务,我们可以用 Rendering Tree 来规划其它格式的页面的布局和渲染。以 DOM Tree 和 Rendering Tree 为核心的 Webkit 渲染机,是一个功能强大,扩展性良好的通用渲染机。它不仅可以用来绘制 HTML 页面,也可以用来渲染其它格式的页面,譬如可 以用它来制作 email 阅读和管理器,制作数据库管理工具,甚至制作游戏界面。

稍微让人有点吃惊的是,对于 HTMLHtmlElement,HTMLBodyElement,HTMLHeadingElement 和 HTMLParagraphElement, 在 Rendering Tree 中通通以 RenderBlock 呼应。如果说 HTMLHeadingElement 和 HTMLParagraphElement 的区别不大,仅仅 是字体和对齐方式有些微小的差别,所以 Rendering Tree 可以用 RenderBlock 来统一应对。那么问题是,HTMLHtmlElement 和 HTMLBodyElement 是两种容器,总是出现在 DOM Tree 的中部,而从来不会作为叶子节点出现,对应于这样的容器节点,为什么 Rendering Tree 不另设一种 class,与 RenderBlock 有所区别呢?不过话又说回来,这不是个大问题,最多是个美感的问题。

The construction sequence of the root of the DOM tree Figure 2. The construction sequence of the root of the DOM tree.

2. DOM Tree 与 Rendering Tree 的根节点

前一节中我们提到 HTMLDocument 是一个比较特殊的 class,它是整个 HTML DOM Tree 的根节点,但是不对应任何 HTML tag。JavaScript 中经常出现的 document,指的就是这个根。例如,
“document.getElementByIdx(x).style.background=”yellow”;”
HTML 文本文件,通常是以<HTML> 开头,以</HTML> 结尾。但是<HTML> tag 并不对应 DOM Tree 的根节点,而是根以下的第一个子节点,即 HTMLHtmlElement 节点。

初看 Figure 2 觉得有点意外,当用户在浏览器里打开一个空白页面的时候,立刻生成了 DOM Tree 的根节点 HTMLDocument,与 Rendering Tree 的根节点 RenderView。而这个时候,用户并没有给定 URL,也就是说,对于浏览器来讲,这时候具体的 HTML 文本文件并不存在。根节点与 具体 HTML 内容相脱节,或许暗示了 Webkit 的两个设计思路,

a. DOM Tree 的根节点 HTMLDocument,与 Rendering Tree 的根节点 RenderView,可以重复利用。

当用户在同一个浏览器页面中,先后打开两个不同的 URLs,也就是两个不同的 HTML 文本文时,HTMLDocument 和 RenderView 两个根节点并没有发生改变,改变的是 HTMLHtmlElement 以下的子树,以及对应的 Rendering Tree 的子树。
为什么这样设计?原因是 HTMLDocument 和 RenderView 服从于浏览器页面的设置,譬如页面的大小和在整个屏幕中的位置等等。这些设置与页面中要显示什 么的内容无关。同时 HTMLDocument 绑定 HTMLTokenizer 和 HTMLParser,这两个构件也与某一个具体的 HTML 内容无关。

b. 同一个 DOM Tree 的根节点可以悬挂多个 HTML 子树,同一个 Rendering Tree 的根节点可以悬挂多个 RenderBlock 子树。

在我们目前所见到的浏览器中,每一个页面通常只显示一个 HTML 文件。虽然一个 HTML 文件可以分割成多个 frames,每个 frame 承载一个独立的 HTML 文件,但是从 DOM Tree 结构来讲,HTMLDocument 根节点以下,只有一个子节点,这个子节点是 HTMLHtmlElement,它领衔某个 HTML 文本文件对应 的子树。Rendering Tree 也一样,目前我们见到的网页中,一个 RenderView 根节点以下,也只有一个 RenderBlock 子节点。

但是 Webkit 的设计,却允许同一个根以下,悬挂多个 HTML 子树。虽然我们目前没有看到一个页面中,并存多个 HTML 文件,并存多个布局和渲染风格的情景,但是 Webkit 为将来的拓展留下了空间。前文中所设想的个性化,多皮肤,多视角的浏览器页面绘制,用 Webkit 实现起来难度不大。

The construction sequence of the DOM Tree and the Rendering Tree Figure 3. The construction sequence of the DOM Tree and the Rendering Tree.

3. DOM Tree 与 Rendering Tree 的构筑

HTMLDocument 根节点包含的最重要的构件是 HTMLTokenizer,而 HTMLTokenizer 又包含 HTMLParser 这个构件。HTMLTokenizer 从前到后读取 HTML 文本文件中每一个字符,并从中提取出各个 HTML tags 以及它们的内容。而 HTMLParser 不仅负责 HTML DOM Tree 的构筑,而且也同时负责 Rendering Tree 的构筑。

在 Figure 3 中,从第 8 步到第 11 步,HTMLParser 根据一个 HTML Tag 生成一个 HTML DOM Tree 节点。从第 12 步到第 17 步,生成相应的 Rendering Tree 的节点,并把它和 HTML DOM Tree 的节点勾连在一起。这张图的细节过多,读解不容易。Figure 4 把第 8 步到第 17 步演示了一下。

An illustration of the construction of a DOM tree node and its corresponding Rendering tree node Figure 4. An illustration of the construction of a DOM tree node and its corresponding Rendering tree node.

值得注意的是,每当 HTMLParser 生成一个 DOM Tree 的节点的时候,相应地,也同时生成一个 Rendering Tree 节点。然后把它们两个新节点勾连在一起。换而言之,Rendering Tree 与 DOM Tree 同步生长。

Webkit 值得赞赏的地方非常多,但是 HTMLParser 让 DOM Tree 和 Rendering Tree 同步生长的做法,却值得商榷。如果同步生长,那么 Rendering Tree 必然平铺直叙地刻板地忠实于 DOM Tree。假设先生成 DOM Tree,再生成 Rendering Tree,把两者割裂开,就有机会让 Webkit 发挥更加奇妙的布局和渲染。平铺直叙固然符合大多数人在大多数时间里的阅读习惯,但是离经叛道的设计,也会有市场。一个例子就是上一章末尾处那张多视点的地图。如果让 DOM Tree 与 Rendering Tree 同步生长,这样的布局和渲染是难以想像的。

影响 2010s 年代的十大消费电子产品
登录,参与讨论前请先登录

评论在审核通过后将对所有人可见

正在加载中

移动互联网的围观者、起哄者、以及肇事者。

本篇来自栏目

解锁订阅模式,获得更多专属优质内容